1use crate::base::Potential2;
30use crate::math::Vector;
31
32#[derive(Clone, Copy, Debug, PartialEq)]
43pub struct Gauss<T> {
44 a: T,
46 neg_b: T,
48}
49
50impl<T: Vector> Gauss<T> {
51 #[inline]
67 pub fn new(a: f64, b: f64) -> Self {
68 Self {
69 a: T::splat(a),
70 neg_b: T::splat(-b),
71 }
72 }
73
74 #[inline]
85 pub fn from_sigma(a: f64, sigma: f64) -> Self {
86 let b = 1.0 / (2.0 * sigma * sigma);
87 Self::new(a, b)
88 }
89
90 #[inline]
92 pub fn amplitude(&self) -> T {
93 self.a
94 }
95}
96
97impl<T: Vector> Potential2<T> for Gauss<T> {
98 #[inline(always)]
104 fn energy(&self, r_sq: T) -> T {
105 let exp_term = (self.neg_b * r_sq).exp();
107 self.a * exp_term
108 }
109
110 #[inline(always)]
117 fn force_factor(&self, r_sq: T) -> T {
118 let exp_term = (self.neg_b * r_sq).exp();
119
120 let two = T::splat(2.0);
122 T::zero() - two * self.neg_b * self.a * exp_term
123 }
124
125 #[inline(always)]
129 fn energy_force(&self, r_sq: T) -> (T, T) {
130 let exp_term = (self.neg_b * r_sq).exp();
131 let a_exp = self.a * exp_term;
132
133 let energy = a_exp;
134
135 let two = T::splat(2.0);
136 let force = T::zero() - two * self.neg_b * a_exp;
137
138 (energy, force)
139 }
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145 use approx::assert_relative_eq;
146
147 #[test]
148 fn test_gauss_at_zero() {
149 let gauss: Gauss<f64> = Gauss::new(5.0, 1.0);
150 let energy = gauss.energy(0.0);
151 assert_relative_eq!(energy, 5.0, epsilon = 1e-10);
152 }
153
154 #[test]
155 fn test_gauss_decay() {
156 let a = 1.0;
157 let b = 0.5;
158 let gauss: Gauss<f64> = Gauss::new(a, b);
159
160 let r = 2.0;
161 let r_sq = r * r;
162 let energy = gauss.energy(r_sq);
163 let expected = a * (-b * r_sq).exp();
164
165 assert_relative_eq!(energy, expected, epsilon = 1e-10);
166 }
167
168 #[test]
169 fn test_gauss_force_at_zero() {
170 let a = 3.0;
171 let b = 0.5;
172 let gauss: Gauss<f64> = Gauss::new(a, b);
173
174 let force = gauss.force_factor(1e-20);
175 let expected = 2.0 * a * b;
176
177 assert_relative_eq!(force, expected, epsilon = 1e-10);
178 }
179
180 #[test]
181 fn test_gauss_from_sigma() {
182 let a = 2.0;
183 let sigma = 1.5;
184 let g1: Gauss<f64> = Gauss::from_sigma(a, sigma);
185 let g2: Gauss<f64> = Gauss::new(a, 1.0 / (2.0 * sigma * sigma));
186
187 let r_sq = 3.0;
188 assert_relative_eq!(g1.energy(r_sq), g2.energy(r_sq), epsilon = 1e-10);
189 }
190
191 #[test]
192 fn test_gauss_energy_force_consistency() {
193 let gauss: Gauss<f64> = Gauss::new(10.0, 0.25);
194 let r_sq = 2.25;
195
196 let (e1, f1) = gauss.energy_force(r_sq);
197 let e2 = gauss.energy(r_sq);
198 let f2 = gauss.force_factor(r_sq);
199
200 assert_relative_eq!(e1, e2, epsilon = 1e-10);
201 assert_relative_eq!(f1, f2, epsilon = 1e-10);
202 }
203
204 #[test]
205 fn test_gauss_numerical_derivative() {
206 let gauss: Gauss<f64> = Gauss::new(10.0, 0.25);
207 let r = 1.5;
208 let r_sq = r * r;
209
210 let h = 1e-6;
211 let v_plus = gauss.energy((r + h) * (r + h));
212 let v_minus = gauss.energy((r - h) * (r - h));
213 let dv_dr_numerical = (v_plus - v_minus) / (2.0 * h);
214
215 let s_numerical = -dv_dr_numerical / r;
216 let s_analytical = gauss.force_factor(r_sq);
217
218 assert_relative_eq!(s_analytical, s_numerical, epsilon = 1e-6);
219 }
220}