allan_tools/
lib.rs

1//! Allan-tools 
2//! 
3//! This lib is a portage / equivalent package to
4//! Allantools (python) library to compute Allan & related
5//! statistics over some data.
6//!
7//! This lib only implements basic (biased) 
8//! error bar estimates at the moment.
9//!
10//! url: <https://github.com/gwbres/allan-tools>  
11//! url: <https://github.com/aewallin/allantools>
12
13pub mod tau;
14pub mod noise;
15pub mod utils;
16
17use thiserror::Error;
18
19/// describes error related to deviation computations
20#[derive(Error, Debug)]
21pub enum Error {
22    #[error("`tau` axis error")]
23    TauAxisEror(#[from] tau::Error), 
24    #[error("non feasible deviation - missing some more samples")]
25    NotEnoughSamplesError, 
26}
27
28#[derive(Clone, Copy)]
29/// describes all known computations
30pub enum Deviation {
31    /// `allan` deviation
32    Allan,    
33    /// `modified allan` deviation 
34    Modified, 
35    /// `time` deviation
36    Time,     
37    /// `hadamard` deviation
38    Hadamard,
39}
40
41/// Computes desired deviation over input data 
42/// for desired tau values.  
43/// data: input vector   
44/// taus: desired `tau` offsets (s)   
45/// sample_rate: sampling rate (Hz)   
46/// is_fractional: true if input vector is made of fractional (n.a) data
47/// overlapping: true if using overlapping interval (increase confidence / errbar narrows down faster)
48/// returns: (dev, err) : deviation & statistical error bars for each
49/// feasible `tau`
50pub fn deviation (data: &Vec<f64>, taus: &Vec<f64>, calc: Deviation, sample_rate: f64, is_fractional: bool, overlapping: bool) 
51        -> Result<(Vec<f64>,Vec<f64>), Error> 
52{
53    tau::tau_sanity_checks(&taus)?;
54    let data = match is_fractional {
55        true => utils::fractional_integral(data, 1.0_f64),
56        false => data.clone(),
57    };
58
59    let mut devs: Vec<f64> = Vec::new();
60    let mut errs: Vec<f64> = Vec::new();
61
62    for i in 0..taus.len() {
63        match calc {
64            Deviation::Allan => {
65                if let Ok((dev, err)) = calc_adev(&data, taus[i], sample_rate, overlapping) {
66                    devs.push(dev);
67                    errs.push(err)
68                } else {
69                    break
70                }
71            },
72            Deviation::Modified => {
73                if let Ok((dev, err)) = calc_mdev(&data, taus[i], sample_rate) {
74                    devs.push(dev);
75                    errs.push(err)
76                } else {
77                    break
78                }
79            },
80            Deviation::Time => {
81                if let Ok((dev, err)) = calc_tdev(&data, taus[i], sample_rate) {
82                    devs.push(dev);
83                    errs.push(err)
84                } else {
85                    break
86                }
87            },
88            Deviation::Hadamard => {
89                if let Ok((dev, err)) = calc_hdev(&data, taus[i], sample_rate, overlapping) {
90                    devs.push(dev);
91                    errs.push(err)
92                } else {
93                    break
94                }
95            },
96        }
97    }
98    Ok((devs, errs))
99}
100
101/// Computes desired variance over input data 
102/// for desired tau values.  
103/// data: input vector   
104/// taus: desired `tau` offsets (s)   
105/// sample_rate: sampling rate (Hz)   
106/// is_fractional: true if input vector is made of fractional (n.a) data
107/// overlapping: true if using overlapping interval (increase confidence / errbar narrows down faster)
108/// returns: (var, err) : variance & statistical error bars for each
109/// feasible `tau`
110pub fn variance (data: &Vec<f64>, taus: &Vec<f64>, dev: Deviation, sample_rate: f64, is_fractional: bool, overlapping: bool) 
111        -> Result<(Vec<f64>,Vec<f64>), Error> 
112{
113    let (mut var, err) = deviation(&data, &taus, dev, sample_rate, is_fractional, overlapping)?;
114    for i in 0..var.len() {
115        var[i] *= var[i]
116    }
117    Ok((var, err))
118}
119/// Computes Allan deviation
120/// @ given tau on input data.   
121/// tau: offset (s)    
122/// sample_rate: (Hz)   
123/// overlapping: true for overlapped deviation
124fn calc_adev (data: &Vec<f64>, tau: f64, sample_rate: f64, overlapping: bool) -> Result<(f64,f64), Error> {
125    let tau_u: usize = tau as usize;
126    let stride: usize = match overlapping {
127        true => 1,
128        false => tau as usize 
129    };
130
131    if tau_u > (data.len()-1) / 2 {
132        return Err(Error::NotEnoughSamplesError)
133    }
134
135    let mut i: usize = 0;
136    let mut n = 0.0_f64;
137    let mut sum = 0.0_f64;
138
139    while i < data.len() -2*tau_u {
140        sum += (data[i+2*tau_u] - 2.0_f64*data[i+tau_u] + data[i]).powf(2.0_f64);
141        n += 1.0_f64;
142        i += stride
143    }
144    
145    let mut dev = sum /2.0;
146    dev = (dev / n).powf(0.5_f64) / tau * sample_rate; 
147    Ok((dev, dev/(n.powf(0.5_f64))))
148}
149
150/// Computes modified Allan deviation
151/// @ given tau on input data.   
152/// sample_rate: sampling rate (Hz).   
153/// Mdev is always computed in overlapping fashion
154fn calc_mdev (data: &Vec<f64>, tau: f64, sample_rate: f64) -> Result<(f64,f64), Error> {
155    let tau_u: usize = tau as usize;
156    if tau_u > (data.len()-1) / 2 {
157        return Err(Error::NotEnoughSamplesError)
158    }
159
160    let mut i: usize = 0;
161    let mut n = 0.0_f64;
162    let (mut v, mut sum) = (0.0_f64, 0.0_f64);
163
164    while (i < data.len() -2*tau_u) && (i < tau_u) {
165        v += data[i+2*tau_u] - 2.0_f64*data[i+tau_u] + data[i];
166        i += 1
167    }
168    sum += v.powf(2.0_f64);
169    n += 1.0_f64;
170
171    i = 0;
172    while i < data.len() -3*tau_u {
173        v += data[i+3*tau_u] - 3.0_f64*data[i+2*tau_u] + 3.0_f64*data[i+tau_u] - data[i];
174        sum += v.powf(2.0_f64);
175        n += 1.0_f64;
176        i += 1 
177    }
178    let mut dev = sum /2.0 /tau /tau;
179    dev = (dev / n).powf(0.5_f64) / tau * sample_rate;
180    Ok((dev, dev/(n.powf(0.5_f64))))
181}
182
183/// Computes `time` deviation at desired `tau` offset (s).   
184/// sample_rate: sampling rate (Hz)
185fn calc_tdev (data: &Vec<f64>, tau: f64, sample_rate: f64) -> Result<(f64,f64), Error> {
186    let (mdev, mderr) = calc_mdev(data, tau, sample_rate)?;
187    Ok((
188        mdev * tau / (3.0_f64).powf(0.5_f64),
189        mderr // mderr / ns.powf(0.5_f64)
190    ))
191}
192
193/// Computes `hdev`
194fn calc_hdev (data: &Vec<f64>, tau: f64, sample_rate: f64, overlapping: bool) -> Result<(f64, f64), Error> {
195    let tau_u = tau as usize;
196    let stride: usize = match overlapping {
197        true => 1,
198        false => tau as usize 
199    };
200
201    if tau_u > (data.len()-1) / 2 {
202        return Err(Error::NotEnoughSamplesError)
203    }
204
205    let mut i: usize = 0;
206    let mut n = 0.0_f64;
207    let mut sum = 0.0_f64;
208
209    while i < data.len() -3*tau_u {
210        sum += (data[i+3*tau_u] - 3.0_f64*data[i+2*tau_u] + 3.0_f64*data[i+tau_u] - data[i]).powf(2.0_f64);
211        n += 1.0_f64;
212        i += stride
213    }
214    sum /= 6.0_f64;
215    let dev = (sum / n).powf(0.5_f64) / tau * sample_rate; 
216    Ok((dev, dev/(n.powf(0.5_f64))))
217}
218
219/// Computes desired statistics in `Three Cornerned Hat` fashion.   
220/// data_ab: A against B data   
221/// data_bc: B against C data   
222/// data_ca: C against A data   
223/// taus: desired tau offsets
224/// sample_rate: sampling rate (Hz)   
225/// is_fractional: true if measured data are fractional errors   
226/// overlapping: true if computing in overlapped fashion    
227/// deviation: which deviation to compute    
228/// returns  ((dev_a,err_a),(dev_b,err_b),(dev_c,err_c))   
229/// where dev_a: deviation clock(a) and related error bar for all feasible tau offsets,
230///       same thing for clock(b) and (c) 
231pub fn three_cornered_hat(data_ab: &Vec<f64>, data_bc: &Vec<f64>, data_ca: &Vec<f64>,
232        taus: &Vec<f64>, sample_rate: f64, is_fractional: bool, 
233            overlapping: bool, calc: Deviation) 
234                -> Result<((Vec<f64>,Vec<f64>), (Vec<f64>,Vec<f64>), (Vec<f64>,Vec<f64>)), Error>
235{
236    let (var_ab, err_ab) = variance(&data_ab, &taus, calc, sample_rate, is_fractional, overlapping)?;
237    let (var_bc, err_bc) = variance(&data_bc, &taus, calc, sample_rate, is_fractional, overlapping)?;
238    let (var_ca, err_ca) = variance(&data_ca, &taus, calc, sample_rate, is_fractional, overlapping)?;
239    let (mut a, mut b, mut c): (Vec<f64>,Vec<f64>,Vec<f64>) = 
240        (Vec::with_capacity(var_ab.len()),Vec::with_capacity(var_ab.len()),Vec::with_capacity(var_ab.len()));
241    for i in 0..var_ab.len() {
242        a.push((0.5 * (var_ab[i] - var_bc[i] + var_ca[i])).powf(0.5_f64));
243        b.push((0.5 * (var_bc[i] - var_ca[i] + var_ab[i])).powf(0.5_f64));
244        c.push((0.5 * (var_ca[i] - var_ab[i] + var_bc[i])).powf(0.5_f64));
245    }
246    Ok(((a, err_ab),(b, err_bc),(c, err_ca)))
247}
248
249/// Structure optimized for `real time` / `rolling` computation,   
250/// refer to dedicated documentation
251#[derive(Debug)]
252pub struct RealTime {
253    tau0: u64,
254    buffer: Vec<f64>,
255    taus: Vec<usize>,
256    devs: Vec<f64>,
257}
258
259impl RealTime {
260    /// Builds a new `real time` core.   
261    /// sample_rate: sampling rate (Hz)
262    pub fn new (sample_rate: u64) -> RealTime {
263        RealTime {
264            tau0: sample_rate,
265            buffer: Vec::new(),
266            taus: vec![],
267            devs: Vec::new(),
268        }
269    }
270
271    /// Pushes 1 symbol into the core
272    pub fn push (&mut self, sample: f64) { 
273        self.buffer.push(sample); 
274        if self.new_tau() {
275            self.taus.push(
276                self.taus[self.taus.len()-1] * 2);
277        }
278    }
279    
280    /// Pushes n symbols into the core
281    pub fn push_n (&mut self, samples: &Vec<f64>) { 
282        for i in 0..samples.len() {
283            self.buffer.push(samples[i]) 
284        }
285        if self.new_tau() {
286            self.taus.push(
287                self.taus[self.taus.len()-1] * 2);
288        }
289    }
290
291    /// Returns (taus, devs, errs) triplet:   
292    /// taus: currently evaluated time offsets (s)   
293    /// devs: related adev estimates (n.a)   
294    /// errs: error bar for each adev estimate
295    pub fn get (&self) -> (&Vec<usize>,&Vec<f64>,Vec<f64>) {
296        let errs: Vec<f64> = Vec::with_capacity(self.devs.len());
297        (&self.taus, &self.devs, errs)
298    }
299
300    fn new_tau (&self) -> bool {
301        self.buffer.len() >= 2*self.taus[self.taus.len()-1]+1
302    }
303
304    /// Processes internal data & evaluates
305    /// new deviation, if possible
306    fn process (&self) -> Option<(f64, f64)> {
307        None
308    }
309
310    fn calc_estimator (&self, tau: f64) -> f64 {
311        let tau_u = tau as usize;
312        let mut sum = 0.0_f64;
313        for i in 0..self.buffer.len()-2*tau_u {
314            sum += (self.buffer[i] - 2.0_f64*self.buffer[i+tau_u] - self.buffer[i+2*tau_u]).powf(2.0_f64)
315        }
316        sum
317    }
318
319    /// Resets internal core
320    pub fn reset (&mut self) {
321        self.buffer.clear();
322        self.taus.clear();
323        self.devs.clear()
324    }
325
326}
327
328#[cfg(test)]
329pub mod plotutils;
330mod tests {
331    use super::*;
332	use std::str::FromStr;
333    #[test]
334    fn test_deviation() {
335        let N: usize = 10000;
336        let noises: Vec<&str> = vec![
337            "whitepm",
338            "flickpm",
339            "whitefm",
340            "pinkfm",
341        ];
342        let axes: Vec<tau::TauAxis> = vec![
343            tau::TauAxis::Octave,
344            tau::TauAxis::Decade,
345            tau::TauAxis::All,
346        ];
347        let calcs: Vec<Deviation> = vec![
348            Deviation::Allan,
349            Deviation::Modified,
350            Deviation::Hadamard,
351            Deviation::Time,
352        ];
353        // test against pure noise
354        for noise in noises {
355            let mut input: Vec<f64>;
356            if noise.eq("white") {
357                input = noise::white_noise(-10.0,1.0, N)
358            } else {
359                input = noise::pink_noise(-10.0,1.0, N)
360            };
361            let is_fract = noise.contains("fm");
362
363            for ax in &axes {
364                let taus = tau::tau_generator(*ax, 1.0, 1000.0); 
365                for overlapping in vec![false, true] {
366                    for calc in &calcs {
367                        let (dev, err) = deviation(
368                            &input,
369                            &taus,
370                            *calc,
371                            1.0_f64,
372                            is_fract,
373                            overlapping)
374                                .unwrap();
375                        let mut fp = String::from("tests/");
376                        fp.push_str(noise);
377                        fp.push_str("-");
378                        if overlapping {
379                            fp.push_str("o")
380                        }
381                        match calc {
382                            Deviation::Allan => fp.push_str("adev"),
383                            Deviation::Modified => fp.push_str("mdev"),
384                            Deviation::Hadamard => fp.push_str("hdev"),
385                            Deviation::Time => fp.push_str("tdev"),
386                        }
387                        fp.push_str(".png");
388                        plotutils::plot1d_err(
389                            vec![(&taus, &dev, &err)],
390                                "test deviation",
391                                vec![&fp],
392                                &fp,
393                        );
394                    }
395                }
396            }
397        }
398    }
399    /*
400    #[test]
401    fn test_against_models() {
402       let names: Vec<&str> = vec!["adev","mdev","tdev"];
403        let (mut xm_adev, mut ym_adev): (Vec<f64>,Vec<f64>) = (Vec::new(),Vec::new());
404        let (mut xm_mdev, mut ym_mdev): (Vec<f64>,Vec<f64>) = (Vec::new(),Vec::new());
405        let (mut xm_tdev, mut ym_tdev): (Vec<f64>,Vec<f64>) = (Vec::new(),Vec::new());
406        // read models
407        for i in 0..names.len() {
408            let content = std::fs::read_to_string(
409                std::path::PathBuf::from(
410                    env!("CARGO_MANIFEST_DIR").to_owned()
411                    +"/tests/holdover-" +names[i] +".csv"))
412                .unwrap();
413            let mut lines = content.lines();
414            let mut line = lines.next()
415                .unwrap();
416            loop {
417                let c = line.find(",").unwrap();
418                let x = f64::from_str(line.split_at(c).0.trim()).unwrap();
419                let y = f64::from_str(line.split_at(c+1).1.trim()).unwrap();
420
421                if names[i].eq("adev") {
422                    xm_adev.push(x);
423                    ym_adev.push(y)
424                } else if names[i].eq("mdev") {
425                    xm_mdev.push(x);
426                    ym_mdev.push(y);
427                } else {
428                    xm_tdev.push(x);
429                    ym_tdev.push(x)
430                }
431
432                if let Some(l) = lines.next() {
433                    line = l;
434                } else {
435                    break
436                }
437            }
438        }
439        // read raw
440        let mut raw_data: Vec<f64> = Vec::new();
441        let mut frac_raw_data: Vec<f64> = Vec::new();
442        let mut x_adev: Vec<f64> = Vec::new();
443        let mut y_adev: Vec<f64> = Vec::new();
444        let names: Vec<&str> = vec!["holdover"];
445        for i in 0..names.len() {
446            let content = std::fs::read_to_string(
447                std::path::PathBuf::from(
448                    env!("CARGO_MANIFEST_DIR").to_owned()
449                    +"/tests/" +names[i] +"-phase.csv"))
450                .unwrap();
451            let mut lines = content.lines();
452            let mut line = lines.next()
453                .unwrap();
454            loop {
455                let raw = f64::from_str(line.trim()).unwrap();
456                raw_data.push(raw);
457                if let Some(l) = lines.next() {
458                    line = l;
459                } else {
460                    break
461                }
462            }
463        }
464        let taus = tau::tau_generator(tau::TauAxis::Decade, 1.0_f64, 1000000.0_f64);
465        let (adev, err) = deviation(&raw_data, &taus, Deviation::Allan, 0.1_f64, false, true).unwrap();
466        let (mdev, err) = deviation(&raw_data, &taus, Deviation::Modified, 0.1_f64, false, true).unwrap();
467
468        let taus = tau::tau_generator(tau::TauAxis::Decade, 0.1_f64, 100000.0_f64);
469        plotutils::plotmodel_tb(
470            // models
471            &xm_adev, 
472            &ym_adev,
473            &xm_mdev,
474            &ym_mdev,
475            // adev from phase
476            &taus, &adev, &mdev,
477            )
478    }*/
479    #[test]
480    fn test_three_cornered_hat() {
481        let pm_pink  = utils::diff(&noise::pink_noise(-10.0,1.0,10000),None);
482        let fm_white = noise::white_noise(-10.0,1.0,10000);
483        let fm_pink = noise::pink_noise(-10.0,1.0,10000);
484        let taus = tau::tau_generator(tau::TauAxis::Octave, 1.0_f64, 10000.0);
485
486        let ((dev_a, err_a),(dev_b,err_b),(dev_c,err_c)) =
487            three_cornered_hat(&pm_pink, &fm_white, &fm_pink,
488                &taus, 1.0, false, true, Deviation::Allan).unwrap();
489
490        let (adev_ab, err_ab) = deviation(&pm_pink , &taus, Deviation::Allan, 1.0_f64, false, true).unwrap();
491        let (adev_bc, err_bc) = deviation(&fm_white, &taus, Deviation::Allan, 1.0_f64, false, true).unwrap();
492        let (adev_ca, err_ca) = deviation(&fm_pink , &taus, Deviation::Allan, 1.0_f64, false, true).unwrap();
493
494        plotutils::plot3corner(
495            &taus,
496            (&adev_ab, &err_ab),
497            (&dev_a,   &err_a),
498            (&adev_bc, &err_bc),
499            (&dev_b,   &err_b),
500            (&adev_ca, &err_ca),
501            (&dev_c,   &err_c),
502        );
503    }
504/*
505    #[test]
506    fn test_realtime_core() {
507        let mut rt = RealTime::new(1);
508        let noise = noise::white_noise(1.0,1.0,100);
509        for i in 0..noise.len() {
510            rt.push(noise[i]);
511            println!("{:#?}", rt)
512        }
513    }*/
514}