1use crate::Model;
2
3pub struct PseudoVoigt;
15
16impl Model for PseudoVoigt {
17 fn eval(&self, x: &[f64], params: &[f64]) -> f64 {
18 let (a, c, sigma, eta) = (params[0], params[1], params[2], params[3]);
19 let eta = eta.clamp(0.0, 1.0);
24 let dx = x[0] - c;
25 let g = (-dx * dx / (2.0 * sigma * sigma)).exp();
26 let l = 1.0 / (1.0 + dx * dx / (sigma * sigma));
27 a * (eta * l + (1.0 - eta) * g)
28 }
29
30 fn jacobian(&self, x: &[f64], params: &[f64]) -> Vec<f64> {
31 let (a, c, sigma, raw_eta) = (params[0], params[1], params[2], params[3]);
32 let eta = raw_eta.clamp(0.0, 1.0);
36 let in_range = (0.0..=1.0).contains(&raw_eta);
37 let dx = x[0] - c;
38 let dx2 = dx * dx;
39 let s2 = sigma * sigma;
40
41 let g = (-dx2 / (2.0 * s2)).exp();
42 let denom = 1.0 + dx2 / s2;
43 let l = 1.0 / denom;
44 let mix = eta * l + (1.0 - eta) * g;
45
46 let da = mix;
48
49 let dg_dc = g * dx / s2;
51 let dl_dc = 2.0 * dx / (s2 * denom * denom);
52 let dc = a * (eta * dl_dc + (1.0 - eta) * dg_dc);
53
54 let dg_ds = g * dx2 / (s2 * sigma);
56 let dl_ds = 2.0 * dx2 / (s2 * sigma * denom * denom);
57 let ds = a * (eta * dl_ds + (1.0 - eta) * dg_ds);
58
59 let df = if in_range { a * (l - g) } else { 0.0 };
61
62 vec![da, dc, ds, df]
63 }
64
65 #[inline]
66 fn jacobian_into(&self, x: &[f64], params: &[f64], out: &mut [f64]) {
67 let (a, c, sigma, raw_eta) = (params[0], params[1], params[2], params[3]);
68 let eta = raw_eta.clamp(0.0, 1.0);
69 let in_range = (0.0..=1.0).contains(&raw_eta);
70 let dx = x[0] - c;
71 let dx2 = dx * dx;
72 let s2 = sigma * sigma;
73 let g = (-dx2 / (2.0 * s2)).exp();
74 let denom = 1.0 + dx2 / s2;
75 let l = 1.0 / denom;
76 let denom2 = denom * denom;
77 let dg_dc = g * dx / s2;
78 let dl_dc = 2.0 * dx / (s2 * denom2);
79 let dg_ds = g * dx2 / (s2 * sigma);
80 let dl_ds = 2.0 * dx2 / (s2 * sigma * denom2);
81 out[0] = eta * l + (1.0 - eta) * g;
82 out[1] = a * (eta * dl_dc + (1.0 - eta) * dg_dc);
83 out[2] = a * (eta * dl_ds + (1.0 - eta) * dg_ds);
84 out[3] = if in_range { a * (l - g) } else { 0.0 };
85 }
86
87 fn param_names(&self) -> Vec<std::borrow::Cow<'static, str>> {
88 vec![
89 "amplitude".into(),
90 "center".into(),
91 "sigma".into(),
92 "fraction".into(),
93 ]
94 }
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100 use approx::assert_relative_eq;
101
102 #[test]
103 fn pure_gaussian_at_eta_zero() {
104 let v = PseudoVoigt.eval(&[0.0], &[3.0, 0.0, 1.0, 0.0]);
106 assert_relative_eq!(v, 3.0, epsilon = 1e-12);
107 }
108
109 #[test]
110 fn pure_lorentzian_at_eta_one() {
111 let v = PseudoVoigt.eval(&[0.0], &[3.0, 0.0, 1.0, 1.0]);
113 assert_relative_eq!(v, 3.0, epsilon = 1e-12);
114 }
115
116 #[test]
117 fn jacobian_shape() {
118 let j = PseudoVoigt.jacobian(&[0.5], &[1.0, 0.0, 1.0, 0.5]);
119 assert_eq!(j.len(), 4);
120 }
121
122 #[test]
123 fn jacobian_finite_diff_check() {
124 let param_sets = [
130 [2.0, 0.3, 0.9, 0.4], [1e-3, 0.0, 0.05, 0.2], [5.0, -2.0, 3.0, 0.8], ];
134 for p in param_sets {
135 let (c, sigma) = (p[1], p[2]);
136 for &mult in &[-3.0_f64, -1.0, -0.1, 0.0, 0.1, 1.0, 3.0] {
137 let x = [c + mult * sigma];
138 let j_anal = PseudoVoigt.jacobian(&x, &p);
139 for i in 0..p.len() {
140 let h = 1e-6 * p[i].abs().max(1.0);
141 let (mut a, mut b) = (p, p);
142 a[i] += h;
143 b[i] -= h;
144 let fd = (PseudoVoigt.eval(&x, &a) - PseudoVoigt.eval(&x, &b)) / (2.0 * h);
145 assert_relative_eq!(j_anal[i], fd, epsilon = 1e-7, max_relative = 1e-6);
146 }
147 }
148 }
149 }
150
151 #[test]
152 fn fraction_clamped_to_unit_interval() {
153 let over = PseudoVoigt.eval(&[0.7], &[2.5, 0.0, 1.3, 1.3]);
155 let one = PseudoVoigt.eval(&[0.7], &[2.5, 0.0, 1.3, 1.0]);
156 assert_relative_eq!(over, one, epsilon = 1e-12);
157 let under = PseudoVoigt.eval(&[0.7], &[2.5, 0.0, 1.3, -0.4]);
158 let zero = PseudoVoigt.eval(&[0.7], &[2.5, 0.0, 1.3, 0.0]);
159 assert_relative_eq!(under, zero, epsilon = 1e-12);
160 let j = PseudoVoigt.jacobian(&[0.7], &[2.5, 0.0, 1.3, 1.3]);
162 assert_relative_eq!(j[3], 0.0, epsilon = 1e-12);
163 }
164
165 #[test]
166 fn da_at_center() {
167 let j = PseudoVoigt.jacobian(&[0.0], &[1.0, 0.0, 1.0, 0.5]);
169 assert_relative_eq!(j[0], 1.0, epsilon = 1e-12);
170 }
171}