liftof_lib/
sine_fitter.rs1use ndarray::Array1;
2use rustfft::{FftPlanner, num_complex::Complex};
5use std::f64::consts::PI;
6use statrs::distribution::ChiSquared;
7use argmin::prelude::*;
10use argmin::solver::neldermead::NelderMead;
14use statrs::distribution::ContinuousCDF;
15
16struct SinFunc<'a> {
17 tt: &'a Array1<f64>,
18 yy: &'a Array1<f64>,
19}
20
21impl ArgminOp for SinFunc<'_> {
22 type Param = Vec<f64>;
23 type Output = f64;
24 type Hessian = ();
25 type Jacobian = ();
26 type Float = f64;
27
28 fn apply(&self, p: &Self::Param) -> Result<Self::Output, Error> {
29 #[allow(non_snake_case)]
30 let (A, w, ph, c) = (p[0], p[1], p[2], p[3]);
31 let sinfunc = |t: f64| A * (w * t + ph).sin() + c;
32 let residuals: f64 = self.tt.iter().zip(self.yy.iter())
33 .map(|(&t, &y)| (y - sinfunc(t)).powi(2))
34 .sum();
35 Ok(residuals)
36 }
37}
38
39pub fn fit_sine(nanoseconds: &Array1<f64>, volts: &Array1<f64>) -> Result<(f64, f64, f64, f64, f64, f64), Box<dyn std::error::Error>> {
40 let tt = nanoseconds;
41 let yy = volts;
42
43 let mut planner = FftPlanner::new();
45 let fft = planner.plan_fft_forward(tt.len());
46 let mut signal: Vec<Complex<f64>> = yy.iter().map(|&y| Complex::new(y, 0.0)).collect();
47 fft.process(&mut signal);
48
49 let mut magnitudes: Vec<f64> = signal.iter().map(|c| c.norm()).collect();
50 magnitudes[0] = 0.0; let guess_freq = (magnitudes.iter().cloned().enumerate().max_by(|a, b| a.1.partial_cmp(&b.1).unwrap()).unwrap().0 as f64) / tt.len() as f64;
53 let guess_amp = yy.std(0.0) * 2f64.sqrt();
54 let guess_offset = yy.mean().unwrap();
55 let guess = vec![guess_amp, 2.0 * PI * guess_freq, 0.0, guess_offset];
56
57 let problem = SinFunc { tt, yy };
59 let solver = NelderMead::new();
60 let res = Executor::new(problem, solver, guess)
61 .max_iters(1000)
62 .run()?;
63
64 let result = &res.state().best_param;
65 #[allow(non_snake_case)]
66 let (A, w, p, c) = (result[0], result[1], result[2], result[3]);
67 let phase_multiple_pi = p / PI;
68
69 let fitfunc = |t: f64| -> f64 {
70 A * (w * t + p).sin() + c
71 };
72
73 let residuals: Vec<f64> = tt.iter().zip(yy.iter()).map(|(&t, &y)| y - fitfunc(t)).collect();
74 let _ss_res = residuals.iter().map(|&r| r.powi(2)).sum::<f64>();
75 let _ss_tot = yy.iter().map(|&y| (y - yy.mean().unwrap()).powi(2)).sum::<f64>();
76 let expected_values: Vec<f64> = tt.iter().map(|&t| fitfunc(t)).collect();
80 let observed_values = yy.to_vec();
81 let chi_squared_stat: f64 = observed_values.iter().zip(expected_values.iter())
82 .map(|(&o, &e)| (o - e).powi(2) / e).sum();
83
84 let df = tt.len() as f64 - result.len() as f64;
85 let reduced_chi_squared = chi_squared_stat / df;
86
87 let chi_squared = ChiSquared::new(df).unwrap();
88 let _p_value = 1.0 - chi_squared.cdf(chi_squared_stat);
89
90 let f = w / (2.0 * PI);
91 let _period = 1.0 / f;
92
93
94 Ok((A, f, p, phase_multiple_pi, c, reduced_chi_squared))
95 }