spectrafit_models/
split_gaussian.rs1use crate::Model;
2
3pub struct SplitGaussian;
18
19impl Model for SplitGaussian {
20 fn eval(&self, x: &[f64], params: &[f64]) -> f64 {
21 let (a, c, sl, sr) = (params[0], params[1], params[2], params[3]);
22 let dx = x[0] - c;
23 let sigma = if x[0] < c { sl } else { sr };
24 a * (-(dx * dx) / (2.0 * sigma * sigma)).exp()
25 }
26
27 fn jacobian(&self, x: &[f64], params: &[f64]) -> Vec<f64> {
30 let mut p = params.to_vec();
31 (0..params.len())
32 .map(|i| {
33 let h = 1e-7_f64 * params[i].abs().max(1e-7);
34 p[i] = params[i] + h;
35 let f_plus = self.eval(x, &p);
36 p[i] = params[i] - h;
37 let f_minus = self.eval(x, &p);
38 p[i] = params[i];
39 (f_plus - f_minus) / (2.0 * h)
40 })
41 .collect()
42 }
43
44 fn param_names(&self) -> Vec<std::borrow::Cow<'static, str>> {
45 vec![
46 "amplitude".into(),
47 "center".into(),
48 "sigma_l".into(),
49 "sigma_r".into(),
50 ]
51 }
52}
53
54#[cfg(test)]
55mod tests {
56 use super::*;
57 use approx::assert_relative_eq;
58
59 #[test]
60 fn eval_at_center_equals_amplitude() {
61 let m = SplitGaussian;
62 assert_relative_eq!(m.eval(&[1.5], &[3.0, 1.5, 0.6, 1.2]), 3.0, epsilon = 1e-12);
63 }
64
65 #[test]
66 fn left_and_right_use_their_own_width() {
67 let m = SplitGaussian;
69 let p = [4.0, 0.0, 0.6, 1.2];
70 assert_relative_eq!(m.eval(&[-0.6], &p), 4.0 * (-0.5_f64).exp(), epsilon = 1e-12);
71 assert_relative_eq!(m.eval(&[1.2], &p), 4.0 * (-0.5_f64).exp(), epsilon = 1e-12);
72 }
73
74 #[test]
75 fn param_names_are_canonical() {
76 assert_eq!(
77 SplitGaussian
78 .param_names()
79 .iter()
80 .map(|c| c.as_ref())
81 .collect::<Vec<_>>(),
82 &["amplitude", "center", "sigma_l", "sigma_r"]
83 );
84 }
85
86 #[test]
87 fn jacobian_shape() {
88 let j = SplitGaussian.jacobian(&[0.5], &[3.0, 0.0, 0.6, 1.2]);
89 assert_eq!(j.len(), 4);
90 assert_relative_eq!(j[0], (-0.25_f64 / (2.0 * 1.44)).exp(), epsilon = 1e-6);
92 }
93
94 #[test]
104 fn symmetric_widths_equal_plain_gaussian() {
105 use crate::gaussian::Gaussian;
106 let split = SplitGaussian;
107 let gauss = Gaussian;
108 let sigma = 0.8_f64;
109 let a = 3.5_f64;
110 let c = 0.4_f64;
111 let p_split = [a, c, sigma, sigma]; let p_gauss = [a, c, sigma];
113 for &xi in &[-2.0_f64, -0.6, c, 0.6, 2.0] {
114 assert_relative_eq!(
115 split.eval(&[xi], &p_split),
116 gauss.eval(&[xi], &p_gauss),
117 epsilon = 1e-12
118 );
119 }
120 }
121}