use cellular_raza_concepts::*;
use nalgebra::SVector;
use serde::{Deserialize, Serialize};
#[cfg(feature = "pyo3")]
use pyo3::prelude::*;
#[cfg(feature = "approx")]
use approx::RelativeEq;
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[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()))
}
}
impl InteractionInformation<()> for NoInteraction {
fn get_interaction_information(&self) {}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "pyo3", pyclass(get_all, set_all))]
#[cfg_attr(feature = "approx", derive(RelativeEq))]
pub struct BoundLennardJones {
pub epsilon: f64,
pub sigma: f64,
pub bound: f64,
pub cutoff: f64,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "pyo3", pyclass(get_all, set_all))]
#[cfg_attr(feature = "approx", derive(RelativeEq))]
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)))
}
}
impl InteractionInformation<()> for $struct_name {
fn get_interaction_information(&self) {}
}
};
);
implement_bound_lennard_jones!(BoundLennardJones, f64);
implement_bound_lennard_jones!(BoundLennardJonesF32, f32);
pub fn calculate_morse_interaction<F, const D: usize>(
own_pos: &nalgebra::SVector<F, D>,
ext_pos: &nalgebra::SVector<F, D>,
own_radius: F,
ext_radius: F,
cutoff: F,
strength: F,
potential_stiffness: F,
) -> Result<(nalgebra::SVector<F, D>, nalgebra::SVector<F, D>), CalcError>
where
F: Copy + nalgebra::RealField,
{
let z = own_pos - ext_pos;
let dist = z.norm();
if dist > cutoff || dist.is_zero() {
return Ok((
nalgebra::SVector::<F, D>::zeros(),
nalgebra::SVector::<F, D>::zeros(),
));
}
let dir = z / dist;
let r = own_radius + ext_radius;
let s = strength;
let a = potential_stiffness;
let two = F::one() + F::one();
let e = (-a * (dist - r)).exp();
let force = -two * s * a * e * (F::one() - e);
Ok((dir * force, -dir * force))
}
macro_rules! implement_morse_potential(
($struct_name:ident, $float_type:ident) => {
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "pyo3", pyclass(set_all, get_all))]
#[cfg_attr(feature = "approx", derive(RelativeEq))]
pub struct $struct_name {
/// Radius of the object
pub radius: $float_type,
pub potential_stiffness: $float_type,
pub cutoff: $float_type,
pub strength: $float_type,
}
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 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,
self.radius,
*ext_info,
self.cutoff,
self.strength,
self.potential_stiffness,
)
}
}
impl InteractionInformation<$float_type> for $struct_name {
fn get_interaction_information(&self) -> $float_type {
self.radius
}
}
#[cfg(feature = "pyo3")]
#[cfg_attr(docsrs, doc(cfg(feature = "pyo3")))]
#[pymethods]
impl $struct_name {
#[doc = stringify!($struct_name)]
#[doc = concat!("use cellular_raza_building_blocks::", stringify!($struct_name), ";")]
#[doc = concat!("let morse_potential = ", stringify!($struct_name), "::new(")]
#[new]
#[pyo3(signature = (radius, potential_stiffness, cutoff, strength))]
pub fn new(
radius: $float_type,
potential_stiffness: $float_type,
cutoff: $float_type,
strength: $float_type
) -> Self {
Self {
radius,
potential_stiffness,
cutoff,
strength,
}
}
}
};
);
implement_morse_potential!(MorsePotential, f64);
implement_morse_potential!(MorsePotentialF32, f32);
macro_rules! implement_mie_potential(
($name:ident, $float_type:ty) => {
#[cfg_attr(feature = "pyo3", pyclass(get_all, set_all))]
#[cfg_attr(feature = "approx", derive(RelativeEq))]
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct $name {
/// Interaction strength $\epsilon$ of the potential.
pub radius: $float_type,
pub strength: $float_type,
pub bound: $float_type,
pub cutoff: $float_type,
pub en: $float_type,
pub em: $float_type,
}
impl<const D: usize> Interaction<
SVector<$float_type, D>,
SVector<$float_type, D>,
SVector<$float_type, D>,
$float_type
>
for $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_radius: &$float_type,
) -> Result<(SVector<$float_type, D>, SVector<$float_type, D>), CalcError> {
let z = own_pos - ext_pos;
let r = z.norm();
if r == 0.0 {
return Err(CalcError(format!(
"identical position for two objects. Cannot Calculate force in Mie potential"
)));
}
if r > self.cutoff {
return Ok((
SVector::<$float_type, D>::zeros(),
SVector::<$float_type, D>::zeros()
));
}
let dir = z / r;
let x = (self.radius + *ext_radius);
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 * (sigma / r).powf(self.en + 1.0)
- self.em /sigma * (sigma / r).powf(self.em + 1.0);
let force = (mie_constant * self.strength * potential_part).min(self.bound);
Ok((dir * force, - dir * force))
}
}
impl InteractionInformation<$float_type> for $name {
fn get_interaction_information(&self) -> $float_type {
self.radius
}
}
impl $name {
fn radius_to_sigma_factor(&self) -> $float_type {
(self.em / self.en).powf(1.0 / (self.en - self.em))
}
}
#[cfg(feature = "pyo3")]
#[cfg_attr(docsrs, doc(cfg(feature = "pyo3")))]
#[pymethods]
impl $name {
#[doc = stringify!($name)]
#[doc = concat!("use cellular_raza_building_blocks::", stringify!($name), ";")]
#[doc = concat!("let mie_potential = ", stringify!($name), "::new(")]
#[new]
#[pyo3(signature = (radius, strength, bound, cutoff, en, em))]
pub fn new(
radius: $float_type,
strength: $float_type,
bound: $float_type,
cutoff: $float_type,
en: $float_type,
em: $float_type,
) -> Self {
Self {
radius,
strength,
bound,
cutoff,
en,
em,
}
}
}
}
);
implement_mie_potential!(MiePotential, f64);
implement_mie_potential!(MiePotentialF32, f32);
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
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<F, const D: usize>(
point: &SVector<F, D>,
line: &(SVector<F, D>, SVector<F, D>),
) -> (F, SVector<F, D>, F)
where
F: Copy + nalgebra::RealField,
{
let ab = line.1 - line.0;
let ap = point - line.0;
let t = (ab.dot(&ap) / ab.norm_squared()).clamp(F::zero(), F::one());
let nearest_point = line.0 * (F::one() - t) + line.1 * t;
((point - nearest_point).norm(), nearest_point, t)
}
pub fn nearest_point_from_point_to_multiple_lines<'a, F>(
point: &Vector2<F>,
polygon_lines: impl IntoIterator<Item = &'a (Vector2<F>, Vector2<F>)>,
) -> Option<(usize, (F, Vector2<F>, F))>
where
F: Copy + nalgebra::RealField,
{
polygon_lines
.into_iter()
.enumerate()
.map(|(n_row, line)| (n_row, nearest_point_from_point_to_line(point, &line)))
.fold(None, |acc, v| {
let distance_v = v.1.0;
match acc {
None => Some(v),
Some(e) => {
let distance_acc = e.1.0;
if distance_v < distance_acc {
Some(v)
} else {
acc
}
}
}
})
}
pub fn ray_intersects_line_segment<F>(
ray: &(Vector2<F>, Vector2<F>),
line_segment: &(Vector2<F>, Vector2<F>),
) -> bool
where
F: Copy + nalgebra::RealField + PartialOrd,
{
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.is_zero() || u_denom.is_zero() {
let d = (r1 - l1).dot(&(l2 - l1));
let e = (l2 - l1).norm_squared();
return F::zero() <= d && d <= e;
}
let t = t_enum / t_denom;
let u = u_enum / u_denom;
F::zero() <= u && u < F::one() && F::zero() <= t
}
impl<A, R, I1, I2> InteractionInformation<(I1, I2)> for VertexDerivedInteraction<A, R, I1, I2>
where
A: InteractionInformation<I1>,
R: InteractionInformation<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)
}
}
impl<F, S, A, R, I1, I2, D>
Interaction<
nalgebra::Matrix<F, D, nalgebra::U2, S>,
nalgebra::Matrix<F, D, nalgebra::U2, S>,
nalgebra::Matrix<F, D, nalgebra::U2, S>,
(I1, I2),
> for VertexDerivedInteraction<A, R, I1, I2>
where
A: Interaction<Vector2<F>, Vector2<F>, Vector2<F>, I1>,
R: Interaction<Vector2<F>, Vector2<F>, Vector2<F>, I2>,
D: nalgebra::Dim,
F: nalgebra::Scalar + nalgebra::RealField,
nalgebra::Matrix<F, D, nalgebra::U2, S>:
core::ops::Mul<F, Output = nalgebra::Matrix<F, D, nalgebra::U2, S>>,
F: Copy,
S: nalgebra::RawStorageMut<F, D, nalgebra::U2>,
S: nalgebra::Storage<F, D, nalgebra::U2>,
S: Clone,
{
fn calculate_force_between(
&self,
own_pos: &nalgebra::Matrix<F, D, nalgebra::U2, S>,
own_vel: &nalgebra::Matrix<F, D, nalgebra::U2, S>,
ext_pos: &nalgebra::Matrix<F, D, nalgebra::U2, S>,
ext_vel: &nalgebra::Matrix<F, D, nalgebra::U2, S>,
ext_information: &(I1, I2),
) -> Result<
(
nalgebra::Matrix<F, D, nalgebra::U2, S>,
nalgebra::Matrix<F, D, nalgebra::U2, S>,
),
CalcError,
> {
let middle_own = own_pos.row_mean().transpose();
let average_vel_own = own_vel.row_mean().transpose();
let middle_ext = ext_pos.row_mean().transpose();
let average_vel_ext = ext_vel.row_mean().transpose();
let own_polygon_lines = own_pos
.row_iter()
.map(|vec| vec.transpose())
.circular_tuple_windows()
.collect::<Vec<(_, _)>>();
let vec_on_edge = (own_pos.view_range(0..1, 0..2) + own_pos.view_range(1..2, 0..2))
.transpose()
/ (F::one() + F::one());
let point_outside_polygon = vec_on_edge * (F::one() + F::one()) - middle_own;
let mut total_force_own = ext_pos.clone() * F::zero();
let mut total_force_ext = ext_pos.clone() * F::zero();
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: [[F; 2]; 2] = own_pos.row_iter().map(|v| v.transpose()).fold(
[[F::max_value().unwrap(), F::min_value().unwrap()]; 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 = -dir * calc_own.norm();
let calc_ext = dir * calc_ext.norm();
let mut force_ext = total_force_ext.row_mut(n_row_ext);
force_ext += calc_ext.transpose();
let n_rows = total_force_own.nrows();
let n_rows_float = F::from_usize(n_rows).unwrap();
total_force_own
.row_iter_mut()
.for_each(|mut r| r += calc_own.transpose() / n_rows_float);
} 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 += calc_own.transpose() * (F::one() - t_frac);
let mut force_own_n1 =
total_force_own.row_mut((n_row_nearest + 1) % total_force_own.nrows());
force_own_n1 += calc_own.transpose() * t_frac;
}
};
}
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
.into_iter()
.circular_tuple_windows::<(_, _)>()
.map(|line| {
super::ray_intersects_line_segment(&(*p, point_outside_polygon), &line) as usize
})
.sum();
assert!(n_intersections % 2 == 1);
}
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
.into_iter()
.circular_tuple_windows()
.map(|line| {
super::ray_intersects_line_segment(&(*q, point_outside_polygon), &line) as usize
})
.sum();
assert!(n_intersections % 2 == 0);
}
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
.into_iter()
.circular_tuple_windows()
.map(|line| {
super::ray_intersects_line_segment(&(*q, new_point_outside_polygon), &line)
as usize
})
.sum();
assert!(n_intersections % 2 != 0);
}
}
}