liftof_lib/
sine_fitter.rsuse ndarray::Array1;
use rustfft::{FftPlanner, num_complex::Complex};
use std::f64::consts::PI;
use statrs::distribution::ChiSquared;
use argmin::prelude::*;
use argmin::solver::neldermead::NelderMead;
use statrs::distribution::ContinuousCDF;
struct SinFunc<'a> {
tt: &'a Array1<f64>,
yy: &'a Array1<f64>,
}
impl ArgminOp for SinFunc<'_> {
type Param = Vec<f64>;
type Output = f64;
type Hessian = ();
type Jacobian = ();
type Float = f64;
fn apply(&self, p: &Self::Param) -> Result<Self::Output, Error> {
#[allow(non_snake_case)]
let (A, w, ph, c) = (p[0], p[1], p[2], p[3]);
let sinfunc = |t: f64| A * (w * t + ph).sin() + c;
let residuals: f64 = self.tt.iter().zip(self.yy.iter())
.map(|(&t, &y)| (y - sinfunc(t)).powi(2))
.sum();
Ok(residuals)
}
}
pub fn fit_sine(nanoseconds: &Array1<f64>, volts: &Array1<f64>) -> Result<(f64, f64, f64, f64, f64, f64), Box<dyn std::error::Error>> {
let tt = nanoseconds;
let yy = volts;
let mut planner = FftPlanner::new();
let fft = planner.plan_fft_forward(tt.len());
let mut signal: Vec<Complex<f64>> = yy.iter().map(|&y| Complex::new(y, 0.0)).collect();
fft.process(&mut signal);
let mut magnitudes: Vec<f64> = signal.iter().map(|c| c.norm()).collect();
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;
let guess_amp = yy.std(0.0) * 2f64.sqrt();
let guess_offset = yy.mean().unwrap();
let guess = vec![guess_amp, 2.0 * PI * guess_freq, 0.0, guess_offset];
let problem = SinFunc { tt, yy };
let solver = NelderMead::new();
let res = Executor::new(problem, solver, guess)
.max_iters(1000)
.run()?;
let result = &res.state().best_param;
#[allow(non_snake_case)]
let (A, w, p, c) = (result[0], result[1], result[2], result[3]);
let phase_multiple_pi = p / PI;
let fitfunc = |t: f64| -> f64 {
A * (w * t + p).sin() + c
};
let residuals: Vec<f64> = tt.iter().zip(yy.iter()).map(|(&t, &y)| y - fitfunc(t)).collect();
let _ss_res = residuals.iter().map(|&r| r.powi(2)).sum::<f64>();
let _ss_tot = yy.iter().map(|&y| (y - yy.mean().unwrap()).powi(2)).sum::<f64>();
let expected_values: Vec<f64> = tt.iter().map(|&t| fitfunc(t)).collect();
let observed_values = yy.to_vec();
let chi_squared_stat: f64 = observed_values.iter().zip(expected_values.iter())
.map(|(&o, &e)| (o - e).powi(2) / e).sum();
let df = tt.len() as f64 - result.len() as f64;
let reduced_chi_squared = chi_squared_stat / df;
let chi_squared = ChiSquared::new(df).unwrap();
let _p_value = 1.0 - chi_squared.cdf(chi_squared_stat);
let f = w / (2.0 * PI);
let _period = 1.0 / f;
Ok((A, f, p, phase_multiple_pi, c, reduced_chi_squared))
}