#[cfg(feature = "simd")]
use wide::f64x4;
trait VarianceMethod: Clone {
fn min_samples(&self, tau: usize) -> usize;
fn calculate_squared_diff(&self, buffer: &[f64], newest_idx: usize, tau: usize, buffer_len: usize) -> f64 {
let indices = self.get_indices(newest_idx, tau, buffer_len);
let diff = self.calculate_diff(buffer, indices.0, indices.1, indices.2, indices.3);
diff * diff
}
fn calculate_old_squared_diff_with_substitution(
&self,
buffer: &[f64],
newest_idx: usize,
tau: usize,
buffer_len: usize,
old_head: usize,
old_value: f64
) -> f64 {
let indices = self.get_indices(newest_idx, tau, buffer_len);
let uses_old_head = indices.0 == old_head || indices.1 == old_head
|| indices.2 == old_head || indices.3 == Some(old_head);
if uses_old_head {
let val0 = if indices.0 == old_head { old_value } else { buffer[indices.0] };
let val1 = if indices.1 == old_head { old_value } else { buffer[indices.1] };
let val2 = if indices.2 == old_head { old_value } else { buffer[indices.2] };
let diff = if let Some(idx3) = indices.3 {
let val3 = if idx3 == old_head { old_value } else { buffer[idx3] };
let temp_buf = [val0, val1, val2, val3];
self.calculate_diff(&temp_buf, 0, 1, 2, Some(3))
} else {
let temp_buf = [val0, val1, val2];
self.calculate_diff(&temp_buf, 0, 1, 2, None)
};
diff * diff
} else {
self.calculate_squared_diff(buffer, newest_idx, tau, buffer_len)
}
}
fn get_indices(&self, newest_idx: usize, tau: usize, buffer_len: usize) -> (usize, usize, usize, Option<usize>) {
let min_samples = self.min_samples(tau);
if min_samples == 2 * tau + 1 {
(
(newest_idx + buffer_len - 2 * tau) % buffer_len,
(newest_idx + buffer_len - tau) % buffer_len,
newest_idx,
None
)
} else {
(
(newest_idx + buffer_len - 3 * tau) % buffer_len,
(newest_idx + buffer_len - 2 * tau) % buffer_len,
(newest_idx + buffer_len - tau) % buffer_len,
Some(newest_idx)
)
}
}
fn calculate_diff(&self, buffer: &[f64], idx0: usize, idx1: usize, idx2: usize, idx3: Option<usize>) -> f64;
fn divisor(&self, tau: u32, count: u64) -> f64;
}
#[derive(Clone)]
struct AllanMethod;
impl VarianceMethod for AllanMethod {
fn min_samples(&self, tau: usize) -> usize {
2 * tau + 1
}
#[inline(always)]
fn calculate_diff(&self, buffer: &[f64], idx0: usize, idx1: usize, idx2: usize, _idx3: Option<usize>) -> f64 {
if buffer[idx0].is_nan() || buffer[idx1].is_nan() || buffer[idx2].is_nan() {
return f64::NAN;
}
buffer[idx2] - 2.0 * buffer[idx1] + buffer[idx0]
}
fn divisor(&self, tau: u32, count: u64) -> f64 {
2.0 * count as f64 * (tau as f64) * (tau as f64)
}
}
#[derive(Clone)]
struct HadamardMethod;
impl VarianceMethod for HadamardMethod {
fn min_samples(&self, tau: usize) -> usize {
3 * tau + 1
}
#[inline(always)]
fn calculate_diff(&self, buffer: &[f64], idx0: usize, idx1: usize, idx2: usize, idx3: Option<usize>) -> f64 {
let idx3 = idx3.expect("Hadamard requires 4 points");
if buffer[idx0].is_nan() || buffer[idx1].is_nan() || buffer[idx2].is_nan() || buffer[idx3].is_nan() {
return f64::NAN;
}
buffer[idx3] - 3.0 * buffer[idx2] + 3.0 * buffer[idx1] - buffer[idx0]
}
fn divisor(&self, tau: u32, count: u64) -> f64 {
6.0 * count as f64 * (tau as f64) * (tau as f64)
}
}
#[derive(Clone)]
struct ModifiedAllanMethod;
impl ModifiedAllanMethod {
#[cfg(not(feature = "simd"))]
fn average(&self, buffer: &[f64], start_idx: usize, tau: usize, buffer_len: usize) -> f64 {
let mut sum = 0.0;
for i in 0..tau {
let idx = (start_idx + i) % buffer_len;
let val = buffer[idx];
if val.is_nan() {
return f64::NAN;
}
sum += val;
}
sum / tau as f64
}
#[cfg(feature = "simd")]
fn average(&self, buffer: &[f64], start_idx: usize, tau: usize, buffer_len: usize) -> f64 {
if start_idx + tau <= buffer_len {
let slice = &buffer[start_idx..start_idx + tau];
for &val in slice {
if val.is_nan() {
return f64::NAN;
}
}
let mut sum_vec = f64x4::splat(0.0);
let chunks = slice.chunks_exact(4);
let remainder = chunks.remainder();
for chunk in chunks {
let vals = f64x4::new([chunk[0], chunk[1], chunk[2], chunk[3]]);
sum_vec += vals;
}
let mut sum = sum_vec.reduce_add();
for &val in remainder {
sum += val;
}
sum / tau as f64
} else {
let mut sum = 0.0;
for i in 0..tau {
let idx = (start_idx + i) % buffer_len;
let val = buffer[idx];
if val.is_nan() {
return f64::NAN;
}
sum += val;
}
sum / tau as f64
}
}
}
impl VarianceMethod for ModifiedAllanMethod {
fn min_samples(&self, tau: usize) -> usize {
3 * tau }
fn calculate_squared_diff(&self, buffer: &[f64], newest_idx: usize, tau: usize, buffer_len: usize) -> f64 {
let start2 = (newest_idx + buffer_len + 1 - tau) % buffer_len;
let start1 = (start2 + buffer_len - tau) % buffer_len;
let start0 = (start1 + buffer_len - tau) % buffer_len;
let avg0 = self.average(buffer, start0, tau, buffer_len);
let avg1 = self.average(buffer, start1, tau, buffer_len);
let avg2 = self.average(buffer, start2, tau, buffer_len);
let diff = avg2 - 2.0 * avg1 + avg0;
diff * diff
}
fn calculate_old_squared_diff_with_substitution(
&self,
buffer: &[f64],
newest_idx: usize,
tau: usize,
buffer_len: usize,
old_head: usize,
old_value: f64
) -> f64 {
let start2 = (newest_idx + buffer_len + 1 - tau) % buffer_len;
let start1 = (start2 + buffer_len - tau) % buffer_len;
let start0 = (start1 + buffer_len - tau) % buffer_len;
let average_with_sub = |start_idx: usize| {
let mut sum = 0.0;
for i in 0..tau {
let idx = (start_idx + i) % buffer_len;
sum += if idx == old_head { old_value } else { buffer[idx] };
}
sum / tau as f64
};
let avg0 = average_with_sub(start0);
let avg1 = average_with_sub(start1);
let avg2 = average_with_sub(start2);
let diff = avg2 - 2.0 * avg1 + avg0;
diff * diff
}
fn calculate_diff(&self, _buffer: &[f64], _idx0: usize, _idx1: usize, _idx2: usize, _idx3: Option<usize>) -> f64 {
unreachable!("ModifiedAllanMethod should use calculate_squared_diff")
}
fn divisor(&self, tau: u32, count: u64) -> f64 {
2.0 * count as f64 * (tau as f64) * (tau as f64)
}
}
#[derive(Clone)]
struct Variance<M: VarianceMethod> {
method: M,
config: Config,
buffer: Vec<f64>,
head: usize,
len: usize,
total_samples: usize,
sums: Vec<f64>,
counts: Vec<u64>,
tau_values: Vec<u32>,
divisor_factors: Vec<f64>,
}
impl<M: VarianceMethod> Variance<M> {
fn new(method: M) -> Self {
Self::with_config(method, Config::default())
}
fn with_config(method: M, config: Config) -> Self {
let max_tau = config.max_tau;
let samples = method.min_samples(max_tau);
let mut buffer = Vec::with_capacity(samples);
buffer.resize(samples, 0.0);
let (tau_values, divisor_factors) = generate_tau_data(&config, &method);
let num_taus = tau_values.len();
Self {
method,
config,
sums: vec![0.0; num_taus],
counts: vec![0; num_taus],
tau_values,
divisor_factors,
buffer,
head: 0,
len: 0,
total_samples: 0,
}
}
fn record(&mut self, value: f64) {
self.total_samples += 1;
if self.len < self.buffer.len() {
self.buffer[self.len] = value;
self.len += 1;
if !value.is_nan() {
self.update_incremental_growing();
}
} else {
match self.config.mode {
Mode::Cumulative => {
let old_head = self.head;
self.buffer[self.head] = value;
self.head = (self.head + 1) % self.buffer.len();
if !value.is_nan() {
self.update_incremental_cumulative(old_head);
}
}
Mode::Sliding => {
let old_value = self.buffer[self.head];
self.buffer[self.head] = value;
let old_head = self.head;
self.head = (self.head + 1) % self.buffer.len();
if !value.is_nan() {
self.update_incremental_sliding(old_head, old_value);
}
}
}
}
}
#[cfg(not(feature = "simd"))]
fn update_incremental_growing(&mut self) {
let current_len = self.len;
let newest_idx = self.len - 1;
for (i, &tau) in self.tau_values.iter().enumerate() {
let t = tau as usize;
let min_samples = self.method.min_samples(t);
if current_len >= min_samples {
let squared_diff = self.method.calculate_squared_diff(&self.buffer, newest_idx, t, self.buffer.len());
if !squared_diff.is_nan() {
self.sums[i] += squared_diff;
self.counts[i] += 1;
}
}
}
}
#[cfg(feature = "simd")]
fn update_incremental_growing(&mut self) {
let newest_idx = self.len - 1;
let current_len = self.len;
let num_taus = self.tau_values.len();
let chunks = num_taus / 4;
let remainder_start = chunks * 4;
for chunk_idx in 0..chunks {
let base_idx = chunk_idx * 4;
let mut can_process = [false; 4];
let mut diffs = [0.0; 4];
for j in 0..4 {
let i = base_idx + j;
let t = self.tau_values[i] as usize;
let min_samples = self.method.min_samples(t);
can_process[j] = current_len >= min_samples;
if can_process[j] {
diffs[j] = self.method.calculate_squared_diff(&self.buffer, newest_idx, t, self.buffer.len());
}
}
let all_valid = can_process.iter().all(|&x| x) &&
diffs.iter().all(|&x| !x.is_nan());
if all_valid {
let sums_vec = f64x4::new([
self.sums[base_idx],
self.sums[base_idx + 1],
self.sums[base_idx + 2],
self.sums[base_idx + 3],
]);
let diffs_vec = f64x4::new(diffs);
let result = sums_vec + diffs_vec;
self.sums[base_idx] = result.as_array_ref()[0];
self.sums[base_idx + 1] = result.as_array_ref()[1];
self.sums[base_idx + 2] = result.as_array_ref()[2];
self.sums[base_idx + 3] = result.as_array_ref()[3];
for j in 0..4 {
self.counts[base_idx + j] += 1;
}
} else {
for j in 0..4 {
if can_process[j] && !diffs[j].is_nan() {
let i = base_idx + j;
self.sums[i] += diffs[j];
self.counts[i] += 1;
}
}
}
}
for i in remainder_start..num_taus {
let t = self.tau_values[i] as usize;
let min_samples = self.method.min_samples(t);
if current_len >= min_samples {
let squared_diff = self.method.calculate_squared_diff(&self.buffer, newest_idx, t, self.buffer.len());
if !squared_diff.is_nan() {
self.sums[i] += squared_diff;
self.counts[i] += 1;
}
}
}
}
#[cfg(not(feature = "simd"))]
fn update_incremental_cumulative(&mut self, _old_head: usize) {
let new_calc_newest = (self.head + self.buffer.len() - 1) % self.buffer.len();
for (i, &tau) in self.tau_values.iter().enumerate() {
let t = tau as usize;
let new_squared_diff = self.method.calculate_squared_diff(&self.buffer, new_calc_newest, t, self.buffer.len());
if !new_squared_diff.is_nan() {
self.sums[i] += new_squared_diff;
self.counts[i] += 1;
}
}
}
#[cfg(feature = "simd")]
fn update_incremental_cumulative(&mut self, _old_head: usize) {
let new_calc_newest = (self.head + self.buffer.len() - 1) % self.buffer.len();
let num_taus = self.tau_values.len();
let chunks = num_taus / 4;
let remainder_start = chunks * 4;
for chunk_idx in 0..chunks {
let base_idx = chunk_idx * 4;
let diffs = [
self.method.calculate_squared_diff(&self.buffer, new_calc_newest, self.tau_values[base_idx] as usize, self.buffer.len()),
self.method.calculate_squared_diff(&self.buffer, new_calc_newest, self.tau_values[base_idx + 1] as usize, self.buffer.len()),
self.method.calculate_squared_diff(&self.buffer, new_calc_newest, self.tau_values[base_idx + 2] as usize, self.buffer.len()),
self.method.calculate_squared_diff(&self.buffer, new_calc_newest, self.tau_values[base_idx + 3] as usize, self.buffer.len()),
];
if diffs.iter().all(|&x| !x.is_nan()) {
let sums_vec = f64x4::new([
self.sums[base_idx],
self.sums[base_idx + 1],
self.sums[base_idx + 2],
self.sums[base_idx + 3],
]);
let diffs_vec = f64x4::new(diffs);
let result = sums_vec + diffs_vec;
self.sums[base_idx] = result.as_array_ref()[0];
self.sums[base_idx + 1] = result.as_array_ref()[1];
self.sums[base_idx + 2] = result.as_array_ref()[2];
self.sums[base_idx + 3] = result.as_array_ref()[3];
for j in 0..4 {
self.counts[base_idx + j] += 1;
}
} else {
for j in 0..4 {
if !diffs[j].is_nan() {
self.sums[base_idx + j] += diffs[j];
self.counts[base_idx + j] += 1;
}
}
}
}
for i in remainder_start..num_taus {
let t = self.tau_values[i] as usize;
let new_squared_diff = self.method.calculate_squared_diff(&self.buffer, new_calc_newest, t, self.buffer.len());
if !new_squared_diff.is_nan() {
self.sums[i] += new_squared_diff;
self.counts[i] += 1;
}
}
}
#[cfg(not(feature = "simd"))]
fn update_incremental_sliding(&mut self, old_head: usize, old_value: f64) {
let new_calc_newest = (self.head + self.buffer.len() - 1) % self.buffer.len();
for (i, &tau) in self.tau_values.iter().enumerate() {
let t = tau as usize;
let min_samples = self.method.min_samples(t);
let old_calc_newest = (old_head + min_samples - 1) % self.buffer.len();
let old_squared_diff = self.method.calculate_old_squared_diff_with_substitution(
&self.buffer, old_calc_newest, t, self.buffer.len(), old_head, old_value
);
let new_squared_diff = self.method.calculate_squared_diff(&self.buffer, new_calc_newest, t, self.buffer.len());
if !old_squared_diff.is_nan() && !new_squared_diff.is_nan() {
self.sums[i] = self.sums[i] - old_squared_diff + new_squared_diff;
}
}
}
#[cfg(feature = "simd")]
fn update_incremental_sliding(&mut self, old_head: usize, old_value: f64) {
let new_calc_newest = (self.head + self.buffer.len() - 1) % self.buffer.len();
let num_taus = self.tau_values.len();
let chunks = num_taus / 4;
let remainder_start = chunks * 4;
for chunk_idx in 0..chunks {
let base_idx = chunk_idx * 4;
let mut old_diffs = [0.0; 4];
let mut new_diffs = [0.0; 4];
for j in 0..4 {
let i = base_idx + j;
let t = self.tau_values[i] as usize;
let min_samples = self.method.min_samples(t);
let old_calc_newest = (old_head + min_samples - 1) % self.buffer.len();
old_diffs[j] = self.method.calculate_old_squared_diff_with_substitution(
&self.buffer, old_calc_newest, t, self.buffer.len(), old_head, old_value
);
new_diffs[j] = self.method.calculate_squared_diff(&self.buffer, new_calc_newest, t, self.buffer.len());
}
let all_valid = old_diffs.iter().all(|&x| !x.is_nan()) &&
new_diffs.iter().all(|&x| !x.is_nan());
if all_valid {
let sums_vec = f64x4::new([
self.sums[base_idx],
self.sums[base_idx + 1],
self.sums[base_idx + 2],
self.sums[base_idx + 3],
]);
let old_vec = f64x4::new(old_diffs);
let new_vec = f64x4::new(new_diffs);
let result = sums_vec - old_vec + new_vec;
self.sums[base_idx] = result.as_array_ref()[0];
self.sums[base_idx + 1] = result.as_array_ref()[1];
self.sums[base_idx + 2] = result.as_array_ref()[2];
self.sums[base_idx + 3] = result.as_array_ref()[3];
} else {
for j in 0..4 {
if !old_diffs[j].is_nan() && !new_diffs[j].is_nan() {
self.sums[base_idx + j] = self.sums[base_idx + j] - old_diffs[j] + new_diffs[j];
}
}
}
}
for i in remainder_start..num_taus {
let t = self.tau_values[i] as usize;
let min_samples = self.method.min_samples(t);
let old_calc_newest = (old_head + min_samples - 1) % self.buffer.len();
let old_squared_diff = self.method.calculate_old_squared_diff_with_substitution(
&self.buffer, old_calc_newest, t, self.buffer.len(), old_head, old_value
);
let new_squared_diff = self.method.calculate_squared_diff(&self.buffer, new_calc_newest, t, self.buffer.len());
if !old_squared_diff.is_nan() && !new_squared_diff.is_nan() {
self.sums[i] = self.sums[i] - old_squared_diff + new_squared_diff;
}
}
}
fn get(&self, tau: usize) -> Option<Tau> {
let index = self.tau_values.iter().position(|&t| t == tau as u32)?;
if self.counts[index] == 0 {
return None;
}
let variance = self.sums[index] / (self.divisor_factors[index] * self.counts[index] as f64);
let deviation = variance.sqrt();
Some(Tau {
tau: self.tau_values[index],
variance,
deviation,
})
}
fn iter(&self) -> impl Iterator<Item = Tau> + '_ {
self.tau_values.iter().enumerate().filter_map(move |(i, &tau)| {
if self.counts[i] == 0 {
return None;
}
let variance = self.sums[i] / (self.divisor_factors[i] * self.counts[i] as f64);
let deviation = variance.sqrt();
Some(Tau {
tau,
variance,
deviation,
})
})
}
fn samples(&self) -> usize {
match self.config.mode {
Mode::Cumulative => self.total_samples,
Mode::Sliding => self.len,
}
}
}
#[derive(Clone, Debug)]
pub struct Tau {
tau: u32,
variance: f64,
deviation: f64,
}
impl Tau {
pub fn tau(&self) -> u32 {
self.tau
}
pub fn variance(&self) -> Option<f64> {
Some(self.variance)
}
pub fn deviation(&self) -> Option<f64> {
Some(self.deviation)
}
}
#[derive(Clone)]
pub struct Allan {
inner: Variance<AllanMethod>,
}
impl Allan {
pub fn new() -> Self {
Self {
inner: Variance::new(AllanMethod),
}
}
pub fn with_config(config: Config) -> Self {
Self {
inner: Variance::with_config(AllanMethod, config),
}
}
pub fn builder() -> Config {
Config::default()
}
pub fn record(&mut self, sample: f64) {
self.inner.record(sample);
}
pub fn get(&self, tau: usize) -> Option<Tau> {
self.inner.get(tau)
}
pub fn iter(&self) -> impl Iterator<Item = Tau> + '_ {
self.inner.iter()
}
pub fn samples(&self) -> usize {
self.inner.samples()
}
}
impl Default for Allan {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone)]
pub struct Hadamard {
inner: Variance<HadamardMethod>,
}
impl Hadamard {
pub fn new() -> Self {
Self {
inner: Variance::new(HadamardMethod),
}
}
pub fn with_config(config: Config) -> Self {
Self {
inner: Variance::with_config(HadamardMethod, config),
}
}
pub fn builder() -> Config {
Config::default()
}
pub fn record(&mut self, sample: f64) {
self.inner.record(sample);
}
pub fn get(&self, tau: usize) -> Option<Tau> {
self.inner.get(tau)
}
pub fn iter(&self) -> impl Iterator<Item = Tau> + '_ {
self.inner.iter()
}
pub fn samples(&self) -> usize {
self.inner.samples()
}
}
impl Default for Hadamard {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone)]
pub struct ModifiedAllan {
inner: Variance<ModifiedAllanMethod>,
}
impl ModifiedAllan {
pub fn new() -> Self {
Self {
inner: Variance::new(ModifiedAllanMethod),
}
}
pub fn with_config(config: Config) -> Self {
Self {
inner: Variance::with_config(ModifiedAllanMethod, config),
}
}
pub fn builder() -> Config {
Config::default()
}
pub fn record(&mut self, sample: f64) {
self.inner.record(sample);
}
pub fn get(&self, tau: usize) -> Option<Tau> {
self.inner.get(tau)
}
pub fn iter(&self) -> impl Iterator<Item = Tau> + '_ {
self.inner.iter()
}
pub fn samples(&self) -> usize {
self.inner.samples()
}
}
impl Default for ModifiedAllan {
fn default() -> Self {
Self::new()
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum Mode {
Cumulative,
Sliding,
}
impl Default for Mode {
fn default() -> Self {
Mode::Cumulative }
}
#[derive(Copy, Clone)]
pub enum Style {
SingleTau(u32), AllTau, Decade, DecadeDeci, Decade124, Decade1248, Decade125, }
#[derive(Copy, Clone)]
pub struct Config {
max_tau: usize,
style: Style,
mode: Mode,
}
impl Default for Config {
fn default() -> Config {
Config {
max_tau: 1_000,
style: Style::DecadeDeci,
mode: Mode::default(),
}
}
}
impl Config {
pub fn new() -> Config {
Default::default()
}
pub fn style(mut self, style: Style) -> Self {
self.style = style;
self
}
pub fn max_tau(mut self, max_tau: usize) -> Self {
self.max_tau = max_tau;
self
}
pub fn mode(mut self, mode: Mode) -> Self {
self.mode = mode;
self
}
pub fn build_allan(self) -> Allan {
Allan::with_config(self)
}
pub fn build_hadamard(self) -> Hadamard {
Hadamard::with_config(self)
}
pub fn build_modified_allan(self) -> ModifiedAllan {
ModifiedAllan::with_config(self)
}
}
fn generate_tau_data<M: VarianceMethod>(config: &Config, method: &M) -> (Vec<u32>, Vec<f64>) {
let mut tau_values = Vec::new();
let mut divisor_factors = Vec::new();
match config.style {
Style::SingleTau(t) => {
tau_values.push(t);
divisor_factors.push(method.divisor(t, 1)); }
Style::AllTau => {
for t in 1..=(config.max_tau as u32) {
tau_values.push(t);
divisor_factors.push(method.divisor(t, 1));
}
}
Style::Decade125 => return decade_tau_data(config.max_tau, &[1, 2, 5], method),
Style::Decade124 => return decade_tau_data(config.max_tau, &[1, 2, 4], method),
Style::Decade1248 => return decade_tau_data(config.max_tau, &[1, 2, 4, 8], method),
Style::DecadeDeci => {
return decade_tau_data(config.max_tau, &[1, 2, 3, 4, 5, 6, 7, 8, 9], method)
}
Style::Decade => return decade_tau_data(config.max_tau, &[1], method),
}
(tau_values, divisor_factors)
}
fn decade_tau_data<M: VarianceMethod>(max: usize, steps: &[usize], method: &M) -> (Vec<u32>, Vec<f64>) {
let mut tau_values = Vec::new();
let mut divisor_factors = Vec::new();
let mut p = 0;
loop {
let base = 10_usize.pow(p);
if base > max {
break;
}
for &step in steps {
let t = step * base;
if t <= max {
tau_values.push(t as u32);
divisor_factors.push(method.divisor(t as u32, 1));
}
}
p += 1;
}
(tau_values, divisor_factors)
}
#[cfg(test)]
mod tests {
extern crate probability;
extern crate rand;
use self::probability::prelude::*;
use self::rand::distributions::{IndependentSample, Range};
use super::*;
#[test]
fn test_constant_values() {
let mut allan = Allan::builder()
.max_tau(10)
.style(Style::AllTau)
.build_allan();
for _ in 0..100 {
allan.record(5.0);
}
for tau in 1..=10 {
let t = allan.get(tau).unwrap();
assert_eq!(
t.variance().unwrap(),
0.0,
"AVAR should be 0 for constant values at tau={}",
tau
);
assert_eq!(
t.deviation().unwrap(),
0.0,
"ADEV should be 0 for constant values at tau={}",
tau
);
}
}
#[test]
fn test_linear_drift() {
let mut allan = Allan::builder()
.max_tau(10)
.style(Style::AllTau)
.build_allan();
for i in 0..100 {
allan.record(i as f64);
}
for tau in 1..=10 {
let t = allan.get(tau).unwrap();
if t.variance().is_some() {
assert!(
(t.variance().unwrap()).abs() < 1e-10,
"AVAR should be ~0 for linear drift at tau={}, got {}",
tau,
t.variance().unwrap()
);
assert!(
(t.deviation().unwrap()).abs() < 1e-10,
"ADEV should be ~0 for linear drift at tau={}, got {}",
tau,
t.deviation().unwrap()
);
}
}
}
#[test]
fn test_quadratic_pattern() {
let mut allan = Allan::builder()
.max_tau(5)
.style(Style::AllTau)
.build_allan();
for i in 0..50 {
allan.record((i * i) as f64);
}
let t1 = allan.get(1).unwrap();
let expected_var: f64 = 2.0; let expected_dev = expected_var.sqrt();
assert!(
(t1.variance().unwrap() - expected_var).abs() < 1e-10,
"AVAR for quadratic at tau=1: expected {}, got {}",
expected_var,
t1.variance().unwrap()
);
assert!(
(t1.deviation().unwrap() - expected_dev).abs() < 1e-10,
"ADEV for quadratic at tau=1: expected {}, got {}",
expected_dev,
t1.deviation().unwrap()
);
}
#[test]
fn test_alternating_values() {
let mut allan = Allan::builder()
.max_tau(10)
.style(Style::AllTau)
.build_allan();
for i in 0..100 {
allan.record((i % 2) as f64);
}
let t1 = allan.get(1).unwrap();
let expected_var: f64 = 2.0; let expected_dev = expected_var.sqrt();
assert!(
(t1.variance().unwrap() - expected_var).abs() < 1e-10,
"AVAR for tau=1: expected {}, got {}",
expected_var,
t1.variance().unwrap()
);
assert!(
(t1.deviation().unwrap() - expected_dev).abs() < 1e-10,
"ADEV for tau=1: expected {}, got {}",
expected_dev,
t1.deviation().unwrap()
);
}
#[test]
fn test_adev_equals_sqrt_avar() {
let mut allan = Allan::builder()
.max_tau(20)
.style(Style::AllTau)
.build_allan();
for i in 0..200 {
allan.record((i as f64).sin() * 10.0);
}
for tau in 1..=20 {
let t = allan.get(tau).unwrap();
if let (Some(avar), Some(adev)) = (t.variance(), t.deviation()) {
let calculated_adev = avar.sqrt();
assert!(
(adev - calculated_adev).abs() < 1e-10,
"ADEV != sqrt(AVAR) at tau={}: ADEV={}, sqrt(AVAR)={}",
tau,
adev,
calculated_adev
);
}
}
}
#[test]
fn test_step_change() {
let mut allan = Allan::builder()
.max_tau(10)
.style(Style::AllTau)
.build_allan();
for _ in 0..15 {
allan.record(0.0);
}
for _ in 0..10 {
allan.record(10.0);
}
let t1 = allan.get(1).unwrap();
assert!(
t1.variance().unwrap() > 0.0,
"AVAR should be non-zero when buffer contains mixed values"
);
assert!(
t1.deviation().unwrap() > 0.0,
"ADEV should be non-zero when buffer contains mixed values"
);
}
#[test]
fn test_known_values() {
let mut allan = Allan::builder()
.max_tau(2)
.style(Style::AllTau)
.build_allan();
for i in 0..10 {
allan.record(i as f64);
}
let t1 = allan.get(1).unwrap();
assert!(
(t1.variance().unwrap()).abs() < 1e-10,
"Linear sequence should have AVAR=0 at tau=1, got {}",
t1.variance().unwrap()
);
}
#[test]
fn test_hadamard_constant_values() {
let mut hadamard = Hadamard::builder()
.max_tau(5)
.style(Style::AllTau)
.build_hadamard();
for _ in 0..50 {
hadamard.record(10.0);
}
for tau in 1..=5 {
let t = hadamard.get(tau).unwrap();
if let Some(var) = t.variance() {
assert_eq!(
var,
0.0,
"HVAR should be 0 for constant values at tau={}",
tau
);
assert_eq!(
t.deviation().unwrap(),
0.0,
"HDEV should be 0 for constant values at tau={}",
tau
);
}
}
}
#[test]
fn test_hadamard_linear_drift() {
let mut hadamard = Hadamard::builder()
.max_tau(5)
.style(Style::AllTau)
.build_hadamard();
for i in 0..50 {
hadamard.record(i as f64);
}
for tau in 1..=5 {
let t = hadamard.get(tau).unwrap();
if let Some(var) = t.variance() {
assert!(
var.abs() < 1e-10,
"HVAR should be ~0 for linear drift at tau={}, got {}",
tau,
var
);
}
}
}
#[test]
fn test_hadamard_vs_allan_relationship() {
let mut hadamard = Hadamard::builder()
.max_tau(10)
.style(Style::AllTau)
.build_hadamard();
for i in 0..100 {
hadamard.record((i as f64).sin() * 5.0 + (i as f64) * 0.1);
}
for tau in 1..=10 {
let t = hadamard.get(tau).unwrap();
if let (Some(hvar), Some(hdev)) = (t.variance(), t.deviation()) {
let calculated_hdev = hvar.sqrt();
assert!(
(hdev - calculated_hdev).abs() < 1e-10,
"HDEV != sqrt(HVAR) at tau={}: HDEV={}, sqrt(HVAR)={}",
tau,
hdev,
calculated_hdev
);
}
}
}
#[test]
fn test_hadamard_quadratic() {
let mut hadamard = Hadamard::builder()
.max_tau(3)
.style(Style::AllTau)
.build_hadamard();
for i in 0..30 {
hadamard.record((i * i) as f64);
}
for tau in 1..=3 {
let t = hadamard.get(tau).unwrap();
if let Some(var) = t.variance() {
assert!(
var.abs() < 1e-10,
"HVAR should be ~0 for quadratic at tau={}, got {}",
tau,
var
);
}
}
}
#[test]
fn test_hadamard_cubic() {
let mut hadamard = Hadamard::builder()
.max_tau(2)
.style(Style::SingleTau(1))
.build_hadamard();
for i in 0..20 {
hadamard.record((i * i * i) as f64);
}
let t1 = hadamard.get(1).unwrap();
let expected_hvar = 6.0;
assert!(
(t1.variance().unwrap() - expected_hvar).abs() < 0.1,
"HVAR for cubic at tau=1: expected ~{}, got {}",
expected_hvar,
t1.variance().unwrap()
);
}
#[test]
fn test_modified_allan_constant_values() {
let mut modified = ModifiedAllan::builder()
.max_tau(5)
.style(Style::AllTau)
.build_modified_allan();
for _ in 0..50 {
modified.record(5.0);
}
for tau in 1..=5 {
let t = modified.get(tau).unwrap();
if let Some(var) = t.variance() {
assert_eq!(
var,
0.0,
"Modified AVAR should be 0 for constant values at tau={}",
tau
);
assert_eq!(
t.deviation().unwrap(),
0.0,
"Modified ADEV should be 0 for constant values at tau={}",
tau
);
}
}
}
#[test]
fn test_modified_allan_linear_drift() {
let mut modified = ModifiedAllan::builder()
.max_tau(5)
.style(Style::AllTau)
.build_modified_allan();
for i in 0..50 {
modified.record(i as f64);
}
for tau in 1..=5 {
let t = modified.get(tau).unwrap();
if let Some(var) = t.variance() {
assert!(
var.abs() < 1e-10,
"Modified AVAR should be ~0 for linear drift at tau={}, got {}",
tau,
var
);
}
}
}
#[test]
fn test_modified_allan_vs_allan() {
let mut allan = Allan::builder()
.max_tau(10)
.style(Style::AllTau)
.build_allan();
let mut modified = ModifiedAllan::builder()
.max_tau(10)
.style(Style::AllTau)
.build_modified_allan();
for i in 0..100 {
let value = (i as f64).sin() * 10.0;
allan.record(value);
modified.record(value);
}
for tau in 1..=10 {
if let (Some(allan_t), Some(mod_t)) = (allan.get(tau), modified.get(tau)) {
if let (Some(allan_var), Some(mod_var)) = (allan_t.variance(), mod_t.variance()) {
assert!(allan_var >= 0.0, "Allan variance should be non-negative");
assert!(mod_var >= 0.0, "Modified Allan variance should be non-negative");
}
}
}
}
#[test]
fn test_ring_buffer_sliding_window() {
let mut allan = Allan::builder()
.max_tau(2)
.style(Style::SingleTau(1))
.mode(Mode::Sliding) .build_allan();
for _ in 0..10 {
allan.record(0.0);
}
let initial_var = allan.get(1).unwrap().variance().unwrap();
assert_eq!(initial_var, 0.0, "Variance should be 0 for constant zeros");
for _ in 0..10 {
allan.record(100.0);
}
let final_var = allan.get(1).unwrap().variance().unwrap();
assert_eq!(final_var, 0.0, "Variance should be 0 after buffer filled with constant 100s");
allan.record(0.0);
let mixed_var = allan.get(1).unwrap().variance().unwrap();
assert!(mixed_var > 0.0, "Variance should be non-zero with mixed values in buffer");
}
#[test]
fn test_buffer_size_limits() {
let allan = Allan::builder()
.max_tau(10)
.style(Style::SingleTau(1))
.build_allan();
let mut allan_clone = allan.clone();
for _ in 0..100 {
allan_clone.record(5.0);
}
let var1 = allan_clone.get(1).unwrap().variance().unwrap();
assert_eq!(var1, 0.0, "Variance should be 0 for constant values");
for _ in 0..100 {
allan_clone.record(5.0);
}
let var2 = allan_clone.get(1).unwrap().variance().unwrap();
assert_eq!(var2, 0.0, "Variance should still be 0 after adding more constant values");
}
#[test]
fn test_cumulative_vs_sliding_modes() {
let mut cumulative = Allan::builder()
.max_tau(5)
.style(Style::SingleTau(1))
.mode(Mode::Cumulative)
.build_allan();
let mut sliding = Allan::builder()
.max_tau(5)
.style(Style::SingleTau(1))
.mode(Mode::Sliding)
.build_allan();
for _ in 0..20 {
cumulative.record(0.0);
sliding.record(0.0);
}
assert_eq!(cumulative.get(1).unwrap().variance().unwrap(), 0.0);
assert_eq!(sliding.get(1).unwrap().variance().unwrap(), 0.0);
for _ in 0..20 {
cumulative.record(10.0);
sliding.record(10.0);
}
assert!(cumulative.get(1).unwrap().variance().unwrap() > 0.0,
"Cumulative mode should have variance from mixed values");
assert_eq!(sliding.get(1).unwrap().variance().unwrap(), 0.0,
"Sliding mode should have 0 variance after buffer fills with constant");
assert_eq!(cumulative.samples(), 40, "Cumulative should report total samples");
assert_eq!(sliding.samples(), 11, "Sliding should report buffer size");
}
#[test]
fn test_cumulative_mode_accumulation() {
let mut allan = Allan::builder()
.max_tau(2)
.style(Style::SingleTau(1))
.mode(Mode::Cumulative)
.build_allan();
for i in 0..100 {
allan.record((i % 3) as f64); }
let tau = allan.get(1).unwrap();
assert!(tau.variance().is_some());
assert_eq!(allan.samples(), 100, "Should report all 100 samples");
}
#[test]
fn white_noise() {
let mut allan = Allan::builder()
.max_tau(1000)
.style(Style::AllTau)
.build_allan();
let mut rng = rand::thread_rng();
let between = Range::new(0.0, 1.0);
for _ in 0..10_000 {
let v = between.ind_sample(&mut rng);
allan.record(v);
}
for t in 1..1000 {
let tau_obj = allan
.get(t)
.unwrap_or_else(|| {
print!("error fetching for tau: {}", t);
panic!("error")
});
if let Some(dev) = tau_obj.deviation() {
let v = dev * t as f64;
if v <= 0.4 || v >= 0.6 {
panic!("tau: {} value: {} outside of range", t, v);
}
}
}
}
#[test]
fn pink_noise() {
let mut allan = Allan::builder()
.max_tau(1000)
.style(Style::AllTau)
.build_allan();
let mut source = source::default();
let distribution = Beta::new(1.0, 3.0, 0.0, 1.0);
for _ in 0..10_000 {
let v = distribution.sample(&mut source);
allan.record(v);
}
for t in 1..1000 {
let tau_obj = allan
.get(t)
.unwrap_or_else(|| {
println!("error fetching for tau: {}", t);
panic!("error")
});
if let Some(dev) = tau_obj.deviation() {
let v = dev * t as f64 * 0.5;
if v <= 0.1 || v >= 0.3 {
panic!("tau: {} value: {} outside of range", t, v);
}
}
}
}
#[test]
fn test_gaps_in_data() {
let mut allan = Allan::new();
for i in 0..10 {
allan.record(i as f64);
}
allan.record(f64::NAN);
for i in 11..20 {
allan.record(i as f64);
}
let tau1 = allan.get(1);
assert!(tau1.is_some());
let var = tau1.unwrap().variance().unwrap();
assert!(var.is_finite());
}
#[test]
fn test_gaps_invalidate_spanning_calculations() {
let mut allan = Allan::builder()
.max_tau(5)
.style(Style::AllTau)
.build_allan();
allan.record(0.0);
allan.record(1.0);
allan.record(2.0);
allan.record(f64::NAN); allan.record(4.0);
allan.record(5.0);
allan.record(6.0);
allan.record(7.0);
allan.record(8.0);
allan.record(9.0);
let tau1 = allan.get(1).unwrap();
assert!(tau1.variance().is_some());
let tau3 = allan.get(3);
if let Some(t) = tau3 {
if let Some(v) = t.variance() {
assert!(v.is_finite());
}
}
}
#[test]
fn test_modified_allan_with_gaps() {
let mut modified = ModifiedAllan::builder()
.max_tau(3)
.style(Style::AllTau)
.build_modified_allan();
for i in 0..5 {
modified.record(i as f64);
}
modified.record(f64::NAN); for i in 6..12 {
modified.record(i as f64);
}
if let Some(tau1) = modified.get(1) {
if let Some(v) = tau1.variance() {
assert!(v.is_finite());
}
}
}
#[test]
fn test_multiple_gaps() {
let mut allan = Allan::builder()
.max_tau(10)
.style(Style::AllTau)
.build_allan();
for i in 0..5 {
allan.record(i as f64);
}
allan.record(f64::NAN);
for i in 6..10 {
allan.record(i as f64);
}
allan.record(f64::NAN);
for i in 11..15 {
allan.record(i as f64);
}
let tau1 = allan.get(1);
assert!(tau1.is_some());
if let Some(t) = tau1 {
assert!(t.variance().unwrap().is_finite());
}
}
#[test]
fn test_minimum_samples_for_tau() {
let mut allan = Allan::builder()
.max_tau(2)
.style(Style::AllTau)
.build_allan();
allan.record(1.0);
allan.record(2.0);
allan.record(3.0);
assert!(allan.get(2).is_none() || allan.get(2).unwrap().variance().is_none());
allan.record(4.0);
allan.record(5.0);
assert!(allan.get(2).is_some());
assert!(allan.get(2).unwrap().variance().is_some());
}
#[test]
fn test_sliding_mode_with_gaps() {
let mut allan = Allan::builder()
.max_tau(3)
.style(Style::AllTau)
.mode(Mode::Sliding)
.build_allan();
for i in 0..10 {
allan.record(i as f64);
}
allan.record(f64::NAN);
allan.record(11.0);
allan.record(12.0);
let tau1 = allan.get(1);
assert!(tau1.is_some());
}
}