cellular_raza_concepts/domain.rs
1use serde::{Deserialize, Serialize};
2use std::collections::{BTreeMap, BTreeSet};
3
4use crate::errors::{BoundaryError, DecomposeError};
5
6/// Identifier for voxels used internally to get rid of user-defined ones.
7#[cfg_attr(feature = "pyo3", pyo3::pyclass)]
8#[derive(Clone, Copy, Debug, Deserialize, Hash, PartialEq, Eq, Ord, PartialOrd, Serialize)]
9pub struct VoxelPlainIndex(pub usize);
10
11/// This is mainly used by the simulation_flow::cocmmunicator for testing purposes
12#[allow(unused)]
13#[doc(hidden)]
14impl VoxelPlainIndex {
15 pub fn new(value: usize) -> Self {
16 Self(value)
17 }
18}
19
20#[cfg(feature = "pyo3")]
21#[pyo3::pymethods]
22impl VoxelPlainIndex {
23 #[new]
24 fn __new__(index: usize) -> Self {
25 Self::new(index)
26 }
27}
28
29/// Identifier or subdomains
30#[derive(Clone, Copy, Debug, Deserialize, Hash, PartialEq, Eq, Ord, PartialOrd, Serialize)]
31#[cfg_attr(feature = "pyo3", pyo3::pyclass)]
32pub struct SubDomainPlainIndex(pub usize);
33
34/// Provides an abstraction of the physical total simulation domain.
35///
36/// [cellular_raza](https://github.com/jonaspleyer/cellular_raza) uses domain-decomposition
37/// algorithms to split up the computational workload over multiple physical regions.
38/// That's why the domain itself is mostly responsible for being deconstructed
39/// into smaller [SubDomains](SubDomain) which can then be used to numerically solve our system.
40///
41/// This trait can be automatically implemented when the [SortCells], [DomainRngSeed],
42/// and [DomainCreateSubDomains] are satisfied together with a small number of trait bounds to hash
43/// and compare indices.
44pub trait Domain<C, S, Ci = Vec<C>> {
45 /// Subdomains can be identified by their unique [SubDomainIndex](Domain::SubDomainIndex).
46 /// The backend uses this property to construct a mapping (graph) between subdomains.
47 type SubDomainIndex;
48
49 /// Similarly to the [SubDomainIndex](Domain::SubDomainIndex), voxels can be accessed by
50 /// their unique index. The backend will use this information to construct a mapping
51 /// (graph) between voxels inside their respective subdomains.
52 type VoxelIndex;
53
54 /// Deconstructs the [Domain] into its respective subdomains.
55 ///
56 /// When using the blanket implementation of this function, the following steps are carried
57 /// out:
58 /// Its functionality consists of the following steps:
59 /// 1. Decompose the Domain into [Subdomains](SubDomain)
60 /// 2. Build a neighbor map between [SubDomains](SubDomain)
61 /// 3. Sort cells to their respective [SubDomain]
62 ///
63 /// However, to increase performance or avoid trait bounds, one can also opt to implement this
64 /// trait directly.
65 fn decompose(
66 self,
67 n_subdomains: core::num::NonZeroUsize,
68 cells: Ci,
69 ) -> Result<DecomposedDomain<Self::SubDomainIndex, S, C>, DecomposeError>;
70}
71
72/// Manage the current rng seed of a [Domain]
73pub trait DomainRngSeed {
74 // fn set_rng_seed(&mut self, seed: u64);
75
76 /// Obtains the current rng seed
77 fn get_rng_seed(&self) -> u64;
78}
79
80/// Generate [SubDomains](SubDomain) from an existing [Domain]
81pub trait DomainCreateSubDomains<S> {
82 /// This should always be identical to [Domain::SubDomainIndex].
83 type SubDomainIndex;
84 /// This should always be identical to [Domain::VoxelIndex].
85 type VoxelIndex;
86
87 /// Generates at most `n_subdomains`. This function can also return a lower amount of
88 /// subdomains but never less than 1.
89 fn create_subdomains(
90 &self,
91 n_subdomains: core::num::NonZeroUsize,
92 ) -> Result<
93 impl IntoIterator<Item = (Self::SubDomainIndex, S, Vec<Self::VoxelIndex>)>,
94 DecomposeError,
95 >;
96}
97
98/// Generated by the [decompose](Domain::decompose) method. The backend will know how to
99/// deal with this type and crate a working simulation from it.
100pub struct DecomposedDomain<I, S, C> {
101 /// Number of spawned [SubDomains](SubDomain). This number is guaranteed to be
102 /// smaller or equal to the number may be different to the one given to the
103 /// [Domain::decompose] method.
104 /// Such behaviour can result from not being able to construct as many subdomains as desired.
105 /// Note that this function will attempt to construct more [SubDomains](SubDomain)
106 /// than available CPUs if given a larger number.
107 pub n_subdomains: core::num::NonZeroUsize,
108 /// Vector containing properties of individual [SubDomains](SubDomain).
109 /// Entries are [Domain::SubDomainIndex], [SubDomain], and a vector of cells.
110 // TODO can we use another iterator than Vec<(I, S, Vec<C>)>?
111 pub index_subdomain_cells: Vec<(I, S, Vec<C>)>,
112 /// Encapsulates how the subdomains are linked to each other.
113 /// Eg. two subdomains without any boundary will never appear in each others collection
114 /// of neighbors.
115 /// For the future, we might opt to change to an undirected graph rather than a [BTreeMap].
116 pub neighbor_map: BTreeMap<I, BTreeSet<I>>,
117 /// Initial seed of the simulation for random number generation.
118 pub rng_seed: u64,
119}
120
121/// Subdomains are produced by decomposing a [Domain] into multiple physical regions.
122///
123/// # Derivation
124/// ```
125/// # use cellular_raza_concepts::*;
126/// struct MySubDomain {
127/// x_min: f32,
128/// x_max: f32,
129/// n: usize,
130/// }
131///
132/// impl SubDomain for MySubDomain {
133/// type VoxelIndex = usize;
134///
135/// fn get_neighbor_voxel_indices(
136/// &self,
137/// voxel_index: &Self::VoxelIndex
138/// ) -> Vec<Self::VoxelIndex> {
139/// (voxel_index.saturating_sub(1)..voxel_index.saturating_add(1).min(self.n)+1)
140/// .filter(|k| k!=voxel_index)
141/// .collect()
142/// }
143///
144/// fn get_all_indices(&self) -> Vec<Self::VoxelIndex> {
145/// (0..self.n).collect()
146/// }
147/// }
148///
149/// #[derive(SubDomain)]
150/// struct MyNewSubDomain {
151/// #[Base]
152/// base: MySubDomain,
153/// }
154/// # let _my_sdm = MyNewSubDomain {
155/// # base: MySubDomain {
156/// # x_min: -20.0,
157/// # x_max: -11.0,
158/// # n: 20,
159/// # }
160/// # };
161/// # assert_eq!(_my_sdm.get_all_indices(), (0..20).collect::<Vec<_>>());
162/// # assert_eq!(_my_sdm.get_neighbor_voxel_indices(&0), vec![1]);
163/// # assert_eq!(_my_sdm.get_neighbor_voxel_indices(&3), vec![2,4]);
164/// # assert_eq!(_my_sdm.get_neighbor_voxel_indices(&7), vec![6,8]);
165/// ```
166pub trait SubDomain {
167 /// Individual Voxels inside each subdomain can be accessed by this index.
168 type VoxelIndex;
169
170 /// Obtains the neighbor voxels of the specified voxel index. This function behaves similarly
171 /// to [SortCells::get_voxel_index_of] in that it also has to return
172 /// indices which are in other [SubDomains](SubDomain).
173 fn get_neighbor_voxel_indices(&self, voxel_index: &Self::VoxelIndex) -> Vec<Self::VoxelIndex>;
174
175 // fn apply_boundary(&self, cell: &mut C) -> Result<(), BoundaryError>;
176
177 /// Get all voxel indices of this [SubDomain].
178 fn get_all_indices(&self) -> Vec<Self::VoxelIndex>;
179}
180
181/// Assign an [VoxelIndex](SortCells::VoxelIndex) to a given cell.
182///
183/// This trait is used by the [Domain] and [SubDomain] trait to assign a [Domain::SubDomainIndex]
184/// and [SubDomain::VoxelIndex] respectively.
185///
186/// # [SubDomain]
187/// This trait is supposed to return the correct voxel index of the cell
188/// even if this index is inside another [SubDomain].
189/// This restriction might be lifted in the future but is still
190/// required now.
191pub trait SortCells<C> {
192 /// An index which determines to which next smaller unit the cell should be assigned.
193 type VoxelIndex;
194
195 /// If given a cell, we can sort this cell into the corresponding sub unit.
196 fn get_voxel_index_of(&self, cell: &C) -> Result<Self::VoxelIndex, BoundaryError>;
197}
198
199/// Apply boundary conditions to a cells position and velocity.
200///
201/// # Derivation
202/// ```
203/// # use cellular_raza_concepts::*;
204/// # use cellular_raza_concepts::BoundaryError;
205/// struct MyMechanics {
206/// x_min: f64,
207/// x_max: f64,
208/// }
209///
210/// impl SubDomainMechanics<f64, f64> for MyMechanics {
211/// fn apply_boundary(&self, pos: &mut f64, vel: &mut f64) -> Result<(), BoundaryError> {
212/// if *pos < self.x_min {
213/// *vel = vel.abs();
214/// }
215/// if *pos > self.x_max {
216/// *vel = -vel.abs();
217/// }
218/// *pos = pos.clamp(self.x_min, self.x_max);
219/// Ok(())
220/// }
221/// }
222///
223/// #[derive(SubDomain)]
224/// struct MySubDomain {
225/// #[Mechanics]
226/// mechanics: MyMechanics,
227/// }
228/// # let _my_sdm = MySubDomain {
229/// # mechanics: MyMechanics {
230/// # x_min: 1.0,
231/// # x_max: 33.0,
232/// # }
233/// # };
234/// # let mut pos = 0.0;
235/// # let mut vel = - 0.1;
236/// # _my_sdm.apply_boundary(&mut pos, &mut vel).unwrap();
237/// # assert_eq!(pos, 1.0);
238/// # assert_eq!(vel, 0.1);
239/// ```
240pub trait SubDomainMechanics<Pos, Vel> {
241 /// If the subdomain has boundary conditions, this function will enforce them onto the cells.
242 /// For the future, we plan to replace this function to additionally obtain information
243 /// about the previous and current location of the cell.
244 fn apply_boundary(&self, pos: &mut Pos, vel: &mut Vel) -> Result<(), BoundaryError>;
245}
246
247/// Apply a force on a cell depending on its position and velocity.
248///
249/// # Derivation
250/// ```
251/// # use cellular_raza_concepts::*;
252/// struct MyForce {
253/// damping: f64,
254/// }
255///
256/// impl SubDomainForce<f64, f64, f64, f64> for MyForce {
257/// fn calculate_custom_force(&self, _: &f64, vel: &f64, _: &f64) -> Result<f64, CalcError> {
258/// Ok(- self.damping * vel)
259/// }
260/// }
261///
262/// #[derive(SubDomain)]
263/// struct MySubDomain {
264/// #[Force]
265/// force: MyForce,
266/// }
267/// # let _my_sdm = MySubDomain {
268/// # force: MyForce {
269/// # damping: 0.1,
270/// # }
271/// # };
272/// # let calculated_force = _my_sdm.calculate_custom_force(&0.0, &1.0, &0.0).unwrap();
273/// # assert_eq!(calculated_force, -0.1);
274/// ```
275pub trait SubDomainForce<Pos, Vel, For, Inf> {
276 /// Calculates the force which acts on the cell agent
277 fn calculate_custom_force(
278 &self,
279 pos: &Pos,
280 vel: &Vel,
281 inf: &Inf,
282 ) -> Result<For, crate::CalcError>;
283}
284
285/// Describes extracellular reactions and fluid dynamics
286///
287/// # Derivation
288/// ```
289/// # use cellular_raza_concepts::*;
290///
291/// #[derive(Clone, Debug)]
292/// struct MyReactions<const N: usize> {
293/// values: Vec<f32>,
294/// pos: [f32; N],
295/// }
296///
297/// impl<const N: usize> SubDomainReactions<[f32; N], Vec<f32>, f32> for MyReactions<N> {
298/// type NeighborValue = Vec<f32>;
299/// type BorderInfo = Self;
300///
301/// fn treat_increments<I, J>(
302/// &mut self,
303/// neighbors: I,
304/// sources: J,
305/// ) -> Result<(), CalcError>
306/// where
307/// I: IntoIterator<Item = Self::NeighborValue>,
308/// J: IntoIterator<Item = ([f32; N], Vec<f32>)>,
309/// {
310/// Ok(())
311/// }
312///
313/// fn update_fluid_dynamics(&mut self, dt: f32) -> Result<(), CalcError> {
314/// Ok(())
315/// }
316///
317/// fn get_extracellular_at_pos(&self, pos: &[f32; N]) -> Result<Vec<f32>, CalcError> {
318/// Ok(self.values.clone())
319/// }
320///
321/// fn get_neighbor_value(&self, border_info: Self::BorderInfo) -> Self::NeighborValue {
322/// self.values.clone()
323/// }
324///
325/// fn get_border_info(&self) -> Self::BorderInfo {
326/// self.clone()
327/// }
328/// }
329///
330/// #[derive(SubDomain)]
331/// struct DerivedSubDomain<const N: usize> {
332/// #[Reactions]
333/// reactions: MyReactions<N>,
334/// }
335/// ```
336pub trait SubDomainReactions<Pos, Re, Float> {
337 /// Extracellular value of neighbor
338 type NeighborValue;
339 /// Exchanged information to locate neighboring subdomains.
340 type BorderInfo;
341
342 /// Combines increments which have been obtained by neighbors and cell-sources
343 fn treat_increments<I, J>(&mut self, neighbors: I, sources: J) -> Result<(), crate::CalcError>
344 where
345 I: IntoIterator<Item = Self::NeighborValue>,
346 J: IntoIterator<Item = (Pos, Re)>;
347
348 /// Main update function to calculate new values of extracellular concentrations.
349 fn update_fluid_dynamics(&mut self, dt: Float) -> Result<(), crate::CalcError>;
350
351 /// Obtain extracellular concentrations at given point.
352 fn get_extracellular_at_pos(&self, pos: &Pos) -> Result<Re, crate::CalcError>;
353 /// Obtains the [SubDomainReactions::NeighborValue] which should be sent to the neighbor which
354 /// has exposed the given [SubDomainReactions::BorderInfo].
355 fn get_neighbor_value(&self, border_info: Self::BorderInfo) -> Self::NeighborValue;
356 /// Obtains the [SubDomainReactions::BorderInfo] used to retrieve the
357 /// [SubDomainReactions::NeighborValue].
358 fn get_border_info(&self) -> Self::BorderInfo;
359}
360
361/// This trait derives the different aspects of a [SubDomain].
362///
363/// It serves similarly as the [cellular_raza_concepts_derive::CellAgent] trait to quickly
364/// build new structures from already existing functionality.
365///
366/// | Attribute | Trait | Implemented |
367/// | --- | --- |:---:|
368/// | `Base` | [SubDomain] | ✅ |
369/// | `SortCells` | [SortCells] | ✅ |
370/// | `Mechanics` | [SubDomainMechanics] | ✅ |
371/// | `Force` | [SubDomainForce] | ✅ |
372/// | `Reactions` | [SubDomainReactions] | ❌ |
373///
374/// # Example Usage
375/// ```
376/// # use cellular_raza_concepts::*;
377/// # struct MySubDomain;
378/// # impl SubDomain for MySubDomain {
379/// # type VoxelIndex = usize;
380/// # fn get_neighbor_voxel_indices(&self, voxel_index: &Self::VoxelIndex) -> Vec<usize> {
381/// # Vec::new()
382/// # }
383/// # fn get_all_indices(&self) -> Vec<Self::VoxelIndex> {
384/// # Vec::new()
385/// # }
386/// # }
387/// #[derive(SubDomain)]
388/// struct MyDerivedSubDomain {
389/// #[Base]
390/// s: MySubDomain,
391/// }
392/// # let derived_subdomain = MyDerivedSubDomain {
393/// # s: MySubDomain,
394/// # };
395/// # let all_indices = derived_subdomain.get_all_indices();
396/// # assert_eq!(all_indices.len(), 0);
397/// ```
398#[doc(inline)]
399pub use cellular_raza_concepts_derive::SubDomain;
400
401/// Derives aspects related to the simulation [Domain]
402///
403/// | Attribute | Trait | Implemented |
404/// | -- | --- |:---:|
405/// | `Base` | [Domain] | ✅ |
406/// | `DomainPartialDerive` | [Domain] | ✅ |
407/// | `DomainRngSeed` | [DomainRngSeed] | ✅ |
408/// | `DomainCreateSubDomains` | [DomainCreateSubDomains] | ✅ |
409/// | `SortCells` | [SortCells] | ✅ |
410#[doc(inline)]
411pub use cellular_raza_concepts_derive::Domain;