spectrafit_models/
lorentzian.rs1use crate::Model;
2
3pub struct Lorentzian;
7
8impl Model for Lorentzian {
9 fn eval(&self, x: &[f64], params: &[f64]) -> f64 {
10 let (a, c, sigma) = (params[0], params[1], params[2]);
11 let d = (x[0] - c) / sigma;
12 a / (1.0 + d * d)
13 }
14
15 fn jacobian(&self, x: &[f64], params: &[f64]) -> Vec<f64> {
16 let (a, c, sigma) = (params[0], params[1], params[2]);
17 let dx = x[0] - c;
18 let d = dx / sigma;
19 let big_d = 1.0 + d * d;
20
21 let da = 1.0 / big_d;
23 let dc = 2.0 * a * dx / (sigma * sigma * big_d * big_d);
25 let ds = 2.0 * a * dx * dx / (sigma * sigma * sigma * big_d * big_d);
27
28 vec![da, dc, ds]
29 }
30
31 #[inline]
32 fn jacobian_into(&self, x: &[f64], params: &[f64], out: &mut [f64]) {
33 let (a, c, sigma) = (params[0], params[1], params[2]);
34 let dx = x[0] - c;
35 let d = dx / sigma;
36 let big_d = 1.0 + d * d;
37 let big_d2 = big_d * big_d;
38 let s2 = sigma * sigma;
39 out[0] = 1.0 / big_d;
40 out[1] = 2.0 * a * dx / (s2 * big_d2);
41 out[2] = 2.0 * a * dx * dx / (s2 * sigma * big_d2);
42 }
43
44 fn param_names(&self) -> Vec<std::borrow::Cow<'static, str>> {
45 vec!["amplitude".into(), "center".into(), "sigma".into()]
46 }
47}
48
49#[cfg(test)]
50mod tests {
51 use super::*;
52 use approx::assert_relative_eq;
53
54 #[test]
55 fn eval_at_center() {
56 let l = Lorentzian;
58 let v = l.eval(&[0.0], &[3.0, 0.0, 1.0]);
59 assert_relative_eq!(v, 3.0, epsilon = 1e-12);
60 }
61
62 #[test]
63 fn eval_at_sigma_offset() {
64 let l = Lorentzian;
66 let v = l.eval(&[1.0], &[4.0, 0.0, 1.0]);
67 assert_relative_eq!(v, 2.0, epsilon = 1e-12);
68 }
69
70 #[test]
71 fn jacobian_shape() {
72 let l = Lorentzian;
73 let j = l.jacobian(&[0.5], &[1.0, 0.0, 1.0]);
74 assert_eq!(j.len(), 3);
75 }
76
77 #[test]
78 fn jacobian_at_center_dc_zero() {
79 let l = Lorentzian;
81 let j = l.jacobian(&[0.0], &[1.0, 0.0, 1.0]);
82 assert_relative_eq!(j[1], 0.0, epsilon = 1e-12);
83 }
84
85 const LORENTZIAN_REGIMES: [[f64; 3]; 3] = [
91 [2.0, 0.0, 1.5], [1e-3, 0.0, 0.05], [5.0, -2.0, 3.0], ];
95
96 fn check_lorentzian_param_central_diff(idx: usize) {
97 let l = Lorentzian;
98 for p in LORENTZIAN_REGIMES {
99 let (c, sigma) = (p[1], p[2]);
100 for &mult in &[-3.0_f64, -1.0, -0.1, 0.0, 0.1, 1.0, 3.0] {
101 let x = [c + mult * sigma];
102 let j = l.jacobian(&x, &p);
103 let h = 1e-6 * p[idx].abs().max(1.0);
104 let (mut a, mut b) = (p, p);
105 a[idx] += h;
106 b[idx] -= h;
107 let fd = (l.eval(&x, &a) - l.eval(&x, &b)) / (2.0 * h);
108 assert_relative_eq!(j[idx], fd, epsilon = 1e-7, max_relative = 1e-6);
109 }
110 }
111 }
112
113 #[test]
114 fn jacobian_numerical_check_amplitude() {
115 check_lorentzian_param_central_diff(0);
116 }
117
118 #[test]
119 fn jacobian_numerical_check_center() {
120 check_lorentzian_param_central_diff(1);
121 }
122
123 #[test]
124 fn jacobian_numerical_check_sigma() {
125 check_lorentzian_param_central_diff(2);
126 }
127}