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}