use crate::base::Potential4;
use crate::math::Vector;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Harm<T> {
two_k: T,
phi0: T,
cos_phi0: T,
sin_phi0: T,
}
impl<T: Vector> Harm<T> {
#[inline]
pub fn new(k: f64, phi0: f64) -> Self {
Self {
two_k: T::splat(2.0 * k),
phi0: T::splat(phi0),
cos_phi0: T::splat(phi0.cos()),
sin_phi0: T::splat(phi0.sin()),
}
}
#[inline]
pub fn cis(k: f64) -> Self {
Self::new(k, 0.0)
}
#[inline]
pub fn trans(k: f64) -> Self {
Self::new(k, core::f64::consts::PI)
}
}
impl<T: Vector> Potential4<T> for Harm<T> {
#[inline(always)]
fn energy(&self, cos_phi: T, sin_phi: T) -> T {
let sin_delta = sin_phi * self.cos_phi0 - cos_phi * self.sin_phi0;
let cos_delta = cos_phi * self.cos_phi0 + sin_phi * self.sin_phi0;
let delta_phi = sin_delta.atan2(cos_delta);
let half = T::splat(0.5);
half * self.two_k * delta_phi * delta_phi
}
#[inline(always)]
fn derivative(&self, cos_phi: T, sin_phi: T) -> T {
let sin_delta = sin_phi * self.cos_phi0 - cos_phi * self.sin_phi0;
let cos_delta = cos_phi * self.cos_phi0 + sin_phi * self.sin_phi0;
let delta_phi = sin_delta.atan2(cos_delta);
self.two_k * delta_phi
}
#[inline(always)]
fn energy_derivative(&self, cos_phi: T, sin_phi: T) -> (T, T) {
let sin_delta = sin_phi * self.cos_phi0 - cos_phi * self.sin_phi0;
let cos_delta = cos_phi * self.cos_phi0 + sin_phi * self.sin_phi0;
let delta_phi = sin_delta.atan2(cos_delta);
let half = T::splat(0.5);
let energy = half * self.two_k * delta_phi * delta_phi;
let derivative = self.two_k * delta_phi;
(energy, derivative)
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
use core::f64::consts::PI;
#[test]
fn test_harm_at_equilibrium() {
let harm: Harm<f64> = Harm::new(100.0, PI / 3.0);
let phi = PI / 3.0;
let e = harm.energy(phi.cos(), phi.sin());
assert_relative_eq!(e, 0.0, epsilon = 1e-10);
}
#[test]
fn test_harm_displacement() {
let k = 50.0;
let harm: Harm<f64> = Harm::cis(k);
let phi = 0.3;
let e = harm.energy(phi.cos(), phi.sin());
let expected = k * phi * phi;
assert_relative_eq!(e, expected, epsilon = 1e-10);
}
#[test]
fn test_harm_trans() {
let k = 75.0;
let harm: Harm<f64> = Harm::trans(k);
let e_eq = harm.energy(-1.0, 0.0);
assert_relative_eq!(e_eq, 0.0, epsilon = 1e-10);
let phi = PI - 0.2;
let e = harm.energy(phi.cos(), phi.sin());
let expected = k * 0.2 * 0.2;
assert_relative_eq!(e, expected, epsilon = 1e-10);
}
#[test]
fn test_harm_derivative_at_equilibrium() {
let harm: Harm<f64> = Harm::new(100.0, PI / 4.0);
let phi = PI / 4.0;
let d = harm.derivative(phi.cos(), phi.sin());
assert_relative_eq!(d, 0.0, epsilon = 1e-10);
}
#[test]
fn test_harm_numerical_derivative() {
let harm: Harm<f64> = Harm::new(50.0, 0.5);
let phi = 0.8;
let h = 1e-7;
let e_plus = harm.energy((phi + h).cos(), (phi + h).sin());
let e_minus = harm.energy((phi - h).cos(), (phi - h).sin());
let deriv_numerical = (e_plus - e_minus) / (2.0 * h);
let deriv_analytical = harm.derivative(phi.cos(), phi.sin());
assert_relative_eq!(deriv_analytical, deriv_numerical, epsilon = 1e-6);
}
#[test]
fn test_harm_energy_derivative_consistency() {
let harm: Harm<f64> = Harm::new(30.0, -PI / 6.0);
let phi = 0.4;
let e = harm.energy(phi.cos(), phi.sin());
let d = harm.derivative(phi.cos(), phi.sin());
let (e2, d2) = harm.energy_derivative(phi.cos(), phi.sin());
assert_relative_eq!(e, e2, epsilon = 1e-10);
assert_relative_eq!(d, d2, epsilon = 1e-10);
}
#[test]
fn test_harm_sign_of_derivative() {
let harm: Harm<f64> = Harm::cis(100.0);
let phi_pos = 0.3;
let d_pos = harm.derivative(phi_pos.cos(), phi_pos.sin());
assert!(d_pos > 0.0, "Expected positive derivative for phi > phi0");
let phi_neg = -0.3;
let d_neg = harm.derivative(phi_neg.cos(), phi_neg.sin());
assert!(d_neg < 0.0, "Expected negative derivative for phi < phi0");
}
}