dreid_kernel/potentials/nonbonded/
vdw.rs1use crate::math::Real;
2use crate::traits::PairKernel;
3use crate::types::EnergyDiff;
4
5#[derive(Clone, Copy, Debug, Default)]
30pub struct LennardJones;
31
32impl<T: Real> PairKernel<T> for LennardJones {
33 type Params = (T, T);
34
35 #[inline(always)]
41 fn energy(r_sq: T, (d0, r0_sq): Self::Params) -> T {
42 let s = r0_sq * r_sq.recip();
43 let s3 = s * s * s;
44 let s6 = s3 * s3;
45
46 d0 * (s6 - T::from(2.0) * s3)
47 }
48
49 #[inline(always)]
58 fn diff(r_sq: T, (d0, r0_sq): Self::Params) -> T {
59 let inv_r2 = r_sq.recip();
60 let s = r0_sq * inv_r2;
61 let s3 = s * s * s;
62 let s6 = s3 * s3;
63
64 T::from(12.0) * d0 * inv_r2 * (s6 - s3)
65 }
66
67 #[inline(always)]
71 fn compute(r_sq: T, (d0, r0_sq): Self::Params) -> EnergyDiff<T> {
72 let inv_r2 = r_sq.recip();
73 let s = r0_sq * inv_r2;
74 let s3 = s * s * s;
75 let s6 = s3 * s3;
76
77 let energy = d0 * (s6 - T::from(2.0) * s3);
78
79 let diff = T::from(12.0) * d0 * inv_r2 * (s6 - s3);
80
81 EnergyDiff { energy, diff }
82 }
83}
84
85#[derive(Clone, Copy, Debug, Default)]
121pub struct Buckingham;
122
123impl<T: Real> PairKernel<T> for Buckingham {
124 type Params = (T, T, T, T, T);
125
126 #[inline(always)]
132 fn energy(r_sq: T, (a, b, c, r_max_sq, two_e_max): Self::Params) -> T {
133 let mask = T::from((r_sq >= r_max_sq) as u8 as f32);
134 let sign = mask + mask - T::from(1.0);
135
136 let r = r_sq.sqrt();
137 let inv_r2 = r_sq.recip();
138 let inv_r6 = inv_r2 * inv_r2 * inv_r2;
139
140 let e_buck = a * T::exp(-b * r) - c * inv_r6;
141
142 sign * e_buck + (T::from(1.0) - mask) * two_e_max
143 }
144
145 #[inline(always)]
157 fn diff(r_sq: T, (a, b, c, r_max_sq, _two_e_max): Self::Params) -> T {
158 let mask = T::from((r_sq >= r_max_sq) as u8 as f32);
159 let sign = mask + mask - T::from(1.0);
160
161 let inv_r = r_sq.rsqrt();
162 let r = r_sq * inv_r;
163 let inv_r2 = inv_r * inv_r;
164 let inv_r4 = inv_r2 * inv_r2;
165 let inv_r8 = inv_r4 * inv_r4;
166
167 let exp_term = T::exp(-b * r);
168 let d_buck = a * b * exp_term * inv_r - T::from(6.0) * c * inv_r8;
169
170 sign * d_buck
171 }
172
173 #[inline(always)]
177 fn compute(r_sq: T, (a, b, c, r_max_sq, two_e_max): Self::Params) -> EnergyDiff<T> {
178 let mask = T::from((r_sq >= r_max_sq) as u8 as f32);
179 let sign = mask + mask - T::from(1.0);
180
181 let inv_r = r_sq.rsqrt();
182 let r = r_sq * inv_r;
183 let inv_r2 = inv_r * inv_r;
184 let inv_r4 = inv_r2 * inv_r2;
185 let inv_r6 = inv_r4 * inv_r2;
186 let inv_r8 = inv_r6 * inv_r2;
187
188 let exp_term = T::exp(-b * r);
189
190 let e_buck = a * exp_term - c * inv_r6;
191 let d_buck = a * b * exp_term * inv_r - T::from(6.0) * c * inv_r8;
192
193 EnergyDiff {
194 energy: sign * e_buck + (T::from(1.0) - mask) * two_e_max,
195 diff: sign * d_buck,
196 }
197 }
198}
199
200#[cfg(test)]
201mod tests {
202 use super::*;
203 use approx::assert_relative_eq;
204
205 const H: f64 = 1e-6;
210 const TOL_DIFF: f64 = 1e-4;
211
212 const D0: f64 = 0.1;
214 const R0: f64 = 3.5;
215 const R0_SQ: f64 = R0 * R0;
216
217 const BUCK_A: f64 = 1000.0;
219 const BUCK_B: f64 = 3.0;
220 const BUCK_C: f64 = 50.0;
221
222 mod lennard_jones {
227 use super::*;
228
229 #[test]
234 fn sanity_compute_equals_separate() {
235 let r_sq = 9.0_f64;
236 let params = (D0, R0_SQ);
237
238 let result = LennardJones::compute(r_sq, params);
239 let energy_only = LennardJones::energy(r_sq, params);
240 let diff_only = LennardJones::diff(r_sq, params);
241
242 assert_relative_eq!(result.energy, energy_only, epsilon = 1e-14);
243 assert_relative_eq!(result.diff, diff_only, epsilon = 1e-14);
244 }
245
246 #[test]
247 fn sanity_f32_f64_consistency() {
248 let r_sq_64 = 12.25_f64;
249 let r_sq_32 = 12.25_f32;
250 let params_64 = (D0, R0_SQ);
251 let params_32 = (D0 as f32, R0_SQ as f32);
252
253 let e64 = LennardJones::energy(r_sq_64, params_64);
254 let e32 = LennardJones::energy(r_sq_32, params_32);
255
256 assert_relative_eq!(e64, e32 as f64, epsilon = 1e-5);
257 }
258
259 #[test]
260 fn sanity_equilibrium_energy_minimum() {
261 let e = LennardJones::energy(R0_SQ, (D0, R0_SQ));
262 assert_relative_eq!(e, -D0, epsilon = 1e-10);
263 }
264
265 #[test]
266 fn sanity_equilibrium_zero_force() {
267 let d = LennardJones::diff(R0_SQ, (D0, R0_SQ));
268 assert_relative_eq!(d, 0.0, epsilon = 1e-10);
269 }
270
271 #[test]
276 fn stability_large_distance() {
277 let r_sq = 1e6_f64;
278 let result = LennardJones::compute(r_sq, (D0, R0_SQ));
279
280 assert!(result.energy.is_finite());
281 assert!(result.diff.is_finite());
282 assert!(result.energy.abs() < 1e-10);
283 }
284
285 #[test]
286 fn stability_small_distance() {
287 let r_sq = 1.0_f64;
288 let result = LennardJones::compute(r_sq, (D0, R0_SQ));
289
290 assert!(result.energy.is_finite());
291 assert!(result.diff.is_finite());
292 assert!(result.energy > 0.0);
293 }
294
295 fn finite_diff_check(r: f64, params: (f64, f64)) {
300 let r_sq = r * r;
301
302 let r_plus = r + H;
303 let r_minus = r - H;
304 let e_plus = LennardJones::energy(r_plus * r_plus, params);
305 let e_minus = LennardJones::energy(r_minus * r_minus, params);
306 let de_dr_numerical = (e_plus - e_minus) / (2.0 * H);
307
308 let d_analytic = LennardJones::diff(r_sq, params);
309 let de_dr_analytic = -d_analytic * r;
310
311 assert_relative_eq!(de_dr_numerical, de_dr_analytic, epsilon = TOL_DIFF);
312 }
313
314 #[test]
315 fn finite_diff_repulsion_region() {
316 finite_diff_check(2.5, (D0, R0_SQ));
317 }
318
319 #[test]
320 fn finite_diff_equilibrium_region() {
321 finite_diff_check(R0, (D0, R0_SQ));
322 }
323
324 #[test]
325 fn finite_diff_attraction_region() {
326 finite_diff_check(5.0, (D0, R0_SQ));
327 }
328
329 #[test]
330 fn finite_diff_long_range() {
331 finite_diff_check(10.0, (D0, R0_SQ));
332 }
333
334 #[test]
339 fn specific_repulsion_positive_energy() {
340 let e = LennardJones::energy(4.0, (D0, R0_SQ));
341 assert!(e > 0.0);
342 }
343
344 #[test]
345 fn specific_attraction_negative_energy() {
346 let e = LennardJones::energy(25.0, (D0, R0_SQ));
347 assert!(e < 0.0);
348 }
349
350 #[test]
351 fn specific_diff_sign_repulsion() {
352 let d = LennardJones::diff(4.0, (D0, R0_SQ));
353 assert!(d > 0.0);
354 }
355
356 #[test]
357 fn specific_diff_sign_attraction() {
358 let d = LennardJones::diff(25.0, (D0, R0_SQ));
359 assert!(d < 0.0);
360 }
361 }
362
363 mod buckingham {
368 use super::*;
369
370 fn reflection_params(a: f64, b: f64, c: f64) -> (f64, f64) {
372 let mut r = 1.0_f64;
373 for _ in 0..100 {
374 let exp_term = (-b * r).exp();
375 let r7 = r.powi(7);
376 let g = a * b * exp_term * r7 - 6.0 * c;
377 let gp = a * b * exp_term * r.powi(6) * (7.0 - b * r);
378 r -= g / gp;
379 }
380 let e_max = a * (-b * r).exp() - c / r.powi(6);
381 (r * r, 2.0 * e_max)
382 }
383
384 fn params() -> (f64, f64, f64, f64, f64) {
385 let (r_max_sq, two_e_max) = reflection_params(BUCK_A, BUCK_B, BUCK_C);
386 (BUCK_A, BUCK_B, BUCK_C, r_max_sq, two_e_max)
387 }
388
389 #[test]
394 fn sanity_compute_equals_separate() {
395 let r_sq = 4.0_f64;
396 let p = params();
397
398 let result = Buckingham::compute(r_sq, p);
399 let energy_only = Buckingham::energy(r_sq, p);
400 let diff_only = Buckingham::diff(r_sq, p);
401
402 assert_relative_eq!(result.energy, energy_only, epsilon = 1e-12);
403 assert_relative_eq!(result.diff, diff_only, epsilon = 1e-12);
404 }
405
406 #[test]
407 fn sanity_compute_equals_separate_reflected() {
408 let p = params();
409 let r_sq = 0.25_f64;
410
411 let result = Buckingham::compute(r_sq, p);
412 let energy_only = Buckingham::energy(r_sq, p);
413 let diff_only = Buckingham::diff(r_sq, p);
414
415 assert_relative_eq!(result.energy, energy_only, epsilon = 1e-12);
416 assert_relative_eq!(result.diff, diff_only, epsilon = 1e-12);
417 }
418
419 #[test]
420 fn sanity_f32_f64_consistency() {
421 let r_sq = 4.0;
422 let p64 = params();
423 let (r_max_sq_32, two_e_max_32) = reflection_params(BUCK_A, BUCK_B, BUCK_C);
424 let p32 = (
425 BUCK_A as f32,
426 BUCK_B as f32,
427 BUCK_C as f32,
428 r_max_sq_32 as f32,
429 two_e_max_32 as f32,
430 );
431
432 let e64 = Buckingham::energy(r_sq, p64);
433 let e32 = Buckingham::energy(r_sq as f32, p32);
434
435 assert_relative_eq!(e64, e32 as f64, epsilon = 1e-3);
436 }
437
438 #[test]
443 fn stability_reflected_region() {
444 let r_sq = 0.1_f64;
445 let result = Buckingham::compute(r_sq, params());
446
447 assert!(result.energy.is_finite());
448 assert!(result.diff.is_finite());
449 assert!(result.energy > 0.0);
450 assert!(result.diff > 0.0);
451 }
452
453 #[test]
454 fn stability_large_distance() {
455 let r_sq = 1e4_f64;
456 let result = Buckingham::compute(r_sq, params());
457
458 assert!(result.energy.is_finite());
459 assert!(result.diff.is_finite());
460 }
461
462 #[test]
463 fn stability_near_zero() {
464 let r_sq = 1e-20_f64;
465 let p = params();
466 let e = Buckingham::energy(r_sq, p);
467
468 assert!(e.is_finite());
469 assert!(e > 0.0);
470 }
471
472 fn finite_diff_check(r: f64) {
477 let p = params();
478 let r_sq = r * r;
479
480 let r_plus = r + H;
481 let r_minus = r - H;
482 let e_plus = Buckingham::energy(r_plus * r_plus, p);
483 let e_minus = Buckingham::energy(r_minus * r_minus, p);
484 let de_dr_numerical = (e_plus - e_minus) / (2.0 * H);
485
486 let d_analytic = Buckingham::diff(r_sq, p);
487 let de_dr_analytic = -d_analytic * r;
488
489 assert_relative_eq!(de_dr_numerical, de_dr_analytic, epsilon = TOL_DIFF);
490 }
491
492 #[test]
493 fn finite_diff_reflected_region() {
494 finite_diff_check(0.8);
495 }
496
497 #[test]
498 fn finite_diff_normal_short_range() {
499 finite_diff_check(1.5);
500 }
501
502 #[test]
503 fn finite_diff_medium_range() {
504 finite_diff_check(3.0);
505 }
506
507 #[test]
508 fn finite_diff_long_range() {
509 finite_diff_check(8.0);
510 }
511
512 #[test]
517 fn specific_reflection_diverges() {
518 let p = params();
519 let e_close = Buckingham::energy(0.01, p);
520 let e_far = Buckingham::energy(0.25, p);
521 assert!(e_close > e_far);
522 }
523
524 #[test]
525 fn specific_diff_at_maximum_is_zero() {
526 let p = params();
527 let r_max_sq = p.3;
528 let d = Buckingham::diff(r_max_sq, p);
529 assert_relative_eq!(d, 0.0, epsilon = 1e-6);
530 }
531
532 #[test]
533 fn specific_c1_continuity_at_maximum() {
534 let p = params();
535 let r_max = p.3.sqrt();
536 let eps = 1e-8;
537
538 let r_inside = r_max - eps;
539 let r_outside = r_max + eps;
540
541 let d_inside = Buckingham::diff(r_inside * r_inside, p);
542 let d_outside = Buckingham::diff(r_outside * r_outside, p);
543
544 let de_dr_inside = -d_inside * r_inside;
545 let de_dr_outside = -d_outside * r_outside;
546
547 assert_relative_eq!(de_dr_inside, de_dr_outside, epsilon = 1e-3);
548 }
549
550 #[test]
551 fn specific_energy_continuity_at_maximum() {
552 let p = params();
553 let r_max = p.3.sqrt();
554 let eps = 1e-8;
555
556 let e_inside = Buckingham::energy((r_max - eps).powi(2), p);
557 let e_outside = Buckingham::energy((r_max + eps).powi(2), p);
558
559 assert_relative_eq!(e_inside, e_outside, epsilon = 1e-4);
560 }
561
562 #[test]
563 fn specific_finite_diff_across_boundary() {
564 let p = params();
565 let r_max = p.3.sqrt();
566
567 let h = 1e-6;
568
569 let r_out = r_max + 0.01;
570 let e_p = Buckingham::energy((r_out + h).powi(2), p);
571 let e_m = Buckingham::energy((r_out - h).powi(2), p);
572 let de_dr_num_out = (e_p - e_m) / (2.0 * h);
573 let de_dr_ana_out = -Buckingham::diff(r_out * r_out, p) * r_out;
574 assert_relative_eq!(de_dr_num_out, de_dr_ana_out, epsilon = TOL_DIFF);
575
576 let r_in = r_max - 0.01;
577 let e_p = Buckingham::energy((r_in + h).powi(2), p);
578 let e_m = Buckingham::energy((r_in - h).powi(2), p);
579 let de_dr_num_in = (e_p - e_m) / (2.0 * h);
580 let de_dr_ana_in = -Buckingham::diff(r_in * r_in, p) * r_in;
581 assert_relative_eq!(de_dr_num_in, de_dr_ana_in, epsilon = TOL_DIFF);
582 }
583 }
584}