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