1use crate::math_backend::batch_exp;
2use crate::Model;
3
4pub struct Gaussian;
8
9impl Model for Gaussian {
10 fn eval(&self, x: &[f64], params: &[f64]) -> f64 {
11 let (a, c, sigma) = (params[0], params[1], params[2]);
12 let z = -(x[0] - c).powi(2) / (2.0 * sigma * sigma);
13 a * z.exp()
14 }
15
16 fn jacobian(&self, x: &[f64], params: &[f64]) -> Vec<f64> {
17 let (a, c, sigma) = (params[0], params[1], params[2]);
18 let dx = x[0] - c;
19 let z = -dx * dx / (2.0 * sigma * sigma);
20 let g = z.exp();
21
22 let da = g;
24 let dc = a * g * dx / (sigma * sigma);
26 let ds = a * g * dx * dx / (sigma * sigma * sigma);
28
29 vec![da, dc, ds]
30 }
31
32 #[inline]
33 fn jacobian_into(&self, x: &[f64], params: &[f64], out: &mut [f64]) {
34 let (a, c, sigma) = (params[0], params[1], params[2]);
35 let dx = x[0] - c;
36 let s2 = sigma * sigma;
37 let g = (-dx * dx / (2.0 * s2)).exp();
38 out[0] = g;
39 out[1] = a * g * dx / s2;
40 out[2] = a * g * dx * dx / (s2 * sigma);
41 }
42
43 fn param_names(&self) -> Vec<std::borrow::Cow<'static, str>> {
44 vec!["amplitude".into(), "center".into(), "sigma".into()]
45 }
46
47 fn eval_slice_into(&self, xs: &[f64], params: &[f64], out: &mut [f64]) {
48 debug_assert_eq!(out.len(), xs.len());
49 let (a, c, sigma) = (params[0], params[1], params[2]);
50 let inv_2s2 = 1.0 / (2.0 * sigma * sigma);
51 let args: Vec<f64> = xs
54 .iter()
55 .map(|xi| {
56 let dx = xi - c;
57 -(dx * dx) * inv_2s2
58 })
59 .collect();
60 batch_exp(out, &args);
62 for slot in out.iter_mut() {
64 *slot *= a;
65 }
66 }
67
68 fn jac_slice_into(&self, xs: &[f64], params: &[f64], out: &mut [f64]) {
69 debug_assert_eq!(out.len(), xs.len() * 3);
70 let (a, c, sigma) = (params[0], params[1], params[2]);
71 let s2 = sigma * sigma;
72 let inv_2s2 = 0.5 / s2;
73 let inv_s2 = 1.0 / s2;
74 let inv_s3 = inv_s2 / sigma;
75 let n = xs.len();
76 let args: Vec<f64> = xs
78 .iter()
79 .map(|xi| {
80 let dx = xi - c;
81 -(dx * dx) * inv_2s2
82 })
83 .collect();
84 let mut g = vec![0.0_f64; n];
86 batch_exp(&mut g, &args);
87 for (i, (&xi, &gi)) in xs.iter().zip(g.iter()).enumerate() {
89 let dx = xi - c;
90 out[i * 3] = gi;
91 out[i * 3 + 1] = a * gi * dx * inv_s2;
92 out[i * 3 + 2] = a * gi * dx * dx * inv_s3;
93 }
94 }
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100 use approx::assert_relative_eq;
101
102 #[test]
103 fn eval_at_center() {
104 let g = Gaussian;
106 let v = g.eval(&[0.0], &[1.0, 0.0, 1.0]);
107 assert_relative_eq!(v, 1.0, epsilon = 1e-12);
108 }
109
110 #[test]
111 fn eval_at_sigma_offset() {
112 let g = Gaussian;
114 let v = g.eval(&[1.0], &[1.0, 0.0, 1.0]);
115 let expected = 1.0 / std::f64::consts::E.sqrt();
116 assert_relative_eq!(v, expected, epsilon = 1e-12);
117 }
118
119 #[test]
120 fn jacobian_shape() {
121 let g = Gaussian;
122 let j = g.jacobian(&[0.5], &[2.0, 0.0, 1.0]);
123 assert_eq!(j.len(), 3);
124 }
125
126 #[test]
127 fn jacobian_at_center_da() {
128 let g = Gaussian;
130 let j = g.jacobian(&[0.0], &[1.0, 0.0, 1.0]);
131 assert_relative_eq!(j[0], 1.0, epsilon = 1e-12);
132 }
133
134 #[test]
135 fn jacobian_at_center_dc_zero() {
136 let g = Gaussian;
138 let j = g.jacobian(&[0.0], &[1.0, 0.0, 1.0]);
139 assert_relative_eq!(j[1], 0.0, epsilon = 1e-12);
140 }
141
142 const GAUSSIAN_REGIMES: [[f64; 3]; 3] = [
148 [2.0, 0.0, 1.0], [1e-3, 0.0, 0.05], [5.0, -2.0, 3.0], ];
152
153 fn check_param_central_diff(idx: usize) {
154 let g = Gaussian;
155 for p in GAUSSIAN_REGIMES {
156 let (c, sigma) = (p[1], p[2]);
157 for &mult in &[-3.0_f64, -1.0, -0.1, 0.0, 0.1, 1.0, 3.0] {
158 let x = [c + mult * sigma];
159 let j = g.jacobian(&x, &p);
160 let h = 1e-6 * p[idx].abs().max(1.0);
161 let (mut a, mut b) = (p, p);
162 a[idx] += h;
163 b[idx] -= h;
164 let fd = (g.eval(&x, &a) - g.eval(&x, &b)) / (2.0 * h);
165 assert_relative_eq!(j[idx], fd, epsilon = 1e-7, max_relative = 1e-6);
166 }
167 }
168 }
169
170 #[test]
171 fn jacobian_numerical_check_amplitude() {
172 check_param_central_diff(0);
173 }
174
175 #[test]
176 fn jacobian_numerical_check_center() {
177 check_param_central_diff(1);
178 }
179
180 #[test]
181 fn jacobian_numerical_check_sigma() {
182 check_param_central_diff(2);
183 }
184
185 #[test]
188 fn eval_slice_into_matches_scalar() {
189 let g = Gaussian;
190 let xs: Vec<f64> = (-5..=5).map(|i| i as f64 * 0.5).collect();
191 let params = [3.0f64, 0.5, 1.2];
192 let mut batch_out = vec![0.0_f64; xs.len()];
193 g.eval_slice_into(&xs, ¶ms, &mut batch_out);
194 for (xi, &bi) in xs.iter().zip(batch_out.iter()) {
195 let scalar = g.eval(&[*xi], ¶ms);
196 assert_relative_eq!(bi, scalar, epsilon = 1e-10, max_relative = 1e-10);
197 }
198 }
199
200 #[test]
201 fn jac_slice_into_matches_scalar() {
202 let g = Gaussian;
203 let xs: Vec<f64> = (-3..=3).map(|i| i as f64 * 0.75).collect();
204 let params = [2.0f64, -0.5, 0.8];
205 let n = xs.len();
206 let mut jac_out = vec![0.0_f64; n * 3];
207 g.jac_slice_into(&xs, ¶ms, &mut jac_out);
208 for (i, xi) in xs.iter().enumerate() {
209 let scalar = g.jacobian(&[*xi], ¶ms);
210 assert_relative_eq!(jac_out[i * 3], scalar[0], epsilon = 1e-10);
211 assert_relative_eq!(jac_out[i * 3 + 1], scalar[1], epsilon = 1e-10);
212 assert_relative_eq!(jac_out[i * 3 + 2], scalar[2], epsilon = 1e-10);
213 }
214 }
215}