Skip to main content

cellular_raza_building_blocks/domains/
cartesian_2d_diffusion.rs

1type ReactionVector<F> = nalgebra::DVector<F>;
2
3use cellular_raza_concepts::*;
4use serde::{Deserialize, Serialize};
5
6use super::{CartesianCuboid, CartesianSubDomain};
7
8/// Domain based on [CartesianCuboid] in 2 dimensions with extracellular diffusion.
9///
10/// We solve the equations
11///
12#[derive(Domain, Clone)]
13pub struct CartesianDiffusion2D<F> {
14    /// See [CartesianCuboid]
15    #[DomainRngSeed]
16    #[DomainPartialDerive]
17    #[SortCells]
18    pub domain: CartesianCuboid<F, 2>,
19    /// The discretization must be a multiple of the voxel size.
20    /// This quantity will be used as an initial estimate and rounded to the nearest candidate.
21    pub reactions_dx: nalgebra::Vector2<F>,
22    /// Diffusion constant
23    pub diffusion_constant: F,
24    /// Initial values which are uniform over the simulation domain
25    pub initial_value: ReactionVector<F>,
26}
27
28/// Subdomain for the [CartesianDiffusion2D] domain.
29#[derive(SubDomain, Clone, Debug, Serialize, Deserialize)]
30#[serde(bound = "F:
31    'static
32    + PartialEq
33    + Clone
34    + core::fmt::Debug
35    + Serialize
36    + for<'a> Deserialize<'a>")]
37pub struct CartesianDiffusion2DSubDomain<F> {
38    /// See [CartesianSubDomain]
39    #[Base]
40    #[SortCells]
41    #[Mechanics]
42    subdomain: CartesianSubDomain<F, 2>,
43    reactions_min: nalgebra::Vector2<F>,
44    // reactions_max: nalgebra::Vector2<F>,
45    reactions_dx: nalgebra::Vector2<F>,
46    index_min: nalgebra::Vector2<usize>,
47    index_max: nalgebra::Vector2<usize>,
48    extracellular: ndarray::Array3<F>,
49    ownership_array: ndarray::Array2<bool>,
50    /// Diffusion constant
51    pub diffusion_constant: F,
52    increments: [ndarray::Array3<F>; 3],
53    increments_start: usize,
54    helper: ndarray::Array3<F>,
55}
56
57/// This type is not intendend for direct usage
58#[doc(hidden)]
59pub struct BorderInfo {
60    min_sent: nalgebra::Vector2<usize>,
61    max_sent: nalgebra::Vector2<usize>,
62}
63
64impl<F> CartesianDiffusion2DSubDomain<F>
65where
66    F: nalgebra::RealField + Clone,
67{
68    fn assign_neighbor(&mut self, neighbor: NeighborValue<F>) {
69        use ndarray::*;
70        let NeighborValue { min, max, values } = neighbor;
71
72        // Cast everything to isize to avoid overflows
73        let min = min.cast::<isize>();
74        let max = max.cast::<isize>();
75        let index_min = self.index_min.cast::<isize>();
76        let index_max = self.index_max.cast::<isize>();
77
78        // Legend
79        // o = own array
80        // x = neighbor value
81        //
82        // x x x x x x . . . .
83        // x x x x x x . . . .
84        // x x x o o o o o o o -- shared_min[1]
85        // x x x o o o o o o o
86        // x x x o o o o o o o -- shared_max[1]
87        // . . . o o o o o o o
88        // . . . o o o o o o o
89        //       |   |
90        //       |   shared_max[0]
91        //       |
92        //       shared_min[0]
93        let shared_min = min.sup(&index_min);
94        let shared_max = max.inf(&index_max);
95
96        // x x x x x x . . . .                   o o o o o o . . . .
97        // x x h h h h . . . . -- helper_min[1]  o o o o o o . . . .
98        // x x h o o o o o o o                   o o o o o o h x x x -- helper_min[1]
99        // x x h o o o o o o o                   o o o o o o h x x x
100        // x x h o o o o o o o -- helper_max[1]  o o o o o o h x x x
101        // . . . o o o o o o o                   . . . h h h h x x x -- helper_max[1]
102        // . . . o o o o o o o                   . . . x x x x x x x
103        //     |     |                                 |     |
104        //     |     helper_max[0]                     |     helper_max[0]
105        //     |                                       |
106        //     heper_min[0]                            helper_min[0]
107        let helper_min = shared_min.add_scalar(-1).sup(&min);
108        let helper_max = shared_max.add_scalar(1).inf(&max);
109
110        let nmin = helper_min - min;
111        let nmax = helper_max - min;
112        let hmin = (helper_min - index_min).add_scalar(1);
113        // let hmax = (helper_max - index_min).inf(&shared_max).add_scalar(1);
114        let hmax = nmax - nmin + hmin;
115
116        Zip::from(
117            self.helper
118                .slice_mut(s![hmin[0]..hmax[0], hmin[1]..hmax[1], ..])
119                .lanes_mut(Axis(2)),
120        )
121        .and(values.slice(s![nmin[0]..nmax[0], nmin[1]..nmax[1]]))
122        .and(
123            self.ownership_array
124                .slice(s![hmin[0]..hmax[0], hmin[1]..hmax[1]]),
125        )
126        .for_each(|mut w, v, t| {
127            if let (false, Some(vi)) = (*t, v) {
128                w.assign(vi);
129            }
130        });
131    }
132}
133
134/// This type is not intendend for direct usage
135#[doc(hidden)]
136pub struct NeighborValue<F> {
137    min: nalgebra::Vector2<usize>,
138    max: nalgebra::Vector2<usize>,
139    values: ndarray::Array2<Option<ndarray::Array1<F>>>,
140}
141
142impl<F> SubDomainReactions<nalgebra::SVector<F, 2>, ReactionVector<F>, F>
143    for CartesianDiffusion2DSubDomain<F>
144where
145    F: nalgebra::RealField + Copy + num::traits::AsPrimitive<usize> + ndarray::ScalarOperand,
146    usize: num::traits::AsPrimitive<F>,
147{
148    type BorderInfo = BorderInfo;
149    type NeighborValue = NeighborValue<F>;
150
151    fn treat_increments<I, J>(&mut self, neighbors: I, sources: J) -> Result<(), CalcError>
152    where
153        I: IntoIterator<Item = Self::NeighborValue>,
154        J: IntoIterator<Item = (nalgebra::SVector<F, 2>, ReactionVector<F>)>,
155    {
156        use core::ops::AddAssign;
157        use ndarray::*;
158        let two = F::one() + F::one();
159        let dx2 = self.reactions_dx[0].powf(-two);
160        let dy2 = self.reactions_dx[1].powf(-two);
161        let dd2 = -two * (dx2 + dy2);
162
163        // Helper variable to store current concentrations
164        let co = &self.extracellular;
165
166        // Use helper array which is +2 in every spatial dimension larger than the original array
167        // We do this to seamlessly incorporate boundary conditions
168        // Fill inner part of the array
169        self.helper.fill(F::zero());
170        // _ _ _ _ _ _ _
171        // _ x x x x x _
172        // _ x x x x x _
173        // _ _ _ _ _ _ _
174        self.helper.slice_mut(s![1..-1, 1..-1, ..]).assign(co);
175
176        // First assume that we obey neumann-boundary conditions and fill outer parts accordingly
177        // _ x x x x x _
178        // x _ _ _ _ _ x
179        // x _ _ _ _ _ x
180        // _ x x x x x _
181        self.helper
182            .slice_mut(s![0, 1..-1, ..])
183            .assign(&co.slice(s![0, .., ..]));
184        self.helper
185            .slice_mut(s![-1, 1..-1, ..])
186            .assign(&co.slice(s![-1, .., ..]));
187        self.helper
188            .slice_mut(s![1..-1, 0, ..])
189            .assign(&co.slice(s![.., 0, ..]));
190        self.helper
191            .slice_mut(s![1..-1, -1, ..])
192            .assign(&co.slice(s![.., -1, ..]));
193
194        for neighbor in neighbors {
195            self.assign_neighbor(neighbor);
196        }
197
198        // Set increment to next time-step to F::zero() everywhere
199        let start = self.increments_start;
200        self.increments[start].fill(F::zero());
201
202        let dc = self.diffusion_constant;
203        // - 2u[i,j] /dx^2 - 2u[i,j]/dy^2
204        self.increments[start].add_assign(&(&self.helper.slice(s![1..-1, 1..-1, ..]) * dd2 * dc));
205        // + u[i-1,j]/dx^2
206        self.increments[start].add_assign(&(&self.helper.slice(s![..-2, 1..-1, ..]) * dx2 * dc));
207        // + u[i+1,j]/dx^2
208        self.increments[start].add_assign(&(&self.helper.slice(s![2.., 1..-1, ..]) * dx2 * dc));
209        // + u[i,j-1]/dy^2
210        self.increments[start].add_assign(&(&self.helper.slice(s![1..-1, ..-2, ..]) * dy2 * dc));
211        // + u[i,j+1]/dy^2
212        self.increments[start].add_assign(&(&self.helper.slice(s![1..-1, 2.., ..]) * dy2 * dc));
213
214        for (pos, dextra) in sources {
215            let index = self.get_extracellular_index(&pos)?;
216            for (n, &v) in dextra.iter().enumerate() {
217                self.increments[start]
218                    .slice_mut(ndarray::s![index[0], index[1], n])
219                    .add_assign(v);
220            }
221        }
222
223        Ok(())
224    }
225
226    fn update_fluid_dynamics(&mut self, dt: F) -> Result<(), CalcError> {
227        use core::ops::AddAssign;
228        use num::traits::AsPrimitive;
229        let start = self.increments_start;
230        let n_incr = self.increments.len();
231
232        // Adams-Bashforth 3rd order
233        let k1: F = 5usize.as_() / 12usize.as_();
234        let k2: F = 8usize.as_() / 12usize.as_();
235        let k3: F = -1usize.as_() / 12usize.as_();
236        self.extracellular.add_assign(
237            &(&self.increments[start] * k1 * dt
238                + &self.increments[(start + 1) % n_incr] * k2 * dt
239                + &self.increments[(start + 2) % n_incr] * k3 * dt),
240        );
241        self.extracellular.map_inplace(|x| *x = x.max(F::zero()));
242
243        // TODO DEBUGGING
244        // if start == 0 {
245        //     todo!();
246        // }
247
248        self.increments_start = (self.increments_start + 1) % n_incr;
249        Ok(())
250    }
251
252    fn get_extracellular_at_pos(
253        &self,
254        pos: &nalgebra::SVector<F, 2>,
255    ) -> Result<ReactionVector<F>, CalcError> {
256        let index = self.get_extracellular_index(pos)?;
257        let res = ReactionVector::<F>::from_iterator(
258            self.helper.dim().2,
259            self.extracellular
260                .slice(ndarray::s![index[0], index[1], ..])
261                .to_owned(),
262        );
263        Ok(res)
264    }
265
266    fn get_neighbor_value(&self, border_info: Self::BorderInfo) -> Self::NeighborValue {
267        use ndarray::*;
268        // Calculate shared indices plus padding of one
269        let BorderInfo { min_sent, max_sent } = border_info;
270        let min = min_sent.map(|x| x.saturating_sub(1)).sup(&self.index_min);
271        let max = max_sent.map(|x| x.saturating_add(1)).inf(&self.index_max);
272
273        let omin = min - self.index_min;
274        let omax = max - self.index_min;
275        let values = Zip::from(
276            self.extracellular
277                .slice(s![omin[0]..omax[0], omin[1]..omax[1], ..])
278                .lanes(Axis(2)),
279        )
280        .and(
281            self.ownership_array
282                .slice(s![omin[0] + 1..omax[0] + 1, omin[1] + 1..omax[1] + 1]),
283        )
284        .map_collect(|v, &o| if o { Some(v.to_owned()) } else { None })
285        .to_owned();
286
287        NeighborValue { min, max, values }
288    }
289
290    fn get_border_info(&self) -> Self::BorderInfo {
291        Self::BorderInfo {
292            min_sent: self.index_min,
293            max_sent: self.index_max,
294        }
295    }
296}
297
298impl<F> CartesianDiffusion2DSubDomain<F>
299where
300    F: nalgebra::RealField + Copy + num::traits::AsPrimitive<usize>,
301    usize: num::traits::AsPrimitive<F>,
302{
303    fn get_extracellular_index(
304        &self,
305        pos: &nalgebra::Vector2<F>,
306    ) -> Result<nalgebra::Vector2<usize>, CalcError> {
307        use num::traits::AsPrimitive;
308        let index = (pos - self.reactions_min).component_div(&self.reactions_dx);
309        if index
310            .iter()
311            .enumerate()
312            .any(|(n, &x)| x < F::zero() || x > self.index_max[n].as_())
313        {
314            return Err(CalcError(format!(
315                "Could not find index for position {:?}",
316                pos
317            )));
318        }
319        Ok(index.map(|x| x.floor().as_()))
320    }
321}
322
323impl<F> DomainCreateSubDomains<CartesianDiffusion2DSubDomain<F>> for CartesianDiffusion2D<F>
324where
325    F: nalgebra::RealField + Copy + num::traits::AsPrimitive<usize>,
326    usize: num::traits::AsPrimitive<F>,
327    F: 'static + num::Float + core::fmt::Debug + num::FromPrimitive,
328{
329    type SubDomainIndex = usize;
330    type VoxelIndex = [usize; 2];
331
332    fn create_subdomains(
333        &self,
334        n_subdomains: core::num::NonZeroUsize,
335    ) -> Result<
336        impl IntoIterator<
337            Item = (
338                Self::SubDomainIndex,
339                CartesianDiffusion2DSubDomain<F>,
340                Vec<Self::VoxelIndex>,
341            ),
342        >,
343        DecomposeError,
344    > {
345        let dx = self.reactions_dx;
346        let dx_domain = self.domain.get_dx();
347        let n_diffusion = dx_domain
348            .component_div(&dx)
349            .map(|x| (<F as num::Float>::round(x).as_()).max(1));
350        let dx = dx_domain
351            .component_div(&n_diffusion.map(|x| <usize as num::traits::AsPrimitive<F>>::as_(x)));
352
353        let diffusion_constant = self.diffusion_constant;
354
355        // Calculate lattice points for subdomains
356        // Introduce padding on the outside of the simulated domain.
357        let [nrows, ncols] = n_diffusion
358            .component_mul(&self.domain.get_n_voxels())
359            .into();
360
361        let extracellular_total = ndarray::Array3::from_shape_fn(
362            (nrows, ncols, self.initial_value.len()),
363            |(_, _, n)| self.initial_value[n],
364        );
365
366        Ok(self
367            .domain
368            .create_subdomains(n_subdomains)?
369            .into_iter()
370            .map(move |(index, subdomain, voxels)| {
371                let max_domain = [nrows, ncols].into();
372                let mut min: nalgebra::Vector2<usize> = max_domain;
373                let mut max: nalgebra::Vector2<usize> = [0; 2].into();
374                for vox in subdomain.get_voxels() {
375                    min = min.inf(&vox.into());
376                    max = max.sup(&vox.into());
377                }
378
379                // Multiply with number of voxels in each dimension
380                let min = min.component_mul(&n_diffusion);
381                // Here we need to add one more step since this is supposed to be an upper limit
382                let max = max.component_mul(&n_diffusion) + n_diffusion;
383                let max_domain = max_domain.component_mul(&n_diffusion);
384
385                let extracellular = extracellular_total
386                    .slice(ndarray::s![min[0]..max[0], min[1]..max[1], ..])
387                    .into_owned();
388
389                let reactions_min = min
390                    .map(|x| <usize as num::traits::AsPrimitive<F>>::as_(x))
391                    .component_mul(&dx);
392                // let reactions_max = max
393                //     .map(|x| <usize as num::traits::AsPrimitive<F>>::as_(x))
394                //     .component_mul(&dx);
395
396                // Has entry `true` if the given point is owned by this subdomain.
397                let d = extracellular.dim();
398                let mut ownership_array =
399                    ndarray::Array2::<bool>::from_elem((d.0 + 2, d.1 + 2), false);
400                for v in subdomain.get_voxels() {
401                    let one = nalgebra::Vector2::from([1; 2]);
402                    let v: nalgebra::Vector2<usize> = v.into();
403                    let vox = v.component_mul(&n_diffusion);
404                    let voxp1 = (v + one).component_mul(&n_diffusion);
405                    let lower = (vox - min).add_scalar(1);
406                    let upper = (voxp1 - min).inf(&max_domain).add_scalar(1);
407                    ownership_array
408                        .slice_mut(ndarray::s![lower[0]..upper[0], lower[1]..upper[1]])
409                        .fill(true);
410                }
411
412                let sh = extracellular.shape();
413                let increment = ndarray::Array3::zeros((sh[0], sh[1], sh[2]));
414                let helper = ndarray::Array3::zeros((sh[0] + 2, sh[1] + 2, sh[2]));
415                (
416                    index,
417                    CartesianDiffusion2DSubDomain {
418                        subdomain,
419                        reactions_min,
420                        // reactions_max,
421                        reactions_dx: dx,
422                        index_min: min,
423                        index_max: max,
424                        extracellular,
425                        ownership_array,
426                        diffusion_constant,
427                        increments_start: 0,
428                        increments: [increment.clone(), increment.clone(), increment.clone()],
429                        helper,
430                    },
431                    voxels,
432                )
433            }))
434    }
435}