spectrafit_models/
voigt.rs1use crate::Model;
2
3pub struct Voigt;
14
15impl Model for Voigt {
16 fn eval(&self, x: &[f64], params: &[f64]) -> f64 {
17 let (a, c, sigma, frac) = (params[0], params[1], params[2], params[3]);
18 let dx = x[0] - c;
19 let z = -dx * dx / (2.0 * sigma * sigma);
20 let g_tilde = z.exp();
21 let d = dx / sigma;
22 let big_d = 1.0 + d * d;
23 let l_tilde = 1.0 / big_d;
24
25 a * (frac * l_tilde + (1.0 - frac) * g_tilde)
26 }
27
28 fn jacobian(&self, x: &[f64], params: &[f64]) -> Vec<f64> {
29 let (a, c, sigma, frac) = (params[0], params[1], params[2], params[3]);
30 let dx = x[0] - c;
31 let z = -dx * dx / (2.0 * sigma * sigma);
32 let g_tilde = z.exp();
33
34 let d = dx / sigma;
35 let big_d = 1.0 + d * d;
36 let l_tilde = 1.0 / big_d;
37
38 let dg_dc = g_tilde * dx / (sigma * sigma);
41 let dg_ds = g_tilde * dx * dx / (sigma * sigma * sigma);
43
44 let dl_dc = 2.0 * dx / (sigma * sigma * big_d * big_d);
46 let dl_ds = 2.0 * dx * dx / (sigma * sigma * sigma * big_d * big_d);
48
49 let da = frac * l_tilde + (1.0 - frac) * g_tilde;
51 let dc = a * (frac * dl_dc + (1.0 - frac) * dg_dc);
53 let ds = a * (frac * dl_ds + (1.0 - frac) * dg_ds);
55 let dfrac = a * (l_tilde - g_tilde);
57
58 vec![da, dc, ds, dfrac]
59 }
60
61 #[inline]
62 fn jacobian_into(&self, x: &[f64], params: &[f64], out: &mut [f64]) {
63 let (a, c, sigma, frac) = (params[0], params[1], params[2], params[3]);
64 let dx = x[0] - c;
65 let s2 = sigma * sigma;
66 let z = -dx * dx / (2.0 * s2);
67 let g_tilde = z.exp();
68 let d = dx / sigma;
69 let big_d = 1.0 + d * d;
70 let big_d2 = big_d * big_d;
71 let l_tilde = 1.0 / big_d;
72 let dg_dc = g_tilde * dx / s2;
73 let dg_ds = g_tilde * dx * dx / (s2 * sigma);
74 let dl_dc = 2.0 * dx / (s2 * big_d2);
75 let dl_ds = 2.0 * dx * dx / (s2 * sigma * big_d2);
76 out[0] = frac * l_tilde + (1.0 - frac) * g_tilde;
77 out[1] = a * (frac * dl_dc + (1.0 - frac) * dg_dc);
78 out[2] = a * (frac * dl_ds + (1.0 - frac) * dg_ds);
79 out[3] = a * (l_tilde - g_tilde);
80 }
81
82 fn param_names(&self) -> Vec<std::borrow::Cow<'static, str>> {
83 vec![
84 "amplitude".into(),
85 "center".into(),
86 "sigma".into(),
87 "fraction".into(),
88 ]
89 }
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95 use crate::gaussian::Gaussian;
96 use crate::lorentzian::Lorentzian;
97 use approx::assert_relative_eq;
98
99 #[test]
100 fn frac_zero_equals_gaussian() {
101 let voigt = Voigt;
102 let gauss = Gaussian;
103 let x = &[0.7f64];
104 let params_v = [2.0, 0.0, 1.0, 0.0]; let params_g = [2.0, 0.0, 1.0];
106 assert_relative_eq!(
107 voigt.eval(x, ¶ms_v),
108 gauss.eval(x, ¶ms_g),
109 epsilon = 1e-12
110 );
111 }
112
113 #[test]
114 fn frac_one_equals_lorentzian() {
115 let voigt = Voigt;
116 let lorentz = Lorentzian;
117 let x = &[0.7f64];
118 let params_v = [2.0, 0.0, 1.0, 1.0]; let params_l = [2.0, 0.0, 1.0];
120 assert_relative_eq!(
121 voigt.eval(x, ¶ms_v),
122 lorentz.eval(x, ¶ms_l),
123 epsilon = 1e-12
124 );
125 }
126
127 #[test]
128 fn jacobian_shape() {
129 let v = Voigt;
130 let j = v.jacobian(&[0.5], &[1.0, 0.0, 1.0, 0.5]);
131 assert_eq!(j.len(), 4);
132 }
133
134 const VOIGT_REGIMES: [[f64; 4]; 3] = [
140 [2.0, 0.0, 1.0, 0.4], [1e-3, 0.0, 0.05, 0.2], [5.0, -2.0, 3.0, 0.8], ];
144
145 fn check_voigt_param_central_diff(idx: usize) {
146 let v = Voigt;
147 for p in VOIGT_REGIMES {
148 let (c, sigma) = (p[1], p[2]);
149 for &mult in &[-3.0_f64, -1.0, -0.1, 0.0, 0.1, 1.0, 3.0] {
150 let x = [c + mult * sigma];
151 let j = v.jacobian(&x, &p);
152 let h = 1e-6 * p[idx].abs().max(1.0);
153 let (mut a, mut b) = (p, p);
154 a[idx] += h;
155 b[idx] -= h;
156 let fd = (v.eval(&x, &a) - v.eval(&x, &b)) / (2.0 * h);
157 assert_relative_eq!(j[idx], fd, epsilon = 1e-7, max_relative = 1e-6);
158 }
159 }
160 }
161
162 #[test]
163 fn jacobian_numerical_check_amplitude() {
164 check_voigt_param_central_diff(0);
165 }
166
167 #[test]
168 fn jacobian_numerical_check_center() {
169 check_voigt_param_central_diff(1);
170 }
171
172 #[test]
173 fn jacobian_numerical_check_sigma() {
174 check_voigt_param_central_diff(2);
175 }
176
177 #[test]
178 fn jacobian_numerical_check_frac() {
179 check_voigt_param_central_diff(3);
180 }
181}