cellular_raza_core/backend/chili/
update_reactions.rs

1use super::{
2    Communicator, ReactionsRungeKuttaSolver, RungeKutta, SimulationError, SubDomainBox,
3    SubDomainPlainIndex, UpdateReactions, UpdateReactionsContact, Voxel, VoxelPlainIndex,
4    reactions_contact_adams_bashforth_3rd,
5};
6use cellular_raza_concepts::*;
7
8use num::FromPrimitive;
9#[cfg(feature = "tracing")]
10use tracing::instrument;
11
12/// Carries information about the border given by the [ReactionsExtra] trait between subdomains.
13pub struct ReactionsExtraBorderInfo<Binfo>(pub SubDomainPlainIndex, pub Binfo);
14
15/// Return information of border value after having obtained the [SubDomainReactions::BorderInfo]
16pub struct ReactionsExtraBorderReturn<Bvalue>(pub SubDomainPlainIndex, pub Bvalue);
17
18impl<I, S, C, A, Com, Sy> SubDomainBox<I, S, C, A, Com, Sy>
19where
20    S: SubDomain,
21{
22    /// Send [ReactionsExtraBorderInfo] to neighboring subdomains
23    #[cfg_attr(feature = "tracing", instrument(skip_all))]
24    pub fn update_reactions_extra_step_1<Pos, Ri, Re, Float>(
25        &mut self,
26    ) -> Result<(), SimulationError>
27    where
28        C: ReactionsExtra<Ri, Re>,
29        S: SubDomainReactions<Pos, Re, Float>,
30        Com: Communicator<
31                SubDomainPlainIndex,
32                ReactionsExtraBorderInfo<<S as SubDomainReactions<Pos, Re, Float>>::BorderInfo>,
33            >,
34    {
35        for neighbor_index in self.neighbors.iter() {
36            let border_info = self.subdomain.get_border_info();
37            self.communicator.send(
38                neighbor_index,
39                ReactionsExtraBorderInfo(self.subdomain_plain_index, border_info),
40            )?;
41        }
42        Ok(())
43    }
44
45    /// Receive [ReactionsExtraBorderInfo] of neighboring subdomains and return
46    /// [ReactionsExtraBorderReturn]
47    #[cfg_attr(feature = "tracing", instrument(skip_all))]
48    pub fn update_reactions_extra_step_2<Pos, Ri, Re, Float>(
49        &mut self,
50        determinism: bool,
51    ) -> Result<(), SimulationError>
52    where
53        C: ReactionsExtra<Ri, Re>,
54        S: SubDomainReactions<Pos, Re, Float>,
55        Com: Communicator<
56                SubDomainPlainIndex,
57                ReactionsExtraBorderReturn<
58                    <S as SubDomainReactions<Pos, Re, Float>>::NeighborValue,
59                >,
60            >,
61        Com: Communicator<
62                SubDomainPlainIndex,
63                ReactionsExtraBorderInfo<<S as SubDomainReactions<Pos, Re, Float>>::BorderInfo>,
64            >,
65    {
66        let mut received_infos = <Com as Communicator<
67            SubDomainPlainIndex,
68            ReactionsExtraBorderInfo<<S as SubDomainReactions<Pos, Re, Float>>::BorderInfo>,
69        >>::receive(&mut self.communicator);
70        if determinism {
71            received_infos.sort_by_key(|info| info.0);
72        }
73        for border_info in received_infos {
74            let boundary_value = self.subdomain.get_neighbor_value(border_info.1);
75            // Send back information about the increment
76            self.communicator.send(
77                &border_info.0,
78                ReactionsExtraBorderReturn(self.subdomain_plain_index, boundary_value),
79            )?;
80        }
81        Ok(())
82    }
83
84    /// Receive [ReactionsExtraBorderReturn] and update values.
85    #[cfg_attr(feature = "tracing", instrument(skip_all))]
86    pub fn update_reactions_extra_step_3<Pos, Ri, Re, Float>(
87        &mut self,
88        determinism: bool,
89    ) -> Result<(), SimulationError>
90    where
91        C: ReactionsExtra<Ri, Re>,
92        C: Intracellular<Ri>,
93        A: UpdateReactions<Ri>,
94        C: Position<Pos>,
95        S: SubDomainReactions<Pos, Re, Float>,
96        Com: Communicator<
97                SubDomainPlainIndex,
98                ReactionsExtraBorderReturn<
99                    <S as SubDomainReactions<Pos, Re, Float>>::NeighborValue,
100                >,
101            >,
102    {
103        let mut errors: Vec<CalcError> = vec![];
104        // TODO Think about doing this in two passes
105        // Pass 1: Calculate increments (dintra, dextra) and store them in AuxStorage (both!)
106        // Pass 2: Use non-mutable iterator (ie. self.voxels.iter()) instead of .iter_mut()
107        //         to give results to treat_increments function.
108        let sources = self
109            .voxels
110            .iter_mut()
111            .map(|(_, vox)| {
112                vox.cells.iter_mut().map(|(cbox, aux_storage)| {
113                    let intracellular = cbox.get_intracellular();
114                    let pos = cbox.pos();
115                    let extracellular = self.subdomain.get_extracellular_at_pos(&pos)?;
116                    let (dintra, dextra) =
117                        cbox.calculate_combined_increment(&intracellular, &extracellular)?;
118                    aux_storage.incr_conc(dintra);
119                    Result::<_, CalcError>::Ok((pos, dextra))
120                })
121            })
122            .flatten()
123            .collect::<Result<Vec<_>, CalcError>>()?;
124        match errors.len() {
125            1 => return Err(errors.pop().unwrap().into()),
126            _ => (),
127        }
128        let mut neighbors = <Com as Communicator<
129            SubDomainPlainIndex,
130            ReactionsExtraBorderReturn<<S as SubDomainReactions<Pos, Re, Float>>::NeighborValue>,
131        >>::receive(&mut self.communicator);
132        if determinism {
133            neighbors.sort_by_key(|v| v.0);
134        }
135        self.subdomain
136            .treat_increments(neighbors.into_iter().map(|n| n.1), sources.into_iter())?;
137        Ok(())
138    }
139}
140
141/// This information will be sent from one cell to another to determine their combined reactions.
142pub struct ReactionsContactInformation<Pos, Ri, RInf> {
143    /// Current position
144    pub pos: Pos,
145    /// Current intracellular values
146    pub intracellular: Ri,
147    /// Information shared between cells
148    pub info: RInf,
149    /// Index of cell in stored vector
150    ///
151    /// When returning information, this property is needed in order
152    /// to get the correct cell in the vector of cells and update its properties.
153    pub cell_index_in_vector: usize,
154    /// Voxel index of the sending cell.
155    /// Information should be returned to this voxel.
156    pub index_sender: VoxelPlainIndex,
157    /// Voxel index of the voxel from which information is requested.
158    /// This index is irrelevant after the initial query has been sent.
159    pub index_receiver: VoxelPlainIndex,
160}
161
162/// This informatino is returned after receiving [ReactionsContactInformation] and delivers the
163/// increment.
164pub struct ReactionsContactReturn<Ri> {
165    /// Increment of intracellular
166    pub intracellular: Ri,
167    /// Index of cell in stored vector
168    ///
169    /// This property works in tandem with [Self::index_sender] in order to send
170    /// the calculated information to the correct cell and update its properties.
171    pub cell_index_in_vector: usize,
172    /// The voxel index where information is returned to
173    pub index_sender: VoxelPlainIndex,
174}
175
176impl<C, A> Voxel<C, A> {
177    #[cfg_attr(feature = "tracing", instrument(skip_all))]
178    pub(crate) fn calculate_contact_reactions_between_cells_internally<
179        Ri,
180        Pos,
181        RInf,
182        Float,
183        const N: usize,
184    >(
185        &mut self,
186    ) -> Result<(), CalcError>
187    where
188        C: cellular_raza_concepts::ReactionsContact<Ri, Pos, Float, RInf>,
189        C: cellular_raza_concepts::Intracellular<Ri>,
190        C: Position<Pos>,
191        Ri: cellular_raza_concepts::Xapy<Float>,
192        A: UpdateReactionsContact<Ri, N>,
193        A: UpdateReactions<Ri>,
194        Float: num::Float,
195    {
196        let one_half: Float = Float::one() / (Float::one() + Float::one());
197
198        for n in 0..self.cells.len() {
199            for m in n + 1..self.cells.len() {
200                let mut cells_mut = self.cells.iter_mut();
201                let (c1, aux1) = cells_mut.nth(n).unwrap();
202                let (c2, aux2) = cells_mut.nth(m - n - 1).unwrap();
203
204                let p1 = c1.pos();
205                let intra1 = c1.get_intracellular();
206                let rinf1 = c1.get_contact_information();
207                let p2 = c2.pos();
208                let intra2 = c2.get_intracellular();
209                let rinf2 = c2.get_contact_information();
210
211                let (dintra11, dintra12) =
212                    c1.calculate_contact_increment(&intra1, &intra2, &p1, &p2, &rinf2)?;
213                let (dintra22, dintra21) =
214                    c2.calculate_contact_increment(&intra2, &intra1, &p2, &p1, &rinf1)?;
215
216                aux1.incr_conc(dintra11.xapy(one_half, &dintra21.xa(one_half)));
217                aux2.incr_conc(dintra22.xapy(one_half, &dintra12.xa(one_half)));
218            }
219        }
220        Ok(())
221    }
222
223    #[cfg_attr(feature = "tracing", instrument(skip_all))]
224    pub(crate) fn calculate_contact_reactions_between_cells_external<
225        Ri,
226        Pos,
227        RInf,
228        Float,
229        const N: usize,
230    >(
231        &mut self,
232        ext_pos: &Pos,
233        ext_intra: &Ri,
234        ext_rinf: &RInf,
235    ) -> Result<Ri, CalcError>
236    where
237        C: cellular_raza_concepts::ReactionsContact<Ri, Pos, Float, RInf>,
238        C: cellular_raza_concepts::Intracellular<Ri>,
239        C: Position<Pos>,
240        Ri: Xapy<Float>,
241        A: UpdateReactions<Ri>,
242        A: UpdateReactionsContact<Ri, N>,
243        Float: num::Float,
244    {
245        let one_half = Float::one() / (Float::one() + Float::one());
246        let mut dextra_total = ext_intra.xa(Float::zero());
247        for (cell, aux_storage) in self.cells.iter_mut() {
248            let own_intra = cell.get_intracellular();
249            let own_pos = cell.pos();
250
251            let (dintra, dextra) = cell.calculate_contact_increment(
252                &own_intra, &ext_intra, &own_pos, &ext_pos, &ext_rinf,
253            )?;
254            aux_storage.incr_conc(dintra.xa(one_half));
255            dextra_total = dextra.xapy(one_half, &dextra_total);
256        }
257        Ok(dextra_total)
258    }
259}
260
261impl<I, S, C, A, Com, Sy> SubDomainBox<I, S, C, A, Com, Sy>
262where
263    S: SubDomain,
264{
265    /// Send [ReactionsContactInformation] to neighboring subdomains.
266    #[cfg_attr(feature = "tracing", instrument(skip_all))]
267    pub fn update_contact_reactions_step_1<Ri, Pos, RInf, Float, const N: usize>(
268        &mut self,
269    ) -> Result<(), SimulationError>
270    where
271        Pos: Clone,
272        C: cellular_raza_concepts::ReactionsContact<Ri, Pos, Float, RInf>,
273        C: cellular_raza_concepts::Intracellular<Ri>,
274        C: cellular_raza_concepts::Position<Pos>,
275        A: UpdateReactions<Ri>,
276        A: UpdateReactionsContact<Ri, N>,
277        Ri: Xapy<Float> + Clone,
278        RInf: Clone,
279        Float: num::Float,
280        // <S as SubDomain>::VoxelIndex: Ord,
281        Com: Communicator<SubDomainPlainIndex, ReactionsContactInformation<Pos, Ri, RInf>>,
282    {
283        for (_, vox) in self.voxels.iter_mut() {
284            vox.calculate_contact_reactions_between_cells_internally::<Ri, Pos, RInf, Float, N>()?;
285        }
286
287        // TODO can we do this without memory allocation?
288        // or simply allocate when creating the subdomain
289        let key_iterator: Vec<_> = self.voxels.keys().map(|k| *k).collect();
290
291        for voxel_index in key_iterator {
292            for cell_index_in_vector in 0..self.voxels[&voxel_index].cells.len() {
293                let cell_pos = self.voxels[&voxel_index].cells[cell_index_in_vector]
294                    .0
295                    .pos();
296                let cell_intra = self.voxels[&voxel_index].cells[cell_index_in_vector]
297                    .0
298                    .get_intracellular();
299                let cell_contact_inf = self.voxels[&voxel_index].cells[cell_index_in_vector]
300                    .0
301                    .get_contact_information();
302                let mut incr = cell_intra.xa(Float::zero());
303                // TODO can we do this without cloning at all?
304                let neighbors = self.voxels[&voxel_index].neighbors.clone();
305                for neighbor_index in neighbors {
306                    match self.voxels.get_mut(&neighbor_index) {
307                        Some(vox) => Ok::<(), CalcError>(
308                            incr = incr.xapy(
309                                Float::one(),
310                                &vox.calculate_contact_reactions_between_cells_external(
311                                    &cell_pos,
312                                    &cell_intra,
313                                    &cell_contact_inf,
314                                )?,
315                            ),
316                            // Ok(())
317                        ),
318                        None => Ok(self.communicator.send(
319                            &self.plain_index_to_subdomain[&neighbor_index],
320                            ReactionsContactInformation {
321                                pos: cell_pos.clone(),
322                                intracellular: cell_intra.clone(),
323                                info: cell_contact_inf.clone(),
324                                cell_index_in_vector,
325                                index_sender: voxel_index,
326                                index_receiver: neighbor_index.clone(),
327                            },
328                        )?),
329                    }?;
330                }
331                self.voxels.get_mut(&voxel_index).unwrap().cells[cell_index_in_vector]
332                    .1
333                    .incr_conc(incr);
334            }
335        }
336        Ok(())
337    }
338
339    /// Receive [ReactionsContactInformation], perform calculations of increments of
340    /// [ReactionsContact] and return [ReactionsContactReturn]
341    #[cfg_attr(feature = "tracing", instrument(skip_all))]
342    pub fn update_contact_reactions_step_2<Ri, Pos, RInf, Float, const N: usize>(
343        &mut self,
344        determinism: bool,
345    ) -> Result<(), SimulationError>
346    where
347        C: cellular_raza_concepts::ReactionsContact<Ri, Pos, Float, RInf> + Position<Pos>,
348        C: cellular_raza_concepts::Intracellular<Ri>,
349        A: UpdateReactions<Ri> + UpdateReactionsContact<Ri, N>,
350        Ri: Xapy<Float>,
351        Float: num::Float,
352        Pos: Clone,
353        Com: Communicator<SubDomainPlainIndex, ReactionsContactInformation<Pos, Ri, RInf>>,
354        Com: Communicator<SubDomainPlainIndex, ReactionsContactReturn<Ri>>,
355    {
356        // Receive contactinformation and send back increments
357        let mut received_infos = <Com as Communicator<
358            SubDomainPlainIndex,
359            ReactionsContactInformation<Pos, Ri, RInf>,
360        >>::receive(&mut self.communicator);
361        if determinism {
362            received_infos.sort_by_key(|info| info.index_sender);
363        }
364        for contact_info in received_infos {
365            let vox = self.voxels.get_mut(&contact_info.index_receiver).ok_or(
366                cellular_raza_concepts::IndexError(format!(
367                    "EngineError: Voxel with index {:?} of ReactionsContactInformation can not be\
368                    found in this threads.",
369                    contact_info.index_receiver
370                )),
371            )?;
372            // Calculate the contact increments from cells in voxel
373            let incr = vox.calculate_contact_reactions_between_cells_external(
374                &contact_info.pos,
375                &contact_info.intracellular,
376                &contact_info.info,
377            )?;
378
379            // Send back information about the increment
380            self.communicator.send(
381                &self.plain_index_to_subdomain[&contact_info.index_sender],
382                ReactionsContactReturn {
383                    intracellular: incr,
384                    cell_index_in_vector: contact_info.cell_index_in_vector,
385                    index_sender: contact_info.index_sender,
386                },
387            )?;
388        }
389        Ok(())
390    }
391
392    /// Receive all calculated increments and include them for later update steps.
393    #[cfg_attr(feature = "tracing", instrument(skip(self)))]
394    pub fn update_contact_reactions_step_3<Ri>(
395        &mut self,
396        determinism: bool,
397    ) -> Result<(), SimulationError>
398    where
399        A: UpdateReactions<Ri>,
400        Com: Communicator<SubDomainPlainIndex, ReactionsContactReturn<Ri>>,
401    {
402        // Update position and velocity of all cells with new information
403        let mut received_infos = <Com as Communicator<
404            SubDomainPlainIndex,
405            ReactionsContactReturn<Ri>,
406        >>::receive(&mut self.communicator);
407        if determinism {
408            received_infos.sort_by_key(|info| info.index_sender);
409        }
410        for obt_intracellular in received_infos {
411            let error_1 = format!(
412                "EngineError: Sender with plain index {:?} was ended up in location\
413                where index is not present anymore",
414                obt_intracellular.index_sender
415            );
416            let vox = self
417                .voxels
418                .get_mut(&obt_intracellular.index_sender)
419                .ok_or(cellular_raza_concepts::IndexError(error_1))?;
420            let error_2 = format!(
421                "\
422                EngineError: Force Information with sender index {:?} and\
423                cell at vector position {} could not be matched",
424                obt_intracellular.index_sender, obt_intracellular.cell_index_in_vector
425            );
426            match vox.cells.get_mut(obt_intracellular.cell_index_in_vector) {
427                Some((_, aux_storage)) => {
428                    Ok(aux_storage.incr_conc(obt_intracellular.intracellular))
429                }
430                None => Err(cellular_raza_concepts::IndexError(error_2)),
431            }?;
432        }
433        Ok(())
434    }
435}
436
437/// Updates the cells intracellular values from the obtained contact informations
438#[cfg_attr(feature = "tracing", instrument(skip_all))]
439pub fn local_update_contact_reactions<
440    C,
441    A,
442    Ri,
443    #[cfg(feature = "tracing")] F: core::fmt::Debug,
444    #[cfg(not(feature = "tracing"))] F,
445>(
446    cell: &mut C,
447    aux_storage: &mut A,
448    _dt: F,
449    _rng: &mut rand_chacha::ChaCha8Rng,
450) -> Result<(), SimulationError>
451where
452    A: UpdateReactions<Ri> + UpdateReactionsContact<Ri, 2>,
453    C: cellular_raza_concepts::Intracellular<Ri>,
454    F: num::Float + FromPrimitive,
455    Ri: Xapy<F> + Clone,
456{
457    reactions_contact_adams_bashforth_3rd(cell, aux_storage)?;
458    Ok(())
459}
460
461/// Calculates the increment from the [Reactions](cellular_raza_concepts::Reactions) trait.
462#[allow(private_bounds)]
463#[cfg_attr(feature = "tracing", instrument(skip_all))]
464pub fn local_reactions_intracellular<
465    C,
466    A,
467    Ri,
468    #[cfg(feature = "tracing")] F: core::fmt::Debug,
469    #[cfg(not(feature = "tracing"))] F,
470    const N: usize,
471>(
472    cell: &mut C,
473    aux_storage: &mut A,
474    dt: F,
475    _rng: &mut rand_chacha::ChaCha8Rng,
476) -> Result<(), SimulationError>
477where
478    A: UpdateReactions<Ri>,
479    C: cellular_raza_concepts::Reactions<Ri>,
480    F: num::Float,
481    Ri: Xapy<F>,
482    ReactionsRungeKuttaSolver<N>: RungeKutta<N>,
483{
484    ReactionsRungeKuttaSolver::<N>::update(cell, aux_storage, dt)?;
485    Ok(())
486}
487
488/// Ensures that intracellular increments have been cleared before the next update step.
489#[cfg_attr(feature = "tracing", instrument(skip_all))]
490pub fn local_reactions_use_increment<
491    C,
492    A,
493    Ri,
494    #[cfg(feature = "tracing")] F: core::fmt::Debug,
495    #[cfg(not(feature = "tracing"))] F,
496>(
497    cell: &mut C,
498    aux_storage: &mut A,
499    dt: F,
500    _rng: &mut rand_chacha::ChaCha8Rng,
501) -> Result<(), SimulationError>
502where
503    C: Intracellular<Ri>,
504    A: UpdateReactions<Ri>,
505    Ri: Xapy<F>,
506    F: num::Zero,
507{
508    let intra = cell.get_intracellular();
509    let dintra = aux_storage.get_conc();
510    cell.set_intracellular(dintra.xapy(dt, &intra));
511    aux_storage.set_conc(aux_storage.get_conc().xa(F::zero()));
512    Ok(())
513}
514
515/// Performs the increment operation.
516///
517/// The [SubDomainReactions::update_fluid_dynamics] and [SubDomainReactions::treat_increments] work
518/// together as an abstraction to allow for more complicated solvers.
519#[cfg_attr(feature = "tracing", instrument(skip_all))]
520pub fn local_subdomain_update_reactions_extra<S, Ri, Re, Float>(
521    subdomain: &mut S,
522    dt: Float,
523) -> Result<(), SimulationError>
524where
525    S: SubDomainReactions<Ri, Re, Float>,
526{
527    subdomain.update_fluid_dynamics(dt)?;
528    Ok(())
529}