1type ReactionVector<F> = nalgebra::DVector<F>;
2
3use cellular_raza_concepts::*;
4use serde::{Deserialize, Serialize};
5
6use super::{CartesianCuboid, CartesianSubDomain};
7
8#[derive(Domain, Clone)]
13pub struct CartesianDiffusion2D<F> {
14 #[DomainRngSeed]
16 #[DomainPartialDerive]
17 #[SortCells]
18 pub domain: CartesianCuboid<F, 2>,
19 pub reactions_dx: nalgebra::Vector2<F>,
22 pub diffusion_constant: F,
24 pub initial_value: ReactionVector<F>,
26}
27
28#[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 #[Base]
40 #[SortCells]
41 #[Mechanics]
42 subdomain: CartesianSubDomain<F, 2>,
43 reactions_min: nalgebra::Vector2<F>,
44 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 pub diffusion_constant: F,
52 increments: [ndarray::Array3<F>; 3],
53 increments_start: usize,
54 helper: ndarray::Array3<F>,
55}
56
57#[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 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 let shared_min = min.sup(&index_min);
94 let shared_max = max.inf(&index_max);
95
96 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 = 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#[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 let co = &self.extracellular;
165
166 self.helper.fill(F::zero());
170 self.helper.slice_mut(s![1..-1, 1..-1, ..]).assign(co);
175
176 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 let start = self.increments_start;
200 self.increments[start].fill(F::zero());
201
202 let dc = self.diffusion_constant;
203 self.increments[start].add_assign(&(&self.helper.slice(s![1..-1, 1..-1, ..]) * dd2 * dc));
205 self.increments[start].add_assign(&(&self.helper.slice(s![..-2, 1..-1, ..]) * dx2 * dc));
207 self.increments[start].add_assign(&(&self.helper.slice(s![2.., 1..-1, ..]) * dx2 * dc));
209 self.increments[start].add_assign(&(&self.helper.slice(s![1..-1, ..-2, ..]) * dy2 * dc));
211 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 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 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 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 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 let min = min.component_mul(&n_diffusion);
381 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 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_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}