use crate::Cutoff;
pub use crate::Vector3;
use core::fmt::Debug;
use core::iter::Sum;
use core::ops::Add;
use dyn_clone::DynClone;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use std::sync::Arc;
mod ashbaugh_hatch;
#[cfg(feature = "custom")]
mod custom;
mod fene;
mod hardsphere;
mod harmonic;
mod hermite;
mod kimhummer;
mod lennard_jones;
mod mie;
mod morse;
mod multipole;
pub mod potential;
mod ureybradley;
mod wca;
pub use ashbaugh_hatch::AshbaughHatch;
#[cfg(feature = "custom")]
pub use custom::{CustomPotential, CustomPotentialError};
pub use fene::FENE;
pub use hardsphere::HardSphere;
pub use harmonic::Harmonic;
pub(crate) use hermite::compute_uniform_hermite_coeffs;
#[cfg(feature = "simd")]
pub use hermite::{
simd_f32_from_array, simd_f32_to_array, SimdArrayF32, SimdF32, SplineTableSimd,
SplineTableSimdF32, LANES_F32,
};
pub use hermite::{
spline_potential, GridType, SplineCoeffs, SplineConfig, SplineStats, SplinedPotential,
ValidationResult,
};
pub use kimhummer::KimHummer;
pub use lennard_jones::LennardJones;
pub use mie::Mie;
pub use morse::Morse;
pub use multipole::{IonIon, IonIonPlain, IonIonPolar, IonIonYukawa};
pub use ureybradley::UreyBradley;
pub use wca::WeeksChandlerAndersen;
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub struct RelativeOrientation {
pub distance: Vector3,
pub orientation: Vector3,
}
pub trait AnisotropicTwobodyEnergy: Send + Sync {
fn anisotropic_twobody_energy(&self, orientation: &RelativeOrientation) -> f64;
fn anisotropic_twobody_force(&self, _: &RelativeOrientation) -> Vector3 {
todo!()
}
}
pub trait IsotropicTwobodyEnergy: AnisotropicTwobodyEnergy + DynClone + Debug + Cutoff {
fn isotropic_twobody_energy(&self, distance_squared: f64) -> f64;
fn isotropic_twobody_force(&self, distance_squared: f64) -> f64 {
const EPS: f64 = 1e-6;
let delta_u = self.isotropic_twobody_energy(distance_squared + EPS)
- self.isotropic_twobody_energy(distance_squared - EPS);
-delta_u / (2.0 * EPS)
}
}
dyn_clone::clone_trait_object!(IsotropicTwobodyEnergy);
fn norm_squared(v: &Vector3) -> f64 {
v.x * v.x + v.y * v.y + v.z * v.z
}
impl<T: IsotropicTwobodyEnergy> AnisotropicTwobodyEnergy for T {
fn anisotropic_twobody_energy(&self, orientation: &RelativeOrientation) -> f64 {
self.isotropic_twobody_energy(norm_squared(&orientation.distance))
}
fn anisotropic_twobody_force(&self, orientation: &RelativeOrientation) -> Vector3 {
let r_squared = norm_squared(&orientation.distance);
let inv_r = 1.0 / r_squared.sqrt();
let f = self.isotropic_twobody_force(r_squared) * inv_r;
Vector3 {
x: f * orientation.distance.x,
y: f * orientation.distance.y,
z: f * orientation.distance.z,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct NoInteraction;
impl Cutoff for NoInteraction {
fn cutoff(&self) -> f64 {
f64::INFINITY
}
}
impl IsotropicTwobodyEnergy for NoInteraction {
#[inline(always)]
fn isotropic_twobody_energy(&self, _distance_squared: f64) -> f64 {
0.0
}
#[inline(always)]
fn isotropic_twobody_force(&self, _distance_squared: f64) -> f64 {
0.0
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct Combined<T, U>(T, U);
impl<T: IsotropicTwobodyEnergy, U: IsotropicTwobodyEnergy> Combined<T, U> {
pub const fn new(t: T, u: U) -> Self {
Self(t, u)
}
}
impl<T: IsotropicTwobodyEnergy + Clone, U: IsotropicTwobodyEnergy + Clone> IsotropicTwobodyEnergy
for Combined<T, U>
{
#[inline(always)]
fn isotropic_twobody_energy(&self, distance_squared: f64) -> f64 {
self.0.isotropic_twobody_energy(distance_squared)
+ self.1.isotropic_twobody_energy(distance_squared)
}
#[inline(always)]
fn isotropic_twobody_force(&self, distance_squared: f64) -> f64 {
self.0.isotropic_twobody_force(distance_squared)
+ self.1.isotropic_twobody_force(distance_squared)
}
}
impl<T: Cutoff, U: Cutoff> Cutoff for Combined<T, U> {
fn cutoff(&self) -> f64 {
self.0.cutoff().min(self.1.cutoff())
}
fn lower_cutoff(&self) -> f64 {
self.0.lower_cutoff().max(self.1.lower_cutoff())
}
}
impl Cutoff for Box<dyn IsotropicTwobodyEnergy> {
fn cutoff(&self) -> f64 {
self.as_ref().cutoff()
}
fn lower_cutoff(&self) -> f64 {
self.as_ref().lower_cutoff()
}
}
impl IsotropicTwobodyEnergy for Box<dyn IsotropicTwobodyEnergy> {
fn isotropic_twobody_energy(&self, distance_squared: f64) -> f64 {
self.as_ref().isotropic_twobody_energy(distance_squared)
}
fn isotropic_twobody_force(&self, distance_squared: f64) -> f64 {
self.as_ref().isotropic_twobody_force(distance_squared)
}
}
#[derive(Clone, Debug)]
pub struct ArcPotential(pub Arc<dyn IsotropicTwobodyEnergy>);
impl ArcPotential {
pub fn new<T: IsotropicTwobodyEnergy + 'static>(potential: T) -> Self {
Self(Arc::new(potential))
}
}
impl Cutoff for ArcPotential {
fn cutoff(&self) -> f64 {
self.0.cutoff()
}
fn lower_cutoff(&self) -> f64 {
self.0.lower_cutoff()
}
}
impl IsotropicTwobodyEnergy for ArcPotential {
fn isotropic_twobody_energy(&self, distance_squared: f64) -> f64 {
self.0.isotropic_twobody_energy(distance_squared)
}
fn isotropic_twobody_force(&self, distance_squared: f64) -> f64 {
self.0.isotropic_twobody_force(distance_squared)
}
}
impl Add for Box<dyn IsotropicTwobodyEnergy> {
type Output = Self;
fn add(self, other: Self) -> Self {
Box::new(Combined::new(self, other))
}
}
impl Sum for Box<dyn IsotropicTwobodyEnergy> {
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
iter.fold(Box::new(NoInteraction {}), |acc, x| acc + x)
}
}
pub type CoulombLennardJones = Combined<IonIon<coulomb::pairwise::Plain>, LennardJones>;
pub type YukawaLennardJones = Combined<IonIon<coulomb::pairwise::Yukawa>, LennardJones>;
pub type WCA = WeeksChandlerAndersen;
#[test]
fn test_combined() {
use approx::assert_relative_eq;
let r2 = 0.5;
let relative_orientation = RelativeOrientation {
distance: [f64::sqrt(r2), 0.0, 0.0].into(),
orientation: [0.0, 1.0, 0.0].into(),
};
let pot1 = LennardJones::new(0.5, 1.0);
let pot2 = Harmonic::new(0.0, 10.0);
let energy = (
pot1.isotropic_twobody_energy(r2),
pot2.isotropic_twobody_energy(r2),
);
assert_relative_eq!(energy.0, 112.0);
assert_relative_eq!(energy.1, 2.5);
let combined = Combined::new(pot1.clone(), pot2.clone());
assert_relative_eq!(combined.isotropic_twobody_energy(r2), energy.0 + energy.1);
assert_relative_eq!(
combined.anisotropic_twobody_energy(&relative_orientation),
energy.0 + energy.1,
epsilon = 1e-7
);
let box1 = Box::new(pot1) as Box<dyn IsotropicTwobodyEnergy>;
let box2 = Box::new(pot2) as Box<dyn IsotropicTwobodyEnergy>;
let combined = box1 + box2;
assert_relative_eq!(combined.isotropic_twobody_energy(r2), energy.0 + energy.1);
assert_relative_eq!(
combined.anisotropic_twobody_energy(&relative_orientation),
energy.0 + energy.1,
epsilon = 1e-7
);
let pot3 = Harmonic::new(1.0, 10.0);
let energy3 = pot3.isotropic_twobody_energy(r2);
let box3 = Box::new(pot3) as Box<dyn IsotropicTwobodyEnergy>;
let combined2 = combined + box3;
assert_relative_eq!(
combined2.isotropic_twobody_energy(r2),
energy.0 + energy.1 + energy3
);
assert_relative_eq!(
combined2.anisotropic_twobody_energy(&relative_orientation),
energy.0 + energy.1 + energy3,
epsilon = 1e-7
);
}
#[test]
fn test_combined_force() {
use approx::assert_relative_eq;
let lj = LennardJones::new(1.5, 2.0);
let h = Harmonic::new(0.0, 10.0);
let combined = Combined::new(lj.clone(), h.clone());
let r2 = 6.25;
assert_relative_eq!(
combined.isotropic_twobody_force(r2),
lj.isotropic_twobody_force(r2) + h.isotropic_twobody_force(r2)
);
}
#[test]
fn test_box_dyn_force() {
use approx::assert_relative_eq;
let lj = LennardJones::new(1.5, 2.0);
let r2 = 6.25;
let expected = lj.isotropic_twobody_force(r2);
let boxed: Box<dyn IsotropicTwobodyEnergy> = Box::new(lj);
assert_relative_eq!(boxed.isotropic_twobody_force(r2), expected);
}
#[test]
fn test_arc_potential_force() {
use approx::assert_relative_eq;
let lj = LennardJones::new(1.5, 2.0);
let r2 = 6.25;
let expected = lj.isotropic_twobody_force(r2);
let arc = ArcPotential::new(lj);
assert_relative_eq!(arc.isotropic_twobody_force(r2), expected);
}
#[test]
fn test_arc_potential() {
use approx::assert_relative_eq;
let lj = LennardJones::new(1.0, 1.0);
let r2 = 1.5_f64.powi(2);
let expected_energy = lj.isotropic_twobody_energy(r2);
let expected_cutoff = lj.cutoff();
let expected_lower_cutoff = lj.lower_cutoff();
let arc_pot = ArcPotential::new(lj);
assert_relative_eq!(arc_pot.isotropic_twobody_energy(r2), expected_energy);
assert_eq!(arc_pot.cutoff(), expected_cutoff);
assert_relative_eq!(arc_pot.lower_cutoff(), expected_lower_cutoff);
let cloned = arc_pot.clone();
assert_relative_eq!(cloned.isotropic_twobody_energy(r2), expected_energy);
let orientation = RelativeOrientation {
distance: [1.5, 0.0, 0.0].into(),
orientation: [0.0, 1.0, 0.0].into(),
};
assert_relative_eq!(
arc_pot.anisotropic_twobody_energy(&orientation),
expected_energy,
epsilon = 1e-10
);
}