dreid_kernel/potentials/bonded/
stretch.rs1use crate::math::Real;
2use crate::traits::PairKernel;
3use crate::types::EnergyDiff;
4
5#[derive(Clone, Copy, Debug, Default)]
29pub struct Harmonic;
30
31impl<T: Real> PairKernel<T> for Harmonic {
32 type Params = (T, T);
33
34 #[inline(always)]
40 fn energy(r_sq: T, (k_half, r0): Self::Params) -> T {
41 let r = r_sq.sqrt();
42 let delta = r - r0;
43 k_half * delta * delta
44 }
45
46 #[inline(always)]
55 fn diff(r_sq: T, (k_half, r0): Self::Params) -> T {
56 let inv_r = r_sq.rsqrt();
57 let r = r_sq * inv_r;
58 let delta = r - r0;
59
60 let k = k_half + k_half;
61
62 -k * delta * inv_r
63 }
64
65 #[inline(always)]
69 fn compute(r_sq: T, (k_half, r0): Self::Params) -> EnergyDiff<T> {
70 let inv_r = r_sq.rsqrt();
71 let r = r_sq * inv_r;
72 let delta = r - r0;
73
74 let energy = k_half * delta * delta;
75
76 let k = k_half + k_half;
77 let diff = -k * delta * inv_r;
78
79 EnergyDiff { energy, diff }
80 }
81}
82
83#[derive(Clone, Copy, Debug, Default)]
107pub struct Morse;
108
109impl<T: Real> PairKernel<T> for Morse {
110 type Params = (T, T, T);
111
112 #[inline(always)]
118 fn energy(r_sq: T, (de, r0, alpha): Self::Params) -> T {
119 let r = r_sq.sqrt();
120 let t_val = T::exp(-alpha * (r - r0));
121 let term = t_val - T::from(1.0);
122
123 de * term * term
124 }
125
126 #[inline(always)]
135 fn diff(r_sq: T, (de, r0, alpha): Self::Params) -> T {
136 let inv_r = r_sq.rsqrt();
137 let r = r_sq * inv_r;
138
139 let t_val = T::exp(-alpha * (r - r0));
140 let term_minus_one = t_val - T::from(1.0);
141
142 let f_mag = T::from(2.0) * alpha * de * t_val * term_minus_one;
143
144 f_mag * inv_r
145 }
146
147 #[inline(always)]
151 fn compute(r_sq: T, (de, r0, alpha): Self::Params) -> EnergyDiff<T> {
152 let inv_r = r_sq.rsqrt();
153 let r = r_sq * inv_r;
154
155 let t_val = T::exp(-alpha * (r - r0));
156 let term_minus_one = t_val - T::from(1.0);
157
158 let energy = de * term_minus_one * term_minus_one;
159
160 let f_mag = T::from(2.0) * alpha * de * t_val * term_minus_one;
161 let diff = f_mag * inv_r;
162
163 EnergyDiff { energy, diff }
164 }
165}
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170 use approx::assert_relative_eq;
171
172 const H: f64 = 1e-6;
177 const TOL_DIFF: f64 = 1e-4;
178
179 mod harmonic {
184 use super::*;
185
186 const K_HALF: f64 = 350.0; const R0: f64 = 1.5; fn params() -> (f64, f64) {
190 (K_HALF, R0)
191 }
192
193 #[test]
198 fn sanity_compute_equals_separate() {
199 let r_sq = 2.25_f64;
200 let p = params();
201
202 let result = Harmonic::compute(r_sq, p);
203 let energy_only = Harmonic::energy(r_sq, p);
204 let diff_only = Harmonic::diff(r_sq, p);
205
206 assert_relative_eq!(result.energy, energy_only, epsilon = 1e-12);
207 assert_relative_eq!(result.diff, diff_only, epsilon = 1e-12);
208 }
209
210 #[test]
211 fn sanity_f32_f64_consistency() {
212 let r_sq = 3.0;
213 let p64 = params();
214 let p32 = (K_HALF as f32, R0 as f32);
215
216 let e64 = Harmonic::energy(r_sq, p64);
217 let e32 = Harmonic::energy(r_sq as f32, p32);
218
219 assert_relative_eq!(e64, e32 as f64, epsilon = 1e-4);
220 }
221
222 #[test]
223 fn sanity_equilibrium() {
224 let r0_sq = R0 * R0;
225 let result = Harmonic::compute(r0_sq, params());
226
227 assert_relative_eq!(result.energy, 0.0, epsilon = 1e-14);
228 assert_relative_eq!(result.diff, 0.0, epsilon = 1e-14);
229 }
230
231 #[test]
236 fn stability_stretched() {
237 let r = 3.0;
238 let result = Harmonic::compute(r * r, params());
239
240 assert!(result.energy.is_finite());
241 assert!(result.diff.is_finite());
242 assert!(result.energy > 0.0);
243 }
244
245 #[test]
246 fn stability_compressed() {
247 let r = 0.5;
248 let result = Harmonic::compute(r * r, params());
249
250 assert!(result.energy.is_finite());
251 assert!(result.diff.is_finite());
252 assert!(result.energy > 0.0);
253 }
254
255 fn finite_diff_check(r: f64) {
260 let p = params();
261 let r_sq = r * r;
262
263 let r_plus = r + H;
264 let r_minus = r - H;
265 let e_plus = Harmonic::energy(r_plus * r_plus, p);
266 let e_minus = Harmonic::energy(r_minus * r_minus, p);
267 let de_dr_numerical = (e_plus - e_minus) / (2.0 * H);
268
269 let d = Harmonic::diff(r_sq, p);
270 let de_dr_analytic = -d * r;
271
272 assert_relative_eq!(de_dr_numerical, de_dr_analytic, epsilon = TOL_DIFF);
273 }
274
275 #[test]
276 fn finite_diff_stretched() {
277 finite_diff_check(2.0);
278 }
279
280 #[test]
281 fn finite_diff_compressed() {
282 finite_diff_check(1.0);
283 }
284
285 #[test]
286 fn finite_diff_near_equilibrium() {
287 finite_diff_check(R0 + 0.01);
288 }
289
290 #[test]
295 fn specific_quadratic_scaling() {
296 let delta1 = 0.1;
297 let delta2 = 0.2;
298 let e1 = Harmonic::energy((R0 + delta1).powi(2), params());
299 let e2 = Harmonic::energy((R0 + delta2).powi(2), params());
300
301 assert_relative_eq!(e2 / e1, 4.0, epsilon = 1e-10);
302 }
303
304 #[test]
305 fn specific_force_restoring() {
306 let d_stretched = Harmonic::diff((R0 + 0.5).powi(2), params());
307 let d_compressed = Harmonic::diff((R0 - 0.5).powi(2), params());
308
309 assert!(d_stretched < 0.0);
310 assert!(d_compressed > 0.0);
311 }
312 }
313
314 mod morse {
319 use super::*;
320
321 const DE: f64 = 70.0; const R0: f64 = 1.5; const ALPHA: f64 = 2.0; fn params() -> (f64, f64, f64) {
326 (DE, R0, ALPHA)
327 }
328
329 #[test]
334 fn sanity_compute_equals_separate() {
335 let r_sq = 2.0_f64;
336 let p = params();
337
338 let result = Morse::compute(r_sq, p);
339 let energy_only = Morse::energy(r_sq, p);
340 let diff_only = Morse::diff(r_sq, p);
341
342 assert_relative_eq!(result.energy, energy_only, epsilon = 1e-12);
343 assert_relative_eq!(result.diff, diff_only, epsilon = 1e-12);
344 }
345
346 #[test]
347 fn sanity_f32_f64_consistency() {
348 let r_sq = 3.0;
349 let p64 = params();
350 let p32 = (DE as f32, R0 as f32, ALPHA as f32);
351
352 let e64 = Morse::energy(r_sq, p64);
353 let e32 = Morse::energy(r_sq as f32, p32);
354
355 assert_relative_eq!(e64, e32 as f64, epsilon = 1e-4);
356 }
357
358 #[test]
359 fn sanity_equilibrium() {
360 let r0_sq = R0 * R0;
361 let result = Morse::compute(r0_sq, params());
362
363 assert_relative_eq!(result.energy, 0.0, epsilon = 1e-14);
364 assert_relative_eq!(result.diff, 0.0, epsilon = 1e-14);
365 }
366
367 #[test]
372 fn stability_large_stretch() {
373 let r = 10.0;
374 let result = Morse::compute(r * r, params());
375
376 assert!(result.energy.is_finite());
377 assert!(result.diff.is_finite());
378 assert_relative_eq!(result.energy, DE, epsilon = 1e-3);
379 }
380
381 #[test]
382 fn stability_compressed() {
383 let r = 0.5;
384 let result = Morse::compute(r * r, params());
385
386 assert!(result.energy.is_finite());
387 assert!(result.diff.is_finite());
388 assert!(result.energy > DE);
389 }
390
391 fn finite_diff_check(r: f64) {
396 let p = params();
397 let r_sq = r * r;
398
399 let r_plus = r + H;
400 let r_minus = r - H;
401 let e_plus = Morse::energy(r_plus * r_plus, p);
402 let e_minus = Morse::energy(r_minus * r_minus, p);
403 let de_dr_numerical = (e_plus - e_minus) / (2.0 * H);
404
405 let d = Morse::diff(r_sq, p);
406 let de_dr_analytic = -d * r;
407
408 assert_relative_eq!(de_dr_numerical, de_dr_analytic, epsilon = TOL_DIFF);
409 }
410
411 #[test]
412 fn finite_diff_stretched() {
413 finite_diff_check(2.5);
414 }
415
416 #[test]
417 fn finite_diff_compressed() {
418 finite_diff_check(1.0);
419 }
420
421 #[test]
422 fn finite_diff_near_equilibrium() {
423 finite_diff_check(R0 + 0.01);
424 }
425
426 #[test]
431 fn specific_dissociation_limit() {
432 let e_far = Morse::energy(100.0_f64.powi(2), params());
433 assert_relative_eq!(e_far, DE, epsilon = 1e-6);
434 }
435
436 #[test]
437 fn specific_anharmonic_asymmetry() {
438 let delta = 0.3;
439 let e_stretch = Morse::energy((R0 + delta).powi(2), params());
440 let e_compress = Morse::energy((R0 - delta).powi(2), params());
441
442 assert!(e_compress > e_stretch);
443 }
444
445 #[test]
446 fn specific_force_restoring() {
447 let d_stretched = Morse::diff((R0 + 0.5).powi(2), params());
448 let d_compressed = Morse::diff((R0 - 0.3).powi(2), params());
449
450 assert!(d_stretched < 0.0);
451 assert!(d_compressed > 0.0);
452 }
453 }
454}