1use crate::Model;
2
3pub struct Fano;
16
17impl Model for Fano {
18 fn eval(&self, x: &[f64], params: &[f64]) -> f64 {
19 let (a, x0, gamma, q) = (params[0], params[1], params[2], params[3]);
20 let eps = (x[0] - x0) / gamma;
21 let num = (q + eps) * (q + eps);
22 let den = 1.0 + eps * eps;
23 a * num / den
24 }
25
26 fn jacobian(&self, x: &[f64], params: &[f64]) -> Vec<f64> {
27 let (a, x0, gamma, q) = (params[0], params[1], params[2], params[3]);
28 let dx = x[0] - x0;
29 let eps = dx / gamma;
30 let qe = q + eps;
31 let e2_1 = 1.0 + eps * eps;
32 let fano = qe * qe / e2_1; let da = fano;
36
37 let dfano_deps = 2.0 * qe * (e2_1 - qe * eps) / (e2_1 * e2_1);
42
43 let dc = a * dfano_deps * (-1.0 / gamma);
45 let dg = a * dfano_deps * (-eps / gamma);
46
47 let dq = a * 2.0 * qe / e2_1;
49
50 vec![da, dc, dg, dq]
51 }
52
53 #[inline]
54 fn jacobian_into(&self, x: &[f64], params: &[f64], out: &mut [f64]) {
55 let (a, x0, gamma, q) = (params[0], params[1], params[2], params[3]);
56 let dx = x[0] - x0;
57 let eps = dx / gamma;
58 let qe = q + eps;
59 let e2_1 = 1.0 + eps * eps;
60 let fano = qe * qe / e2_1;
61 let dfano_deps = 2.0 * qe * (e2_1 - qe * eps) / (e2_1 * e2_1);
62 out[0] = fano;
63 out[1] = a * dfano_deps * (-1.0 / gamma);
64 out[2] = a * dfano_deps * (-eps / gamma);
65 out[3] = a * 2.0 * qe / e2_1;
66 }
67
68 fn param_names(&self) -> Vec<std::borrow::Cow<'static, str>> {
69 vec![
70 "amplitude".into(),
71 "center".into(),
72 "gamma".into(),
73 "q".into(),
74 ]
75 }
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81 use approx::assert_relative_eq;
82
83 #[test]
84 fn at_center_eps_zero() {
85 let v = Fano.eval(&[0.0], &[1.0, 0.0, 1.0, 2.0]);
87 assert_relative_eq!(v, 4.0, epsilon = 1e-12);
88 }
89
90 #[test]
91 fn zero_at_eps_minus_q() {
92 let v = Fano.eval(&[-2.0], &[1.0, 0.0, 1.0, 2.0]);
94 assert_relative_eq!(v, 0.0, epsilon = 1e-12);
95 }
96
97 #[test]
98 fn jacobian_shape() {
99 let j = Fano.jacobian(&[0.5], &[1.0, 0.0, 1.0, 1.5]);
100 assert_eq!(j.len(), 4);
101 }
102
103 #[test]
104 fn jacobian_finite_diff_check() {
105 let param_sets = [
110 [2.0, 0.2, 0.8, 1.5], [1e-3, 0.0, 0.05, 0.5], [5.0, -2.0, 3.0, 10.0], ];
114 for p in param_sets {
115 let (center, gamma) = (p[1], p[2]);
116 for &mult in &[-3.0_f64, -1.0, -0.1, 0.0, 0.1, 1.0, 3.0] {
117 let x = [center + mult * gamma];
118 let j_anal = Fano.jacobian(&x, &p);
119 for i in 0..p.len() {
120 let h = 1e-6 * p[i].abs().max(1.0);
121 let (mut a, mut b) = (p, p);
122 a[i] += h;
123 b[i] -= h;
124 let fd = (Fano.eval(&x, &a) - Fano.eval(&x, &b)) / (2.0 * h);
125 assert_relative_eq!(j_anal[i], fd, epsilon = 1e-7, max_relative = 1e-6);
126 }
127 }
128 }
129 }
130
131 #[test]
132 fn da_equals_fano_value_over_amplitude() {
133 let p = [3.0, 0.0, 1.0, 1.0];
135 let x = [0.5];
136 let j = Fano.jacobian(&x, &p);
137 let expected_da = Fano.eval(&x, &[1.0, p[1], p[2], p[3]]);
138 assert_relative_eq!(j[0], expected_da, epsilon = 1e-12);
139 }
140
141 #[test]
152 fn fano_q_large_normalised_matches_lorentzian() {
153 use crate::lorentzian::Lorentzian;
154 let x = [0.4_f64];
155 let q = 1.0e6;
156 let p_fano = [1.0, 0.0, 1.0, q]; let p_lor = [1.0, 0.0, 1.0]; let fano_normalised = Fano.eval(&x, &p_fano) / (q * q);
159 let lor = Lorentzian.eval(&x, &p_lor);
160 assert_relative_eq!(fano_normalised, lor, epsilon = 1e-5);
162 }
163}