cellular_raza_building_blocks/cell_building_blocks/
interaction.rs

1use cellular_raza_concepts::*;
2
3use nalgebra::SVector;
4use serde::{Deserialize, Serialize};
5
6#[cfg(feature = "pyo3")]
7use pyo3::prelude::*;
8
9#[cfg(feature = "approx")]
10use approx::RelativeEq;
11
12/// No interaction of the cell with any other.
13#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
14#[cfg_attr(feature = "pyo3", pyclass)]
15pub struct NoInteraction;
16
17impl<Pos, Vel, For> Interaction<Pos, Vel, For> for NoInteraction
18where
19    For: num::Zero,
20{
21    fn calculate_force_between(
22        &self,
23        _: &Pos,
24        _: &Vel,
25        _: &Pos,
26        _: &Vel,
27        _ext_information: &(),
28    ) -> Result<(For, For), CalcError> {
29        Ok((For::zero(), For::zero()))
30    }
31}
32
33impl InteractionInformation<()> for NoInteraction {
34    fn get_interaction_information(&self) {}
35}
36
37/// Lennard-Jones interaction potential with numerical upper and lower limit.
38///
39/// # Parameters & Variables
40/// | Symbol | Struct Field | Description |
41/// |:---:| --- | --- |
42/// | $\sigma$ | `sigma` | Measure for the size of the particle. |
43/// | $\epsilon$ | `epsilon` | Interaction Strength |
44/// | $\beta$ | `bound` | Upper bound on the interaction strength. |
45/// | $\xi$ | `cutoff` | Cutoff after which the interaction strength is identically 0 |
46/// | | | |
47/// | $r$ | | Distance between interacting particles |
48///
49/// # Equations
50///
51/// The pure Lennard-Jones potential has many numerical downsides as it is very unstable to use
52/// and thus typically only recommended with extremely small integration steps.
53/// Here, we artificially limit the repelling part of the potential thus increasing numerical
54/// usability.
55/// However, it also has in principle infinite range.
56/// This is directly contrary to one of the fundamental assumptions of `cellular_raza`.
57/// We resolve the latter problem by simply assigning the value `0` if $r>=\zeta$ although this
58/// behavior is not continuous anymore.
59/// The potential of the interaction is given by
60/// \\begin{align}
61///     U(r) &= 4\epsilon\left[ \left(\frac{\sigma}{r}\right)^{12} -
62///         \left(\frac{\sigma}{r}\right)^6\right]\\\\
63///     V(r) &= \min(U(r), \beta)\theta(r-\zeta)
64/// \\end{align}
65/// where $\epsilon$ determines the overall interaction strength of the potential
66/// and $\sigma$ the shape and interaction range.
67/// The function $\theta(r-\zeta)$ is the heaviside function which sets the interaction to zero
68/// when reaching the cutoff point.
69/// The minimum of this potential is at $r_\text{min}=2^{1/6}\sigma$.
70/// For two identically-sized spherical interacting particles $r_\text{min}$ has to align with
71/// the diameter of their size.
72/// The interaction is artificially bound from above by a value $\beta$ in
73/// order to obtain better numerical stability.
74///
75/// # References
76/// \[1\]
77/// “On the determination of molecular fields.—I. From the variation of the viscosity of a gas with
78/// temperature,”
79/// Proceedings of the Royal Society of London.
80/// Series A, Containing Papers of a Mathematical and Physical Character, vol. 106, no. 738.
81/// The Royal Society, pp. 441–462, Oct. 1924. doi:
82/// [10.1098/rspa.1924.0081](https://doi.org/10.1098/rspa.1924.0081).
83///
84#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
85#[cfg_attr(feature = "pyo3", pyclass(get_all, set_all))]
86#[cfg_attr(feature = "approx", derive(RelativeEq))]
87pub struct BoundLennardJones {
88    /// Interaction strength $\epsilon$ of the potential.
89    pub epsilon: f64,
90    /// Overall size $\sigma$ of the object of the potential.
91    pub sigma: f64,
92    /// Numerical bound $\beta$ of the interaction strength.
93    pub bound: f64,
94    /// Defines a cutoff $\zeta$ after which the potential will be fixed to exactly zero.
95    pub cutoff: f64,
96}
97
98#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
99#[cfg_attr(feature = "pyo3", pyclass(get_all, set_all))]
100#[cfg_attr(feature = "approx", derive(RelativeEq))]
101/// Identical to [BoundLennardJones] but for `f32` type.
102pub struct BoundLennardJonesF32 {
103    /// Interaction strength $\epsilon$ of the potential.
104    pub epsilon: f32,
105    /// Overall size $\sigma$ of the object of the potential.
106    pub sigma: f32,
107    /// Numerical bound $\beta$ of the interaction strength.
108    pub bound: f32,
109    /// Defines a cutoff $\zeta$ after which the potential will be fixed to exactly zero.
110    pub cutoff: f32,
111}
112
113macro_rules! implement_bound_lennard_jones(
114    ($struct_name:ident, $float_type:ident) => {
115        impl<const D: usize> Interaction<
116            SVector<$float_type, D>,
117            SVector<$float_type, D>,
118            SVector<$float_type, D>
119        >
120            for $struct_name
121        {
122            fn calculate_force_between(
123                &self,
124                own_pos: &SVector<$float_type, D>,
125                _own_vel: &SVector<$float_type, D>,
126                ext_pos: &SVector<$float_type, D>,
127                _ext_vel: &SVector<$float_type, D>,
128                _ext_information: &(),
129            ) -> Result<(SVector<$float_type, D>, SVector<$float_type, D>), CalcError> {
130                let z = own_pos - ext_pos;
131                let r = z.norm();
132                let dir = z / r;
133                let val = 4.0 * self.epsilon / r
134                    * (12.0 * (self.sigma / r).powf(11.0) - 6.0 * (self.sigma / r).powf(5.0));
135                let max = self.bound / r;
136                let q = if self.cutoff >= r { 1.0 } else { 0.0 };
137                Ok((- dir * q * max.min(val), dir * q * max.min(val)))
138            }
139        }
140        impl InteractionInformation<()> for $struct_name {
141            fn get_interaction_information(&self) {}
142        }
143    };
144);
145
146implement_bound_lennard_jones!(BoundLennardJones, f64);
147implement_bound_lennard_jones!(BoundLennardJonesF32, f32);
148
149/// Calculates the interaction strength behind the [MorsePotential] and [MorsePotentialF32]
150/// structs.
151pub fn calculate_morse_interaction<F, const D: usize>(
152    own_pos: &nalgebra::SVector<F, D>,
153    ext_pos: &nalgebra::SVector<F, D>,
154    own_radius: F,
155    ext_radius: F,
156    cutoff: F,
157    strength: F,
158    potential_stiffness: F,
159) -> Result<(nalgebra::SVector<F, D>, nalgebra::SVector<F, D>), CalcError>
160where
161    F: Copy + nalgebra::RealField,
162{
163    let z = own_pos - ext_pos;
164    let dist = z.norm();
165
166    // If the distance between the two objects is greater than the cutoff, we
167    // immediately return zero.
168    if dist > cutoff || dist.is_zero() {
169        return Ok((
170            nalgebra::SVector::<F, D>::zeros(),
171            nalgebra::SVector::<F, D>::zeros(),
172        ));
173    }
174    let dir = z / dist;
175    let r = own_radius + ext_radius;
176    let s = strength;
177    let a = potential_stiffness;
178    let two = F::one() + F::one();
179    let e = (-a * (dist - r)).exp();
180    let force = -two * s * a * e * (F::one() - e);
181    Ok((dir * force, -dir * force))
182}
183
184macro_rules! implement_morse_potential(
185    ($struct_name:ident, $float_type:ident) => {
186        /// Famous [Morse](https://doi.org/10.1103/PhysRev.34.57) potential for diatomic molecules.
187        ///
188        /// # Parameters & Variables
189        /// | Symbol | Struct Field | Description |
190        /// |:---:| --- | --- |
191        /// | $R$ | `radius` | Radius of the particle |
192        /// | $\lambda$ | `potential_stiffness` | Can be interpreted as the inverse width of the potential |
193        /// | | `cutoff` | Cutoff after which the interaction strength is identically 0 |
194        /// | $V_0$ | `strength` | Interaction strength |
195        /// | | | |
196        /// | $r$ | | Distance between interacting particles |
197        ///
198        /// \\begin{equation}
199        ///     V(r) = V_0\left(1 - e^{-\lambda(r-R)}\right)^2
200        /// \\end{equation}
201        ///
202        // TODO remove this entirely or make it somehow work
203        // #[doc = include_str!("plot_morse_potential.html")]
204        ///
205        /// # References
206        /// \[1\]
207        /// P. M. Morse,
208        /// “Diatomic Molecules According to the Wave Mechanics. II. Vibrational Levels,”
209        /// Physical Review, vol. 34, no. 1. American Physical Society (APS),
210        /// pp. 57–64, Jul. 01, 1929.
211        /// doi: [10.1103/physrev.34.57](https://doi.org/10.1103/PhysRev.34.57).
212        #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
213        #[cfg_attr(feature = "pyo3", pyclass(set_all, get_all))]
214        #[cfg_attr(feature = "approx", derive(RelativeEq))]
215        pub struct $struct_name {
216            /// Radius of the object
217            pub radius: $float_type,
218            /// Defines the length for the interaction range
219            pub potential_stiffness: $float_type,
220            /// Cutoff after which the interaction is exactly 0
221            pub cutoff: $float_type,
222            /// Strength of the interaction
223            pub strength: $float_type,
224        }
225
226        impl<const D: usize>
227            Interaction<
228                nalgebra::SVector<$float_type, D>,
229                nalgebra::SVector<$float_type, D>,
230                nalgebra::SVector<$float_type, D>,
231                $float_type,
232            > for $struct_name
233        {
234            fn calculate_force_between(
235                &self,
236                own_pos: &nalgebra::SVector<$float_type, D>,
237                _own_vel: &nalgebra::SVector<$float_type, D>,
238                ext_pos: &nalgebra::SVector<$float_type, D>,
239                _ext_vel: &nalgebra::SVector<$float_type, D>,
240                ext_info: &$float_type,
241            ) -> Result<
242                (nalgebra::SVector<$float_type, D>, nalgebra::SVector<$float_type, D>),
243                CalcError
244            > {
245                calculate_morse_interaction(
246                    own_pos,
247                    ext_pos,
248                    self.radius,
249                    *ext_info,
250                    self.cutoff,
251                    self.strength,
252                    self.potential_stiffness,
253                )
254            }
255        }
256        impl InteractionInformation<$float_type> for $struct_name {
257            fn get_interaction_information(&self) -> $float_type {
258                self.radius
259            }
260        }
261
262        #[cfg(feature = "pyo3")]
263        #[cfg_attr(docsrs, doc(cfg(feature = "pyo3")))]
264        #[pymethods]
265        impl $struct_name {
266            /// Constructs a new [
267            #[doc = stringify!($struct_name)]
268            /// ]
269            /// ```
270            #[doc = concat!("use cellular_raza_building_blocks::", stringify!($struct_name), ";")]
271            /// # let (radius, potential_stiffness, cutoff, strength) = (1.0, 1.0, 1.0, 1.0);
272            #[doc = concat!("let morse_potential = ", stringify!($struct_name), "::new(")]
273            ///     radius,
274            ///     potential_stiffness,
275            ///     cutoff,
276            ///     strength,
277            /// );
278            /// ```
279            #[new]
280            #[pyo3(signature = (radius, potential_stiffness, cutoff, strength))]
281            pub fn new(
282                radius: $float_type,
283                potential_stiffness: $float_type,
284                cutoff: $float_type,
285                strength: $float_type
286            ) -> Self {
287                Self {
288                    radius,
289                    potential_stiffness,
290                    cutoff,
291                    strength,
292                }
293            }
294        }
295    };
296);
297
298implement_morse_potential!(MorsePotential, f64);
299implement_morse_potential!(MorsePotentialF32, f32);
300
301macro_rules! implement_mie_potential(
302    ($name:ident, $float_type:ty) => {
303        /// Generalizeation of the [BoundLennardJones] potential.
304        ///
305        /// \\begin{align}
306        ///     U(r) &= C\epsilon\left[ \left(\frac{\sigma}{r}\right)^n -
307        ///         \left(\frac{\sigma}{r}\right)^m\right]\\\\
308        ///     C &= \frac{n}{n-m}\left(\frac{n}{m}\right)^{\frac{n}{n-m}}\\\\
309        ///     V(r) &= \min(U(r), \beta)\theta(r-\zeta)
310        /// \\end{align}
311        ///
312        /// This struct comes in a 64bit version [MiePotential] and a 32bit variant
313        /// [MiePotentialF32].
314        ///
315        /// # References
316        /// G. Mie, “Zur kinetischen Theorie der einatomigen Körper,”
317        /// Annalen der Physik, vol. 316, no. 8. Wiley, pp. 657–697, Jan. 1903.
318        /// doi: [10.1002/andp.19033160802](https://doi.org/10.1002/andp.19033160802).
319        #[cfg_attr(feature = "pyo3", pyclass(get_all, set_all))]
320        #[cfg_attr(feature = "approx", derive(RelativeEq))]
321        #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
322        pub struct $name {
323            /// Interaction strength $\epsilon$ of the potential.
324            pub radius: $float_type,
325            /// Overall size $\sigma$ of the object of the potential.
326            pub strength: $float_type,
327            /// Numerical bound $\beta$ of the interaction strength.
328            pub bound: $float_type,
329            /// Defines a cutoff $\zeta$ after which the potential will be fixed to exactly zero.
330            pub cutoff: $float_type,
331            /// Exponent $n$ of the potential
332            pub en: $float_type,
333            /// Exponent $m$ of the potential
334            pub em: $float_type,
335        }
336
337        impl<const D: usize> Interaction<
338            SVector<$float_type, D>,
339            SVector<$float_type, D>,
340            SVector<$float_type, D>,
341            $float_type
342        >
343            for $name
344        {
345            fn calculate_force_between(
346                &self,
347                own_pos: &SVector<$float_type, D>,
348                _own_vel: &SVector<$float_type, D>,
349                ext_pos: &SVector<$float_type, D>,
350                _ext_vel: &SVector<$float_type, D>,
351                ext_radius: &$float_type,
352            ) -> Result<(SVector<$float_type, D>, SVector<$float_type, D>), CalcError> {
353                let z = own_pos - ext_pos;
354                let r = z.norm();
355                if r == 0.0 {
356                    return Err(CalcError(format!(
357                        "identical position for two objects. Cannot Calculate force in Mie potential"
358                    )));
359                }
360                if r > self.cutoff {
361                    return Ok((
362                        SVector::<$float_type, D>::zeros(),
363                        SVector::<$float_type, D>::zeros()
364                    ));
365                }
366                let dir = z / r;
367                let x = (self.radius + *ext_radius);
368                let sigma = self.radius_to_sigma_factor() * x;
369                let mie_constant =
370                    self.en / (self.en - self.em)
371                        * (self.en / self.em).powf(self.em / (self.en - self.em));
372                let potential_part = self.en / sigma * (sigma / r).powf(self.en + 1.0)
373                    - self.em /sigma * (sigma / r).powf(self.em + 1.0);
374                let force = (mie_constant * self.strength * potential_part).min(self.bound);
375                Ok((dir * force, - dir * force))
376            }
377        }
378
379        impl InteractionInformation<$float_type> for $name {
380            fn get_interaction_information(&self) -> $float_type {
381                self.radius
382            }
383        }
384
385        impl $name {
386            fn radius_to_sigma_factor(&self) -> $float_type {
387                (self.em / self.en).powf(1.0 / (self.en - self.em))
388            }
389        }
390
391        #[cfg(feature = "pyo3")]
392        #[cfg_attr(docsrs, doc(cfg(feature = "pyo3")))]
393        #[pymethods]
394        impl $name {
395            /// Constructs a new [
396            #[doc = stringify!($name)]
397            /// ]
398            /// ```
399            #[doc = concat!("use cellular_raza_building_blocks::", stringify!($name), ";")]
400            /// let (radius, strength, bound, cutoff, en, em) = (1.0, 1.0, 1.0, 1.0, 1.0, 1.0);
401            #[doc = concat!("let mie_potential = ", stringify!($name), "::new(")]
402            ///     radius,
403            ///     strength,
404            ///     bound,
405            ///     cutoff,
406            ///     en,
407            ///     em,
408            /// );
409            /// ```
410            #[new]
411            #[pyo3(signature = (radius, strength, bound, cutoff, en, em))]
412            pub fn new(
413                radius: $float_type,
414                strength: $float_type,
415                bound: $float_type,
416                cutoff: $float_type,
417                en: $float_type,
418                em: $float_type,
419            ) -> Self {
420                Self {
421                    radius,
422                    strength,
423                    bound,
424                    cutoff,
425                    en,
426                    em,
427                }
428            }
429        }
430    }
431);
432
433implement_mie_potential!(MiePotential, f64);
434implement_mie_potential!(MiePotentialF32, f32);
435
436/// Derives an interaction potential from a point-like potential.
437#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
438pub struct VertexDerivedInteraction<A, R, I1 = (), I2 = ()> {
439    /// Interaction potential used when other vertex is outside of current polygon.
440    pub outside_interaction: A,
441    /// Interaction potential when vertex is inside current polygon.
442    pub inside_interaction: R,
443    phantom_inf_1: core::marker::PhantomData<I1>,
444    phantom_inf_2: core::marker::PhantomData<I2>,
445}
446
447impl<A, R, I1, I2> VertexDerivedInteraction<A, R, I1, I2> {
448    /// Constructs a new [VertexDerivedInteraction] from two Interaction potentials.
449    ///
450    /// One serves as the inside and one for the outside interaction.
451    pub fn from_two_forces(attracting_force: A, repelling_force: R) -> Self
452    where
453        A: Interaction<Vector2<f64>, Vector2<f64>, Vector2<f64>, I1>,
454        R: Interaction<Vector2<f64>, Vector2<f64>, Vector2<f64>, I2>,
455    {
456        VertexDerivedInteraction {
457            outside_interaction: attracting_force,
458            inside_interaction: repelling_force,
459            phantom_inf_1: core::marker::PhantomData::<I1>,
460            phantom_inf_2: core::marker::PhantomData::<I2>,
461        }
462    }
463}
464
465use itertools::Itertools;
466use nalgebra::Vector2;
467
468/// Calculate the point on a line which is closest to an external given point.
469///
470/// This function takes an external point $\vec{p}$ and a line segment described by two points
471/// $\vec{p}_1,\vec{p}_2$.
472/// It returns a tuple $(d, \vec{x}, q)$ which contains the distance $d$ between the calculated
473/// nearest point and the external point, the calculated point $\vec{x}$ and a fractional value
474/// $0\leq q\leq 1$ which is the relative length of the calculated nearest point between the
475/// points of the given line segment given by:
476/// $$\vec{x} = (1-q)\vec{p}_1 + q\vec{p}_2$$
477///
478/// ```
479/// use nalgebra::Vector2;
480/// # use cellular_raza_building_blocks::nearest_point_from_point_to_line;
481///
482/// let external_point = Vector2::from([0f64; 2]);
483/// let line_segment = (
484///     Vector2::from([1.0, 0.0]),
485///     Vector2::from([-1.0, 2.0]),
486/// );
487/// let (dist, point, rel_length) = nearest_point_from_point_to_line(
488///     &external_point,
489///     &line_segment,
490/// );
491///
492/// assert!((dist - 1.0/2f64.sqrt()).abs() < 1e-10);
493/// assert!((rel_length - 0.25).abs() < 1e-10);
494/// assert!((point.x - 0.5).abs() < 1e-10);
495/// assert!((point.y - 0.5).abs() < 1e-10);
496/// ```
497pub fn nearest_point_from_point_to_line<F, const D: usize>(
498    point: &SVector<F, D>,
499    line: &(SVector<F, D>, SVector<F, D>),
500) -> (F, SVector<F, D>, F)
501where
502    F: Copy + nalgebra::RealField,
503{
504    let ab = line.1 - line.0;
505    let ap = point - line.0;
506    let t = (ab.dot(&ap) / ab.norm_squared()).clamp(F::zero(), F::one());
507    let nearest_point = line.0 * (F::one() - t) + line.1 * t;
508    ((point - nearest_point).norm(), nearest_point, t)
509}
510
511/// Generalizes the [nearest_point_from_point_to_line] function for a collection of line segments.
512/// ```
513/// # use cellular_raza_building_blocks::nearest_point_from_point_to_multiple_lines;
514/// # use nalgebra::Vector2;
515/// # use itertools::Itertools;
516/// let point = Vector2::from([0.5; 2]);
517/// let polygon = vec![
518///     Vector2::from([0.0, 0.0]),
519///     Vector2::from([0.5, 0.0]),
520///     Vector2::from([0.7, 0.4]),
521///     Vector2::from([0.9, 0.8]),
522/// ];
523/// let lines = polygon.clone().into_iter().tuple_windows::<(_, _)>().collect::<Vec<_>>();
524/// // This looks something like this:
525/// //             3
526/// //            /
527/// //       p   /
528/// //          2
529/// //         /
530/// //        /
531/// // 0 --- 1
532/// let (n, (dist, calculated_point, t)) = nearest_point_from_point_to_multiple_lines(
533///     &point,
534///     &lines,
535/// ).unwrap();
536/// assert_eq!(n, 1);
537/// let test_dist: f64 = (calculated_point - point).norm();
538/// assert!((dist - test_dist).abs() < 1e-6);
539/// let (v1, v2) = lines[n];
540/// assert!((calculated_point - ((1.0 - t)*v1 + t*v2)).norm() < 1e-6);
541/// for p in polygon {
542///     assert!(dist <= (p - point).norm());
543/// }
544/// ```
545pub fn nearest_point_from_point_to_multiple_lines<'a, F>(
546    point: &Vector2<F>,
547    polygon_lines: impl IntoIterator<Item = &'a (Vector2<F>, Vector2<F>)>,
548) -> Option<(usize, (F, Vector2<F>, F))>
549where
550    F: Copy + nalgebra::RealField,
551{
552    polygon_lines
553        .into_iter()
554        .enumerate()
555        .map(|(n_row, line)| (n_row, nearest_point_from_point_to_line(point, &line)))
556        .fold(None, |acc, v| {
557            let distance_v = v.1.0;
558            match acc {
559                None => Some(v),
560                Some(e) => {
561                    let distance_acc = e.1.0;
562                    if distance_v < distance_acc {
563                        Some(v)
564                    } else {
565                        acc
566                    }
567                }
568            }
569        })
570}
571
572/// Implements the ray-casting algorithm to check if a ray intersects with a given line segment.
573///
574/// This method can be applied to polygons to calculate if a point is inside of a given polygon or
575/// outside of it.
576/// ```
577/// # use cellular_raza_building_blocks::ray_intersects_line_segment;
578/// use nalgebra::Vector2;
579/// let line_segment = (
580///     Vector2::from([1.0, 3.0]),
581///     Vector2::from([1.0, 1.0]),
582/// );
583/// let ray = (
584///     Vector2::from([4.0, 1.5]),
585///     Vector2::from([2.0, 1.5]),
586/// );
587/// // This should look something like this:
588/// // |
589/// // |  <------
590/// // |
591/// assert!(ray_intersects_line_segment(&ray, &line_segment));
592/// // Chaning the order of arguments will look like this
593/// // |
594/// // |  ------
595/// // |
596/// // v
597/// assert!(!ray_intersects_line_segment(&line_segment, &ray));
598/// ```
599pub fn ray_intersects_line_segment<F>(
600    ray: &(Vector2<F>, Vector2<F>),
601    line_segment: &(Vector2<F>, Vector2<F>),
602) -> bool
603where
604    F: Copy + nalgebra::RealField + PartialOrd,
605{
606    // Calculate the intersection point as if the ray and line were infinite
607    let (r1, r2) = ray;
608    let (l1, l2) = line_segment;
609
610    // We solve the formulas
611    // p = r1 + t*(r2-r1)
612    // p = l1 + u*(l2-l1)
613    // This can be done by using the orthogonal complements for (r2-r1) and (l2-l1) respectively.
614    // Let r_o and l_o be the orthogonals to (r2-r1) and (l2-l1).
615    // Then the above formulas can be set equal and multiplied by either one of the orthogonals.
616    // Note that x.dot(y_o) == x.perp(y) holds for the nalgebra crate!
617    // Also the perpendicular product of a vector with itself is zero.
618    // This yields the two formulas (without borrowing for better readability)
619    //  => r1.perp(r2-r1) + t*(r2-r1).perp(r2-r1) = l1.perp(r2-r1) + u*(l2-l1).perp(r2-r1)
620    // <=> r1.perp(r2-r1)                         = l1.perp(r2-r1) + u*(l2-l1).perp(r2-r1)
621    // <=> (r1-l1).perp(r2-r1)                    =                  u*(l2-l1).perp(r2-r1)
622    //  => l1.perp(l2-l1) + u*(l2-l1).perp(l2-l1) = r1.perp(l2-l1) + t*(r2-r1).perp(l2-l1)
623    // <=> r1.perp(l2-l1) + t*(r2-r1).perp(l2-l1) = l1.perp(l2-l1)
624    // <=>                  t*(r2-r1).perp(l2-l1) = (l1-r1).perp(l2-l1)
625
626    // Split the result in enominator and denominator
627    let t_enum = (l1 - r1).perp(&(l2 - l1));
628    let u_enum = (r1 - l1).perp(&(r2 - r1));
629    let t_denom = (r2 - r1).perp(&(l2 - l1));
630    let u_denom = -t_denom;
631
632    // If the denominators are zero, the following possibilities arise
633    // 1) Either r1 and r2 are identical or l1 and l2
634    // 2) The lines are parallel and cannot intersect
635    if t_denom.is_zero() || u_denom.is_zero() {
636        // We can directly test if some of the points are on the same line
637        // Test this by calculating the dot product of r1-l1 with l2-l1.
638        // This value must be between the norm of l2-l1 squared
639        let d = (r1 - l1).dot(&(l2 - l1));
640        let e = (l2 - l1).norm_squared();
641        return F::zero() <= d && d <= e;
642    }
643
644    // Handles the case where the points for the ray were layed on top of each other.
645    // Then the ray will only hit the point if r1 and r2 are on the line segment itself.
646    let t = t_enum / t_denom;
647    let u = u_enum / u_denom;
648
649    // In order to be on the line-segment, we require that u is between 0.0 and 1.0
650    // Additionally for p to be on the ray, we require t >= 0.0
651    F::zero() <= u && u < F::one() && F::zero() <= t
652}
653
654impl<A, R, I1, I2> InteractionInformation<(I1, I2)> for VertexDerivedInteraction<A, R, I1, I2>
655where
656    A: InteractionInformation<I1>,
657    R: InteractionInformation<I2>,
658{
659    fn get_interaction_information(&self) -> (I1, I2) {
660        let i1 = self.outside_interaction.get_interaction_information();
661        let i2 = self.inside_interaction.get_interaction_information();
662        (i1, i2)
663    }
664}
665
666impl<F, S, A, R, I1, I2, D>
667    Interaction<
668        nalgebra::Matrix<F, D, nalgebra::U2, S>,
669        nalgebra::Matrix<F, D, nalgebra::U2, S>,
670        nalgebra::Matrix<F, D, nalgebra::U2, S>,
671        (I1, I2),
672    > for VertexDerivedInteraction<A, R, I1, I2>
673where
674    A: Interaction<Vector2<F>, Vector2<F>, Vector2<F>, I1>,
675    R: Interaction<Vector2<F>, Vector2<F>, Vector2<F>, I2>,
676    D: nalgebra::Dim,
677    F: nalgebra::Scalar + nalgebra::RealField,
678    nalgebra::Matrix<F, D, nalgebra::U2, S>:
679        core::ops::Mul<F, Output = nalgebra::Matrix<F, D, nalgebra::U2, S>>,
680    F: Copy,
681    // nalgebra::DefaultAllocator: nalgebra::allocator::Allocator<F, D, nalgebra::Const<2>>,
682    // nalgebra::VecStorage<F, D, nalgebra::Const<2>>: nalgebra::Storage<F, D, nalgebra::Const<2>>
683    // S: nalgebra::RawStorage<F, D, nalgebra::U2>,
684    S: nalgebra::RawStorageMut<F, D, nalgebra::U2>,
685    S: nalgebra::Storage<F, D, nalgebra::U2>,
686    S: Clone,
687{
688    fn calculate_force_between(
689        &self,
690        own_pos: &nalgebra::Matrix<F, D, nalgebra::U2, S>,
691        own_vel: &nalgebra::Matrix<F, D, nalgebra::U2, S>,
692        ext_pos: &nalgebra::Matrix<F, D, nalgebra::U2, S>,
693        ext_vel: &nalgebra::Matrix<F, D, nalgebra::U2, S>,
694        ext_information: &(I1, I2),
695    ) -> Result<
696        (
697            nalgebra::Matrix<F, D, nalgebra::U2, S>,
698            nalgebra::Matrix<F, D, nalgebra::U2, S>,
699        ),
700        CalcError,
701    > {
702        // IMPORTANT!!
703        // This code assumes that we are dealing with a regular! polygon!
704
705        // Calculate the middle point which we will need later
706        let middle_own = own_pos.row_mean().transpose();
707        let average_vel_own = own_vel.row_mean().transpose();
708        let middle_ext = ext_pos.row_mean().transpose();
709        let average_vel_ext = ext_vel.row_mean().transpose();
710
711        // Also calculate our own polygon defined by lines between points
712        let own_polygon_lines = own_pos
713            .row_iter()
714            .map(|vec| vec.transpose())
715            .circular_tuple_windows()
716            .collect::<Vec<(_, _)>>();
717
718        let vec_on_edge = (own_pos.view_range(0..1, 0..2) + own_pos.view_range(1..2, 0..2))
719            .transpose()
720            / (F::one() + F::one());
721        // This is simplified notation for the following
722        // p = 2*v - middle = (v-middle) + v
723        let point_outside_polygon = vec_on_edge * (F::one() + F::one()) - middle_own;
724
725        // Store the total calculated force here
726        let mut total_force_own = ext_pos.clone() * F::zero();
727        let mut total_force_ext = ext_pos.clone() * F::zero();
728
729        // Match the obtained interaction information
730        let (inf1, inf2) = ext_information;
731
732        // Pick one point from the external positions
733        // and calculate which would be the nearest point on the own positions
734        for (n_row_ext, point_ext) in ext_pos.row_iter().enumerate() {
735            let point_ext = point_ext.transpose();
736            // Check if the point is inside the polygon.
737            // If this is the case, do not calculate any attracting force.
738
739            // We first calculate a bounding box and test quickly with this
740            let bounding_box: [[F; 2]; 2] = own_pos.row_iter().map(|v| v.transpose()).fold(
741                [[F::max_value().unwrap(), F::min_value().unwrap()]; 2],
742                |mut accumulator, polygon_edge| {
743                    accumulator[0][0] = accumulator[0][0].min(polygon_edge.x);
744                    accumulator[0][1] = accumulator[0][1].max(polygon_edge.x);
745                    accumulator[1][0] = accumulator[1][0].min(polygon_edge.y);
746                    accumulator[1][1] = accumulator[1][1].max(polygon_edge.y);
747                    accumulator
748                },
749            );
750
751            let point_is_out_of_bounding_box = point_ext.x < bounding_box[0][0]
752                || point_ext.x > bounding_box[0][1]
753                || point_ext.y < bounding_box[1][0]
754                || point_ext.y > bounding_box[1][1];
755
756            let external_point_is_in_polygon = match point_is_out_of_bounding_box {
757                true => false,
758                false => {
759                    // If the bounding box was not successful,
760                    // we use the ray-casting algorithm to check.
761                    let n_intersections: usize = own_polygon_lines
762                        .iter()
763                        .map(|line| {
764                            ray_intersects_line_segment(&(point_ext, point_outside_polygon), line)
765                                as usize
766                        })
767                        .sum();
768
769                    // An even number means that the point was outside
770                    // while odd numbers mean that the point was inside.
771                    n_intersections % 2 == 1
772                }
773            };
774
775            if external_point_is_in_polygon {
776                // Calculate the force inside the cell
777                let (calc_own, calc_ext) = self.inside_interaction.calculate_force_between(
778                    &middle_own,
779                    &average_vel_own,
780                    &point_ext,
781                    &average_vel_ext,
782                    inf2,
783                )?;
784                let dir = (middle_ext - middle_own).normalize();
785                let calc_own = -dir * calc_own.norm();
786                let calc_ext = dir * calc_ext.norm();
787                let mut force_ext = total_force_ext.row_mut(n_row_ext);
788                force_ext += calc_ext.transpose();
789                let n_rows = total_force_own.nrows();
790                let n_rows_float = F::from_usize(n_rows).unwrap();
791                total_force_own
792                    .row_iter_mut()
793                    .for_each(|mut r| r += calc_own.transpose() / n_rows_float);
794            } else {
795                // Calculate the force outside
796                if let Some((n_row_nearest, (_, nearest_point, t_frac))) =
797                    nearest_point_from_point_to_multiple_lines(&point_ext, &own_polygon_lines)
798                {
799                    let (calc_own, calc_ext) = self.outside_interaction.calculate_force_between(
800                        &nearest_point,
801                        &average_vel_own,
802                        &point_ext,
803                        &average_vel_ext,
804                        inf1,
805                    )?;
806                    let mut force_ext = total_force_ext.row_mut(n_row_ext);
807                    force_ext += calc_ext.transpose();
808                    let mut force_own_n = total_force_own.row_mut(n_row_nearest);
809                    force_own_n += calc_own.transpose() * (F::one() - t_frac);
810                    let mut force_own_n1 =
811                        total_force_own.row_mut((n_row_nearest + 1) % total_force_own.nrows());
812                    force_own_n1 += calc_own.transpose() * t_frac;
813                }
814            };
815        }
816        Ok((total_force_own, total_force_ext))
817    }
818}
819
820mod test {
821    #[test]
822    fn test_closest_points() {
823        // Define the line we will be using
824        let p1 = nalgebra::Vector2::from([0.0, 0.0]);
825        let p2 = nalgebra::Vector2::from([2.0, 0.0]);
826
827        // Create a vector of tuples which have (input_point,
828        // expected_nearest_point, expected_distance)
829        let mut test_points = Vec::new();
830
831        // Define the points we will be testing
832        // Normal point which lies perpendicular to line
833        test_points.push((
834            nalgebra::Vector2::from([0.5, 1.0]),
835            nalgebra::Vector2::from([0.5, 0.0]),
836            1.0,
837        ));
838
839        // Point to check left edge of line
840        test_points.push((nalgebra::Vector2::from([-1.0, 2.0]), p1, 5.0_f64.sqrt()));
841
842        // Point to check right edge of line
843        test_points.push((nalgebra::Vector2::from([3.0, -2.0]), p2, 5.0_f64.sqrt()));
844
845        // Check if the distance and point are matching
846        for (q, r, d) in test_points.iter() {
847            let (dist, nearest_point, _) = super::nearest_point_from_point_to_line(q, &(p1, p2));
848            assert_eq!(dist, *d);
849            assert_eq!(nearest_point, *r);
850        }
851    }
852
853    #[test]
854    fn test_point_is_in_regular_polygon() {
855        use itertools::Itertools;
856        // Define the polygon for which we are testing
857        let polygon = [
858            nalgebra::Vector2::from([-1.0, 0.0]),
859            nalgebra::Vector2::from([0.0, 1.0]),
860            nalgebra::Vector2::from([1.0, 0.0]),
861            nalgebra::Vector2::from([0.0, -1.0]),
862        ];
863        // This is the polygon
864        //       / \
865        //     /     \
866        //   /         \
867        // /             \
868        // \             /
869        //   \         /
870        //     \     /
871        //       \ /
872
873        // For testing, we need to pick a point outside of the polygon
874        let point_outside_polygon = nalgebra::Vector::from([-3.0, 0.0]);
875
876        // Define points which should be inside the polygon
877        let points_inside = [
878            nalgebra::Vector2::from([0.0, 0.0]),
879            nalgebra::Vector2::from([0.0, 0.1]),
880            nalgebra::Vector2::from([0.0, 0.99999]),
881            nalgebra::Vector2::from([0.99999, 0.0]),
882            nalgebra::Vector2::from([0.0, 1.0]),
883            // This point here was always a problem!
884            // TODO write function above such that point [1.0, 0.0] is also inside!
885            // For now this is enough since this is only a very narrow edge case
886            nalgebra::Vector2::from([0.99999, 0.0]),
887            nalgebra::Vector2::from([-1.0, 0.0]),
888            nalgebra::Vector2::from([0.0, -1.0]),
889        ];
890
891        // Check the points inside the polygon
892        for p in points_inside.iter() {
893            let n_intersections: usize = polygon
894                .into_iter()
895                .circular_tuple_windows::<(_, _)>()
896                .map(|line| {
897                    super::ray_intersects_line_segment(&(*p, point_outside_polygon), &line) as usize
898                })
899                .sum();
900            assert!(n_intersections % 2 == 1);
901        }
902
903        // Define points which should be outside the polygon
904        let points_outside = [
905            nalgebra::Vector2::from([2.0, 0.0]),
906            nalgebra::Vector2::from([-1.5, 0.0]),
907            nalgebra::Vector2::from([0.5, 1.2]),
908            nalgebra::Vector2::from([1.3, -1.0001]),
909            nalgebra::Vector2::from([1.0000000000001, 0.0]),
910            nalgebra::Vector2::from([0.0, -1.000000000001]),
911        ];
912
913        // Check them
914        for q in points_outside.iter() {
915            let n_intersections: usize = polygon
916                .into_iter()
917                .circular_tuple_windows()
918                .map(|line| {
919                    super::ray_intersects_line_segment(&(*q, point_outside_polygon), &line) as usize
920                })
921                .sum();
922
923            assert!(n_intersections % 2 == 0);
924        }
925
926        // These are sample values taken from a random simulation
927        let new_polygon = [
928            nalgebra::Vector2::from([89.8169131069576, 105.21635977300497]),
929            nalgebra::Vector2::from([88.08135232199689, 107.60515425930363]),
930            nalgebra::Vector2::from([85.27315598238903, 106.69271595767589]),
931            nalgebra::Vector2::from([85.27315598238903, 103.74000358833405]),
932            nalgebra::Vector2::from([88.08135232199689, 102.8275652867063]),
933        ];
934        let new_point_outside_polygon = nalgebra::Vector2::from([80.0, 90.0]);
935
936        let points_inside_2 = [nalgebra::Vector2::from([
937            88.08135232199689,
938            102.8275652867063,
939        ])];
940
941        for q in points_inside_2.iter() {
942            let n_intersections: usize = new_polygon
943                .into_iter()
944                .circular_tuple_windows()
945                .map(|line| {
946                    super::ray_intersects_line_segment(&(*q, new_point_outside_polygon), &line)
947                        as usize
948                })
949                .sum();
950
951            assert!(n_intersections % 2 != 0);
952        }
953    }
954}