use crate::base::Potential3;
use crate::math::Vector;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Urey<T> {
k_cos: T,
cos0: T,
k_ub: T,
r_ub: T,
}
impl<T: Vector> Urey<T> {
#[inline]
pub fn new(k_cos: f64, cos0: f64, k_ub: f64, r_ub: f64) -> Self {
Self {
k_cos: T::splat(k_cos),
cos0: T::splat(cos0),
k_ub: T::splat(k_ub),
r_ub: T::splat(r_ub),
}
}
#[inline]
pub fn from_degrees(k_cos: f64, theta0_deg: f64, k_ub: f64, r_ub: f64) -> Self {
let theta0_rad = theta0_deg * core::f64::consts::PI / 180.0;
Self::new(k_cos, theta0_rad.cos(), k_ub, r_ub)
}
#[inline]
pub fn k_cos(&self) -> T {
self.k_cos
}
#[inline]
pub fn k_ub(&self) -> T {
self.k_ub
}
}
impl<T: Vector> Potential3<T> for Urey<T> {
#[inline(always)]
fn energy(&self, r_ij_sq: T, r_jk_sq: T, cos_theta: T) -> T {
let delta_cos = cos_theta - self.cos0;
let e_angle = self.k_cos * delta_cos * delta_cos;
let r_ij = r_ij_sq.sqrt();
let r_jk = r_jk_sq.sqrt();
let two = T::splat(2.0);
let r_ik_sq = r_ij_sq + r_jk_sq - two * r_ij * r_jk * cos_theta;
let r_ik = r_ik_sq.sqrt();
let delta_r = r_ik - self.r_ub;
let e_ub = self.k_ub * delta_r * delta_r;
e_angle + e_ub
}
#[inline(always)]
fn derivative(&self, r_ij_sq: T, r_jk_sq: T, cos_theta: T) -> T {
let delta_cos = cos_theta - self.cos0;
let two = T::splat(2.0);
let d_angle = two * self.k_cos * delta_cos;
let r_ij = r_ij_sq.sqrt();
let r_jk = r_jk_sq.sqrt();
let r_ik_sq = r_ij_sq + r_jk_sq - two * r_ij * r_jk * cos_theta;
let r_ik = r_ik_sq.sqrt();
let dr_ik_dcos = T::zero() - r_ij * r_jk / r_ik;
let delta_r = r_ik - self.r_ub;
let d_ub = two * self.k_ub * delta_r * dr_ik_dcos;
d_angle + d_ub
}
#[inline(always)]
fn energy_derivative(&self, r_ij_sq: T, r_jk_sq: T, cos_theta: T) -> (T, T) {
let two = T::splat(2.0);
let r_ij = r_ij_sq.sqrt();
let r_jk = r_jk_sq.sqrt();
let r_ij_r_jk = r_ij * r_jk;
let r_ik_sq = r_ij_sq + r_jk_sq - two * r_ij_r_jk * cos_theta;
let r_ik = r_ik_sq.sqrt();
let delta_cos = cos_theta - self.cos0;
let e_angle = self.k_cos * delta_cos * delta_cos;
let d_angle = two * self.k_cos * delta_cos;
let delta_r = r_ik - self.r_ub;
let e_ub = self.k_ub * delta_r * delta_r;
let dr_ik_dcos = T::zero() - r_ij_r_jk / r_ik;
let d_ub = two * self.k_ub * delta_r * dr_ik_dcos;
(e_angle + e_ub, d_angle + d_ub)
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_urey_at_equilibrium() {
let cos0 = 0.0;
let r_ij = 1.0;
let r_jk = 1.0;
let r_ub = (r_ij * r_ij + r_jk * r_jk).sqrt();
let urey: Urey<f64> = Urey::new(100.0, cos0, 50.0, r_ub);
let energy = urey.energy(r_ij * r_ij, r_jk * r_jk, cos0);
assert_relative_eq!(energy, 0.0, epsilon = 1e-10);
}
#[test]
fn test_urey_only_angle() {
let k_cos = 100.0;
let cos0 = 0.5;
let urey: Urey<f64> = Urey::new(k_cos, cos0, 0.0, 1.0);
let cos_angle = crate::angle::Cos::<f64>::new(k_cos, cos0);
let cos_theta = 0.3;
assert_relative_eq!(
urey.energy(1.0, 1.0, cos_theta),
cos_angle.energy(1.0, 1.0, cos_theta),
epsilon = 1e-10
);
}
#[test]
fn test_urey_numerical_derivative() {
let urey: Urey<f64> = Urey::new(100.0, 0.5, 50.0, 1.5);
let cos_theta = 0.3;
let r_ij_sq = 1.0;
let r_jk_sq = 1.2;
let h = 1e-7;
let e_plus = urey.energy(r_ij_sq, r_jk_sq, cos_theta + h);
let e_minus = urey.energy(r_ij_sq, r_jk_sq, cos_theta - h);
let deriv_numerical = (e_plus - e_minus) / (2.0 * h);
let deriv_analytical = urey.derivative(r_ij_sq, r_jk_sq, cos_theta);
assert_relative_eq!(deriv_analytical, deriv_numerical, epsilon = 1e-6);
}
}