use super::geom::{add_grad, norm, pt, scale, sub};
use super::params::{COULOMB, ELE_BUFFER};
const VDW1: f64 = 1.07;
const VDW1M1: f64 = VDW1 - 1.0;
const VDW2: f64 = 1.12;
const VDW2M1: f64 = VDW2 - 1.0;
const SC1_4: f64 = 0.75;
#[derive(Clone, Copy, Debug)]
pub(super) struct NonbondedTerm {
pub i: usize,
pub j: usize,
pub vdw: Option<(f64, f64)>,
pub charge: Option<f64>,
pub is_1_4: bool,
}
impl NonbondedTerm {
pub(super) fn energy_grad(&self, coords: &[f64], grad: &mut [f64]) -> f64 {
let rij = sub(pt(coords, self.i), pt(coords, self.j));
let dist = norm(rij);
let mut energy = 0.0;
let mut vdw_grad = 0.0;
let mut ele_grad = 0.0;
if dist <= 0.0 {
if let Some((r_star, _)) = self.vdw {
let g = [r_star * 0.01; 3];
add_grad(grad, self.i, g);
add_grad(grad, self.j, scale(g, -1.0));
}
if self.charge.is_some() {
let g = [0.02; 3];
add_grad(grad, self.i, g);
add_grad(grad, self.j, scale(g, -1.0));
}
return 0.0;
}
if let Some((r_star, well_depth)) = self.vdw {
let dist7 = dist.powi(7);
let a_term = VDW1 * r_star / (dist + VDW1M1 * r_star);
let a_term7 = a_term.powi(7);
let rs7 = r_star.powi(7);
let b_term = VDW2 * rs7 / (dist7 + VDW2M1 * rs7) - 2.0;
energy += well_depth * a_term7 * b_term;
let q = dist / r_star;
let q6 = q.powi(6);
let q7 = q6 * q;
let q7p = q7 + VDW2M1;
let t = VDW1 / (q + VDW1M1);
let t7 = t.powi(7);
let de_dr = well_depth / r_star
* t7
* (-VDW2 * 7.0 * q6 / (q7p * q7p) + ((-VDW2 * 7.0 / q7p + 14.0) / (q + VDW1M1)));
vdw_grad = de_dr / dist;
}
if let Some(charge_term) = self.charge {
let scale_14 = if self.is_1_4 { SC1_4 } else { 1.0 };
let corr = dist + ELE_BUFFER; energy += COULOMB * charge_term / corr * scale_14;
let de_dr = -COULOMB * charge_term / (corr * corr) * scale_14;
ele_grad = de_dr / dist;
}
let de_dr = vdw_grad + ele_grad;
let g = scale(rij, de_dr);
add_grad(grad, self.i, g);
add_grad(grad, self.j, scale(g, -1.0));
energy
}
}