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;
91
92 #[test]
97 fn sanity_compute_equals_separate() {
98 let r_sq = 4.0_f64;
99 let params = Q_EFF;
100
101 let result = Coulomb::compute(r_sq, params);
102 let energy_only = Coulomb::energy(r_sq, params);
103 let diff_only = Coulomb::diff(r_sq, params);
104
105 assert_relative_eq!(result.energy, energy_only, epsilon = 1e-14);
106 assert_relative_eq!(result.diff, diff_only, epsilon = 1e-14);
107 }
108
109 #[test]
110 fn sanity_f32_f64_consistency() {
111 let r_sq_64 = 4.0_f64;
112 let r_sq_32 = 4.0_f32;
113
114 let e64 = Coulomb::energy(r_sq_64, Q_EFF);
115 let e32 = Coulomb::energy(r_sq_32, Q_EFF as f32);
116
117 assert_relative_eq!(e64, e32 as f64, epsilon = 1e-5);
118 }
119
120 #[test]
121 fn sanity_energy_positive_for_like_charges() {
122 let r_sq = 4.0_f64;
123 let e = Coulomb::energy(r_sq, 1.0);
124 assert!(e > 0.0);
125 }
126
127 #[test]
128 fn sanity_energy_negative_for_unlike_charges() {
129 let r_sq = 4.0_f64;
130 let e: f64 = Coulomb::energy(r_sq, -1.0);
131 assert!(e < 0.0);
132 }
133
134 #[test]
139 fn stability_large_distance() {
140 let r_sq = 1e10_f64;
141 let result = Coulomb::compute(r_sq, Q_EFF);
142
143 assert!(result.energy.is_finite());
144 assert!(result.diff.is_finite());
145 assert!(result.energy.abs() < 1e-4);
146 assert!(result.diff.abs() < 1e-14);
147 }
148
149 #[test]
150 fn stability_small_distance() {
151 let r_sq = 1e-6_f64;
152 let result = Coulomb::compute(r_sq, Q_EFF);
153
154 assert!(result.energy.is_finite());
155 assert!(result.diff.is_finite());
156 assert!(result.energy > 0.0);
157 }
158
159 fn finite_diff_check(r: f64, params: f64) {
164 let r_sq = r * r;
165
166 let r_plus = r + H;
167 let r_minus = r - H;
168 let e_plus = Coulomb::energy(r_plus * r_plus, params);
169 let e_minus = Coulomb::energy(r_minus * r_minus, params);
170 let de_dr_numerical = (e_plus - e_minus) / (2.0 * H);
171
172 let d_analytic = Coulomb::diff(r_sq, params);
173 let de_dr_analytic = -d_analytic * r;
174
175 assert_relative_eq!(de_dr_numerical, de_dr_analytic, epsilon = TOL_DIFF);
176 }
177
178 #[test]
179 fn finite_diff_short_range() {
180 finite_diff_check(1.0, Q_EFF);
181 }
182
183 #[test]
184 fn finite_diff_medium_range() {
185 finite_diff_check(3.0, Q_EFF);
186 }
187
188 #[test]
189 fn finite_diff_long_range() {
190 finite_diff_check(10.0, Q_EFF);
191 }
192
193 #[test]
194 fn finite_diff_negative_charge() {
195 finite_diff_check(2.5, -Q_EFF);
196 }
197
198 #[test]
203 fn specific_inverse_r_scaling() {
204 let e1 = Coulomb::energy(1.0, Q_EFF);
205 let e2 = Coulomb::energy(4.0, Q_EFF);
206
207 assert_relative_eq!(e1 / e2, 2.0, epsilon = 1e-10);
208 }
209
210 #[test]
211 fn specific_inverse_r3_scaling_for_diff() {
212 let d1 = Coulomb::diff(1.0, Q_EFF);
213 let d2 = Coulomb::diff(4.0, Q_EFF);
214
215 assert_relative_eq!(d1 / d2, 8.0, epsilon = 1e-10);
216 }
217}