1use super::*;
7
8use gchemol::Molecule;
9use vecfx::*;
10#[derive(Clone, Copy, Debug)]
14pub struct LennardJones {
15 pub epsilon: f64,
17 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 derivative_order: 0,
30 }
31 }
32}
33
34impl LennardJones {
35 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 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 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 for i in 0..n {
55 for j in 0..3 {
56 forces[i][j] = 0.0;
57 }
58 }
59
60 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 let energy: f64 = parts.iter().map(|(e, _, _)| *e).sum();
75
76 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}
88impl 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 for _ in 0..natoms {
103 forces.push([0.0; 3]);
104 }
105
106 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
138fn get_distance_matrix(points: &[[f64; 3]]) -> Vec<Vec<f64>> {
140 use gchemol::geom::prelude::*;
141
142 let npts = points.len();
143
144 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#[test]
161fn test_lj_model() {
162 use vecfx::approx::*;
163
164 let mut lj = LennardJones::default();
167 lj.derivative_order = 1;
168
169 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 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