use crate::base::Potential2;
use crate::math::Vector;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Quart<T> {
k2: T,
k3: T,
k4: T,
r0: T,
}
impl<T: Vector> Quart<T> {
#[inline]
pub fn new(k2: f64, k3: f64, k4: f64, r0: f64) -> Self {
Self {
k2: T::splat(k2),
k3: T::splat(k3),
k4: T::splat(k4),
r0: T::splat(r0),
}
}
#[inline]
pub fn symmetric(k2: f64, k4: f64, r0: f64) -> Self {
Self::new(k2, 0.0, k4, r0)
}
#[inline]
pub fn k2(&self) -> T {
self.k2
}
#[inline]
pub fn k3(&self) -> T {
self.k3
}
#[inline]
pub fn k4(&self) -> T {
self.k4
}
#[inline]
pub fn r0(&self) -> T {
self.r0
}
}
impl<T: Vector> Potential2<T> for Quart<T> {
#[inline(always)]
fn energy(&self, r_sq: T) -> T {
let r = r_sq.sqrt();
let dr = r - self.r0;
let dr_sq = dr * dr;
let inner = self.k3 + dr * self.k4;
let result = self.k2 + dr * inner;
dr_sq * result
}
#[inline(always)]
fn force_factor(&self, r_sq: T) -> T {
let r = r_sq.sqrt();
let r_inv = r.recip();
let dr = r - self.r0;
let two = T::splat(2.0);
let three = T::splat(3.0);
let four = T::splat(4.0);
let inner = three * self.k3 + dr * four * self.k4;
let dv_dr = dr * (two * self.k2 + dr * inner);
T::zero() - dv_dr * r_inv
}
#[inline(always)]
fn energy_force(&self, r_sq: T) -> (T, T) {
let r = r_sq.sqrt();
let r_inv = r.recip();
let dr = r - self.r0;
let dr_sq = dr * dr;
let two = T::splat(2.0);
let three = T::splat(3.0);
let four = T::splat(4.0);
let e_inner = self.k3 + dr * self.k4;
let energy = dr_sq * (self.k2 + dr * e_inner);
let f_inner = three * self.k3 + dr * four * self.k4;
let dv_dr = dr * (two * self.k2 + dr * f_inner);
let force = T::zero() - dv_dr * r_inv;
(energy, force)
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_quart_at_equilibrium() {
let quart: Quart<f64> = Quart::new(300.0, -50.0, 10.0, 1.5);
let r0 = 1.5;
let energy = quart.energy(r0 * r0);
assert_relative_eq!(energy, 0.0, epsilon = 1e-10);
}
#[test]
fn test_quart_force_at_equilibrium() {
let quart: Quart<f64> = Quart::new(300.0, -50.0, 10.0, 1.5);
let r0 = 1.5;
let force = quart.force_factor(r0 * r0);
assert_relative_eq!(force, 0.0, epsilon = 1e-10);
}
#[test]
fn test_quart_reduces_to_harmonic() {
let k = 300.0;
let r0 = 1.5;
let quart: Quart<f64> = Quart::new(k, 0.0, 0.0, r0);
let harm = crate::bond::Harm::<f64>::new(k, r0);
let r_sq = 1.6 * 1.6;
assert_relative_eq!(quart.energy(r_sq), harm.energy(r_sq), epsilon = 1e-10);
}
#[test]
fn test_quart_symmetric() {
let quart: Quart<f64> = Quart::symmetric(300.0, 10.0, 1.0);
let dr = 0.2;
let e_stretch = quart.energy((1.0 + dr).powi(2));
let e_compress = quart.energy((1.0 - dr).powi(2));
assert_relative_eq!(e_stretch, e_compress, epsilon = 1e-10);
}
#[test]
fn test_quart_numerical_derivative() {
let quart: Quart<f64> = Quart::new(300.0, -50.0, 10.0, 1.5);
let r = 1.6;
let r_sq = r * r;
let h = 1e-6;
let v_plus = quart.energy((r + h) * (r + h));
let v_minus = quart.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 = quart.force_factor(r_sq);
assert_relative_eq!(s_analytical, s_numerical, epsilon = 1e-6);
}
}