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    pub(crate) index: I,
226    pub(crate) subdomain_plain_index: SubDomainPlainIndex,
227    pub(crate) neighbors: BTreeSet<SubDomainPlainIndex>,
228    pub(crate) subdomain: S,
229    pub(crate) voxels: std::collections::BTreeMap<VoxelPlainIndex, Voxel<C, A>>,
230    pub(crate) voxel_index_to_plain_index: BTreeMap<S::VoxelIndex, VoxelPlainIndex>,
231    pub(crate) plain_index_to_subdomain:
232        std::collections::BTreeMap<VoxelPlainIndex, SubDomainPlainIndex>,
233    pub(crate) communicator: Com,
234    pub(crate) syncer: Sy,
235}
236
237impl<I, S, C, A, Com, Sy> SubDomainBox<I, S, C, A, Com, Sy>
238where
239    S: SubDomain,
240{
241    /// Allows to sync between threads. In the most simplest
242    /// case of [BarrierSync] syncing is done by a global barrier.
243    #[cfg_attr(feature = "tracing", instrument(skip_all))]
244    pub fn sync(&mut self) -> Result<(), SimulationError>
245    where
246        Sy: SyncSubDomains,
247    {
248        self.syncer.sync()
249    }
250
251    /// Stores an error which has occurred and notifies other running threads to wind down.
252    #[cfg_attr(feature = "tracing", instrument(skip_all))]
253    pub fn store_error(
254        &mut self,
255        maybe_error: Result<(), SimulationError>,
256    ) -> Result<bool, SimulationError>
257    where
258        Sy: SyncSubDomains,
259    {
260        self.syncer.store_error(maybe_error)
261    }
262
263    // TODO this is not a boundary error!
264    /// Allows insertion of cells into the subdomain.
265    #[cfg_attr(feature = "tracing", instrument(skip_all))]
266    pub fn insert_initial_cells(
267        &mut self,
268        new_cells: &mut Vec<(C, Option<A>)>,
269        init_aux_storage: impl Fn(&C) -> A,
270    ) -> Result<(), cellular_raza_concepts::BoundaryError>
271    where
272        <S as SubDomain>::VoxelIndex: Eq + Hash + Ord,
273        S: SortCells<C, VoxelIndex = <S as SubDomain>::VoxelIndex>,
274    {
275        for (n_cell, (cell, aux_storage)) in new_cells.drain(..).enumerate() {
276            let voxel_index = self.subdomain.get_voxel_index_of(&cell)?;
277            let plain_index = self.voxel_index_to_plain_index[&voxel_index];
278            let voxel = self.voxels.get_mut(&plain_index).ok_or(BoundaryError(
279                "Could not find correct voxel for cell".to_owned(),
280            ))?;
281            let aux_storage = aux_storage.map_or(init_aux_storage(&cell), |x| x);
282            let cbox = CellBox::new_initial(n_cell, cell);
283            voxel.cells.push((cbox, aux_storage));
284            voxel.id_counter += 1;
285        }
286        Ok(())
287    }
288
289    /// Update all purely local functions
290    ///
291    /// Used to iterate over all cells in the current subdomain and running local functions which
292    /// need no communication with other subdomains
293    pub fn run_local_cell_funcs<Func, F>(
294        &mut self,
295        func: Func,
296        next_time_point: &crate::time::NextTimePoint<F>,
297    ) -> Result<(), super::SimulationError>
298    where
299        Func: Fn(
300            &mut C,
301            &mut A,
302            F,
303            &mut rand_chacha::ChaCha8Rng,
304        ) -> Result<(), super::SimulationError>,
305        F: Copy,
306    {
307        let dt = next_time_point.increment;
308        for (_, voxel) in self.voxels.iter_mut() {
309            for (cellbox, aux_storage) in voxel.cells.iter_mut() {
310                func(&mut cellbox.cell, aux_storage, dt, &mut voxel.rng)?;
311            }
312        }
313        Ok(())
314    }
315
316    /// TODO
317    pub fn run_local_subdomain_funcs<Func, F>(
318        &mut self,
319        func: Func,
320        next_time_point: &crate::time::NextTimePoint<F>,
321    ) -> Result<(), super::SimulationError>
322    where
323        Func: Fn(
324            &mut S,
325            F,
326            // &mut rand_chacha::ChaCha8Rng,
327        ) -> Result<(), super::SimulationError>,
328        F: Copy,
329    {
330        let dt = next_time_point.increment;
331        func(&mut self.subdomain, dt)?;
332        Ok(())
333    }
334
335    /// Save all subdomains with the given storage manager.
336    #[cfg_attr(feature = "tracing", instrument(skip(self, storage_manager)))]
337    pub fn save_subdomains<
338        #[cfg(feature = "tracing")] F: core::fmt::Debug,
339        #[cfg(not(feature = "tracing"))] F,
340    >(
341        &self,
342        storage_manager: &mut crate::storage::StorageManager<SubDomainPlainIndex, S>,
343        next_time_point: &crate::time::NextTimePoint<F>,
344    ) -> Result<(), StorageError>
345    where
346        S: Clone + Serialize,
347    {
348        if let Some(crate::time::TimeEvent::PartialSave) = next_time_point.event {
349            use crate::storage::StorageInterfaceStore;
350            storage_manager.store_single_element(
351                next_time_point.iteration as u64,
352                &self.subdomain_plain_index,
353                &self.subdomain,
354            )?;
355        }
356        Ok(())
357    }
358
359    /// Stores all cells of the subdomain via the given [storage_manager](crate::storage)
360    #[cfg_attr(feature = "tracing", instrument(skip(self, storage_manager)))]
361    pub fn save_cells<
362        #[cfg(feature = "tracing")] F: core::fmt::Debug,
363        #[cfg(not(feature = "tracing"))] F,
364    >(
365        &self,
366        storage_manager: &mut crate::storage::StorageManager<CellIdentifier, (CellBox<C>, A)>,
367        next_time_point: &crate::time::NextTimePoint<F>,
368    ) -> Result<(), StorageError>
369    where
370        A: Clone + Serialize,
371        C: Clone + Serialize,
372        CellBox<C>: cellular_raza_concepts::Id<Identifier = CellIdentifier>,
373    {
374        if let Some(crate::time::TimeEvent::PartialSave) = next_time_point.event {
375            use crate::storage::StorageInterfaceStore;
376            let cells = self
377                .voxels
378                .iter()
379                .map(|(_, vox)| vox.cells.iter().map(|ca| (ca.0.ref_id(), ca)))
380                .flatten();
381            storage_manager.store_batch_elements(next_time_point.iteration as u64, cells)?;
382        }
383        Ok(())
384    }
385}