use crate::base::Potential2;
use crate::math::Vector;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Lj<T> {
c12: T,
c6: T,
}
impl<T: Vector> Lj<T> {
#[inline]
pub fn new(epsilon: f64, sigma: f64) -> Self {
let sigma_sq = sigma * sigma;
let sigma_6 = sigma_sq * sigma_sq * sigma_sq;
let sigma_12 = sigma_6 * sigma_6;
let four_eps = 4.0 * epsilon;
Self {
c12: T::splat(four_eps * sigma_12),
c6: T::splat(four_eps * sigma_6),
}
}
#[inline]
pub fn from_coefficients(c12: f64, c6: f64) -> Self {
Self {
c12: T::splat(c12),
c6: T::splat(c6),
}
}
#[inline]
pub fn c12(&self) -> T {
self.c12
}
#[inline]
pub fn c6(&self) -> T {
self.c6
}
}
impl<T: Vector> Potential2<T> for Lj<T> {
#[inline(always)]
fn energy(&self, r_sq: T) -> T {
let r_sq_inv = r_sq.recip(); let r6_inv = r_sq_inv * r_sq_inv * r_sq_inv; let r12_inv = r6_inv * r6_inv;
self.c12 * r12_inv - self.c6 * r6_inv
}
#[inline(always)]
fn force_factor(&self, r_sq: T) -> T {
let r_sq_inv = r_sq.recip();
let r6_inv = r_sq_inv * r_sq_inv * r_sq_inv;
let r12_inv = r6_inv * r6_inv;
let six = T::splat(6.0);
let twelve = T::splat(12.0);
(twelve * self.c12 * r12_inv - six * self.c6 * r6_inv) * r_sq_inv
}
#[inline(always)]
fn energy_force(&self, r_sq: T) -> (T, T) {
let r_sq_inv = r_sq.recip();
let r6_inv = r_sq_inv * r_sq_inv * r_sq_inv;
let r12_inv = r6_inv * r6_inv;
let c12_r12 = self.c12 * r12_inv;
let c6_r6 = self.c6 * r6_inv;
let energy = c12_r12 - c6_r6;
let six = T::splat(6.0);
let twelve = T::splat(12.0);
let force = (twelve * c12_r12 - six * c6_r6) * r_sq_inv;
(energy, force)
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_lj_at_sigma() {
let lj: Lj<f64> = Lj::new(1.0, 1.0);
let r_sq = 1.0;
let energy = lj.energy(r_sq);
assert_relative_eq!(energy, 0.0, epsilon = 1e-10);
}
#[test]
fn test_lj_at_r_min() {
let epsilon = 1.0;
let sigma = 1.0;
let lj: Lj<f64> = Lj::new(epsilon, sigma);
let r_min = 2.0_f64.powf(1.0 / 6.0) * sigma;
let r_sq = r_min * r_min;
let energy = lj.energy(r_sq);
assert_relative_eq!(energy, -epsilon, epsilon = 1e-10);
}
#[test]
fn test_lj_force_at_r_min() {
let lj: Lj<f64> = Lj::new(1.0, 1.0);
let r_min = 2.0_f64.powf(1.0 / 6.0);
let r_sq = r_min * r_min;
let force = lj.force_factor(r_sq);
assert_relative_eq!(force, 0.0, epsilon = 1e-10);
}
#[test]
fn test_lj_energy_force_consistency() {
let lj: Lj<f64> = Lj::new(0.238, 3.4);
let r_sq = 16.0;
let (e1, f1) = lj.energy_force(r_sq);
let e2 = lj.energy(r_sq);
let f2 = lj.force_factor(r_sq);
assert_relative_eq!(e1, e2, epsilon = 1e-12);
assert_relative_eq!(f1, f2, epsilon = 1e-12);
}
#[test]
fn test_lj_numerical_derivative() {
let lj: Lj<f64> = Lj::new(1.0, 1.0);
let r = 1.5;
let r_sq = r * r;
let h = 1e-6;
let v_plus = lj.energy((r + h) * (r + h));
let v_minus = lj.energy((r - h) * (r - h));
let dv_dr_numerical = (v_plus - v_minus) / (2.0 * h);
let s_numerical = -dv_dr_numerical / r;
let s_analytical = lj.force_factor(r_sq);
assert_relative_eq!(s_analytical, s_numerical, epsilon = 1e-6);
}
}