use crate::base::Potential4;
use crate::math::Vector;
#[derive(Clone, Copy, Debug)]
pub struct Cos<T> {
k: T,
n: i32,
cos_delta: T,
sin_delta: T,
}
impl<T: Vector> Cos<T> {
#[inline]
pub fn new(k: f64, n: i32, delta: f64) -> Self {
Self {
k: T::splat(k),
n,
cos_delta: T::splat(delta.cos()),
sin_delta: T::splat(delta.sin()),
}
}
#[inline]
pub fn from_degrees(k: f64, n: i32, delta_deg: f64) -> Self {
let delta_rad = delta_deg * core::f64::consts::PI / 180.0;
Self::new(k, n, delta_rad)
}
#[inline]
pub fn k(&self) -> T {
self.k
}
#[inline]
pub fn n(&self) -> i32 {
self.n
}
#[inline(always)]
fn cos_n_phi(&self, cos_phi: T) -> T {
match self.n {
0 => T::one(),
1 => cos_phi,
2 => {
let two = T::splat(2.0);
two * cos_phi * cos_phi - T::one()
}
3 => {
let three = T::splat(3.0);
let four = T::splat(4.0);
four * cos_phi * cos_phi * cos_phi - three * cos_phi
}
_ => {
let two = T::splat(2.0);
let mut t_prev2 = T::one();
let mut t_prev1 = cos_phi;
for _ in 2..=self.n.unsigned_abs() {
let t_curr = two * cos_phi * t_prev1 - t_prev2;
t_prev2 = t_prev1;
t_prev1 = t_curr;
}
t_prev1
}
}
}
#[inline(always)]
fn sin_n_phi(&self, cos_phi: T, sin_phi: T) -> T {
match self.n {
0 => T::zero(),
1 => sin_phi,
2 => {
let two = T::splat(2.0);
two * sin_phi * cos_phi
}
3 => {
let four = T::splat(4.0);
sin_phi * (four * cos_phi * cos_phi - T::one())
}
_ => {
let two = T::splat(2.0);
let n_abs = self.n.unsigned_abs() as i32;
if n_abs == 0 {
return T::zero();
}
let mut u_prev2 = T::one();
let mut u_prev1 = two * cos_phi;
if n_abs == 1 {
return sin_phi;
}
for _ in 2..n_abs {
let u_curr = two * cos_phi * u_prev1 - u_prev2;
u_prev2 = u_prev1;
u_prev1 = u_curr;
}
let result = sin_phi * u_prev1;
if self.n < 0 {
T::zero() - result } else {
result
}
}
}
}
}
impl<T: Vector> Potential4<T> for Cos<T> {
#[inline(always)]
fn energy(&self, cos_phi: T, sin_phi: T) -> T {
let cos_n = self.cos_n_phi(cos_phi);
let sin_n = self.sin_n_phi(cos_phi, sin_phi);
let cos_term = cos_n * self.cos_delta + sin_n * self.sin_delta;
self.k * (T::one() + cos_term)
}
#[inline(always)]
fn derivative(&self, cos_phi: T, sin_phi: T) -> T {
let cos_n = self.cos_n_phi(cos_phi);
let sin_n = self.sin_n_phi(cos_phi, sin_phi);
let sin_term = sin_n * self.cos_delta - cos_n * self.sin_delta;
let n_t = T::splat(self.n as f64);
T::zero() - self.k * n_t * sin_term
}
#[inline(always)]
fn energy_derivative(&self, cos_phi: T, sin_phi: T) -> (T, T) {
let cos_n = self.cos_n_phi(cos_phi);
let sin_n = self.sin_n_phi(cos_phi, sin_phi);
let cos_term = cos_n * self.cos_delta + sin_n * self.sin_delta;
let sin_term = sin_n * self.cos_delta - cos_n * self.sin_delta;
let energy = self.k * (T::one() + cos_term);
let n_t = T::splat(self.n as f64);
let derivative = T::zero() - self.k * n_t * sin_term;
(energy, derivative)
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
use core::f64::consts::PI;
#[test]
fn test_cos_n1_delta0() {
let k = 2.0;
let torsion: Cos<f64> = Cos::new(k, 1, 0.0);
let e0 = torsion.energy(1.0, 0.0);
assert_relative_eq!(e0, 2.0 * k, epsilon = 1e-10);
let e_pi = torsion.energy(-1.0, 0.0);
assert_relative_eq!(e_pi, 0.0, epsilon = 1e-10);
}
#[test]
fn test_cos_n3() {
let k = 1.0;
let torsion: Cos<f64> = Cos::new(k, 3, 0.0);
let e0 = torsion.energy(1.0, 0.0);
assert_relative_eq!(e0, 2.0, epsilon = 1e-10);
let phi = PI / 3.0;
let e60 = torsion.energy(phi.cos(), phi.sin());
assert_relative_eq!(e60, 0.0, epsilon = 1e-10);
}
#[test]
fn test_cos_with_phase() {
let k = 1.0;
let delta = PI;
let torsion: Cos<f64> = Cos::new(k, 1, delta);
let e0 = torsion.energy(1.0, 0.0);
assert_relative_eq!(e0, 0.0, epsilon = 1e-10);
let e_pi = torsion.energy(-1.0, 0.0);
assert_relative_eq!(e_pi, 2.0 * k, epsilon = 1e-10);
}
#[test]
fn test_cos_derivative_at_extrema() {
let torsion: Cos<f64> = Cos::new(1.0, 1, 0.0);
let d0 = torsion.derivative(1.0, 0.0);
assert_relative_eq!(d0, 0.0, epsilon = 1e-10);
let d_pi = torsion.derivative(-1.0, 0.0);
assert_relative_eq!(d_pi, 0.0, epsilon = 1e-10);
}
#[test]
fn test_cos_numerical_derivative() {
let torsion: Cos<f64> = Cos::new(2.0, 3, PI / 4.0);
let phi = 0.7;
let h = 1e-7;
let e_plus = torsion.energy((phi + h).cos(), (phi + h).sin());
let e_minus = torsion.energy((phi - h).cos(), (phi - h).sin());
let deriv_numerical = (e_plus - e_minus) / (2.0 * h);
let deriv_analytical = torsion.derivative(phi.cos(), phi.sin());
assert_relative_eq!(deriv_analytical, deriv_numerical, epsilon = 1e-6);
}
}