use crate::twobody::AshbaughHatch;
use crate::twobody::IsotropicTwobodyEnergy;
use crate::Cutoff;
#[cfg(feature = "serde")]
use crate::{divide4_serialize, multiply4_deserialize, sqrt_serialize, square_deserialize};
use std::fmt;
use std::fmt::{Display, Formatter};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Default)]
#[cfg_attr(
feature = "serde",
derive(Deserialize, Serialize),
serde(deny_unknown_fields)
)]
pub struct LennardJones {
#[cfg_attr(
feature = "serde",
serde(
rename = "epsilon",
alias = "eps",
alias = "ε",
serialize_with = "divide4_serialize",
deserialize_with = "multiply4_deserialize"
)
)]
pub(crate) four_times_epsilon: f64,
#[cfg_attr(
feature = "serde",
serde(
rename = "sigma",
alias = "σ",
serialize_with = "sqrt_serialize",
deserialize_with = "square_deserialize"
)
)]
pub(crate) sigma_squared: f64,
}
impl LennardJones {
pub const fn new(epsilon: f64, sigma: f64) -> Self {
Self {
four_times_epsilon: 4.0 * epsilon,
sigma_squared: sigma * sigma,
}
}
pub fn from_ab(a: f64, b: f64) -> Self {
Self {
four_times_epsilon: b * b / a,
sigma_squared: (a / b).cbrt(),
}
}
#[inline(always)]
pub const fn epsilon(&self) -> f64 {
self.four_times_epsilon * 0.25
}
pub fn sigma(&self) -> f64 {
self.sigma_squared.sqrt()
}
}
impl Cutoff for LennardJones {
fn cutoff(&self) -> f64 {
f64::INFINITY
}
fn cutoff_squared(&self) -> f64 {
f64::INFINITY
}
fn lower_cutoff(&self) -> f64 {
self.sigma_squared.sqrt() * 0.6
}
}
impl IsotropicTwobodyEnergy for LennardJones {
#[inline(always)]
fn isotropic_twobody_energy(&self, squared_distance: f64) -> f64 {
let x = self.sigma_squared / squared_distance; let x = x * x * x; self.four_times_epsilon * x.mul_add(x, -x)
}
#[inline(always)]
fn isotropic_twobody_force(&self, distance_squared: f64) -> f64 {
let x = self.sigma_squared / distance_squared; let x = x * x * x; 3.0 * self.four_times_epsilon * x * (2.0 * x - 1.0) / distance_squared
}
}
impl From<AshbaughHatch> for LennardJones {
fn from(ah: AshbaughHatch) -> Self {
ah.lennard_jones
}
}
impl Display for LennardJones {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(
f,
"Lennard-Jones: ε = {:.3}, σ = {:.3}",
self.epsilon(),
self.sigma()
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_lennard_jones_force() {
let lj = LennardJones::new(1.5, 2.0);
assert_relative_eq!(lj.isotropic_twobody_force(4.0), 4.5, epsilon = 1e-10);
let r2_min = 4.0 * 1.2599210498948732;
assert_relative_eq!(lj.isotropic_twobody_force(r2_min), 0.0, epsilon = 1e-10);
assert_relative_eq!(
lj.isotropic_twobody_force(6.25),
-0.359150764401,
epsilon = 1e-6
);
}
}