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}