stabilizer_stream/var.rs
1use core::f32::consts::PI;
2use derive_builder::Builder;
3
4#[derive(Debug, Builder, Clone, Copy, PartialEq)]
5pub struct Var {
6 /// exponent of `pi*f*tau` in the variance frequency response (-2 for AVAR, -4 for MVAR)
7 #[builder(default = "-2")]
8 x_exp: i32,
9 /// Exponent of `sin(pi*f*tau)` in the variance frequency response (4 for AVAR, 6 for MVAR)
10 #[builder(default = "4")]
11 sinx_exp: i32,
12 /// Response clip (infinite for AVAR and MVAR, 1 for the main lobe: FVAR)
13 #[builder(default = "f32::MAX")]
14 clip: f32,
15 /// skip the first `dc_cut` bins to suppress DC window leakage
16 #[builder(default = "2")]
17 dc_cut: usize,
18}
19
20impl Var {
21 /// Compute statistical variance estimator (AVAR, MVAR, FVAR...) from Phase PSD
22 ///
23 /// # Args
24 /// * `phase_psd`: Phase noise PSD vector from Nyquist down
25 /// * `frequencies`: PSD bin frequencies, Nyquist first
26 pub fn eval(&self, phase_psd: &[f32], frequencies: &[f32], tau: f32) -> f32 {
27 phase_psd
28 .iter()
29 .rev()
30 .zip(
31 frequencies
32 .iter()
33 .rev()
34 .take_while(|&f| f * tau <= self.clip),
35 )
36 .skip(self.dc_cut)
37 // force DC bin to 0
38 .fold((0.0, (0.0, 0.0)), |(accu, (a0, f0)), (&sp, &f)| {
39 // frequency PSD
40 let sy = sp * f * f;
41 let pft = PI * (f * tau);
42 // Allan variance transfer function (rectangular window: sinc**2 and differencing: 2*sin**2)
43 // Cancel the 2 here with the 0.5 in the trapezoidal rule
44 let hahd = pft.sin().powi(self.sinx_exp) * pft.powi(self.x_exp);
45 let a = sy * hahd;
46 // trapezoidal integration
47 (accu + (a + a0) * (f - f0), (a, f))
48 })
49 .0
50 }
51}
52
53#[cfg(test)]
54mod test {
55 use super::*;
56
57 #[test]
58 fn basic() {
59 let mut p = [1000.0, 100.0, 1.2, 3.4, 5.6];
60 let mut f = [0.0, 1.0, 3.0, 6.0, 9.0];
61 p.reverse();
62 f.reverse();
63 let var = VarBuilder::default().build().unwrap();
64 let v = var.eval(&p, &f, 2.7);
65 println!("{}", v);
66 assert!((0.13478442 - v).abs() < 1e-6);
67 }
68}