use super::*;
use gchemol::Molecule;
use vecfx::*;
#[derive(Clone, Copy, Debug)]
pub struct LennardJones {
pub epsilon: f64,
pub sigma: f64,
pub derivative_order: usize,
}
impl Default for LennardJones {
fn default() -> Self {
LennardJones {
epsilon: 1.0,
sigma: 1.0,
derivative_order: 0,
}
}
}
impl LennardJones {
fn pair_energy(&self, r: f64) -> f64 {
let s6 = f64::powi(self.sigma / r, 6);
4.0 * self.epsilon * (f64::powi(s6, 2) - s6)
}
fn pair_gradient(&self, r: f64) -> f64 {
let s6 = f64::powi(self.sigma / r, 6);
24.0 * self.epsilon * (s6 - 2.0 * f64::powi(s6, 2)) / r
}
pub fn evaluate(&self, positions: &[[f64; 3]], forces: &mut [[f64; 3]]) -> f64 {
let n = positions.len();
debug_assert_eq!(n, forces.len(), "positions.len() != forces.len()");
for i in 0..n {
for j in 0..3 {
forces[i][j] = 0.0;
}
}
let parts: Vec<_> = (0..n)
.into_par_iter()
.flat_map(|i| {
(0..i).into_par_iter().map(move |j| {
let r = positions[i].vecdist(&positions[j]);
let e = self.pair_energy(r);
let g = self.pair_gradient(r) / r;
(e, g, (i, j))
})
})
.collect();
let energy: f64 = parts.iter().map(|(e, _, _)| *e).sum();
for (_, g, (i, j)) in parts {
for k in 0..3 {
let dr = positions[j][k] - positions[i][k];
forces[i][k] += 1.0 * g * dr;
forces[j][k] += -1.0 * g * dr;
}
}
energy
}
}
impl ChemicalModel for LennardJones {
fn compute(&mut self, mol: &Molecule) -> Result<Computed> {
if mol.lattice.is_some() {
warn!("LJ model: periodic lattice will be ignored!");
}
let natoms = mol.natoms();
let mut energy = 0.0;
let mut forces = Vec::with_capacity(natoms);
for _ in 0..natoms {
forces.push([0.0; 3]);
}
let positions: Vec<_> = mol.positions().collect();
let dm = get_distance_matrix(&positions);
for i in 0..natoms {
for j in 0..i {
let r = dm[i][j];
energy += self.pair_energy(r);
if self.derivative_order >= 1 {
let g = self.pair_gradient(r);
for k in 0..3 {
let dr = positions[j][k] - positions[i][k];
forces[i][k] += 1.0 * g * dr / r;
forces[j][k] += -1.0 * g * dr / r;
}
}
}
}
let mut computed = Computed::default();
computed.set_energy(energy);
if self.derivative_order >= 1 {
computed.set_forces(forces);
}
if self.derivative_order >= 2 {
unimplemented!();
}
Ok(computed)
}
}
fn get_distance_matrix(points: &[[f64; 3]]) -> Vec<Vec<f64>> {
use gchemol::geom::prelude::*;
let npts = points.len();
let mut distmat = vec![];
for i in 0..npts {
let mut dijs = vec![];
for j in 0..npts {
let dij = points[i].distance(points[j]);
dijs.push(dij);
}
distmat.push(dijs);
}
distmat
}
#[test]
fn test_lj_model() {
use vecfx::approx::*;
let mut lj = LennardJones::default();
lj.derivative_order = 1;
let mol = Molecule::from_file("tests/files/LennardJones/LJ3.xyz").expect("lj3 test file");
let mr = lj.compute(&mol).expect("lj model: LJ3");
let e = mr.get_energy().expect("lj model energy: LJ3");
assert_relative_eq!(-3.0, e, epsilon = 1e-3);
let forces = mr.get_forces().expect("lj model forces: LJ3");
for i in 0..mol.natoms() {
for j in 0..3 {
assert_relative_eq!(0.0, forces[i][j], epsilon = 1e-3);
}
}
let mol = Molecule::from_file("tests/files/LennardJones/LJ38.xyz").expect("lj38 test file");
let mr = lj.compute(&mol).expect("lj model: LJ38");
let e = mr.get_energy().expect("lj model energy: LJ38");
assert_relative_eq!(-173.92843, e, epsilon = 1e-3);
let forces = mr.get_forces().expect("lj model forces: LJ3");
for i in 0..mol.natoms() {
for j in 0..3 {
assert_relative_eq!(0.0, forces[i][j], epsilon = 1e-3);
}
}
}