use crate::base::Potential3;
use crate::math::Vector;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Cos<T> {
k: T,
cos0: T,
}
impl<T: Vector> Cos<T> {
#[inline]
pub fn new(k: f64, cos0: f64) -> Self {
Self {
k: T::splat(k),
cos0: T::splat(cos0),
}
}
#[inline]
pub fn from_theta(k: f64, theta0: f64) -> Self {
Self::new(k, theta0.cos())
}
#[inline]
pub fn from_degrees(k: f64, theta0_deg: f64) -> Self {
let theta0_rad = theta0_deg * core::f64::consts::PI / 180.0;
Self::from_theta(k, theta0_rad)
}
#[inline]
pub fn k(&self) -> T {
self.k
}
#[inline]
pub fn cos0(&self) -> T {
self.cos0
}
}
impl<T: Vector> Potential3<T> for Cos<T> {
#[inline(always)]
fn energy(&self, _r_ij_sq: T, _r_jk_sq: T, cos_theta: T) -> T {
let delta = cos_theta - self.cos0;
self.k * delta * delta
}
#[inline(always)]
fn derivative(&self, _r_ij_sq: T, _r_jk_sq: T, cos_theta: T) -> T {
let delta = cos_theta - self.cos0;
let two = T::splat(2.0);
two * self.k * delta
}
#[inline(always)]
fn energy_derivative(&self, _r_ij_sq: T, _r_jk_sq: T, cos_theta: T) -> (T, T) {
let delta = cos_theta - self.cos0;
let two = T::splat(2.0);
let energy = self.k * delta * delta;
let derivative = two * self.k * delta;
(energy, derivative)
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
use core::f64::consts::PI;
#[test]
fn test_cos_at_equilibrium() {
let cos0 = 0.5;
let angle: Cos<f64> = Cos::new(100.0, cos0);
let energy = angle.energy(1.0, 1.0, cos0);
assert_relative_eq!(energy, 0.0, epsilon = 1e-10);
}
#[test]
fn test_cos_derivative_at_equilibrium() {
let cos0 = 0.5;
let angle: Cos<f64> = Cos::new(100.0, cos0);
let deriv = angle.derivative(1.0, 1.0, cos0);
assert_relative_eq!(deriv, 0.0, epsilon = 1e-10);
}
#[test]
fn test_cos_displaced() {
let k = 100.0;
let cos0 = 0.5;
let angle: Cos<f64> = Cos::new(k, cos0);
let cos_theta = 0.7;
let energy = angle.energy(1.0, 1.0, cos_theta);
let delta = cos_theta - cos0;
let expected = k * delta * delta;
assert_relative_eq!(energy, expected, epsilon = 1e-10);
}
#[test]
fn test_cos_from_constructors() {
let theta0 = PI / 3.0;
let cos0 = theta0.cos();
let a1: Cos<f64> = Cos::new(100.0, cos0);
let a2: Cos<f64> = Cos::from_theta(100.0, theta0);
let a3: Cos<f64> = Cos::from_degrees(100.0, 60.0);
let cos_theta = 0.3;
assert_relative_eq!(
a1.energy(1.0, 1.0, cos_theta),
a2.energy(1.0, 1.0, cos_theta),
epsilon = 1e-10
);
assert_relative_eq!(
a1.energy(1.0, 1.0, cos_theta),
a3.energy(1.0, 1.0, cos_theta),
epsilon = 1e-10
);
}
#[test]
fn test_cos_numerical_derivative() {
let angle: Cos<f64> = Cos::new(100.0, 0.5);
let cos_theta = 0.3;
let h = 1e-7;
let e_plus = angle.energy(1.0, 1.0, cos_theta + h);
let e_minus = angle.energy(1.0, 1.0, cos_theta - h);
let deriv_numerical = (e_plus - e_minus) / (2.0 * h);
let deriv_analytical = angle.derivative(1.0, 1.0, cos_theta);
assert_relative_eq!(deriv_analytical, deriv_numerical, epsilon = 1e-6);
}
#[test]
fn test_cos_energy_derivative_consistency() {
let angle: Cos<f64> = Cos::new(100.0, 0.5);
let cos_theta = 0.3;
let (e1, d1) = angle.energy_derivative(1.0, 1.0, cos_theta);
let e2 = angle.energy(1.0, 1.0, cos_theta);
let d2 = angle.derivative(1.0, 1.0, cos_theta);
assert_relative_eq!(e1, e2, epsilon = 1e-10);
assert_relative_eq!(d1, d2, epsilon = 1e-10);
}
}