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