dreid_kernel/potentials/nonbonded/
vdw.rs1use crate::math::Real;
2use crate::traits::PairKernel;
3use crate::types::EnergyDiff;
4
5#[derive(Clone, Copy, Debug, Default)]
35pub struct LennardJones;
36
37impl LennardJones {
38 #[inline(always)]
55 pub fn precompute<T: Real>(d0: T, r0: T) -> (T, T) {
56 (d0, r0 * r0)
57 }
58}
59
60impl<T: Real> PairKernel<T> for LennardJones {
61 type Params = (T, T);
62
63 #[inline(always)]
69 fn energy(r_sq: T, (d0, r0_sq): Self::Params) -> T {
70 let s = r0_sq * r_sq.recip();
71 let s3 = s * s * s;
72 let s6 = s3 * s3;
73
74 d0 * (s6 - T::from(2.0) * s3)
75 }
76
77 #[inline(always)]
86 fn diff(r_sq: T, (d0, r0_sq): Self::Params) -> T {
87 let inv_r2 = r_sq.recip();
88 let s = r0_sq * inv_r2;
89 let s3 = s * s * s;
90 let s6 = s3 * s3;
91
92 T::from(12.0) * d0 * inv_r2 * (s6 - s3)
93 }
94
95 #[inline(always)]
99 fn compute(r_sq: T, (d0, r0_sq): Self::Params) -> EnergyDiff<T> {
100 let inv_r2 = r_sq.recip();
101 let s = r0_sq * inv_r2;
102 let s3 = s * s * s;
103 let s6 = s3 * s3;
104
105 let energy = d0 * (s6 - T::from(2.0) * s3);
106
107 let diff = T::from(12.0) * d0 * inv_r2 * (s6 - s3);
108
109 EnergyDiff { energy, diff }
110 }
111}
112
113#[derive(Clone, Copy, Debug, Default)]
156pub struct Buckingham;
157
158impl Buckingham {
159 #[inline(always)]
182 pub fn precompute<T: Real>(d0: T, r0: T, zeta: T) -> (T, T, T, T, T) {
183 let six = T::from(6.0);
184 let zeta_minus_6 = zeta - six;
185
186 let r0_2 = r0 * r0;
187 let r0_3 = r0_2 * r0;
188 let r0_6 = r0_3 * r0_3;
189
190 let a = six * d0 * T::exp(zeta) / zeta_minus_6;
191 let b = zeta / r0;
192 let c = zeta * d0 * r0_6 / zeta_minus_6;
193
194 let seven = T::from(7.0);
195 let mut r = r0;
196 for _ in 0..32 {
197 let exp_term = T::exp(-b * r);
198 let r2 = r * r;
199 let r3 = r2 * r;
200 let r6 = r3 * r3;
201 let r7 = r6 * r;
202
203 let g = a * b * exp_term * r7 - six * c;
204 let gp = a * b * exp_term * r6 * (seven - b * r);
205 r = r - g / gp;
206 }
207
208 let r_max_sq = r * r;
209
210 let inv_r = r.recip();
211 let inv_r2 = inv_r * inv_r;
212 let inv_r3 = inv_r2 * inv_r;
213 let inv_r6 = inv_r3 * inv_r3;
214 let e_max = a * T::exp(-b * r) - c * inv_r6;
215 let two_e_max = e_max + e_max;
216
217 (a, b, c, r_max_sq, two_e_max)
218 }
219}
220
221impl<T: Real> PairKernel<T> for Buckingham {
222 type Params = (T, T, T, T, T);
223
224 #[inline(always)]
230 fn energy(r_sq: T, (a, b, c, r_max_sq, two_e_max): Self::Params) -> T {
231 let mask = T::from((r_sq >= r_max_sq) as u8 as f32);
232 let sign = mask + mask - T::from(1.0);
233
234 let r = r_sq.sqrt();
235 let inv_r2 = r_sq.recip();
236 let inv_r6 = inv_r2 * inv_r2 * inv_r2;
237
238 let e_buck = a * T::exp(-b * r) - c * inv_r6;
239
240 sign * e_buck + (T::from(1.0) - mask) * two_e_max
241 }
242
243 #[inline(always)]
255 fn diff(r_sq: T, (a, b, c, r_max_sq, _two_e_max): Self::Params) -> T {
256 let mask = T::from((r_sq >= r_max_sq) as u8 as f32);
257 let sign = mask + mask - T::from(1.0);
258
259 let inv_r = r_sq.rsqrt();
260 let r = r_sq * inv_r;
261 let inv_r2 = inv_r * inv_r;
262 let inv_r4 = inv_r2 * inv_r2;
263 let inv_r8 = inv_r4 * inv_r4;
264
265 let exp_term = T::exp(-b * r);
266 let d_buck = a * b * exp_term * inv_r - T::from(6.0) * c * inv_r8;
267
268 sign * d_buck
269 }
270
271 #[inline(always)]
275 fn compute(r_sq: T, (a, b, c, r_max_sq, two_e_max): Self::Params) -> EnergyDiff<T> {
276 let mask = T::from((r_sq >= r_max_sq) as u8 as f32);
277 let sign = mask + mask - T::from(1.0);
278
279 let inv_r = r_sq.rsqrt();
280 let r = r_sq * inv_r;
281 let inv_r2 = inv_r * inv_r;
282 let inv_r4 = inv_r2 * inv_r2;
283 let inv_r6 = inv_r4 * inv_r2;
284 let inv_r8 = inv_r6 * inv_r2;
285
286 let exp_term = T::exp(-b * r);
287
288 let e_buck = a * exp_term - c * inv_r6;
289 let d_buck = a * b * exp_term * inv_r - T::from(6.0) * c * inv_r8;
290
291 EnergyDiff {
292 energy: sign * e_buck + (T::from(1.0) - mask) * two_e_max,
293 diff: sign * d_buck,
294 }
295 }
296}
297
298#[cfg(test)]
299mod tests {
300 use super::*;
301 use approx::assert_relative_eq;
302
303 const H: f64 = 1e-6;
308 const TOL_DIFF: f64 = 1e-4;
309
310 mod lennard_jones {
315 use super::*;
316
317 const D0: f64 = 0.1;
318 const R0: f64 = 3.5;
319 const R0_SQ: f64 = R0 * R0;
320
321 fn params() -> (f64, f64) {
322 LennardJones::precompute(D0, R0)
323 }
324
325 #[test]
330 fn sanity_compute_equals_separate() {
331 let r_sq = 9.0_f64;
332 let p = params();
333
334 let result = LennardJones::compute(r_sq, p);
335 let energy_only = LennardJones::energy(r_sq, p);
336 let diff_only = LennardJones::diff(r_sq, p);
337
338 assert_relative_eq!(result.energy, energy_only, epsilon = 1e-14);
339 assert_relative_eq!(result.diff, diff_only, epsilon = 1e-14);
340 }
341
342 #[test]
343 fn sanity_f32_f64_consistency() {
344 let r_sq = 12.25;
345 let p64 = params();
346 let p32 = LennardJones::precompute(D0 as f32, R0 as f32);
347
348 let e64 = LennardJones::energy(r_sq, p64);
349 let e32 = LennardJones::energy(r_sq as f32, p32);
350
351 assert_relative_eq!(e64, e32 as f64, epsilon = 1e-5);
352 }
353
354 #[test]
355 fn sanity_equilibrium_energy_minimum() {
356 let e = LennardJones::energy(R0_SQ, params());
357 assert_relative_eq!(e, -D0, epsilon = 1e-10);
358 }
359
360 #[test]
361 fn sanity_equilibrium_zero_force() {
362 let d = LennardJones::diff(R0_SQ, params());
363 assert_relative_eq!(d, 0.0, epsilon = 1e-10);
364 }
365
366 #[test]
371 fn stability_large_distance() {
372 let r_sq = 1e6_f64;
373 let result = LennardJones::compute(r_sq, params());
374
375 assert!(result.energy.is_finite());
376 assert!(result.diff.is_finite());
377 assert!(result.energy.abs() < 1e-10);
378 }
379
380 #[test]
381 fn stability_small_distance() {
382 let r_sq = 1.0_f64;
383 let result = LennardJones::compute(r_sq, params());
384
385 assert!(result.energy.is_finite());
386 assert!(result.diff.is_finite());
387 assert!(result.energy > 0.0);
388 }
389
390 fn finite_diff_check(r: f64) {
395 let p = params();
396 let r_sq = r * r;
397
398 let r_plus = r + H;
399 let r_minus = r - H;
400 let e_plus = LennardJones::energy(r_plus * r_plus, p);
401 let e_minus = LennardJones::energy(r_minus * r_minus, p);
402 let de_dr_numerical = (e_plus - e_minus) / (2.0 * H);
403
404 let d_analytic = LennardJones::diff(r_sq, p);
405 let de_dr_analytic = -d_analytic * r;
406
407 assert_relative_eq!(de_dr_numerical, de_dr_analytic, epsilon = TOL_DIFF);
408 }
409
410 #[test]
411 fn finite_diff_repulsion_region() {
412 finite_diff_check(2.5);
413 }
414
415 #[test]
416 fn finite_diff_equilibrium_region() {
417 finite_diff_check(R0);
418 }
419
420 #[test]
421 fn finite_diff_attraction_region() {
422 finite_diff_check(5.0);
423 }
424
425 #[test]
426 fn finite_diff_long_range() {
427 finite_diff_check(10.0);
428 }
429
430 #[test]
435 fn specific_repulsion_positive_energy() {
436 let e = LennardJones::energy(4.0, params());
437 assert!(e > 0.0);
438 }
439
440 #[test]
441 fn specific_attraction_negative_energy() {
442 let e = LennardJones::energy(25.0, params());
443 assert!(e < 0.0);
444 }
445
446 #[test]
447 fn specific_diff_sign_repulsion() {
448 let d = LennardJones::diff(4.0, params());
449 assert!(d > 0.0);
450 }
451
452 #[test]
453 fn specific_diff_sign_attraction() {
454 let d = LennardJones::diff(25.0, params());
455 assert!(d < 0.0);
456 }
457
458 #[test]
463 fn precompute_values() {
464 let (d0, r0_sq) = LennardJones::precompute(D0, R0);
465 assert_relative_eq!(d0, D0, epsilon = 1e-14);
466 assert_relative_eq!(r0_sq, R0_SQ, epsilon = 1e-14);
467 }
468
469 #[test]
470 fn precompute_round_trip() {
471 let p = LennardJones::precompute(D0, R0);
472 let e = LennardJones::energy(R0_SQ, p);
473 assert_relative_eq!(e, -D0, epsilon = 1e-10);
474 }
475 }
476
477 mod buckingham {
482 use super::*;
483
484 const D0: f64 = 1.0;
485 const R0: f64 = 2.0;
486 const ZETA: f64 = 12.0;
487
488 fn params() -> (f64, f64, f64, f64, f64) {
489 Buckingham::precompute(D0, R0, ZETA)
490 }
491
492 #[test]
497 fn sanity_compute_equals_separate() {
498 let r_sq = 4.0_f64;
499 let p = params();
500
501 let result = Buckingham::compute(r_sq, p);
502 let energy_only = Buckingham::energy(r_sq, p);
503 let diff_only = Buckingham::diff(r_sq, p);
504
505 assert_relative_eq!(result.energy, energy_only, epsilon = 1e-12);
506 assert_relative_eq!(result.diff, diff_only, epsilon = 1e-12);
507 }
508
509 #[test]
510 fn sanity_compute_equals_separate_reflected() {
511 let p = params();
512 let r_sq = 0.25_f64;
513
514 let result = Buckingham::compute(r_sq, p);
515 let energy_only = Buckingham::energy(r_sq, p);
516 let diff_only = Buckingham::diff(r_sq, p);
517
518 assert_relative_eq!(result.energy, energy_only, epsilon = 1e-12);
519 assert_relative_eq!(result.diff, diff_only, epsilon = 1e-12);
520 }
521
522 #[test]
523 fn sanity_f32_f64_consistency() {
524 let r_sq = 4.0;
525 let p64 = params();
526 let p32 = Buckingham::precompute(D0 as f32, R0 as f32, ZETA as f32);
527
528 let e64 = Buckingham::energy(r_sq, p64);
529 let e32 = Buckingham::energy(r_sq as f32, p32);
530
531 assert_relative_eq!(e64, e32 as f64, epsilon = 1e-3);
532 }
533
534 #[test]
539 fn stability_reflected_region() {
540 let r_sq = 0.1_f64;
541 let result = Buckingham::compute(r_sq, params());
542
543 assert!(result.energy.is_finite());
544 assert!(result.diff.is_finite());
545 assert!(result.energy > 0.0);
546 assert!(result.diff > 0.0);
547 }
548
549 #[test]
550 fn stability_large_distance() {
551 let r_sq = 1e4_f64;
552 let result = Buckingham::compute(r_sq, params());
553
554 assert!(result.energy.is_finite());
555 assert!(result.diff.is_finite());
556 }
557
558 #[test]
559 fn stability_near_zero() {
560 let r_sq = 1e-20_f64;
561 let p = params();
562 let e = Buckingham::energy(r_sq, p);
563
564 assert!(e.is_finite());
565 assert!(e > 0.0);
566 }
567
568 fn finite_diff_check(r: f64) {
573 let p = params();
574 let r_sq = r * r;
575
576 let r_plus = r + H;
577 let r_minus = r - H;
578 let e_plus = Buckingham::energy(r_plus * r_plus, p);
579 let e_minus = Buckingham::energy(r_minus * r_minus, p);
580 let de_dr_numerical = (e_plus - e_minus) / (2.0 * H);
581
582 let d_analytic = Buckingham::diff(r_sq, p);
583 let de_dr_analytic = -d_analytic * r;
584
585 assert_relative_eq!(de_dr_numerical, de_dr_analytic, epsilon = TOL_DIFF);
586 }
587
588 #[test]
589 fn finite_diff_reflected_region() {
590 finite_diff_check(0.8);
591 }
592
593 #[test]
594 fn finite_diff_normal_short_range() {
595 finite_diff_check(1.5);
596 }
597
598 #[test]
599 fn finite_diff_medium_range() {
600 finite_diff_check(3.0);
601 }
602
603 #[test]
604 fn finite_diff_long_range() {
605 finite_diff_check(8.0);
606 }
607
608 #[test]
613 fn specific_reflection_diverges() {
614 let p = params();
615 let e_close = Buckingham::energy(0.01, p);
616 let e_far = Buckingham::energy(0.25, p);
617 assert!(e_close > e_far);
618 }
619
620 #[test]
621 fn specific_diff_at_maximum_is_zero() {
622 let p = params();
623 let r_max_sq = p.3;
624 let d = Buckingham::diff(r_max_sq, p);
625 assert_relative_eq!(d, 0.0, epsilon = 1e-6);
626 }
627
628 #[test]
629 fn specific_c1_continuity_at_maximum() {
630 let p = params();
631 let r_max = p.3.sqrt();
632 let eps = 1e-8;
633
634 let r_inside = r_max - eps;
635 let r_outside = r_max + eps;
636
637 let d_inside = Buckingham::diff(r_inside * r_inside, p);
638 let d_outside = Buckingham::diff(r_outside * r_outside, p);
639
640 let de_dr_inside = -d_inside * r_inside;
641 let de_dr_outside = -d_outside * r_outside;
642
643 assert_relative_eq!(de_dr_inside, de_dr_outside, epsilon = 1e-3);
644 }
645
646 #[test]
647 fn specific_energy_continuity_at_maximum() {
648 let p = params();
649 let r_max = p.3.sqrt();
650 let eps = 1e-8;
651
652 let e_inside = Buckingham::energy((r_max - eps).powi(2), p);
653 let e_outside = Buckingham::energy((r_max + eps).powi(2), p);
654
655 assert_relative_eq!(e_inside, e_outside, epsilon = 1e-4);
656 }
657
658 #[test]
659 fn specific_finite_diff_across_boundary() {
660 let p = params();
661 let r_max = p.3.sqrt();
662
663 let h = 1e-6;
664
665 let r_out = r_max + 0.01;
666 let e_p = Buckingham::energy((r_out + h).powi(2), p);
667 let e_m = Buckingham::energy((r_out - h).powi(2), p);
668 let de_dr_num_out = (e_p - e_m) / (2.0 * h);
669 let de_dr_ana_out = -Buckingham::diff(r_out * r_out, p) * r_out;
670 assert_relative_eq!(de_dr_num_out, de_dr_ana_out, epsilon = TOL_DIFF);
671
672 let r_in = r_max - 0.01;
673 let e_p = Buckingham::energy((r_in + h).powi(2), p);
674 let e_m = Buckingham::energy((r_in - h).powi(2), p);
675 let de_dr_num_in = (e_p - e_m) / (2.0 * h);
676 let de_dr_ana_in = -Buckingham::diff(r_in * r_in, p) * r_in;
677 assert_relative_eq!(de_dr_num_in, de_dr_ana_in, epsilon = TOL_DIFF);
678 }
679
680 #[test]
685 fn precompute_values() {
686 let (a, b, c, r_max_sq, two_e_max) = Buckingham::precompute(D0, R0, ZETA);
687 assert_relative_eq!(a, 12.0_f64.exp(), epsilon = 1e-4);
688 assert_relative_eq!(b, 6.0, epsilon = 1e-14);
689 assert_relative_eq!(c, 128.0, epsilon = 1e-10);
690 assert!(r_max_sq > 0.0);
691 assert!(two_e_max.is_finite());
692 }
693
694 #[test]
695 fn precompute_round_trip() {
696 let p = Buckingham::precompute(D0, R0, ZETA);
697 let e = Buckingham::energy(R0 * R0, p);
698 assert_relative_eq!(e, -D0, epsilon = 1e-6);
699 }
700 }
701}