1use super::traits::ForceFieldContribution;
2
3pub struct UffHarmonicBondStretch {
5 pub atom_i_idx: usize,
6 pub atom_j_idx: usize,
7 pub force_constant_kb: f64, pub equilibrium_r0: f64, }
10
11impl ForceFieldContribution for UffHarmonicBondStretch {
12 fn evaluate_energy_and_inject_gradient(&self, coords: &[f64], grad: &mut [f64]) -> f64 {
13 let root_i = self.atom_i_idx * 3;
14 let root_j = self.atom_j_idx * 3;
15
16 let mut diff_x = coords[root_i] - coords[root_j];
17 let diff_y = coords[root_i + 1] - coords[root_j + 1];
18 let diff_z = coords[root_i + 2] - coords[root_j + 2];
19
20 let mut inter_r = (diff_x * diff_x + diff_y * diff_y + diff_z * diff_z).sqrt();
21
22 if inter_r < 1e-10 {
23 inter_r = 1e-10;
24 diff_x = 1e-10;
25 }
26
27 let spatial_delta = inter_r - self.equilibrium_r0;
28 let bond_energy = 0.5 * self.force_constant_kb * spatial_delta * spatial_delta;
29
30 let vectorial_scalar_prefactor = self.force_constant_kb * spatial_delta / inter_r;
31 let force_x = vectorial_scalar_prefactor * diff_x;
32 let force_y = vectorial_scalar_prefactor * diff_y;
33 let force_z = vectorial_scalar_prefactor * diff_z;
34
35 grad[root_i] += force_x;
36 grad[root_i + 1] += force_y;
37 grad[root_i + 2] += force_z;
38
39 grad[root_j] -= force_x;
40 grad[root_j + 1] -= force_y;
41 grad[root_j + 2] -= force_z;
42
43 bond_energy
44 }
45}
46
47pub struct UffAngleBend {
49 pub atom_i_idx: usize,
50 pub atom_j_idx: usize, pub atom_k_idx: usize,
52 pub force_constant_ka: f64,
53 pub equilibrium_theta0: f64,
54 pub coordination_n: usize, }
56
57impl ForceFieldContribution for UffAngleBend {
58 fn evaluate_energy_and_inject_gradient(&self, coords: &[f64], grad: &mut [f64]) -> f64 {
59 let root_i = self.atom_i_idx * 3;
60 let root_j = self.atom_j_idx * 3;
61 let root_k = self.atom_k_idx * 3;
62
63 let r_ji = [
64 coords[root_i] - coords[root_j],
65 coords[root_i + 1] - coords[root_j + 1],
66 coords[root_i + 2] - coords[root_j + 2],
67 ];
68 let r_jk = [
69 coords[root_k] - coords[root_j],
70 coords[root_k + 1] - coords[root_j + 1],
71 coords[root_k + 2] - coords[root_j + 2],
72 ];
73
74 let d_ji = (r_ji[0] * r_ji[0] + r_ji[1] * r_ji[1] + r_ji[2] * r_ji[2]).sqrt();
75 let d_jk = (r_jk[0] * r_jk[0] + r_jk[1] * r_jk[1] + r_jk[2] * r_jk[2]).sqrt();
76
77 if d_ji < 1e-10 || d_jk < 1e-10 {
78 return 0.0;
79 }
80
81 let cos_theta = (r_ji[0] * r_jk[0] + r_ji[1] * r_jk[1] + r_ji[2] * r_jk[2]) / (d_ji * d_jk);
82 let cos_theta = cos_theta.clamp(-1.0, 1.0);
83 let sin_theta = (1.0 - cos_theta * cos_theta).sqrt().max(1e-8);
84
85 let (energy, d_e_dtheta) = match self.coordination_n {
86 0 => {
87 let e = self.force_constant_ka * (1.0 + cos_theta);
90 let de = -self.force_constant_ka * sin_theta;
91 (e, de)
92 }
93 _ => {
94 let cos_theta0 = self.equilibrium_theta0.cos();
96 let sin_theta0 = self.equilibrium_theta0.sin();
97
98 let c2 = 1.0 / (4.0 * sin_theta0 * sin_theta0).max(1e-8);
99 let c1 = -4.0 * c2 * cos_theta0;
100 let c0 = c2 * (2.0 * cos_theta0 * cos_theta0 + 1.0);
101
102 let cos_2theta = 2.0 * cos_theta * cos_theta - 1.0;
103 let energy = self.force_constant_ka * (c0 + c1 * cos_theta + c2 * cos_2theta);
104
105 let sin_2theta = 2.0 * sin_theta * cos_theta;
107 let de = self.force_constant_ka * (-c1 * sin_theta - 2.0 * c2 * sin_2theta);
108 (energy, de)
109 }
110 };
111
112 let pre_i = d_e_dtheta / (d_ji * sin_theta);
114 let pre_k = d_e_dtheta / (d_jk * sin_theta);
115
116 for dim in 0..3 {
117 let gi = pre_i * (r_jk[dim] / d_jk - cos_theta * (r_ji[dim] / d_ji));
118 let gk = pre_k * (r_ji[dim] / d_ji - cos_theta * (r_jk[dim] / d_jk));
119
120 grad[root_i + dim] += gi;
121 grad[root_k + dim] += gk;
122 grad[root_j + dim] -= gi + gk;
123 }
124
125 energy
126 }
127}
128
129pub struct UffTorsion {
131 pub atom_i_idx: usize,
132 pub atom_j_idx: usize,
133 pub atom_k_idx: usize,
134 pub atom_l_idx: usize,
135 pub force_constant_v: f64,
136 pub periodicity_n: f64,
137 pub cos_phi0: f64, }
139
140impl ForceFieldContribution for UffTorsion {
141 fn evaluate_energy_and_inject_gradient(&self, coords: &[f64], grad: &mut [f64]) -> f64 {
142 let i = self.atom_i_idx * 3;
143 let j = self.atom_j_idx * 3;
144 let k = self.atom_k_idx * 3;
145 let l = self.atom_l_idx * 3;
146
147 let b1 = [
148 coords[i] - coords[j],
149 coords[i + 1] - coords[j + 1],
150 coords[i + 2] - coords[j + 2],
151 ];
152 let b2 = [
153 coords[k] - coords[j],
154 coords[k + 1] - coords[j + 1],
155 coords[k + 2] - coords[j + 2],
156 ];
157 let b3 = [
158 coords[l] - coords[k],
159 coords[l + 1] - coords[k + 1],
160 coords[l + 2] - coords[k + 2],
161 ];
162
163 let n1 = [
164 b1[1] * b2[2] - b1[2] * b2[1],
165 b1[2] * b2[0] - b1[0] * b2[2],
166 b1[0] * b2[1] - b1[1] * b2[0],
167 ];
168 let n2 = [
169 b2[1] * b3[2] - b2[2] * b3[1],
170 b2[2] * b3[0] - b2[0] * b3[2],
171 b2[0] * b3[1] - b2[1] * b3[0],
172 ];
173
174 let m1 = (n1[0] * n1[0] + n1[1] * n1[1] + n1[2] * n1[2]).sqrt();
175 let m2 = (n2[0] * n2[0] + n2[1] * n2[1] + n2[2] * n2[2]).sqrt();
176 if m1 < 1e-10 || m2 < 1e-10 {
177 return 0.0;
178 }
179
180 let cos_phi = (n1[0] * n2[0] + n1[1] * n2[1] + n1[2] * n2[2]) / (m1 * m2);
181 let cos_phi = cos_phi.clamp(-1.0, 1.0);
182 let phi = cos_phi.acos();
183
184 let cross_n1_n2 = [
186 n1[1] * n2[2] - n1[2] * n2[1],
187 n1[2] * n2[0] - n1[0] * n2[2],
188 n1[0] * n2[1] - n1[1] * n2[0],
189 ];
190 let dot_dir = cross_n1_n2[0] * b2[0] + cross_n1_n2[1] * b2[1] + cross_n1_n2[2] * b2[2];
191 let phi = if dot_dir < 0.0 { -phi } else { phi };
192
193 let energy =
194 0.5 * self.force_constant_v * (1.0 - self.cos_phi0 * (self.periodicity_n * phi).cos());
195 let d_e_dphi = 0.5
196 * self.force_constant_v
197 * self.cos_phi0
198 * self.periodicity_n
199 * (self.periodicity_n * phi).sin();
200
201 let f_i = [
203 -d_e_dphi * n1[0] / (m1 * m1),
204 -d_e_dphi * n1[1] / (m1 * m1),
205 -d_e_dphi * n1[2] / (m1 * m1),
206 ];
207
208 let f_l = [
209 d_e_dphi * n2[0] / (m2 * m2),
210 d_e_dphi * n2[1] / (m2 * m2),
211 d_e_dphi * n2[2] / (m2 * m2),
212 ];
213
214 for dim in 0..3 {
217 grad[i + dim] += f_i[dim];
218 grad[l + dim] += f_l[dim];
219 grad[j + dim] -= f_i[dim]; grad[k + dim] -= f_l[dim]; }
222
223 energy
224 }
225}
226
227pub struct UffInversion {
229 pub idx_i: usize,
230 pub idx_j: usize, pub idx_k: usize,
232 pub idx_l: usize,
233 pub k_inv: f64,
234 pub c0: f64,
235 pub c1: f64,
236 pub c2: f64,
237}
238
239impl ForceFieldContribution for UffInversion {
240 fn evaluate_energy_and_inject_gradient(&self, coords: &[f64], grad: &mut [f64]) -> f64 {
241 let j = self.idx_j * 3;
242 let i = self.idx_i * 3;
243 let k = self.idx_k * 3;
244 let l = self.idx_l * 3;
245
246 let r_ji = [
247 coords[i] - coords[j],
248 coords[i + 1] - coords[j + 1],
249 coords[i + 2] - coords[j + 2],
250 ];
251 let r_jk = [
252 coords[k] - coords[j],
253 coords[k + 1] - coords[j + 1],
254 coords[k + 2] - coords[j + 2],
255 ];
256 let r_jl = [
257 coords[l] - coords[j],
258 coords[l + 1] - coords[j + 1],
259 coords[l + 2] - coords[j + 2],
260 ];
261
262 let n = [
264 r_ji[1] * r_jk[2] - r_ji[2] * r_jk[1],
265 r_ji[2] * r_jk[0] - r_ji[0] * r_jk[2],
266 r_ji[0] * r_jk[1] - r_ji[1] * r_jk[0],
267 ];
268 let n_len = (n[0] * n[0] + n[1] * n[1] + n[2] * n[2]).sqrt();
269 if n_len < 1e-10 {
270 return 0.0;
271 }
272
273 let r_jl_len = (r_jl[0] * r_jl[0] + r_jl[1] * r_jl[1] + r_jl[2] * r_jl[2]).sqrt();
274 if r_jl_len < 1e-10 {
275 return 0.0;
276 }
277
278 let sin_psi = (n[0] * r_jl[0] + n[1] * r_jl[1] + n[2] * r_jl[2]) / (n_len * r_jl_len);
279 let sin_psi = sin_psi.clamp(-1.0, 1.0);
280 let psi = sin_psi.asin();
281
282 let energy = self.k_inv * (self.c0 + self.c1 * sin_psi + self.c2 * (2.0 * psi).cos());
283 let d_e_dpsi = self.k_inv * (self.c1 * psi.cos() - 2.0 * self.c2 * (2.0 * psi).sin());
284
285 let cos_psi = psi.cos().max(1e-8);
286 let pre_l = d_e_dpsi / (n_len * r_jl_len * cos_psi);
287
288 for dim in 0..3 {
289 let gi = pre_l * (n[dim] - sin_psi * r_jl[dim] / r_jl_len);
290 grad[l + dim] += gi;
291 grad[j + dim] -= gi;
292 }
293
294 energy
295 }
296}
297
298pub struct UffLennardJones {
306 pub atom_i_idx: usize,
307 pub atom_j_idx: usize,
308 pub r_star: f64, pub epsilon: f64, }
311
312impl ForceFieldContribution for UffLennardJones {
313 fn evaluate_energy_and_inject_gradient(&self, coords: &[f64], grad: &mut [f64]) -> f64 {
314 let ri = self.atom_i_idx * 3;
315 let rj = self.atom_j_idx * 3;
316
317 let dx = coords[ri] - coords[rj];
318 let dy = coords[ri + 1] - coords[rj + 1];
319 let dz = coords[ri + 2] - coords[rj + 2];
320 let r2 = (dx * dx + dy * dy + dz * dz).max(1e-6);
321 let r = r2.sqrt();
322
323 let u = self.r_star / r;
324 let u6 = u * u * u * u * u * u;
325 let u12 = u6 * u6;
326
327 let energy = self.epsilon * (u12 - 2.0 * u6);
328
329 let de_dr = self.epsilon * 12.0 * (u6 - u12) / r;
331 let pre = de_dr / r;
332
333 grad[ri] += pre * dx;
334 grad[ri + 1] += pre * dy;
335 grad[ri + 2] += pre * dz;
336 grad[rj] -= pre * dx;
337 grad[rj + 1] -= pre * dy;
338 grad[rj + 2] -= pre * dz;
339
340 energy
341 }
342}