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    /// Controls magnitude of stochastic motion
98    pub diffusion_constant: F,
99    /// Spring tension between individual vertices
100    pub spring_tension: F,
101    /// Stiffness at each joint connecting two edges
102    pub rigidity: F,
103    /// Target spring length
104    pub spring_length: F,
105    /// Daming constant
106    pub damping: F,
107}
108
109#[cfg(feature = "approx")]
110impl<F, const D: usize> AbsDiffEq for RodMechanics<F, D>
111where
112    F: AbsDiffEq + nalgebra::Scalar,
113    F::Epsilon: Clone,
114{
115    type Epsilon = F::Epsilon;
116
117    fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
118        let RodMechanics {
119            pos,
120            vel,
121            diffusion_constant,
122            spring_tension,
123            rigidity,
124            spring_length,
125            damping,
126        } = &self;
127        pos.iter()
128            .zip(other.pos.iter())
129            .all(|(x, y)| x.abs_diff_eq(y, epsilon.clone()))
130            && vel
131                .iter()
132                .zip(other.vel.iter())
133                .all(|(x, y)| x.abs_diff_eq(y, epsilon.clone()))
134            && diffusion_constant.abs_diff_eq(&other.diffusion_constant, epsilon.clone())
135            && spring_tension.abs_diff_eq(&other.spring_tension, epsilon.clone())
136            && rigidity.abs_diff_eq(&other.rigidity, epsilon.clone())
137            && spring_length.abs_diff_eq(&other.spring_length, epsilon.clone())
138            && damping.abs_diff_eq(&other.damping, epsilon)
139    }
140
141    fn default_epsilon() -> Self::Epsilon {
142        F::default_epsilon()
143    }
144}
145
146impl<F, const D: usize>
147    Mechanics<
148        Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
149        Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
150        Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
151        F,
152    > for RodMechanics<F, D>
153where
154    F: nalgebra::RealField + Clone + num::Float,
155    rand_distr::StandardNormal: rand_distr::Distribution<F>,
156{
157    fn calculate_increment(
158        &self,
159        force: Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
160    ) -> Result<
161        (
162            Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
163            Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
164        ),
165        CalcError,
166    > {
167        use core::ops::AddAssign;
168        let one_half = F::one() / (F::one() + F::one());
169        let two = F::one() + F::one();
170
171        // Calculate internal force between the two points of the Agent
172        let mut total_force = force;
173
174        // Calculate force exerted by spring action between individual vertices
175        let n_rows = self.pos.nrows();
176        let dist_internal = self.pos.rows(0, n_rows - 1) - self.pos.rows(1, n_rows - 1);
177        dist_internal.row_iter().enumerate().for_each(|(i, dist)| {
178            if !dist.norm().is_zero() {
179                let dir = dist.normalize();
180                let force_internal =
181                    -dir * self.spring_tension * (dist.norm() - self.spring_length);
182                total_force.row_mut(i).add_assign(force_internal * one_half);
183                total_force
184                    .row_mut(i + 1)
185                    .add_assign(-force_internal * one_half);
186            }
187        });
188
189        // Calculate force exerted by curvature-contributions
190        use itertools::Itertools;
191        dist_internal
192            .row_iter()
193            .tuple_windows::<(_, _)>()
194            .enumerate()
195            .for_each(|(i, (conn1, conn2))| {
196                let angle = conn1.angle(&conn2);
197                let force_d = conn1 - conn2;
198                let force_dir = if !force_d.norm().is_zero() {
199                    force_d.normalize()
200                } else {
201                    force_d
202                };
203                let force =
204                    force_dir * two * self.rigidity * <F as num::Float>::tan(one_half * angle);
205                total_force.row_mut(i).add_assign(-force * one_half);
206                total_force.row_mut(i + 1).add_assign(force);
207                total_force.row_mut(i + 2).add_assign(-force * one_half);
208            });
209
210        // Calculate damping force
211        total_force -= &self.vel * self.damping;
212        Ok((self.vel.clone(), total_force))
213    }
214
215    fn get_random_contribution(
216        &self,
217        rng: &mut rand_chacha::ChaCha8Rng,
218        dt: F,
219    ) -> Result<
220        (
221            Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
222            Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
223        ),
224        RngError,
225    > {
226        let distr = match rand_distr::Normal::new(F::zero(), <F as num::Float>::sqrt(dt)) {
227            Ok(e) => Ok(e),
228            Err(e) => Err(cellular_raza_concepts::RngError(format!("{e}"))),
229        }?;
230        let dpos = nalgebra::Matrix::<F, Dyn, Const<D>, _>::from_distribution(
231            self.pos.nrows(),
232            &distr,
233            rng,
234        ) * <F as num::Float>::powi(F::one() + F::one(), -2)
235            * self.diffusion_constant
236            / dt;
237        let dvel = nalgebra::Matrix::<F, Dyn, Const<D>, _>::zeros(self.pos.nrows());
238
239        Ok((dpos, dvel))
240    }
241}
242
243impl<F: Clone, const D: usize> Position<Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>>
244    for RodMechanics<F, D>
245{
246    fn pos(&self) -> Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>> {
247        self.pos.clone()
248    }
249
250    fn set_pos(&mut self, position: &Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>) {
251        self.pos = position.clone();
252    }
253}
254
255impl<F: Clone, const D: usize> Velocity<Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>>
256    for RodMechanics<F, D>
257{
258    fn velocity(&self) -> Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>> {
259        self.vel.clone()
260    }
261
262    fn set_velocity(&mut self, velocity: &Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>) {
263        self.vel = velocity.clone();
264    }
265}
266
267/// Automatically derives a [Interaction] suitable for rods from a point-wise interaction.
268#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
269#[cfg_attr(feature = "approx", derive(AbsDiffEq))]
270pub struct RodInteraction<I>(pub I);
271
272impl<I, Inf> InteractionInformation<Inf> for RodInteraction<I>
273where
274    I: InteractionInformation<Inf>,
275{
276    fn get_interaction_information(&self) -> Inf {
277        self.0.get_interaction_information()
278    }
279}
280
281impl<I, F, Inf, const D: usize>
282    Interaction<
283        Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
284        Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
285        Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
286        Inf,
287    > for RodInteraction<I>
288where
289    I: Interaction<nalgebra::SVector<F, D>, nalgebra::SVector<F, D>, nalgebra::SVector<F, D>, Inf>,
290    F: 'static + nalgebra::RealField + Copy + core::fmt::Debug + num::Zero,
291{
292    fn calculate_force_between(
293        &self,
294        own_pos: &Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
295        own_vel: &Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
296        ext_pos: &Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
297        ext_vel: &Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
298        ext_inf: &Inf,
299    ) -> Result<
300        (
301            Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
302            Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
303        ),
304        CalcError,
305    > {
306        use core::ops::AddAssign;
307        use itertools::Itertools;
308        let mut force_own = nalgebra::Matrix::<F, Dyn, Const<D>, _>::zeros(own_vel.nrows());
309        let mut force_ext = nalgebra::Matrix::<F, Dyn, Const<D>, _>::zeros(own_vel.nrows());
310        for (i, p1) in own_pos.row_iter().enumerate() {
311            for (j, (p2_n0, p2_n1)) in ext_pos.row_iter().tuple_windows::<(_, _)>().enumerate() {
312                // Calculate the closest point of the external position
313                let (_, nearest_point, rel_length) = crate::nearest_point_from_point_to_line(
314                    &p1.transpose(),
315                    &(p2_n0.transpose(), p2_n1.transpose()),
316                );
317
318                let (f_own, f_ext) = self.0.calculate_force_between(
319                    &p1.transpose(),
320                    &own_vel.row(i).transpose(),
321                    &nearest_point,
322                    &ext_vel.row(j).transpose(),
323                    ext_inf,
324                )?;
325
326                force_own.row_mut(i).add_assign(f_own.transpose());
327                force_ext
328                    .row_mut(j)
329                    .add_assign(f_ext.transpose() * (F::one() - rel_length));
330                force_ext
331                    .row_mut((j + 1) % own_pos.nrows())
332                    .add_assign(f_ext.transpose() * rel_length);
333            }
334        }
335        Ok((force_own, force_ext))
336    }
337}
338
339/// Cells are represented by rods
340#[derive(Domain)]
341pub struct CartesianCuboidRods<F, const D: usize> {
342    /// The base-cuboid which is being repurposed
343    #[DomainRngSeed]
344    pub domain: CartesianCuboid<F, D>,
345    /// Gel pressure which is only relevant for 3D simulations and always acts with constant
346    /// force downwards (negative z-direction).
347    pub gel_pressure: F,
348    /// Computes friction at all surfaces of the box
349    pub surface_friction: F,
350    /// The distance for which the friction will be applied
351    pub surface_friction_distance: F,
352}
353
354impl<C, F, const D: usize> SortCells<C> for CartesianCuboidRods<F, D>
355where
356    C: Position<Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>>,
357    F: 'static
358        + nalgebra::Field
359        + Clone
360        + core::fmt::Debug
361        + num::FromPrimitive
362        + num::ToPrimitive
363        + num::Float
364        + Copy,
365{
366    type VoxelIndex = [usize; D];
367
368    fn get_voxel_index_of(&self, cell: &C) -> Result<Self::VoxelIndex, BoundaryError> {
369        let pos = cell.pos().row_sum().transpose() / F::from_usize(cell.pos().nrows()).unwrap();
370        let index = self.domain.get_voxel_index_of_raw(&pos)?;
371        Ok(index)
372    }
373}
374
375impl<F, const D: usize> DomainCreateSubDomains<CartesianSubDomainRods<F, D>>
376    for CartesianCuboidRods<F, D>
377where
378    F: 'static + num::Float + core::fmt::Debug + num::FromPrimitive,
379{
380    type SubDomainIndex = usize;
381    type VoxelIndex = [usize; D];
382
383    fn create_subdomains(
384        &self,
385        n_subdomains: std::num::NonZeroUsize,
386    ) -> Result<
387        impl IntoIterator<
388            Item = (
389                Self::SubDomainIndex,
390                CartesianSubDomainRods<F, D>,
391                Vec<Self::VoxelIndex>,
392            ),
393        >,
394        DecomposeError,
395    > {
396        let subdomains = self.domain.create_subdomains(n_subdomains)?;
397        Ok(subdomains
398            .into_iter()
399            .map(move |(subdomain_index, subdomain, voxels)| {
400                (
401                    subdomain_index,
402                    CartesianSubDomainRods::<F, D> {
403                        subdomain,
404                        gel_pressure: self.gel_pressure,
405                        surface_friction: self.surface_friction,
406                        surface_friction_distance: self.surface_friction,
407                    },
408                    voxels,
409                )
410            }))
411    }
412}
413
414/// The corresponding SubDomain of the [CartesianCuboidRods] domain.
415#[derive(Clone, SubDomain, Serialize, Deserialize)]
416#[serde(bound = "
417F: 'static
418    + PartialEq
419    + Clone
420    + core::fmt::Debug
421    + Serialize
422    + for<'a> Deserialize<'a>,
423[usize; D]: Serialize + for<'a> Deserialize<'a>,
424")]
425pub struct CartesianSubDomainRods<F, const D: usize> {
426    /// Base subdomain as created by the [CartesianCuboid] domain.
427    #[Base]
428    pub subdomain: CartesianSubDomain<F, D>,
429    /// See [CartesianCuboidRods]
430    pub gel_pressure: F,
431    /// Computes friction at all surfaces of the box
432    pub surface_friction: F,
433    /// The distance for which the friction will be applied
434    pub surface_friction_distance: F,
435}
436
437impl<F>
438    SubDomainForce<
439        Matrix<F, Dyn, Const<3>, VecStorage<F, Dyn, Const<3>>>,
440        Matrix<F, Dyn, Const<3>, VecStorage<F, Dyn, Const<3>>>,
441        Matrix<F, Dyn, Const<3>, VecStorage<F, Dyn, Const<3>>>,
442        F,
443    > for CartesianSubDomainRods<F, 3>
444where
445    F: nalgebra::RealField + num::Float,
446{
447    fn calculate_custom_force(
448        &self,
449        pos: &Matrix<F, Dyn, Const<3>, VecStorage<F, Dyn, Const<3>>>,
450        vel: &Matrix<F, Dyn, Const<3>, VecStorage<F, Dyn, Const<3>>>,
451        _: &F,
452    ) -> Result<
453        Matrix<F, Dyn, Const<3>, VecStorage<F, Dyn, Const<3>>>,
454        cellular_raza_concepts::CalcError,
455    > {
456        use core::ops::AddAssign;
457        let mut force = nalgebra::MatrixXx3::from_fn(pos.nrows(), |_, m| {
458            if m == 2 {
459                -self.gel_pressure
460            } else {
461                F::zero()
462            }
463        });
464        for (i, (p, v)) in pos.row_iter().zip(vel.row_iter()).enumerate() {
465            let d1 = (p.transpose() - self.subdomain.domain_min)
466                .map(|x| <F as num::Float>::abs(x) <= self.surface_friction_distance);
467            let d2 = (p.transpose() - self.subdomain.domain_max)
468                .map(|x| <F as num::Float>::abs(x) <= self.surface_friction_distance);
469            let q = v.norm();
470            if q != F::zero() && d1.zip_map(&d2, |x, y| x || y).into_iter().any(|x| *x) {
471                let dir = v / q;
472                force
473                    .row_mut(i)
474                    .add_assign(-dir * self.gel_pressure * self.surface_friction);
475            }
476        }
477        Ok(force)
478    }
479}
480
481impl<F, const D: usize>
482    SubDomainMechanics<
483        Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
484        Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
485    > for CartesianSubDomainRods<F, D>
486where
487    F: nalgebra::RealField + num::Float,
488{
489    fn apply_boundary(
490        &self,
491        pos: &mut Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
492        vel: &mut Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>,
493    ) -> Result<(), BoundaryError> {
494        // TODO refactor this with matrix multiplication!!!
495        // This will probably be much more efficient and less error-prone!
496
497        // For each position in the springs Agent<D1, D2>
498        let two = F::one() + F::one();
499        pos.row_iter_mut()
500            .zip(vel.row_iter_mut())
501            .for_each(|(mut p, mut v)| {
502                // For each dimension in the space
503                for i in 0..p.ncols() {
504                    // Check if the particle is below lower edge
505                    if p[i] < self.subdomain.get_domain_min()[i] {
506                        p[i] = self.subdomain.get_domain_min()[i] * two - p[i];
507                        v[i] = <F as num::Float>::abs(v[i]);
508                    }
509
510                    // Check if the particle is over the edge
511                    if p[i] > self.subdomain.get_domain_max()[i] {
512                        p[i] = self.subdomain.get_domain_max()[i] * two - p[i];
513                        v[i] = -<F as num::Float>::abs(v[i]);
514                    }
515                }
516            });
517
518        // If new pos is still out of boundary return error
519        for j in 0..pos.nrows() {
520            let p = pos.row(j);
521            for i in 0..pos.ncols() {
522                if p[i] < self.subdomain.get_domain_min()[i]
523                    || p[i] > self.subdomain.get_domain_max()[i]
524                {
525                    return Err(BoundaryError(format!(
526                        "Particle is out of domain at pos {:?}",
527                        pos
528                    )));
529                }
530            }
531        }
532        Ok(())
533    }
534}
535
536impl<C, F, const D: usize> SortCells<C> for CartesianSubDomainRods<F, D>
537where
538    C: Position<Matrix<F, Dyn, Const<D>, VecStorage<F, Dyn, Const<D>>>>,
539    F: 'static
540        + nalgebra::Field
541        + Clone
542        + core::fmt::Debug
543        + num::FromPrimitive
544        + num::ToPrimitive
545        + num::Float
546        + Copy,
547{
548    type VoxelIndex = [usize; D];
549
550    fn get_voxel_index_of(&self, cell: &C) -> Result<Self::VoxelIndex, BoundaryError> {
551        let pos = cell.pos().row_sum().transpose() / F::from_usize(cell.pos().nrows()).unwrap();
552        let index = self.subdomain.get_index_of(pos)?;
553        Ok(index)
554    }
555}
556
557impl<F, const D: usize> RodMechanics<F, D> {
558    /// Divides a [RodMechanics] struct into two thus separating their positions
559    ///
560    /// ```
561    /// # use cellular_raza_building_blocks::*;
562    /// use nalgebra::MatrixXx2;
563    /// let n_vertices = 7;
564    /// let mut pos = MatrixXx2::zeros(n_vertices);
565    /// pos
566    ///     .row_iter_mut()
567    ///     .enumerate()
568    ///     .for_each(|(n_row, mut r)| r[0] += n_row as f32 * 0.5);
569    /// let mut m1 = RodMechanics {
570    ///     pos,
571    ///     vel: MatrixXx2::zeros(n_vertices),
572    ///     diffusion_constant: 0.0,
573    ///     spring_tension: 0.1,
574    ///     rigidity: 0.05,
575    ///     spring_length: 0.5,
576    ///     damping: 0.0,
577    /// };
578    /// let radius = 0.25;
579    /// let m2 = m1.divide(radius)?;
580    ///
581    /// let last_pos_m1 = m1.pos.row(6);
582    /// let first_pos_m2 = m2.pos.row(0);
583    /// assert!(((last_pos_m1 - first_pos_m2).norm() - 2.0 * radius).abs() < 1e-3);
584    /// # Result::<(), cellular_raza_concepts::DivisionError>::Ok(())
585    /// ```
586    pub fn divide(
587        &mut self,
588        radius: F,
589    ) -> Result<RodMechanics<F, D>, cellular_raza_concepts::DivisionError>
590    where
591        F: num::Float + nalgebra::RealField + FromPrimitive + std::iter::Sum,
592    {
593        use itertools::Itertools;
594        let pos = self.pos();
595        let c1 = self;
596        let mut c2 = c1.clone();
597
598        let n_rows = c1.pos.nrows();
599        // Calculate the fraction of how much we need to scale down the individual spring lengths
600        // in order for the distances to still work.
601        let two = F::one() + F::one();
602        let one_half = F::one() / two;
603        let div_factor = one_half - radius / (F::from_usize(n_rows).unwrap() * c1.spring_length);
604
605        // Shrink spring length
606        let new_spring_length = div_factor * c1.spring_length;
607        c1.spring_length = new_spring_length;
608        c2.spring_length = new_spring_length;
609
610        // Set starting point
611        c1.pos.set_row(0, &pos.row(0));
612        c2.pos
613            .set_row(c2.pos.nrows() - 1, &pos.row(c2.pos.nrows() - 1));
614
615        let segments: Vec<_> = pos
616            .row_iter()
617            .tuple_windows::<(_, _)>()
618            .map(|(x, y)| (x - y).norm())
619            .collect();
620        let segment_length = (segments.iter().copied().sum::<F>() - two * radius)
621            / F::from_usize(c2.pos.nrows() - 1).unwrap()
622            / two;
623
624        for n_vertex in 0..c2.pos.nrows() {
625            // Get smallest index k such that the beginning of the new segment is "farther" than the
626            // original vertex at this index k.
627            let k = (0..segments.len())
628                .filter(|n| {
629                    segments.iter().copied().take(*n).sum::<F>()
630                        <= F::from_usize(n_vertex).unwrap() * segment_length
631                })
632                .max()
633                .ok_or(DivisionError(
634                    "Could not calculate new segment lengths".to_string(),
635                ))?;
636            let q = (F::from_usize(n_vertex).unwrap() * segment_length
637                - segments.iter().copied().take(k).sum::<F>())
638                / segments[k];
639            c1.pos.set_row(
640                n_vertex,
641                &(pos.row(k) * (F::one() - q) + pos.row(k + 1) * q),
642            );
643
644            let m = (0..c2.pos.nrows())
645                .filter(|n| {
646                    segments.iter().rev().copied().take(*n).sum::<F>()
647                        <= F::from_usize(n_vertex).unwrap() * segment_length
648                })
649                .max()
650                .ok_or(DivisionError(
651                    "Could not calculate new segment lengths".to_string(),
652                ))?;
653            let p = (F::from_usize(n_vertex).unwrap() * segment_length
654                - segments.iter().rev().copied().take(m).sum::<F>())
655                / segments[c2.pos.nrows() - m - 2];
656            c2.pos.set_row(
657                c2.pos.nrows() - n_vertex - 1,
658                &(pos.row(c2.pos.nrows() - m - 1) * (F::one() - p)
659                    + pos.row(c2.pos.nrows() - m - 2) * p),
660            );
661        }
662        Ok(c2)
663    }
664}