gosh_model/
lj.rs

1// [[file:../models.note::*header][header:1]]
2//! The Lennard-Jones model for test purpose
3// header:1 ends here
4
5// [[file:../models.note::5aceffc7][5aceffc7]]
6use super::*;
7
8use gchemol::Molecule;
9use vecfx::*;
10// 5aceffc7 ends here
11
12// [[file:../models.note::*core][core:1]]
13#[derive(Clone, Copy, Debug)]
14pub struct LennardJones {
15    /// Energy constant of the Lennard-Jones potential
16    pub epsilon: f64,
17    /// Distance constant of the Lennard-Jones potential
18    pub sigma: f64,
19
20    pub derivative_order: usize,
21}
22
23impl Default for LennardJones {
24    fn default() -> Self {
25        LennardJones {
26            epsilon: 1.0,
27            sigma: 1.0,
28            // energy only
29            derivative_order: 0,
30        }
31    }
32}
33
34impl LennardJones {
35    // vij
36    fn pair_energy(&self, r: f64) -> f64 {
37        let s6 = f64::powi(self.sigma / r, 6);
38        4.0 * self.epsilon * (f64::powi(s6, 2) - s6)
39    }
40
41    // dvij
42    fn pair_gradient(&self, r: f64) -> f64 {
43        let s6 = f64::powi(self.sigma / r, 6);
44
45        24.0 * self.epsilon * (s6 - 2.0 * f64::powi(s6, 2)) / r
46    }
47
48    /// Evaluate energy and forces
49    pub fn evaluate(&self, positions: &[[f64; 3]], forces: &mut [[f64; 3]]) -> f64 {
50        let n = positions.len();
51        debug_assert_eq!(n, forces.len(), "positions.len() != forces.len()");
52
53        // initialize with zeros
54        for i in 0..n {
55            for j in 0..3 {
56                forces[i][j] = 0.0;
57            }
58        }
59
60        // collect parts in parallel
61        let parts: Vec<_> = (0..n)
62            .into_par_iter()
63            .flat_map(|i| {
64                (0..i).into_par_iter().map(move |j| {
65                    let r = positions[i].vecdist(&positions[j]);
66                    let e = self.pair_energy(r);
67                    let g = self.pair_gradient(r) / r;
68                    (e, g, (i, j))
69                })
70            })
71            .collect();
72
73        // calculate energy
74        let energy: f64 = parts.iter().map(|(e, _, _)| *e).sum();
75
76        // calculate force
77        for (_, g, (i, j)) in parts {
78            for k in 0..3 {
79                let dr = positions[j][k] - positions[i][k];
80                forces[i][k] += 1.0 * g * dr;
81                forces[j][k] += -1.0 * g * dr;
82            }
83        }
84
85        energy
86    }
87}
88// core:1 ends here
89
90// [[file:../models.note::d55b0da4][d55b0da4]]
91impl ChemicalModel for LennardJones {
92    fn compute(&mut self, mol: &Molecule) -> Result<Computed> {
93        if mol.lattice.is_some() {
94            warn!("LJ model: periodic lattice will be ignored!");
95        }
96
97        let natoms = mol.natoms();
98        let mut energy = 0.0;
99        let mut forces = Vec::with_capacity(natoms);
100
101        // initialize with zeros
102        for _ in 0..natoms {
103            forces.push([0.0; 3]);
104        }
105
106        // calculate energy and forces
107        let positions: Vec<_> = mol.positions().collect();
108        let dm = get_distance_matrix(&positions);
109        for i in 0..natoms {
110            for j in 0..i {
111                let r = dm[i][j];
112                energy += self.pair_energy(r);
113                if self.derivative_order >= 1 {
114                    let g = self.pair_gradient(r);
115                    for k in 0..3 {
116                        let dr = positions[j][k] - positions[i][k];
117                        forces[i][k] += 1.0 * g * dr / r;
118                        forces[j][k] += -1.0 * g * dr / r;
119                    }
120                }
121            }
122        }
123
124        let mut computed = Computed::default();
125        computed.set_energy(energy);
126
127        if self.derivative_order >= 1 {
128            computed.set_forces(forces);
129        }
130        if self.derivative_order >= 2 {
131            unimplemented!();
132        }
133
134        Ok(computed)
135    }
136}
137
138/// Return all distances between any pair of points
139fn get_distance_matrix(points: &[[f64; 3]]) -> Vec<Vec<f64>> {
140    use gchemol::geom::prelude::*;
141
142    let npts = points.len();
143
144    // fill distance matrix
145    let mut distmat = vec![];
146    for i in 0..npts {
147        let mut dijs = vec![];
148        for j in 0..npts {
149            let dij = points[i].distance(points[j]);
150            dijs.push(dij);
151        }
152        distmat.push(dijs);
153    }
154
155    distmat
156}
157// d55b0da4 ends here
158
159// [[file:../models.note::*test][test:1]]
160#[test]
161fn test_lj_model() {
162    use vecfx::approx::*;
163
164    // gosh_core::gut::cli::setup_logger();
165
166    let mut lj = LennardJones::default();
167    lj.derivative_order = 1;
168
169    // LJ3
170    let mol = Molecule::from_file("tests/files/LennardJones/LJ3.xyz").expect("lj3 test file");
171    let mr = lj.compute(&mol).expect("lj model: LJ3");
172    let e = mr.get_energy().expect("lj model energy: LJ3");
173    assert_relative_eq!(-3.0, e, epsilon = 1e-3);
174
175    let forces = mr.get_forces().expect("lj model forces: LJ3");
176    for i in 0..mol.natoms() {
177        for j in 0..3 {
178            assert_relative_eq!(0.0, forces[i][j], epsilon = 1e-3);
179        }
180    }
181
182    // LJ38
183    let mol = Molecule::from_file("tests/files/LennardJones/LJ38.xyz").expect("lj38 test file");
184    let mr = lj.compute(&mol).expect("lj model: LJ38");
185    let e = mr.get_energy().expect("lj model energy: LJ38");
186    assert_relative_eq!(-173.92843, e, epsilon = 1e-3);
187
188    let forces = mr.get_forces().expect("lj model forces: LJ3");
189    for i in 0..mol.natoms() {
190        for j in 0..3 {
191            assert_relative_eq!(0.0, forces[i][j], epsilon = 1e-3);
192        }
193    }
194}
195// test:1 ends here