use cellular_raza_concepts::*;
use nalgebra::SVector;
use serde::{Deserialize, Serialize};
#[cfg(feature = "pyo3")]
use pyo3::prelude::*;
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "pyo3", pyclass)]
pub struct NoInteraction;
impl<Pos, Vel, For> Interaction<Pos, Vel, For> for NoInteraction
where
For: num::Zero,
{
fn calculate_force_between(
&self,
_: &Pos,
_: &Vel,
_: &Pos,
_: &Vel,
_ext_information: &(),
) -> Result<(For, For), CalcError> {
Ok((For::zero(), For::zero()))
}
fn get_interaction_information(&self) -> () {}
}
#[doc = include_str!("plot_bound_lennard_jones.html")]
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "pyo3", pyclass(get_all, set_all))]
pub struct BoundLennardJones {
pub epsilon: f64,
pub sigma: f64,
pub bound: f64,
pub cutoff: f64,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "pyo3", pyclass(get_all, set_all))]
pub struct BoundLennardJonesF32 {
pub epsilon: f32,
pub sigma: f32,
pub bound: f32,
pub cutoff: f32,
}
macro_rules! implement_bound_lennard_jones(
($struct_name:ident, $float_type:ident) => {
impl<const D: usize> Interaction<SVector<$float_type, D>, SVector<$float_type, D>, SVector<$float_type, D>>
for $struct_name
{
fn calculate_force_between(
&self,
own_pos: &SVector<$float_type, D>,
_own_vel: &SVector<$float_type, D>,
ext_pos: &SVector<$float_type, D>,
_ext_vel: &SVector<$float_type, D>,
_ext_information: &(),
) -> Result<(SVector<$float_type, D>, SVector<$float_type, D>), CalcError> {
let z = own_pos - ext_pos;
let r = z.norm();
let dir = z / r;
let val = 4.0 * self.epsilon / r
* (12.0 * (self.sigma / r).powf(11.0) - 6.0 * (self.sigma / r).powf(5.0));
let max = self.bound / r;
let q = if self.cutoff >= r { 1.0 } else { 0.0 };
Ok((- dir * q * max.min(val), dir * q * max.min(val)))
}
fn get_interaction_information(&self) -> () {}
}
};
);
implement_bound_lennard_jones!(BoundLennardJones, f64);
implement_bound_lennard_jones!(BoundLennardJonesF32, f32);
#[doc = include_str!("plot_morse_potential.html")]
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "pyo3", pyclass(set_all, get_all))]
pub struct MorsePotential {
pub length_repelling: f64,
pub length_attracting: f64,
pub cutoff: f64,
pub strength_repelling: f64,
pub strength_attracting: f64,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "pyo3", pyclass(get_all, set_all))]
pub struct MorsePotentialF32 {
pub length_repelling: f32,
pub length_attracting: f32,
pub cutoff: f32,
pub strength_repelling: f32,
pub strength_attracting: f32,
}
pub fn calculate_morse_interaction<F, const D: usize>(
own_pos: &nalgebra::SVector<F, D>,
ext_pos: &nalgebra::SVector<F, D>,
ext_length_repelling: &F,
cutoff: F,
length_repelling: F,
length_attracting: F,
strength_repelling: F,
strength_attracting: F,
) -> Result<(nalgebra::SVector<F, D>, nalgebra::SVector<F, D>), CalcError>
where
F: Copy + nalgebra::RealField,
{
let dir = own_pos - ext_pos;
let dist = dir.norm();
if dist > cutoff {
return Ok((
nalgebra::SVector::<F, D>::zeros(),
nalgebra::SVector::<F, D>::zeros(),
));
}
let lr = length_repelling;
let la = length_attracting;
let cr = strength_repelling;
let ca = strength_attracting;
let lr_combined = *ext_length_repelling + length_repelling;
let force = cr / lr * (-dist / lr_combined).exp() - ca / la * (-dist / la).exp();
Ok((-dir * force, dir * force))
}
fn product_log<F>(x: F, n_steps: usize) -> F
where
F: Copy + nalgebra::RealField,
{
let mut w = F::zero();
for _ in 0..n_steps {
w = w - (w * w.exp() - x) / (w.exp() + w * w.exp());
}
w
}
pub fn calculate_morse_interaction_cell_radii<F, const D: usize>(
own_pos: &nalgebra::SVector<F, D>,
ext_pos: &nalgebra::SVector<F, D>,
ext_cell_radius: &F,
cutoff: F,
own_cell_radius: F,
interaction_range: F,
strength_repelling: F,
strength_attracting: F,
) -> Result<(nalgebra::SVector<F, D>, nalgebra::SVector<F, D>), CalcError>
where
F: Copy + nalgebra::RealField,
{
let r_both = own_cell_radius + *ext_cell_radius;
let length_repelling = r_both
* product_log(
-strength_attracting / strength_repelling * r_both / interaction_range
* (-r_both / interaction_range).exp(),
3,
);
let ext_length_repelling = length_repelling * *ext_cell_radius / r_both;
let own_length_repelling = length_repelling * own_cell_radius / r_both;
calculate_morse_interaction(
own_pos,
ext_pos,
&ext_length_repelling,
cutoff,
own_length_repelling,
interaction_range,
strength_repelling,
strength_attracting,
)
}
macro_rules! implement_morse_potential(
($struct_name:ident, $float_type:ident) => {
impl<const D: usize>
Interaction<
nalgebra::SVector<$float_type, D>,
nalgebra::SVector<$float_type, D>,
nalgebra::SVector<$float_type, D>,
$float_type,
> for $struct_name
{
fn get_interaction_information(&self) -> $float_type {
self.length_repelling.clone()
}
fn calculate_force_between(
&self,
own_pos: &nalgebra::SVector<$float_type, D>,
_own_vel: &nalgebra::SVector<$float_type, D>,
ext_pos: &nalgebra::SVector<$float_type, D>,
_ext_vel: &nalgebra::SVector<$float_type, D>,
ext_info: &$float_type,
) -> Result<
(nalgebra::SVector<$float_type, D>, nalgebra::SVector<$float_type, D>),
CalcError
> {
calculate_morse_interaction(
own_pos,
ext_pos,
ext_info,
self.cutoff,
self.length_repelling,
self.length_attracting,
self.strength_repelling,
self.strength_attracting,
)
}
}
};
);
implement_morse_potential!(MorsePotential, f64);
implement_morse_potential!(MorsePotentialF32, f32);
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct MiePotential<const N: usize, const M: usize, F = f64> {
pub radius: F,
pub potential_strength: F,
pub bound: F,
pub cutoff: F,
en: F,
em: F,
}
impl<F, const D: usize, const N: usize, const M: usize>
Interaction<SVector<F, D>, SVector<F, D>, SVector<F, D>, F> for MiePotential<N, M, F>
where
F: nalgebra::RealField + Copy,
{
fn calculate_force_between(
&self,
own_pos: &SVector<F, D>,
_own_vel: &SVector<F, D>,
ext_pos: &SVector<F, D>,
_ext_vel: &SVector<F, D>,
ext_radius: &F,
) -> Result<(SVector<F, D>, SVector<F, D>), CalcError> {
let z = own_pos - ext_pos;
let r = z.norm();
if r == F::zero() {
return Err(CalcError(format!(
"identical position for two objects. Cannot Calculate force in Mie potential"
)));
}
if r > self.cutoff {
return Ok((SVector::<F, D>::zeros(), SVector::<F, D>::zeros()));
}
let dir = z / r;
let x = (self.radius + *ext_radius) / r;
let sigma = self.radius_to_sigma_factor() * x;
let mie_constant =
self.en / (self.en - self.em) * (self.en / self.em).powf(self.em / (self.en - self.em));
let potential_part =
self.en * (sigma.powf(self.en + F::one()) - x.powf(self.em + F::one()));
let force = self.potential_strength * mie_constant * potential_part;
let force = force.min(self.bound);
Ok((-dir * force, dir * force))
}
fn get_interaction_information(&self) -> F {
self.radius
}
}
impl<F, const N: usize, const M: usize> MiePotential<N, M, F>
where
F: nalgebra::RealField + num::FromPrimitive + Copy,
{
fn radius_to_sigma_factor(&self) -> F {
(self.em / self.en).powf(F::one() / (self.en - self.em))
}
pub fn new(radius: F, potential_strength: F, bound: F, cutoff: F) -> Result<Self, CalcError> {
let em = F::from_usize(M).ok_or(CalcError(format!(
"could not convert usize {} to float of type {}",
M,
std::any::type_name::<F>()
)))?;
let en = F::from_usize(N).ok_or(CalcError(format!(
"could not convert usize {} to float of type {}",
N,
std::any::type_name::<F>()
)))?;
Ok(Self {
radius,
potential_strength,
bound,
cutoff,
en,
em,
})
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct VertexDerivedInteraction<A, R, I1 = (), I2 = ()> {
pub outside_interaction: A,
pub inside_interaction: R,
phantom_inf_1: core::marker::PhantomData<I1>,
phantom_inf_2: core::marker::PhantomData<I2>,
}
impl<A, R, I1, I2> VertexDerivedInteraction<A, R, I1, I2> {
pub fn from_two_forces(attracting_force: A, repelling_force: R) -> Self
where
A: Interaction<Vector2<f64>, Vector2<f64>, Vector2<f64>, I1>,
R: Interaction<Vector2<f64>, Vector2<f64>, Vector2<f64>, I2>,
{
VertexDerivedInteraction {
outside_interaction: attracting_force,
inside_interaction: repelling_force,
phantom_inf_1: core::marker::PhantomData::<I1>,
phantom_inf_2: core::marker::PhantomData::<I2>,
}
}
}
use itertools::Itertools;
use nalgebra::Vector2;
pub fn nearest_point_from_point_to_line<const D: usize>(
point: &SVector<f64, D>,
line: (SVector<f64, D>, SVector<f64, D>),
) -> (f64, SVector<f64, D>, f64) {
let ab = line.1 - line.0;
let ap = point - line.0;
let t = (ab.dot(&ap) / ab.norm_squared()).clamp(0.0, 1.0);
let nearest_point = (1.0 - t) * line.0 + t * line.1;
((point - nearest_point).norm(), nearest_point, t)
}
fn nearest_point_from_point_to_multiple_lines(
point: &Vector2<f64>,
polygon_lines: &[(Vector2<f64>, Vector2<f64>)],
) -> Option<(usize, (f64, Vector2<f64>, f64))> {
polygon_lines
.iter()
.enumerate()
.map(|(n_row, &line)| (n_row, nearest_point_from_point_to_line(point, line)))
.min_by(|(_, (distance1, _, _)), (_, (distance2, _, _))| distance1.total_cmp(&distance2))
}
fn ray_intersects_line_segment(
ray: &(Vector2<f64>, Vector2<f64>),
line_segment: &(Vector2<f64>, Vector2<f64>),
) -> bool {
let (r1, r2) = ray;
let (l1, l2) = line_segment;
let t_enum = (l1 - r1).perp(&(l2 - l1));
let u_enum = (r1 - l1).perp(&(r2 - r1));
let t_denom = (r2 - r1).perp(&(l2 - l1));
let u_denom = -t_denom;
if t_denom == 0.0 || u_denom == 0.0 {
let d = (r1 - l1).dot(&(l2 - l1));
let e = (l2 - l1).norm_squared();
return 0.0 <= d && d <= e;
}
let t = t_enum / t_denom;
let u = u_enum / u_denom;
0.0 <= u && u < 1.0 && 0.0 <= t
}
impl<A, R, I1, I2, const D: usize>
Interaction<
nalgebra::SMatrix<f64, D, 2>,
nalgebra::SMatrix<f64, D, 2>,
nalgebra::SMatrix<f64, D, 2>,
(I1, I2),
> for VertexDerivedInteraction<A, R, I1, I2>
where
A: Interaction<Vector2<f64>, Vector2<f64>, Vector2<f64>, I1>,
R: Interaction<Vector2<f64>, Vector2<f64>, Vector2<f64>, I2>,
{
fn get_interaction_information(&self) -> (I1, I2) {
let i1 = self.outside_interaction.get_interaction_information();
let i2 = self.inside_interaction.get_interaction_information();
(i1, i2)
}
fn calculate_force_between(
&self,
own_pos: &nalgebra::SMatrix<f64, D, 2>,
own_vel: &nalgebra::SMatrix<f64, D, 2>,
ext_pos: &nalgebra::SMatrix<f64, D, 2>,
ext_vel: &nalgebra::SMatrix<f64, D, 2>,
ext_information: &(I1, I2),
) -> Result<(nalgebra::SMatrix<f64, D, 2>, nalgebra::SMatrix<f64, D, 2>), CalcError> {
let middle_own: Vector2<f64> = own_pos
.row_iter()
.map(|v| v.transpose())
.sum::<Vector2<f64>>()
/ own_pos.shape().0 as f64;
let average_vel_own: Vector2<f64> = own_vel
.row_iter()
.map(|v| v.transpose())
.sum::<Vector2<f64>>()
/ own_vel.shape().0 as f64;
let middle_ext: Vector2<f64> = ext_pos
.row_iter()
.map(|v| v.transpose())
.sum::<Vector2<f64>>()
/ ext_pos.shape().0 as f64;
let average_vel_ext: Vector2<f64> = ext_vel
.row_iter()
.map(|v| v.transpose())
.sum::<Vector2<f64>>()
/ ext_vel.shape().0 as f64;
let own_polygon_lines = own_pos
.row_iter()
.map(|vec| vec.transpose())
.circular_tuple_windows()
.collect::<Vec<(_, _)>>();
let vec_on_edge =
0.5 * (own_pos.view_range(0..1, 0..2) + own_pos.view_range(1..2, 0..2)).transpose();
let point_outside_polygon = 2.0 * vec_on_edge - middle_own;
let mut total_force_own = ext_pos.clone() * 0.0;
let mut total_force_ext = ext_pos.clone() * 0.0;
let (inf1, inf2) = ext_information;
for (n_row_ext, point_ext) in ext_pos.row_iter().enumerate() {
let point_ext = point_ext.transpose();
let bounding_box: [[f64; 2]; 2] = own_pos.row_iter().map(|v| v.transpose()).fold(
[[std::f64::INFINITY, -std::f64::INFINITY]; 2],
|mut accumulator, polygon_edge| {
accumulator[0][0] = accumulator[0][0].min(polygon_edge.x);
accumulator[0][1] = accumulator[0][1].max(polygon_edge.x);
accumulator[1][0] = accumulator[1][0].min(polygon_edge.y);
accumulator[1][1] = accumulator[1][1].max(polygon_edge.y);
accumulator
},
);
let point_is_out_of_bounding_box = point_ext.x < bounding_box[0][0]
|| point_ext.x > bounding_box[0][1]
|| point_ext.y < bounding_box[1][0]
|| point_ext.y > bounding_box[1][1];
let external_point_is_in_polygon = match point_is_out_of_bounding_box {
true => false,
false => {
let n_intersections: usize = own_polygon_lines
.iter()
.map(|line| {
ray_intersects_line_segment(&(point_ext, point_outside_polygon), line)
as usize
})
.sum();
n_intersections % 2 == 1
}
};
if external_point_is_in_polygon {
let (calc_own, calc_ext) = self.inside_interaction.calculate_force_between(
&middle_own,
&average_vel_own,
&point_ext,
&average_vel_ext,
&inf2,
)?;
let dir = (middle_ext - middle_own).normalize();
let calc_own = -calc_own.norm() * dir;
let calc_ext = calc_ext.norm() * dir;
let mut force_ext = total_force_ext.row_mut(n_row_ext);
force_ext += calc_ext.transpose();
total_force_own
.row_iter_mut()
.for_each(|mut r| r += calc_own.transpose() / D as f64);
} else {
if let Some((n_row_nearest, (_, nearest_point, t_frac))) =
nearest_point_from_point_to_multiple_lines(&point_ext, &own_polygon_lines)
{
let (calc_own, calc_ext) = self.outside_interaction.calculate_force_between(
&nearest_point,
&average_vel_own,
&point_ext,
&average_vel_ext,
&inf1,
)?;
let mut force_ext = total_force_ext.row_mut(n_row_ext);
force_ext += calc_ext.transpose();
let mut force_own_n = total_force_own.row_mut(n_row_nearest);
force_own_n += (1.0 - t_frac) * calc_own.transpose();
let mut force_own_n1 = total_force_own.row_mut((n_row_nearest + 1) % D);
force_own_n1 += t_frac * calc_own.transpose();
}
};
}
Ok((total_force_own, total_force_ext))
}
}
mod test {
#[test]
fn test_closest_points() {
let p1 = nalgebra::Vector2::from([0.0, 0.0]);
let p2 = nalgebra::Vector2::from([2.0, 0.0]);
let mut test_points = Vec::new();
test_points.push((
nalgebra::Vector2::from([0.5, 1.0]),
nalgebra::Vector2::from([0.5, 0.0]),
1.0,
));
test_points.push((nalgebra::Vector2::from([-1.0, 2.0]), p1, 5.0_f64.sqrt()));
test_points.push((nalgebra::Vector2::from([3.0, -2.0]), p2, 5.0_f64.sqrt()));
for (q, r, d) in test_points.iter() {
let (dist, nearest_point, _) = super::nearest_point_from_point_to_line(&q, (p1, p2));
assert_eq!(dist, *d);
assert_eq!(nearest_point, *r);
}
}
#[test]
fn test_point_is_in_regular_polygon() {
use itertools::Itertools;
let polygon = [
nalgebra::Vector2::from([-1.0, 0.0]),
nalgebra::Vector2::from([0.0, 1.0]),
nalgebra::Vector2::from([1.0, 0.0]),
nalgebra::Vector2::from([0.0, -1.0]),
];
let point_outside_polygon = nalgebra::Vector::from([-3.0, 0.0]);
let points_inside = [
nalgebra::Vector2::from([0.0, 0.0]),
nalgebra::Vector2::from([0.0, 0.1]),
nalgebra::Vector2::from([0.0, 0.99999]),
nalgebra::Vector2::from([0.99999, 0.0]),
nalgebra::Vector2::from([0.0, 1.0]),
nalgebra::Vector2::from([0.99999, 0.0]),
nalgebra::Vector2::from([-1.0, 0.0]),
nalgebra::Vector2::from([0.0, -1.0]),
];
for p in points_inside.iter() {
let n_intersections: usize = polygon
.clone()
.into_iter()
.circular_tuple_windows::<(_, _)>()
.map(|line| {
super::ray_intersects_line_segment(&(*p, point_outside_polygon), &line) as usize
})
.sum();
assert_eq!(n_intersections % 2 == 1, true);
}
let points_outside = [
nalgebra::Vector2::from([2.0, 0.0]),
nalgebra::Vector2::from([-1.5, 0.0]),
nalgebra::Vector2::from([0.5, 1.2]),
nalgebra::Vector2::from([1.3, -1.0001]),
nalgebra::Vector2::from([1.0000000000001, 0.0]),
nalgebra::Vector2::from([0.0, -1.000000000001]),
];
for q in points_outside.iter() {
let n_intersections: usize = polygon
.clone()
.into_iter()
.circular_tuple_windows()
.map(|line| {
super::ray_intersects_line_segment(&(*q, point_outside_polygon), &line) as usize
})
.sum();
assert_eq!(n_intersections % 2 == 0, true);
}
let new_polygon = [
nalgebra::Vector2::from([89.8169131069576, 105.21635977300497]),
nalgebra::Vector2::from([88.08135232199689, 107.60515425930363]),
nalgebra::Vector2::from([85.27315598238903, 106.69271595767589]),
nalgebra::Vector2::from([85.27315598238903, 103.74000358833405]),
nalgebra::Vector2::from([88.08135232199689, 102.8275652867063]),
];
let new_point_outside_polygon = nalgebra::Vector2::from([80.0, 90.0]);
let points_inside_2 = [nalgebra::Vector2::from([
88.08135232199689,
102.8275652867063,
])];
for q in points_inside_2.iter() {
let n_intersections: usize = new_polygon
.clone()
.into_iter()
.circular_tuple_windows()
.map(|line| {
super::ray_intersects_line_segment(&(*q, new_point_outside_polygon), &line)
as usize
})
.sum();
assert_eq!(n_intersections % 2 == 0, false);
}
}
}