1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
use core::f32::consts::PI;
use derive_builder::Builder;

#[derive(Debug, Builder, Clone, Copy, PartialEq)]
pub struct Var {
    /// exponent of `pi*f*tau` in the variance frequency response (-2 for AVAR, -4 for MVAR)
    #[builder(default = "-2")]
    x_exp: i32,
    /// Exponent of `sin(pi*f*tau)` in the variance frequency response (4 for AVAR, 6 for MVAR)
    #[builder(default = "4")]
    sinx_exp: i32,
    /// Response clip (infinite for AVAR and MVAR, 1 for the main lobe: FVAR)
    #[builder(default = "f32::MAX")]
    clip: f32,
    /// skip the first `dc_cut` bins to suppress DC window leakage
    #[builder(default = "2")]
    dc_cut: usize,
}

impl Var {
    /// Compute statistical variance estimator (AVAR, MVAR, FVAR...) from Phase PSD
    ///
    /// # Args
    /// * `phase_psd`: Phase noise PSD vector from Nyquist down
    /// * `frequencies`: PSD bin frequencies, Nyquist first
    pub fn eval(&self, phase_psd: &[f32], frequencies: &[f32], tau: f32) -> f32 {
        phase_psd
            .iter()
            .rev()
            .zip(
                frequencies
                    .iter()
                    .rev()
                    .take_while(|&f| f * tau <= self.clip),
            )
            .skip(self.dc_cut)
            // force DC bin to 0
            .fold((0.0, (0.0, 0.0)), |(accu, (a0, f0)), (&sp, &f)| {
                // frequency PSD
                let sy = sp * f * f;
                let pft = PI * (f * tau);
                // Allan variance transfer function (rectangular window: sinc**2 and differencing: 2*sin**2)
                // Cancel the 2 here with the 0.5 in the trapezoidal rule
                let hahd = pft.sin().powi(self.sinx_exp) * pft.powi(self.x_exp);
                let a = sy * hahd;
                // trapezoidal integration
                (accu + (a + a0) * (f - f0), (a, f))
            })
            .0
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn basic() {
        let mut p = [1000.0, 100.0, 1.2, 3.4, 5.6];
        let mut f = [0.0, 1.0, 3.0, 6.0, 9.0];
        p.reverse();
        f.reverse();
        let var = VarBuilder::default().build().unwrap();
        let v = var.eval(&p, &f, 2.7);
        println!("{}", v);
        assert!((0.13478442 - v).abs() < 1e-6);
    }
}