cellular_raza_building_blocks/cell_building_blocks/
bacterial_rods.rs

1use crate::{CartesianCuboid, CartesianSubDomain};
2use cellular_raza_concepts::*;
3
4#[cfg(feature = "approx")]
5use approx::AbsDiffEq;
6use num::FromPrimitive;
7use serde::{Deserialize, Serialize};
8
9use nalgebra::{Const, Dyn, Matrix, VecStorage};
10
11/// A mechanical model for Bacterial Rods
12///
13/// See the [Bacterial Rods](https://cellular-raza.com/showcase/bacterial-rods) example for a
14/// detailed example.
15///
16/// # Parameters & Variables
17///
18/// | Symbol | Struct Field | Description |
19/// | --- | --- | --- |
20/// | $\gamma$ | `spring_tension` | Tension of the springs connecting the vertices. |
21/// | $D$ | `diffusion_constant` | Diffusion constant corresponding to brownian motion. |
22/// | $\lambda$ | `damping` | Damping constant. |
23/// | $l$ | `spring_length` | Length of an individual segment between two vertices. |
24/// | $\eta$ | `rigidity` | Rigidity with respect to bending the rod. |
25///
26/// # Equations
27///
28/// The vertices which are being modeled are stored in the `pos` struct field and their
29/// corresponding velocities in the `vel` field.
30///
31/// \\begin{equation}
32///     \vec{v}_i= \text{\texttt{rod\\_mechanics\.pos\.row(i)}}
33/// \\end{equation}
34///
35/// We define the edge $\vec{c}\_i:=\vec{v}\_i-\vec{v}\_{i-1}$.
36/// The first force acts between the vertices $v\_i$ of the model and aims to maintain an equal
37/// distance between all vertices via
38///
39/// \\begin{equation}
40///     \vec{F}\_{i,\text{springs}} = -\gamma\left(1-\frac{l}{||\vec{c}\_i||}\right)\vec{c}\_i
41///         +\gamma\left(1-\frac{l}{||\vec{c}\_{i+1}||}\right)\vec{c}\_{i+1}.
42/// \\end{equation}
43///
44/// We assume the properties of a simple elastic rod.
45/// With the angle $\alpha_i$ between adjacent edges $\vec{c}\_{i-1},\vec{c}\_i$ we can formulate
46/// the bending force which is proportional to the curvature $\kappa\_i$ at vertex $i$
47///
48/// \\begin{equation}
49///     \kappa\_i = 2\tan\left(\frac{\alpha\_i}{2}\right).
50/// \\end{equation}
51///
52/// The resulting force acts along the angle bisector which can be calculated from the edge
53/// vectors.
54/// The forces acting on vertices $\vec{v}\_i,\vec{v}\_{i-1},\vec{v}\_{i+1}$ are given by
55///
56/// \\begin{align}
57///     \vec{F}\_{i,\text{curvature}} &= \eta\kappa_i
58///         \frac{\vec{c}\_i - \vec{c}\_{i+1}}{|\vec{c}\_i-\vec{c}\_{i+1}|}\\\\
59///     \vec{F}\_{i-1,\text{curvature}} &= -\frac{1}{2}\vec{F}\_{i,\text{curvature}}\\\\
60///     \vec{F}\_{i+1,\text{curvature}} &= -\frac{1}{2}\vec{F}\_{i,\text{curvature}}
61/// \\end{align}
62///
63/// where $\eta\_i$ is the angle curvature at vertex $\vec{v}\_i$.
64/// The total force $\vec{F}_{i,\text{total}}$ at vertex $i$ consists of multiple contributions.
65///
66/// \\begin{equation}
67///     \vec{F}\_{i,\text{total}} = \vec{F}\_{i,\text{springs}} + \vec{F}\_{i,\text{curvature}}
68///         + \vec{F}\_{i,\text{external}}
69/// \\end{equation}
70///
71/// # References
72///
73#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
74#[serde(bound = "
75F: 'static
76    + PartialEq
77    + Clone
78    + core::fmt::Debug
79    + Serialize
80    + for<'a> Deserialize<'a>,
81")]
82pub struct RodMechanics<F, const D: usize> {
83    /// The current position
84    pub pos: Matrix<
85        F,
86        nalgebra::Dyn,
87        nalgebra::Const<D>,
88        nalgebra::VecStorage<F, nalgebra::Dyn, nalgebra::Const<D>>,
89    >,
90    /// The current velocity
91    pub vel: Matrix<
92        F,
93        nalgebra::Dyn,
94        nalgebra::Const<D>,
95        nalgebra::VecStorage<F, nalgebra::Dyn, nalgebra::Const<D>>,
96    >,
97    // pos_min: nalgebra::SVector<F, D>,
98    // pos_max: nalgebra::SVector<F, D>,
99    /// Controls magnitude of stochastic motion
100    pub diffusion_constant: F,
101    /// Spring tension between individual vertices
102    pub spring_tension: F,
103    /// Stiffness at each joint connecting two edges
104    pub rigidity: F,
105    /// Target spring length
106    pub spring_length: F,
107    /// Daming constant
108    pub damping: F,
109}
110
111#[cfg(feature = "approx")]
112impl<F, const D: usize> AbsDiffEq for RodMechanics<F, D>
113where
114    F: AbsDiffEq + nalgebra::Scalar,
115    F::Epsilon: Clone,
116{
117    type Epsilon = F::Epsilon;
118
119    fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
120        let RodMechanics {
121            pos,
122            vel,
123            diffusion_constant,
124            spring_tension,
125            rigidity,
126            spring_length,
127            damping,
128        } = &self;
129        pos.iter()
130            .zip(other.pos.iter())
131            .all(|(x, y)| x.abs_diff_eq(y, epsilon.clone()))
132            && vel
133                .iter()
134                .zip(other.vel.iter())
135                .all(|(x, y)| x.abs_diff_eq(y, epsilon.clone()))
136            && diffusion_constant.abs_diff_eq(&other.diffusion_constant, epsilon.clone())
137            && spring_tension.abs_diff_eq(&other.spring_tension, epsilon.clone())
138            && rigidity.abs_diff_eq(&other.rigidity, epsilon.clone())
139            && spring_length.abs_diff_eq(&other.spring_length, epsilon.clone())
140            && damping.abs_diff_eq(&other.damping, epsilon)
141    }
142
143    fn default_epsilon() -> Self::Epsilon {
144        F::default_epsilon()
145    }
146}
147
148impl<F, const D: usize>
149    Mechanics<
150        Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
151        Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
152        Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
153        F,
154    > for RodMechanics<F, D>
155where
156    F: nalgebra::RealField + Clone + num::Float,
157    rand_distr::StandardNormal: rand_distr::Distribution<F>,
158{
159    fn calculate_increment(
160        &self,
161        force: Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
162    ) -> Result<
163        (
164            Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
165            Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
166        ),
167        CalcError,
168    > {
169        use core::ops::AddAssign;
170        let one_half = F::one() / (F::one() + F::one());
171        let two = F::one() + F::one();
172
173        // Calculate internal force between the two points of the Agent
174        let mut total_force = force;
175
176        // Calculate force exerted by spring action between individual vertices
177        let n_rows = self.pos.nrows();
178        let dist_internal = self.pos.rows(0, n_rows - 1) - self.pos.rows(1, n_rows - 1);
179        dist_internal.row_iter().enumerate().for_each(|(i, dist)| {
180            if !dist.norm().is_zero() {
181                let dir = dist.normalize();
182                let force_internal =
183                    -dir * self.spring_tension * (dist.norm() - self.spring_length);
184                total_force.row_mut(i).add_assign(force_internal * one_half);
185                total_force
186                    .row_mut(i + 1)
187                    .add_assign(-force_internal * one_half);
188            }
189        });
190
191        // Calculate force exerted by curvature-contributions
192        use itertools::Itertools;
193        dist_internal
194            .row_iter()
195            .tuple_windows::<(_, _)>()
196            .enumerate()
197            .for_each(|(i, (conn1, conn2))| {
198                let angle = conn1.angle(&conn2);
199                let force_d = conn1 - conn2;
200                let force_dir = if !force_d.norm().is_zero() {
201                    force_d.normalize()
202                } else {
203                    force_d
204                };
205                let force =
206                    force_dir * two * self.rigidity * <F as num::Float>::tan(one_half * angle);
207                total_force.row_mut(i).add_assign(-force * one_half);
208                total_force.row_mut(i + 1).add_assign(force);
209                total_force.row_mut(i + 2).add_assign(-force * one_half);
210            });
211
212        // Calculate damping force
213        total_force -= &self.vel * self.damping;
214        Ok((self.vel.clone(), total_force))
215    }
216
217    fn get_random_contribution(
218        &self,
219        rng: &mut rand_chacha::ChaCha8Rng,
220        dt: F,
221    ) -> Result<
222        (
223            Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
224            Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
225        ),
226        RngError,
227    > {
228        let distr = match rand_distr::Normal::new(F::zero(), <F as num::Float>::sqrt(dt)) {
229            Ok(e) => Ok(e),
230            Err(e) => Err(cellular_raza_concepts::RngError(format!("{e}"))),
231        }?;
232        let dpos = nalgebra::Matrix::<F, Dyn, Const<D>, _>::from_distribution(
233            self.pos.nrows(),
234            &distr,
235            rng,
236        ) * <F as num::Float>::powi(F::one() + F::one(), -2)
237            * self.diffusion_constant
238            / dt;
239        let dvel = nalgebra::Matrix::<F, Dyn, Const<D>, _>::zeros(self.pos.nrows());
240
241        Ok((dpos, dvel))
242    }
243}
244
245impl<F: Clone, const D: usize> Position<Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>>
246    for RodMechanics<F, D>
247{
248    fn pos(&self) -> Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>> {
249        self.pos.clone()
250    }
251
252    fn set_pos(&mut self, position: &Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>) {
253        self.pos = position.clone();
254    }
255}
256
257impl<F: Clone, const D: usize> Velocity<Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>>
258    for RodMechanics<F, D>
259{
260    fn velocity(&self) -> Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>> {
261        self.vel.clone()
262    }
263
264    fn set_velocity(&mut self, velocity: &Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>) {
265        self.vel = velocity.clone();
266    }
267}
268
269/// Automatically derives a [Interaction] suitable for rods from a point-wise interaction.
270#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
271#[cfg_attr(feature = "approx", derive(AbsDiffEq))]
272pub struct RodInteraction<I>(pub I);
273
274impl<I, Inf> InteractionInformation<Inf> for RodInteraction<I>
275where
276    I: InteractionInformation<Inf>,
277{
278    fn get_interaction_information(&self) -> Inf {
279        self.0.get_interaction_information()
280    }
281}
282
283impl<I, F, Inf, const D: usize>
284    Interaction<
285        Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
286        Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
287        Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
288        Inf,
289    > for RodInteraction<I>
290where
291    I: Interaction<nalgebra::SVector<F, D>, nalgebra::SVector<F, D>, nalgebra::SVector<F, D>, Inf>,
292    F: 'static + nalgebra::RealField + Copy + core::fmt::Debug + num::Zero,
293{
294    fn calculate_force_between(
295        &self,
296        own_pos: &Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
297        own_vel: &Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
298        ext_pos: &Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
299        ext_vel: &Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
300        ext_inf: &Inf,
301    ) -> Result<
302        (
303            Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
304            Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
305        ),
306        CalcError,
307    > {
308        use core::ops::AddAssign;
309        use itertools::Itertools;
310        let mut force_own = nalgebra::Matrix::<F, Dyn, Const<D>, _>::zeros(own_vel.nrows());
311        let mut force_ext = nalgebra::Matrix::<F, Dyn, Const<D>, _>::zeros(own_vel.nrows());
312        for (i, p1) in own_pos.row_iter().enumerate() {
313            for (j, (p2_n0, p2_n1)) in ext_pos.row_iter().tuple_windows::<(_, _)>().enumerate() {
314                // Calculate the closest point of the external position
315                let (_, nearest_point, rel_length) = crate::nearest_point_from_point_to_line(
316                    &p1.transpose(),
317                    &(p2_n0.transpose(), p2_n1.transpose()),
318                );
319
320                let (f_own, f_ext) = self.0.calculate_force_between(
321                    &p1.transpose(),
322                    &own_vel.row(i).transpose(),
323                    &nearest_point,
324                    &ext_vel.row(j).transpose(),
325                    ext_inf,
326                )?;
327
328                force_own.row_mut(i).add_assign(f_own.transpose());
329                force_ext
330                    .row_mut(j)
331                    .add_assign(f_ext.transpose() * (F::one() - rel_length));
332                force_ext
333                    .row_mut((j + 1) % own_pos.nrows())
334                    .add_assign(f_ext.transpose() * rel_length);
335            }
336        }
337        Ok((force_own, force_ext))
338    }
339
340    fn is_neighbor(
341        &self,
342        own_pos: &Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
343        ext_pos: &Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
344        ext_inf: &Inf,
345    ) -> Result<bool, CalcError> {
346        for p in own_pos.row_iter() {
347            for q in ext_pos.row_iter() {
348                if self
349                    .0
350                    .is_neighbor(&p.transpose(), &q.transpose(), ext_inf)?
351                {
352                    return Ok(true);
353                }
354            }
355        }
356        Ok(false)
357    }
358
359    fn react_to_neighbors(&mut self, neighbors: usize) -> Result<(), CalcError> {
360        self.0.react_to_neighbors(neighbors)
361    }
362}
363
364/* type RodPos<F, const D: usize> = Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>;
365
366/// TODO
367pub fn calculate_force_rods<F, Inf, Func, const D: usize>(
368    own_pos: &RodPos<F, D>,
369    own_vel: &RodPos<F, D>,
370    ext_pos: &RodPos<F, D>,
371    ext_vel: &RodPos<F, D>,
372    ext_inf: &Inf,
373    own_min: &SVector<F, D>,
374    own_max: &SVector<F, D>,
375    ext_min: &SVector<F, D>,
376    ext_max: &SVector<F, D>,
377    cutoff: F,
378    force_func: Func,
379) -> Result<
380    (
381        Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
382        Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
383    ),
384    CalcError,
385>
386where
387    F: 'static + nalgebra::RealField + Copy + core::fmt::Debug + num::Zero,
388    Func: Fn(
389        &SVector<F, D>,
390        &SVector<F, D>,
391        &SVector<F, D>,
392        &SVector<F, D>,
393        &Inf,
394    ) -> Result<(SVector<F, D>, SVector<F, D>), CalcError>,
395{
396    use core::ops::AddAssign;
397    use itertools::Itertools;
398
399    let mut force_own = nalgebra::Matrix::<F, Dyn, Const<D>, _>::zeros(own_vel.nrows());
400    let mut force_ext = nalgebra::Matrix::<F, Dyn, Const<D>, _>::zeros(own_vel.nrows());
401    for (i, p1) in own_pos.row_iter().enumerate() {
402        for (j, (p2_n0, p2_n1)) in ext_pos.row_iter().tuple_windows::<(_, _)>().enumerate() {
403            // Calculate the closest point of the external position
404            let (_, nearest_point, rel_length) = crate::nearest_point_from_point_to_line(
405                &p1.transpose(),
406                &(p2_n0.transpose(), p2_n1.transpose()),
407            );
408
409            let (f_own, f_ext) = force_func(
410                &p1.transpose(),
411                &own_vel.row(i).transpose(),
412                &nearest_point,
413                &ext_vel.row(j).transpose(),
414                ext_inf,
415            )?;
416
417            force_own.row_mut(i).add_assign(f_own.transpose());
418            force_ext
419                .row_mut(j)
420                .add_assign(f_ext.transpose() * (F::one() - rel_length));
421            force_ext
422                .row_mut((j + 1) % own_pos.nrows())
423                .add_assign(f_ext.transpose() * rel_length);
424        }
425    }
426    Ok((force_own, force_ext))
427}*/
428
429/// Cells are represented by rods
430#[derive(Domain)]
431pub struct CartesianCuboidRods<F, const D: usize> {
432    /// The base-cuboid which is being repurposed
433    #[DomainRngSeed]
434    pub domain: CartesianCuboid<F, D>,
435    /// Gel pressure which is only relevant for 3D simulations and always acts with constant
436    /// force downwards (negative z-direction).
437    pub gel_pressure: F,
438    /// Computes friction at all surfaces of the box
439    pub surface_friction: F,
440    /// The distance for which the friction will be applied
441    pub surface_friction_distance: F,
442}
443
444impl<C, F, const D: usize> SortCells<C> for CartesianCuboidRods<F, D>
445where
446    C: Position<Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>>,
447    F: 'static
448        + nalgebra::Field
449        + Clone
450        + core::fmt::Debug
451        + num::FromPrimitive
452        + num::ToPrimitive
453        + num::Float
454        + Copy,
455{
456    type VoxelIndex = [usize; D];
457
458    fn get_voxel_index_of(&self, cell: &C) -> Result<Self::VoxelIndex, BoundaryError> {
459        let pos = cell.pos().row_sum().transpose() / F::from_usize(cell.pos().nrows()).unwrap();
460        let index = self.domain.get_voxel_index_of_raw(&pos)?;
461        Ok(index)
462    }
463}
464
465impl<F, const D: usize> DomainCreateSubDomains<CartesianSubDomainRods<F, D>>
466    for CartesianCuboidRods<F, D>
467where
468    F: 'static + num::Float + core::fmt::Debug + num::FromPrimitive,
469{
470    type SubDomainIndex = usize;
471    type VoxelIndex = [usize; D];
472
473    fn create_subdomains(
474        &self,
475        n_subdomains: std::num::NonZeroUsize,
476    ) -> Result<
477        impl IntoIterator<
478            Item = (
479                Self::SubDomainIndex,
480                CartesianSubDomainRods<F, D>,
481                Vec<Self::VoxelIndex>,
482            ),
483        >,
484        DecomposeError,
485    > {
486        let subdomains = self.domain.create_subdomains(n_subdomains)?;
487        Ok(subdomains
488            .into_iter()
489            .map(move |(subdomain_index, subdomain, voxels)| {
490                (
491                    subdomain_index,
492                    CartesianSubDomainRods::<F, D> {
493                        subdomain,
494                        gel_pressure: self.gel_pressure,
495                        surface_friction: self.surface_friction,
496                        surface_friction_distance: self.surface_friction,
497                    },
498                    voxels,
499                )
500            }))
501    }
502}
503
504/// The corresponding SubDomain of the [CartesianCuboidRods] domain.
505#[derive(Clone, SubDomain, Serialize, Deserialize)]
506#[serde(bound = "
507F: 'static
508    + PartialEq
509    + Clone
510    + core::fmt::Debug
511    + Serialize
512    + for<'a> Deserialize<'a>,
513[usize; D]: Serialize + for<'a> Deserialize<'a>,
514")]
515pub struct CartesianSubDomainRods<F, const D: usize> {
516    /// Base subdomain as created by the [CartesianCuboid] domain.
517    #[Base]
518    pub subdomain: CartesianSubDomain<F, D>,
519    /// See [CartesianCuboidRods]
520    pub gel_pressure: F,
521    /// Computes friction at all surfaces of the box
522    pub surface_friction: F,
523    /// The distance for which the friction will be applied
524    pub surface_friction_distance: F,
525}
526
527impl<F>
528    SubDomainForce<
529        Matrix<F, Dyn, Const<3>, VecStorage<F, Dyn, Const<3>>>,
530        Matrix<F, Dyn, Const<3>, VecStorage<F, Dyn, Const<3>>>,
531        Matrix<F, Dyn, Const<3>, VecStorage<F, Dyn, Const<3>>>,
532        F,
533    > for CartesianSubDomainRods<F, 3>
534where
535    F: nalgebra::RealField + num::Float,
536{
537    fn calculate_custom_force(
538        &self,
539        pos: &Matrix<F, Dyn, Const<3>, VecStorage<F, Dyn, Const<3>>>,
540        vel: &Matrix<F, Dyn, Const<3>, VecStorage<F, Dyn, Const<3>>>,
541        _: &F,
542    ) -> Result<
543        Matrix<F, Dyn, Const<3>, VecStorage<F, Dyn, Const<3>>>,
544        cellular_raza_concepts::CalcError,
545    > {
546        use core::ops::AddAssign;
547        let mut force = nalgebra::MatrixXx3::from_fn(pos.nrows(), |_, m| {
548            if m == 2 {
549                -self.gel_pressure
550            } else {
551                F::zero()
552            }
553        });
554        for (i, (p, v)) in pos.row_iter().zip(vel.row_iter()).enumerate() {
555            let d1 = (p.transpose() - self.subdomain.domain_min)
556                .map(|x| <F as num::Float>::abs(x) <= self.surface_friction_distance);
557            let d2 = (p.transpose() - self.subdomain.domain_max)
558                .map(|x| <F as num::Float>::abs(x) <= self.surface_friction_distance);
559            let q = v.norm();
560            if q != F::zero() && d1.zip_map(&d2, |x, y| x || y).into_iter().any(|x| *x) {
561                let dir = v / q;
562                force
563                    .row_mut(i)
564                    .add_assign(-dir * self.gel_pressure * self.surface_friction);
565            }
566        }
567        Ok(force)
568    }
569}
570
571impl<F, const D: usize>
572    SubDomainMechanics<
573        Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
574        Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
575    > for CartesianSubDomainRods<F, D>
576where
577    F: nalgebra::RealField + num::Float,
578{
579    fn apply_boundary(
580        &self,
581        pos: &mut Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
582        vel: &mut Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
583    ) -> Result<(), BoundaryError> {
584        // TODO refactor this with matrix multiplication!!!
585        // This will probably be much more efficient and less error-prone!
586
587        // For each position in the springs Agent<D1, D2>
588        let two = F::one() + F::one();
589        pos.row_iter_mut()
590            .zip(vel.row_iter_mut())
591            .for_each(|(mut p, mut v)| {
592                // For each dimension in the space
593                for i in 0..p.ncols() {
594                    // Check if the particle is below lower edge
595                    if p[i] < self.subdomain.get_domain_min()[i] {
596                        p[i] = self.subdomain.get_domain_min()[i] * two - p[i];
597                        v[i] = <F as num::Float>::abs(v[i]);
598                    }
599
600                    // Check if the particle is over the edge
601                    if p[i] > self.subdomain.get_domain_max()[i] {
602                        p[i] = self.subdomain.get_domain_max()[i] * two - p[i];
603                        v[i] = -<F as num::Float>::abs(v[i]);
604                    }
605                }
606            });
607
608        // If new pos is still out of boundary return error
609        for j in 0..pos.nrows() {
610            let p = pos.row(j);
611            for i in 0..pos.ncols() {
612                if p[i] < self.subdomain.get_domain_min()[i]
613                    || p[i] > self.subdomain.get_domain_max()[i]
614                {
615                    return Err(BoundaryError(format!(
616                        "Particle is out of domain at pos {:?}",
617                        pos
618                    )));
619                }
620            }
621        }
622        Ok(())
623    }
624}
625
626impl<C, F, const D: usize> SortCells<C> for CartesianSubDomainRods<F, D>
627where
628    C: Position<Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>>,
629    F: 'static
630        + nalgebra::Field
631        + Clone
632        + core::fmt::Debug
633        + num::FromPrimitive
634        + num::ToPrimitive
635        + num::Float
636        + Copy,
637{
638    type VoxelIndex = [usize; D];
639
640    fn get_voxel_index_of(&self, cell: &C) -> Result<Self::VoxelIndex, BoundaryError> {
641        let pos = cell.pos().row_sum().transpose() / F::from_usize(cell.pos().nrows()).unwrap();
642        let index = self.subdomain.get_index_of(pos)?;
643        Ok(index)
644    }
645}
646
647impl<F, const D: usize> RodMechanics<F, D> {
648    /// Divides a [RodMechanics] struct into two thus separating their positions
649    ///
650    /// ```
651    /// # use cellular_raza_building_blocks::*;
652    /// use nalgebra::MatrixXx2;
653    /// let n_vertices = 7;
654    /// let mut pos = MatrixXx2::zeros(n_vertices);
655    /// pos
656    ///     .row_iter_mut()
657    ///     .enumerate()
658    ///     .for_each(|(n_row, mut r)| r[0] += n_row as f32 * 0.5);
659    /// let mut m1 = RodMechanics {
660    ///     pos,
661    ///     vel: MatrixXx2::zeros(n_vertices),
662    ///     diffusion_constant: 0.0,
663    ///     spring_tension: 0.1,
664    ///     rigidity: 0.05,
665    ///     spring_length: 0.5,
666    ///     damping: 0.0,
667    /// };
668    /// let radius = 0.25;
669    /// let m2 = m1.divide(radius)?;
670    ///
671    /// let last_pos_m1 = m1.pos.row(6);
672    /// let first_pos_m2 = m2.pos.row(0);
673    /// assert!(((last_pos_m1 - first_pos_m2).norm() - 2.0 * radius).abs() < 1e-3);
674    /// # Result::<(), cellular_raza_concepts::DivisionError>::Ok(())
675    /// ```
676    pub fn divide(
677        &mut self,
678        radius: F,
679    ) -> Result<RodMechanics<F, D>, cellular_raza_concepts::DivisionError>
680    where
681        F: num::Float + nalgebra::RealField + FromPrimitive + std::iter::Sum,
682    {
683        use itertools::Itertools;
684        let pos = self.pos();
685        let c1 = self;
686        let mut c2 = c1.clone();
687
688        let n_rows = c1.pos.nrows();
689        // Calculate the fraction of how much we need to scale down the individual spring lengths
690        // in order for the distances to still work.
691        let two = F::one() + F::one();
692        let one_half = F::one() / two;
693        let div_factor = one_half - radius / (F::from_usize(n_rows).unwrap() * c1.spring_length);
694
695        // Shrink spring length
696        let new_spring_length = div_factor * c1.spring_length;
697        c1.spring_length = new_spring_length;
698        c2.spring_length = new_spring_length;
699
700        // Set starting point
701        c1.pos.set_row(0, &pos.row(0));
702        c2.pos
703            .set_row(c2.pos.nrows() - 1, &pos.row(c2.pos.nrows() - 1));
704
705        let segments: Vec<_> = pos
706            .row_iter()
707            .tuple_windows::<(_, _)>()
708            .map(|(x, y)| (x - y).norm())
709            .collect();
710        let segment_length = (segments.iter().copied().sum::<F>() - two * radius)
711            / F::from_usize(c2.pos.nrows() - 1).unwrap()
712            / two;
713
714        for n_vertex in 0..c2.pos.nrows() {
715            // Get smallest index k such that the beginning of the new segment is "farther" than the
716            // original vertex at this index k.
717            let k = (0..segments.len())
718                .filter(|n| {
719                    segments.iter().copied().take(*n).sum::<F>()
720                        <= F::from_usize(n_vertex).unwrap() * segment_length
721                })
722                .max()
723                .ok_or(DivisionError(
724                    "Could not calculate new segment lengths".to_string(),
725                ))?;
726            let q = (F::from_usize(n_vertex).unwrap() * segment_length
727                - segments.iter().copied().take(k).sum::<F>())
728                / segments[k];
729            c1.pos.set_row(
730                n_vertex,
731                &(pos.row(k) * (F::one() - q) + pos.row(k + 1) * q),
732            );
733
734            let m = (0..c2.pos.nrows())
735                .filter(|n| {
736                    segments.iter().rev().copied().take(*n).sum::<F>()
737                        <= F::from_usize(n_vertex).unwrap() * segment_length
738                })
739                .max()
740                .ok_or(DivisionError(
741                    "Could not calculate new segment lengths".to_string(),
742                ))?;
743            let p = (F::from_usize(n_vertex).unwrap() * segment_length
744                - segments.iter().rev().copied().take(m).sum::<F>())
745                / segments[c2.pos.nrows() - m - 2];
746            c2.pos.set_row(
747                c2.pos.nrows() - n_vertex - 1,
748                &(pos.row(c2.pos.nrows() - m - 1) * (F::one() - p)
749                    + pos.row(c2.pos.nrows() - m - 2) * p),
750            );
751        }
752        Ok(c2)
753    }
754}