cellular_raza_core/backend/chili/
datastructures.rs

1use cellular_raza_concepts::*;
2use serde::{Deserialize, Serialize};
3
4#[cfg(feature = "tracing")]
5use tracing::instrument;
6
7use std::collections::{BTreeMap, BTreeSet};
8use std::hash::Hash;
9use std::num::NonZeroUsize;
10
11use rand::SeedableRng;
12
13use super::errors::*;
14use super::simulation_flow::*;
15
16use super::{CellIdentifier, SubDomainPlainIndex, VoxelPlainIndex};
17
18/// Intermediate object which gets consumed once the simulation is run
19///
20/// This Setup contains structural information needed to run a simulation.
21/// In the future, we hope to change the types stored in this object to
22/// simple iterators and non-allocating types in general.
23pub struct SimulationRunner<I, Sb> {
24    // TODO make this private
25    /// One [SubDomainBox] represents one single thread over which we are parallelizing
26    /// our simulation.
27    pub subdomain_boxes: BTreeMap<I, Sb>,
28}
29
30/// Stores information related to a voxel of the physical simulation domain.
31#[derive(Clone, Deserialize, Serialize)]
32pub struct Voxel<C, A> {
33    /// The index which is given when decomposing the domain and all indices are counted.
34    pub plain_index: VoxelPlainIndex,
35    /// Indices of neighboring voxels
36    pub neighbors: BTreeSet<VoxelPlainIndex>,
37    /// Cells currently in the voxel
38    pub cells: Vec<(CellBox<C>, A)>,
39    /// New cells which are about to be included into this voxels cells.
40    pub new_cells: Vec<(C, Option<CellIdentifier>)>,
41    /// A counter to make sure that each Id of a cell is unique.
42    pub id_counter: u64,
43    /// A random number generator which is unique to this voxel and thus able
44    /// to produce repeatable results even for parallelized simulations.
45    pub rng: rand_chacha::ChaCha8Rng,
46}
47
48/// Construct a new [SimulationRunner] from a given auxiliary storage and communicator object
49#[allow(unused)]
50#[cfg_attr(feature = "tracing", instrument(skip_all))]
51pub fn construct_simulation_runner<D, S, C, A, Com, Sy, Ci>(
52    domain: D,
53    agents: Ci,
54    n_subdomains: NonZeroUsize,
55    init_aux_storage: impl Fn(&C) -> A,
56) -> Result<
57    SimulationRunner<D::SubDomainIndex, SubDomainBox<D::SubDomainIndex, S, C, A, Com, Sy>>,
58    SimulationError,
59>
60where
61    Ci: IntoIterator<Item = C>,
62    D: Domain<C, S, Ci>,
63    D::SubDomainIndex: Eq + PartialEq + core::hash::Hash + Clone + Ord,
64    <S as SubDomain>::VoxelIndex: Eq + Hash + Ord + Clone,
65    S: SortCells<C, VoxelIndex = <S as SubDomain>::VoxelIndex> + SubDomain,
66    Sy: super::simulation_flow::FromMap<SubDomainPlainIndex>,
67    Com: super::simulation_flow::FromMap<SubDomainPlainIndex>,
68{
69    #[cfg(feature = "tracing")]
70    tracing::info!("Decomposing");
71    let decomposed_domain = domain.decompose(n_subdomains, agents)?;
72
73    #[cfg(feature = "tracing")]
74    tracing::info!("Validating Map");
75    if !validate_map(&decomposed_domain.neighbor_map) {
76        return Err(SimulationError::IndexError(IndexError(format!(
77            "Neighbor Map is invalid"
78        ))));
79    }
80
81    #[cfg(feature = "tracing")]
82    tracing::info!("Constructing Neighbor Map");
83    let subdomain_index_to_subdomain_plain_index = decomposed_domain
84        .index_subdomain_cells
85        .iter()
86        .enumerate()
87        .map(|(i, (subdomain_index, _, _))| (subdomain_index.clone(), SubDomainPlainIndex(i)))
88        .collect::<BTreeMap<_, _>>();
89    let mut neighbor_map = decomposed_domain
90        .neighbor_map
91        .into_iter()
92        .map(|(index, neighbors)| {
93            (
94                subdomain_index_to_subdomain_plain_index[&index],
95                neighbors
96                    .into_iter()
97                    .filter_map(|sindex| {
98                        if index != sindex {
99                            Some(subdomain_index_to_subdomain_plain_index[&sindex])
100                        } else {
101                            None
102                        }
103                    })
104                    .collect::<BTreeSet<_>>(),
105            )
106        })
107        .collect::<BTreeMap<_, _>>();
108
109    #[cfg(feature = "tracing")]
110    tracing::info!("Constructing Syncers and communicators");
111    let mut syncers = Sy::from_map(&neighbor_map)?;
112    let mut communicators = Com::from_map(&neighbor_map)?;
113    let voxel_index_to_plain_index = decomposed_domain
114        .index_subdomain_cells
115        .iter()
116        .map(|(_, subdomain, _)| subdomain.get_all_indices().into_iter())
117        .flatten()
118        .enumerate()
119        .map(|(i, x)| (x, VoxelPlainIndex(i)))
120        .collect::<BTreeMap<<S as SubDomain>::VoxelIndex, VoxelPlainIndex>>();
121    let plain_index_to_subdomain: std::collections::BTreeMap<_, _> = decomposed_domain
122        .index_subdomain_cells
123        .iter()
124        .enumerate()
125        .map(|(subdomain_index, (_, subdomain, _))| {
126            subdomain
127                .get_all_indices()
128                .into_iter()
129                .map(move |index| (subdomain_index, index))
130        })
131        .flatten()
132        .map(|(subdomain_index, voxel_index)| {
133            (
134                voxel_index_to_plain_index[&voxel_index],
135                SubDomainPlainIndex(subdomain_index),
136            )
137        })
138        .collect();
139
140    #[cfg(feature = "tracing")]
141    tracing::info!("Creating Subdomain Boxes");
142    let subdomain_boxes = decomposed_domain
143        .index_subdomain_cells
144        .into_iter()
145        .map(|(index, subdomain, cells)| {
146            let subdomain_plain_index = subdomain_index_to_subdomain_plain_index[&index];
147            let mut cells = cells.into_iter().map(|c| (c, None)).collect();
148            let mut voxel_index_to_neighbor_plain_indices: BTreeMap<_, _> = subdomain
149                .get_all_indices()
150                .into_iter()
151                .map(|voxel_index| {
152                    (
153                        voxel_index.clone(),
154                        subdomain
155                            .get_neighbor_voxel_indices(&voxel_index)
156                            .into_iter()
157                            .map(|neighbor_index| voxel_index_to_plain_index[&neighbor_index])
158                            .collect::<BTreeSet<_>>(),
159                    )
160                })
161                .collect();
162            let voxels = subdomain.get_all_indices().into_iter().map(|voxel_index| {
163                let plain_index = voxel_index_to_plain_index[&voxel_index];
164                let neighbors = voxel_index_to_neighbor_plain_indices
165                    .remove(&voxel_index)
166                    .ok_or(super::IndexError(format!(
167                        "cellular_raza::core::chili internal error in decomposition:\
168                        please file a bug report!\
169                        https://github.com/jonaspleyer/cellular_raza/issues/new?\
170                        title=internal%20error%20during%20domain%20decomposition",
171                    )))?;
172                Ok((
173                    plain_index,
174                    Voxel {
175                        plain_index,
176                        neighbors,
177                        cells: Vec::new(),
178                        new_cells: Vec::new(),
179                        id_counter: 0,
180                        rng: rand_chacha::ChaCha8Rng::seed_from_u64(
181                            decomposed_domain.rng_seed + plain_index.0 as u64,
182                        ),
183                    },
184                ))
185            });
186            let syncer = syncers.remove(&subdomain_plain_index).ok_or(BoundaryError(
187                "Index was not present in subdomain map".into(),
188            ))?;
189            use cellular_raza_concepts::BoundaryError;
190            let communicator =
191                communicators
192                    .remove(&subdomain_plain_index)
193                    .ok_or(BoundaryError(
194                        "Index was not present in subdomain map".into(),
195                    ))?;
196            let mut subdomain_box = SubDomainBox {
197                index: index.clone(),
198                subdomain_plain_index,
199                neighbors: neighbor_map
200                    .remove(&subdomain_plain_index)
201                    .unwrap()
202                    .into_iter()
203                    .collect(),
204                subdomain,
205                voxels: voxels.collect::<Result<_, SimulationError>>()?,
206                voxel_index_to_plain_index: voxel_index_to_plain_index.clone(),
207                plain_index_to_subdomain: plain_index_to_subdomain.clone(),
208                communicator,
209                syncer,
210            };
211            subdomain_box.insert_initial_cells(&mut cells, &init_aux_storage)?;
212            Ok((index, subdomain_box))
213        })
214        .collect::<Result<BTreeMap<_, _>, SimulationError>>()?;
215    let simulation_runner = SimulationRunner { subdomain_boxes };
216    Ok(simulation_runner)
217}
218
219/// Encapsulates a subdomain with cells and other simulation aspects.
220pub struct SubDomainBox<I, S, C, A, Com, Sy = BarrierSync>
221where
222    S: SubDomain,
223{
224    #[allow(unused)]
225    /// SubdomainIndex as given by the [Domain] and [SubDomain] traits.
226    pub index: I,
227    /// [SubDomainPlainIndex]
228    pub subdomain_plain_index: SubDomainPlainIndex,
229    /// Collection of neighboring [SubDomainPlainIndexs](SubDomainPlainIndex)
230    pub neighbors: BTreeSet<SubDomainPlainIndex>,
231    /// The constructed [SubDomain] as given by the [Domain] trait
232    pub subdomain: S,
233    /// Voxels of the subdomain as given by the [Domain] trait
234    pub voxels: std::collections::BTreeMap<VoxelPlainIndex, Voxel<C, A>>,
235    /// Helper map to convert from [SubDomain::VoxelIndex] to [SubDomainPlainIndex]
236    pub voxel_index_to_plain_index: BTreeMap<S::VoxelIndex, VoxelPlainIndex>,
237    /// Helper map to convert from [VoxelPlainIndex] to [SubDomainPlainIndex]
238    pub plain_index_to_subdomain: std::collections::BTreeMap<VoxelPlainIndex, SubDomainPlainIndex>,
239    /// Allows communication with other [SubDomains](SubDomain)
240    ///
241    /// This is used to exchange information about positions of agents at boundaries to calculate
242    /// forces or concentrations of nutrients etc.
243    pub communicator: Com,
244    /// Handles syncing of simulation steps between multiple parallel executions of [SubDomain]
245    /// update functions
246    pub syncer: Sy,
247}
248
249impl<I, S, C, A, Com, Sy> SubDomainBox<I, S, C, A, Com, Sy>
250where
251    S: SubDomain,
252{
253    /// Allows to sync between threads. In the most simplest
254    /// case of [BarrierSync] syncing is done by a global barrier.
255    #[cfg_attr(feature = "tracing", instrument(skip_all))]
256    pub fn sync(&mut self) -> Result<(), SimulationError>
257    where
258        Sy: SyncSubDomains,
259    {
260        self.syncer.sync()
261    }
262
263    /// Stores an error which has occurred and notifies other running threads to wind down.
264    #[cfg_attr(feature = "tracing", instrument(skip_all))]
265    pub fn store_error(
266        &mut self,
267        maybe_error: Result<(), SimulationError>,
268    ) -> Result<bool, SimulationError>
269    where
270        Sy: SyncSubDomains,
271    {
272        self.syncer.store_error(maybe_error)
273    }
274
275    // TODO this is not a boundary error!
276    /// Allows insertion of cells into the subdomain.
277    #[cfg_attr(feature = "tracing", instrument(skip_all))]
278    pub fn insert_initial_cells(
279        &mut self,
280        new_cells: &mut Vec<(C, Option<A>)>,
281        init_aux_storage: impl Fn(&C) -> A,
282    ) -> Result<(), cellular_raza_concepts::BoundaryError>
283    where
284        <S as SubDomain>::VoxelIndex: Eq + Hash + Ord,
285        S: SortCells<C, VoxelIndex = <S as SubDomain>::VoxelIndex>,
286    {
287        for (n_cell, (cell, aux_storage)) in new_cells.drain(..).enumerate() {
288            let voxel_index = self.subdomain.get_voxel_index_of(&cell)?;
289            let plain_index = self.voxel_index_to_plain_index[&voxel_index];
290            let voxel = self.voxels.get_mut(&plain_index).ok_or(BoundaryError(
291                "Could not find correct voxel for cell".to_owned(),
292            ))?;
293            let aux_storage = aux_storage.map_or(init_aux_storage(&cell), |x| x);
294            let cbox = CellBox::new_initial(n_cell, cell);
295            voxel.cells.push((cbox, aux_storage));
296            voxel.id_counter += 1;
297        }
298        Ok(())
299    }
300
301    /// Update all purely local functions
302    ///
303    /// Used to iterate over all cells in the current subdomain and running local functions which
304    /// need no communication with other subdomains
305    pub fn run_local_cell_funcs<Func, F>(
306        &mut self,
307        func: Func,
308        next_time_point: &crate::time::NextTimePoint<F>,
309    ) -> Result<(), super::SimulationError>
310    where
311        Func: Fn(
312            &mut C,
313            &mut A,
314            F,
315            &mut rand_chacha::ChaCha8Rng,
316        ) -> Result<(), super::SimulationError>,
317        F: Copy,
318    {
319        let dt = next_time_point.increment;
320        for (_, voxel) in self.voxels.iter_mut() {
321            for (cellbox, aux_storage) in voxel.cells.iter_mut() {
322                func(&mut cellbox.cell, aux_storage, dt, &mut voxel.rng)?;
323            }
324        }
325        Ok(())
326    }
327
328    /// TODO
329    pub fn run_local_subdomain_funcs<Func, F>(
330        &mut self,
331        func: Func,
332        next_time_point: &crate::time::NextTimePoint<F>,
333    ) -> Result<(), super::SimulationError>
334    where
335        Func: Fn(
336            &mut S,
337            F,
338            // &mut rand_chacha::ChaCha8Rng,
339        ) -> Result<(), super::SimulationError>,
340        F: Copy,
341    {
342        let dt = next_time_point.increment;
343        func(&mut self.subdomain, dt)?;
344        Ok(())
345    }
346
347    /// Save all subdomains with the given storage manager.
348    #[cfg_attr(feature = "tracing", instrument(skip(self, storage_manager)))]
349    pub fn save_subdomains<
350        #[cfg(feature = "tracing")] F: core::fmt::Debug,
351        #[cfg(not(feature = "tracing"))] F,
352    >(
353        &self,
354        storage_manager: &mut crate::storage::StorageManager<SubDomainPlainIndex, S>,
355        next_time_point: &crate::time::NextTimePoint<F>,
356    ) -> Result<(), StorageError>
357    where
358        S: Clone + Serialize,
359    {
360        if let Some(crate::time::TimeEvent::PartialSave) = next_time_point.event {
361            use crate::storage::StorageInterfaceStore;
362            storage_manager.store_single_element(
363                next_time_point.iteration as u64,
364                &self.subdomain_plain_index,
365                &self.subdomain,
366            )?;
367        }
368        Ok(())
369    }
370
371    /// Stores all cells of the subdomain via the given [storage_manager](crate::storage)
372    #[cfg_attr(feature = "tracing", instrument(skip(self, storage_manager)))]
373    pub fn save_cells<
374        #[cfg(feature = "tracing")] F: core::fmt::Debug,
375        #[cfg(not(feature = "tracing"))] F,
376    >(
377        &self,
378        storage_manager: &mut crate::storage::StorageManager<CellIdentifier, (CellBox<C>, A)>,
379        next_time_point: &crate::time::NextTimePoint<F>,
380    ) -> Result<(), StorageError>
381    where
382        A: Clone + Serialize,
383        C: Clone + Serialize,
384        CellBox<C>: cellular_raza_concepts::Id<Identifier = CellIdentifier>,
385    {
386        if let Some(crate::time::TimeEvent::PartialSave) = next_time_point.event {
387            use crate::storage::StorageInterfaceStore;
388            let cells = self
389                .voxels
390                .iter()
391                .map(|(_, vox)| vox.cells.iter().map(|ca| (ca.0.ref_id(), ca)))
392                .flatten();
393            storage_manager.store_batch_elements(next_time_point.iteration as u64, cells)?;
394        }
395        Ok(())
396    }
397}