dreid_kernel/potentials/nonbonded/
coulomb.rs1use crate::math::Real;
2use crate::traits::PairKernel;
3use crate::types::EnergyDiff;
4
5#[derive(Clone, Copy, Debug, Default)]
28pub struct Coulomb;
29
30impl<T: Real> PairKernel<T> for Coulomb {
31 type Params = T;
32
33 #[inline(always)]
39 fn energy(r_sq: T, q_product: Self::Params) -> T {
40 q_product * r_sq.rsqrt()
41 }
42
43 #[inline(always)]
52 fn diff(r_sq: T, q_product: Self::Params) -> T {
53 let inv_r = r_sq.rsqrt();
54 let energy = q_product * inv_r;
55
56 let inv_r2 = inv_r * inv_r;
57 energy * inv_r2
58 }
59
60 #[inline(always)]
64 fn compute(r_sq: T, q_product: Self::Params) -> EnergyDiff<T> {
65 let inv_r = r_sq.rsqrt();
66 let inv_r2 = inv_r * inv_r;
67
68 let energy = q_product * inv_r;
69
70 let diff = energy * inv_r2;
71
72 EnergyDiff { energy, diff }
73 }
74}
75
76#[cfg(test)]
77mod tests {
78 use super::*;
79 use approx::assert_relative_eq;
80
81 const H: f64 = 1e-6;
86 const TOL_DIFF: f64 = 1e-5;
87
88 const Q_EFF: f64 = 1.0;
89
90 fn params() -> f64 {
91 Q_EFF
92 }
93
94 #[test]
99 fn sanity_compute_equals_separate() {
100 let r_sq = 4.0_f64;
101 let p = params();
102
103 let result = Coulomb::compute(r_sq, p);
104 let energy_only = Coulomb::energy(r_sq, p);
105 let diff_only = Coulomb::diff(r_sq, p);
106
107 assert_relative_eq!(result.energy, energy_only, epsilon = 1e-14);
108 assert_relative_eq!(result.diff, diff_only, epsilon = 1e-14);
109 }
110
111 #[test]
112 fn sanity_f32_f64_consistency() {
113 let r_sq = 4.0;
114 let p64 = params();
115 let p32 = Q_EFF as f32;
116
117 let e64 = Coulomb::energy(r_sq, p64);
118 let e32 = Coulomb::energy(r_sq as f32, p32);
119
120 assert_relative_eq!(e64, e32 as f64, epsilon = 1e-5);
121 }
122
123 #[test]
124 fn sanity_energy_positive_for_like_charges() {
125 let r_sq = 4.0_f64;
126 let e = Coulomb::energy(r_sq, 1.0);
127 assert!(e > 0.0);
128 }
129
130 #[test]
131 fn sanity_energy_negative_for_unlike_charges() {
132 let r_sq = 4.0_f64;
133 let e: f64 = Coulomb::energy(r_sq, -1.0);
134 assert!(e < 0.0);
135 }
136
137 #[test]
142 fn stability_large_distance() {
143 let r_sq = 1e10_f64;
144 let result = Coulomb::compute(r_sq, params());
145
146 assert!(result.energy.is_finite());
147 assert!(result.diff.is_finite());
148 assert!(result.energy.abs() < 1e-4);
149 assert!(result.diff.abs() < 1e-14);
150 }
151
152 #[test]
153 fn stability_small_distance() {
154 let r_sq = 1e-6_f64;
155 let result = Coulomb::compute(r_sq, params());
156
157 assert!(result.energy.is_finite());
158 assert!(result.diff.is_finite());
159 assert!(result.energy > 0.0);
160 }
161
162 fn finite_diff_check(r: f64, p: f64) {
167 let r_sq = r * r;
168
169 let r_plus = r + H;
170 let r_minus = r - H;
171 let e_plus = Coulomb::energy(r_plus * r_plus, p);
172 let e_minus = Coulomb::energy(r_minus * r_minus, p);
173 let de_dr_numerical = (e_plus - e_minus) / (2.0 * H);
174
175 let d_analytic = Coulomb::diff(r_sq, p);
176 let de_dr_analytic = -d_analytic * r;
177
178 assert_relative_eq!(de_dr_numerical, de_dr_analytic, epsilon = TOL_DIFF);
179 }
180
181 #[test]
182 fn finite_diff_short_range() {
183 finite_diff_check(1.0, params());
184 }
185
186 #[test]
187 fn finite_diff_medium_range() {
188 finite_diff_check(3.0, params());
189 }
190
191 #[test]
192 fn finite_diff_long_range() {
193 finite_diff_check(10.0, params());
194 }
195
196 #[test]
197 fn finite_diff_negative_charge() {
198 finite_diff_check(2.5, -Q_EFF);
199 }
200
201 #[test]
206 fn specific_inverse_r_scaling() {
207 let e1 = Coulomb::energy(1.0, params());
208 let e2 = Coulomb::energy(4.0, params());
209
210 assert_relative_eq!(e1 / e2, 2.0, epsilon = 1e-10);
211 }
212
213 #[test]
214 fn specific_inverse_r3_scaling_for_diff() {
215 let d1 = Coulomb::diff(1.0, params());
216 let d2 = Coulomb::diff(4.0, params());
217
218 assert_relative_eq!(d1 / d2, 8.0, epsilon = 1e-10);
219 }
220}