1use crate::Model;
2
3pub struct ArctanStep;
11
12impl Model for ArctanStep {
13 fn eval(&self, x: &[f64], params: &[f64]) -> f64 {
14 let (a, x0, sigma) = (params[0], params[1], params[2]);
15 let eps = (x[0] - x0) / sigma;
16 a * (0.5 + eps.atan() / std::f64::consts::PI)
17 }
18
19 fn jacobian(&self, x: &[f64], params: &[f64]) -> Vec<f64> {
20 let (a, x0, sigma) = (params[0], params[1], params[2]);
21 let pi = std::f64::consts::PI;
22 let eps = (x[0] - x0) / sigma;
23 let eps2_1 = 1.0 + eps * eps;
24 let atan_term = 0.5 + eps.atan() / pi;
25
26 let da = atan_term;
27 let dx0 = -a / (pi * sigma * eps2_1);
28 let ds = -a * eps / (pi * sigma * eps2_1);
29
30 vec![da, dx0, ds]
31 }
32
33 #[inline]
34 fn jacobian_into(&self, x: &[f64], params: &[f64], out: &mut [f64]) {
35 let (a, x0, sigma) = (params[0], params[1], params[2]);
36 let pi = std::f64::consts::PI;
37 let eps = (x[0] - x0) / sigma;
38 let eps2_1 = 1.0 + eps * eps;
39 out[0] = 0.5 + eps.atan() / pi;
40 out[1] = -a / (pi * sigma * eps2_1);
41 out[2] = -a * eps / (pi * sigma * eps2_1);
42 }
43
44 fn param_names(&self) -> Vec<std::borrow::Cow<'static, str>> {
45 vec!["amplitude".into(), "center".into(), "sigma".into()]
46 }
47}
48
49pub struct TanhStep;
57
58impl Model for TanhStep {
59 fn eval(&self, x: &[f64], params: &[f64]) -> f64 {
60 let (a, x0, sigma) = (params[0], params[1], params[2]);
61 let eps = (x[0] - x0) / sigma;
62 a * 0.5 * (1.0 + eps.tanh())
63 }
64
65 fn jacobian(&self, x: &[f64], params: &[f64]) -> Vec<f64> {
66 let (a, x0, sigma) = (params[0], params[1], params[2]);
67 let eps = (x[0] - x0) / sigma;
68 let t = eps.tanh();
69 let sech2 = 1.0 - t * t; let da = 0.5 * (1.0 + t);
72 let dx0 = -a * sech2 / (2.0 * sigma);
73 let ds = -a * eps * sech2 / (2.0 * sigma);
74
75 vec![da, dx0, ds]
76 }
77
78 #[inline]
79 fn jacobian_into(&self, x: &[f64], params: &[f64], out: &mut [f64]) {
80 let (a, x0, sigma) = (params[0], params[1], params[2]);
81 let eps = (x[0] - x0) / sigma;
82 let t = eps.tanh();
83 let sech2 = 1.0 - t * t;
84 out[0] = 0.5 * (1.0 + t);
85 out[1] = -a * sech2 / (2.0 * sigma);
86 out[2] = -a * eps * sech2 / (2.0 * sigma);
87 }
88
89 fn param_names(&self) -> Vec<std::borrow::Cow<'static, str>> {
90 vec!["amplitude".into(), "center".into(), "sigma".into()]
91 }
92}
93
94pub struct ErfcStep;
104
105impl Model for ErfcStep {
106 fn eval(&self, x: &[f64], params: &[f64]) -> f64 {
107 let (a, x0, sigma) = (params[0], params[1], params[2]);
108 let u = (x[0] - x0) / (sigma * std::f64::consts::SQRT_2);
109 0.5 * a * libm::erfc(u)
110 }
111
112 fn jacobian(&self, x: &[f64], params: &[f64]) -> Vec<f64> {
113 let (a, x0, sigma) = (params[0], params[1], params[2]);
114 let sqrt2 = std::f64::consts::SQRT_2;
115 let u = (x[0] - x0) / (sigma * sqrt2);
116 let gauss = (-u * u).exp() / (sigma * (2.0 * std::f64::consts::PI).sqrt());
117
118 let da = 0.5 * libm::erfc(u);
119 let dx0 = a * gauss;
120 let ds = a * u * gauss * std::f64::consts::SQRT_2;
122
123 vec![da, dx0, ds]
124 }
125
126 #[inline]
127 fn jacobian_into(&self, x: &[f64], params: &[f64], out: &mut [f64]) {
128 let (a, x0, sigma) = (params[0], params[1], params[2]);
129 let sqrt2 = std::f64::consts::SQRT_2;
130 let u = (x[0] - x0) / (sigma * sqrt2);
131 let gauss = (-u * u).exp() / (sigma * (2.0 * std::f64::consts::PI).sqrt());
132 out[0] = 0.5 * libm::erfc(u);
133 out[1] = a * gauss;
134 out[2] = a * u * gauss * sqrt2;
135 }
136
137 fn param_names(&self) -> Vec<std::borrow::Cow<'static, str>> {
138 vec!["amplitude".into(), "center".into(), "sigma".into()]
139 }
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145 use approx::assert_relative_eq;
146
147 #[test]
150 fn arctan_at_center_is_half_amplitude() {
151 let v = ArctanStep.eval(&[0.0], &[2.0, 0.0, 1.0]);
152 assert_relative_eq!(v, 1.0, epsilon = 1e-12); }
154
155 #[test]
156 fn arctan_large_x_approaches_amplitude() {
157 let v = ArctanStep.eval(&[1e6], &[1.0, 0.0, 1.0]);
158 assert_relative_eq!(v, 1.0, epsilon = 1e-6);
159 }
160
161 #[test]
162 fn arctan_jacobian_shape() {
163 let j = ArctanStep.jacobian(&[0.5], &[1.0, 0.0, 1.0]);
164 assert_eq!(j.len(), 3);
165 }
166
167 #[test]
168 fn arctan_jacobian_finite_diff_check() {
169 let param_sets = [
173 [2.0, 0.5, 0.8], [1e-3, 0.0, 0.05], [5.0, -2.0, 3.0], ];
177 for p in param_sets {
178 let (c, sigma) = (p[1], p[2]);
179 for &mult in &[-3.0_f64, -1.0, -0.1, 0.0, 0.1, 1.0, 3.0] {
180 let x = [c + mult * sigma];
181 let j_analytical = ArctanStep.jacobian(&x, &p);
182 for i in 0..p.len() {
183 let h = 1e-6 * p[i].abs().max(1.0);
184 let (mut a, mut b) = (p, p);
185 a[i] += h;
186 b[i] -= h;
187 let fd = (ArctanStep.eval(&x, &a) - ArctanStep.eval(&x, &b)) / (2.0 * h);
188 assert_relative_eq!(j_analytical[i], fd, epsilon = 1e-7, max_relative = 1e-6);
189 }
190 }
191 }
192 }
193
194 #[test]
197 fn tanh_at_center_is_half_amplitude() {
198 let v = TanhStep.eval(&[0.0], &[2.0, 0.0, 1.0]);
199 assert_relative_eq!(v, 1.0, epsilon = 1e-12);
200 }
201
202 #[test]
203 fn tanh_large_x_approaches_amplitude() {
204 let v = TanhStep.eval(&[1e2], &[1.0, 0.0, 1.0]);
205 assert_relative_eq!(v, 1.0, epsilon = 1e-6);
206 }
207
208 #[test]
209 fn tanh_jacobian_finite_diff_check() {
210 let param_sets = [
214 [1.5, 1.0, 0.5], [1e-3, 0.0, 0.05], [5.0, -2.0, 3.0], ];
218 for p in param_sets {
219 let (c, sigma) = (p[1], p[2]);
220 for &mult in &[-3.0_f64, -1.0, -0.1, 0.0, 0.1, 1.0, 3.0] {
221 let x = [c + mult * sigma];
222 let j_analytical = TanhStep.jacobian(&x, &p);
223 for i in 0..p.len() {
224 let h = 1e-6 * p[i].abs().max(1.0);
225 let (mut a, mut b) = (p, p);
226 a[i] += h;
227 b[i] -= h;
228 let fd = (TanhStep.eval(&x, &a) - TanhStep.eval(&x, &b)) / (2.0 * h);
229 assert_relative_eq!(j_analytical[i], fd, epsilon = 1e-7, max_relative = 1e-6);
230 }
231 }
232 }
233 }
234
235 #[test]
238 fn erfc_at_center_is_half_amplitude() {
239 let v = ErfcStep.eval(&[0.0], &[2.0, 0.0, 1.0]);
241 assert_relative_eq!(v, 1.0, epsilon = 1e-12);
242 }
243
244 #[test]
245 fn erfc_large_neg_x_approaches_amplitude() {
246 let v = ErfcStep.eval(&[-1e6], &[1.0, 0.0, 1.0]);
247 assert_relative_eq!(v, 1.0, epsilon = 1e-6);
248 }
249
250 #[test]
251 fn erfc_jacobian_finite_diff_check() {
252 let param_sets = [
256 [1.0, 0.0, 1.0], [1e-3, 0.0, 0.05], [5.0, -2.0, 3.0], ];
260 for p in param_sets {
261 let (c, sigma) = (p[1], p[2]);
262 for &mult in &[-3.0_f64, -1.0, -0.1, 0.0, 0.1, 1.0, 3.0] {
263 let x = [c + mult * sigma];
264 let j_analytical = ErfcStep.jacobian(&x, &p);
265 for i in 0..p.len() {
266 let h = 1e-6 * p[i].abs().max(1.0);
267 let (mut a, mut b) = (p, p);
268 a[i] += h;
269 b[i] -= h;
270 let fd = (ErfcStep.eval(&x, &a) - ErfcStep.eval(&x, &b)) / (2.0 * h);
271 assert_relative_eq!(j_analytical[i], fd, epsilon = 1e-7, max_relative = 1e-6);
272 }
273 }
274 }
275 }
276}