1use crate::Model;
2
3pub struct Mgh09Rational;
28
29impl Model for Mgh09Rational {
30 fn eval(&self, x: &[f64], p: &[f64]) -> f64 {
31 let amplitude = p[0];
32 let num_lin = p[1];
33 let den_lin = p[2];
34 let den_const = p[3];
35 let xi = x[0];
36 let n = xi * xi + num_lin * xi;
37 let d = xi * xi + den_lin * xi + den_const;
38 if d == 0.0 {
39 return f64::NAN;
40 }
41 amplitude * n / d
42 }
43
44 fn jacobian_into(&self, x: &[f64], p: &[f64], out: &mut [f64]) {
45 let amplitude = p[0];
46 let num_lin = p[1];
47 let den_lin = p[2];
48 let den_const = p[3];
49 let xi = x[0];
50 let n = xi * xi + num_lin * xi;
51 let d = xi * xi + den_lin * xi + den_const;
52 if d == 0.0 {
53 out[0] = f64::NAN;
54 out[1] = f64::NAN;
55 out[2] = f64::NAN;
56 out[3] = f64::NAN;
57 return;
58 }
59 let n_over_d = n / d;
60 let inv_d2 = 1.0 / (d * d);
61 out[0] = n_over_d; out[1] = amplitude * xi / d; out[2] = -amplitude * n * xi * inv_d2; out[3] = -amplitude * n * inv_d2; }
66
67 fn jacobian(&self, x: &[f64], p: &[f64]) -> Vec<f64> {
68 let amplitude = p[0];
69 let num_lin = p[1];
70 let den_lin = p[2];
71 let den_const = p[3];
72 let xi = x[0];
73 let n = xi * xi + num_lin * xi;
74 let d = xi * xi + den_lin * xi + den_const;
75 if d == 0.0 {
76 return vec![f64::NAN, f64::NAN, f64::NAN, f64::NAN];
77 }
78 let n_over_d = n / d;
79 let inv_d2 = 1.0 / (d * d);
80 vec![
81 n_over_d, amplitude * xi / d, -amplitude * n * xi * inv_d2, -amplitude * n * inv_d2, ]
86 }
87
88 fn param_names(&self) -> Vec<std::borrow::Cow<'static, str>> {
89 vec![
90 "amplitude".into(),
91 "num_lin".into(),
92 "den_lin".into(),
93 "den_const".into(),
94 ]
95 }
96
97 fn eval_slice_into(&self, xs: &[f64], params: &[f64], out: &mut [f64]) {
98 debug_assert_eq!(out.len(), xs.len());
99 let amplitude = params[0];
100 let num_lin = params[1];
101 let den_lin = params[2];
102 let den_const = params[3];
103 for (slot, &xi) in out.iter_mut().zip(xs.iter()) {
104 let n = xi * xi + num_lin * xi;
105 let d = xi * xi + den_lin * xi + den_const;
106 *slot = if d == 0.0 {
107 f64::NAN
108 } else {
109 amplitude * n / d
110 };
111 }
112 }
113
114 fn jac_slice_into(&self, xs: &[f64], params: &[f64], out: &mut [f64]) {
115 debug_assert_eq!(out.len(), xs.len() * 4);
116 let amplitude = params[0];
117 let num_lin = params[1];
118 let den_lin = params[2];
119 let den_const = params[3];
120 for (i, &xi) in xs.iter().enumerate() {
121 let n = xi * xi + num_lin * xi;
122 let d = xi * xi + den_lin * xi + den_const;
123 if d == 0.0 {
124 out[i * 4] = f64::NAN;
125 out[i * 4 + 1] = f64::NAN;
126 out[i * 4 + 2] = f64::NAN;
127 out[i * 4 + 3] = f64::NAN;
128 } else {
129 let inv_d2 = 1.0 / (d * d);
130 out[i * 4] = n / d; out[i * 4 + 1] = amplitude * xi / d; out[i * 4 + 2] = -amplitude * n * xi * inv_d2; out[i * 4 + 3] = -amplitude * n * inv_d2; }
135 }
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142 use approx::assert_relative_eq;
143
144 fn model() -> Mgh09Rational {
145 Mgh09Rational
146 }
147
148 #[test]
154 fn eval_first_mgh09_point_approx() {
155 let p = [
156 1.9280693458e-01_f64,
157 1.9128232873e-01,
158 1.2305650693e-01,
159 1.3606233068e-01,
160 ];
161 let y = model().eval(&[4.0], &p);
162 assert!((y - 0.1957).abs() < 5e-3, "Expected ~0.1957, got {y}");
163 }
164
165 #[test]
168 fn eval_zero_denominator_returns_nan() {
169 let p = [1.0_f64, 0.5, 0.0, 0.0]; let y = model().eval(&[0.0], &p);
171 assert!(y.is_nan(), "Expected NaN for D=0, got {y}");
172 }
173
174 #[test]
175 fn jacobian_shape() {
176 let j = model().jacobian(&[2.0], &[0.19, 0.19, 0.12, 0.14]);
177 assert_eq!(j.len(), 4);
178 assert!(
179 j.iter().all(|v| v.is_finite()),
180 "Jacobian must be finite: {j:?}"
181 );
182 }
183
184 #[test]
186 fn jacobian_at_x_zero() {
187 let p = [0.2_f64, 0.2, 0.12, 0.14];
188 let j = model().jacobian(&[0.0], &p);
189 assert_eq!(j.len(), 4);
190 assert_relative_eq!(j[0], 0.0, epsilon = 1e-12); assert_relative_eq!(j[1], 0.0, epsilon = 1e-12); assert_relative_eq!(j[2], 0.0, epsilon = 1e-12); assert_relative_eq!(j[3], 0.0, epsilon = 1e-12); }
195
196 const MGH09_REGIMES: [[f64; 4]; 3] = [
203 [0.19, 0.19, 0.12, 0.14], [1e-3, 1e-3, 1e-3, 1e-2], [5.0, -2.0, 3.0, 8.0], ];
207 const MGH09_XS: [f64; 5] = [0.1, 0.5, 1.5, 2.0, 4.0];
208
209 fn check_mgh09_param_central_diff(idx: usize) {
210 for p in MGH09_REGIMES {
211 for &x in &MGH09_XS {
212 let j = model().jacobian(&[x], &p);
213 let h = 1e-6 * p[idx].abs().max(1.0);
214 let mut pp = p;
215 pp[idx] += h;
216 let mut pm = p;
217 pm[idx] -= h;
218 let fd = (model().eval(&[x], &pp) - model().eval(&[x], &pm)) / (2.0 * h);
219 assert_relative_eq!(j[idx], fd, epsilon = 1e-7, max_relative = 1e-6);
220 }
221 }
222 }
223
224 #[test]
226 fn jacobian_fd_amplitude() {
227 check_mgh09_param_central_diff(0);
228 }
229
230 #[test]
232 fn jacobian_fd_num_lin() {
233 check_mgh09_param_central_diff(1);
234 }
235
236 #[test]
238 fn jacobian_fd_den_lin() {
239 check_mgh09_param_central_diff(2);
240 }
241
242 #[test]
244 fn jacobian_fd_den_const() {
245 check_mgh09_param_central_diff(3);
246 }
247
248 #[test]
249 fn jacobian_into_matches_jacobian() {
250 let x = &[1.5_f64];
251 let p = [0.19_f64, 0.19, 0.12, 0.14];
252 let j_vec = model().jacobian(x, &p);
253 let mut out = [0.0_f64; 4];
254 model().jacobian_into(x, &p, &mut out);
255 for k in 0..4 {
256 assert_relative_eq!(out[k], j_vec[k], epsilon = 1e-12);
257 }
258 }
259
260 #[test]
261 fn eval_slice_matches_scalar() {
262 let xs: Vec<f64> = [
263 4.0, 2.0, 1.0, 0.5, 0.25, 0.167, 0.125, 0.1, 0.0833, 0.0714, 0.0625,
264 ]
265 .to_vec();
266 let p = [
267 1.9280693458e-01_f64,
268 1.9128232873e-01,
269 1.2305650693e-01,
270 1.3606233068e-01,
271 ];
272 let mut out = vec![0.0_f64; xs.len()];
273 model().eval_slice_into(&xs, &p, &mut out);
274 for (&xi, &bi) in xs.iter().zip(out.iter()) {
275 assert_relative_eq!(bi, model().eval(&[xi], &p), epsilon = 1e-10);
276 }
277 }
278
279 #[test]
280 fn jac_slice_matches_scalar() {
281 let xs: Vec<f64> = [4.0, 2.0, 1.0, 0.5, 0.25].to_vec();
282 let p = [0.19_f64, 0.19, 0.12, 0.14];
283 let n = xs.len();
284 let mut out = vec![0.0_f64; n * 4];
285 model().jac_slice_into(&xs, &p, &mut out);
286 for (i, xi) in xs.iter().enumerate() {
287 let j = model().jacobian(&[*xi], &p);
288 for k in 0..4 {
289 assert_relative_eq!(out[i * 4 + k], j[k], epsilon = 1e-10);
290 }
291 }
292 }
293}