#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum NoiseType {
WhitePM,
FlickerPM,
WhiteFM,
FlickerFM,
RandomWalkFM,
}
impl NoiseType {
#[must_use]
pub fn alpha(self) -> i32 {
match self {
NoiseType::WhitePM => 2,
NoiseType::FlickerPM => 1,
NoiseType::WhiteFM => 0,
NoiseType::FlickerFM => -1,
NoiseType::RandomWalkFM => -2,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum VarType {
Allan,
Modified,
Hadamard,
Total,
}
#[must_use]
pub fn edf(noise: NoiseType, n: usize, m: usize, var: VarType) -> f64 {
if m == 0 || n < 4 || 2 * m >= n {
return f64::NAN;
}
let alpha = noise.alpha();
match var {
VarType::Allan => edf_allan_table5(alpha, n, m),
VarType::Modified => edf_greenhall(alpha, 2, m, n, true),
VarType::Hadamard => edf_greenhall(alpha, 3, m, n, false),
VarType::Total => edf_totvar_table7(alpha, n, m),
}
}
fn edf_allan_table5(alpha: i32, n: usize, m: usize) -> f64 {
let nn = n as f64;
let mm = m as f64;
match alpha {
2 => {
(nn + 1.0) * (nn - 2.0 * mm) / (2.0 * (nn - mm))
}
1 => {
let a = (nn - 1.0) / (2.0 * mm);
let b = (2.0 * mm + 1.0) * (nn - 1.0) / 4.0;
(a.ln() * b.ln()).sqrt().exp()
}
0 => {
((3.0 * (nn - 1.0) / (2.0 * mm)) - (2.0 * (nn - 2.0) / nn))
* ((4.0 * mm * mm) / ((4.0 * mm * mm) + 5.0))
}
-1 => {
if m == 1 {
2.0 * (nn - 2.0) / (2.3 * nn - 4.9)
} else {
5.0 * nn * nn / (4.0 * mm * (nn + 3.0 * mm))
}
}
-2 => {
let a = (nn - 2.0) / (mm * (nn - 3.0) * (nn - 3.0));
let b = (nn - 1.0) * (nn - 1.0);
let c = 3.0 * mm * (nn - 1.0);
let d = 4.0 * mm * mm;
a * (b - c + d)
}
_ => f64::NAN,
}
}
fn edf_totvar_table7(alpha: i32, n: usize, m: usize) -> f64 {
let bc = match alpha {
0 => Some((1.50_f64, 0.0_f64)), -1 => Some((1.17_f64, 0.22_f64)), -2 => Some((0.93_f64, 0.36_f64)), _ => None,
};
match bc {
Some((b, c)) => b * (n as f64 / m as f64) - c,
None => edf_allan_table5(alpha, n, m),
}
}
fn edf_greenhall(alpha: i32, d: i32, m: usize, n: usize, modified: bool) -> f64 {
if alpha + 2 * d <= 1 {
return f64::NAN;
}
let f: f64 = if modified { 1.0 } else { m as f64 };
let s: f64 = m as f64; let mm = m as f64;
let dd = d as f64;
let nn = n as f64;
let l = mm / f + mm * dd;
let big_m = 1.0 + (s * (nn - l) / mm).floor();
let j = big_m.min((dd + 1.0) * s);
let r = big_m / s;
const J_MAX: f64 = 100.0;
if modified {
if j <= J_MAX {
let sz0 = greenhall_sz(0.0, 1.0, alpha, d);
let inv = (1.0 / (sz0 * sz0 * big_m)) * greenhall_basic_sum(j, big_m, s, 1.0, alpha, d);
return 1.0 / inv;
}
return f64::NAN;
}
if alpha <= 0 {
if j <= J_MAX {
let m_prime = if mm * (dd + 1.0) <= J_MAX {
mm
} else {
f64::INFINITY
};
let sz0 = greenhall_sz(0.0, m_prime, alpha, d);
let inv =
(1.0 / (sz0 * sz0 * big_m)) * greenhall_basic_sum(j, big_m, s, m_prime, alpha, d);
return 1.0 / inv;
}
return f64::NAN;
}
if alpha == 1 {
if j <= J_MAX {
let sz0 = greenhall_sz(0.0, mm, 1, d);
let inv = (1.0 / (sz0 * sz0 * big_m)) * greenhall_basic_sum(j, big_m, s, mm, 1, d);
return 1.0 / inv;
}
return f64::NAN;
}
if alpha == 2 {
let k = r.ceil();
if k <= dd {
return f64::NAN; }
let a0 = binom(4 * d, 2 * d) / (binom(2 * d, d) * binom(2 * d, d));
let a1 = dd / 2.0;
let inv = (1.0 / big_m) * (a0 - a1 / r);
return 1.0 / inv;
}
f64::NAN
}
fn greenhall_sw(t: f64, alpha: i32) -> f64 {
match alpha {
2 => -t.abs(),
1 => {
if t == 0.0 {
0.0
} else {
t * t * t.abs().ln()
}
}
0 => (t * t * t).abs(),
-1 => {
if t == 0.0 {
0.0
} else {
t.powi(4) * t.abs().ln()
}
}
-2 => (t.powi(5)).abs(),
-3 => {
if t == 0.0 {
0.0
} else {
t.powi(6) * t.abs().ln()
}
}
-4 => (t.powi(7)).abs(),
_ => f64::NAN,
}
}
fn greenhall_sx(t: f64, f: f64, alpha: i32) -> f64 {
if f.is_infinite() {
return greenhall_sw(t, alpha + 2);
}
let a = 2.0 * greenhall_sw(t, alpha);
let b = greenhall_sw(t - 1.0 / f, alpha);
let c = greenhall_sw(t + 1.0 / f, alpha);
f * f * (a - b - c)
}
fn greenhall_sz(t: f64, f: f64, alpha: i32, d: i32) -> f64 {
match d {
1 => {
let a = 2.0 * greenhall_sx(t, f, alpha);
let b = greenhall_sx(t - 1.0, f, alpha);
let c = greenhall_sx(t + 1.0, f, alpha);
a - b - c
}
2 => {
let a = 6.0 * greenhall_sx(t, f, alpha);
let b = 4.0 * greenhall_sx(t - 1.0, f, alpha);
let c = 4.0 * greenhall_sx(t + 1.0, f, alpha);
let dd = greenhall_sx(t - 2.0, f, alpha);
let e = greenhall_sx(t + 2.0, f, alpha);
a - b - c + dd + e
}
3 => {
let a = 20.0 * greenhall_sx(t, f, alpha);
let b = 15.0 * greenhall_sx(t - 1.0, f, alpha);
let c = 15.0 * greenhall_sx(t + 1.0, f, alpha);
let dd = 6.0 * greenhall_sx(t - 2.0, f, alpha);
let e = 6.0 * greenhall_sx(t + 2.0, f, alpha);
let g = greenhall_sx(t - 3.0, f, alpha);
let h = greenhall_sx(t + 3.0, f, alpha);
a - b - c + dd + e - g - h
}
_ => f64::NAN,
}
}
fn greenhall_basic_sum(j: f64, big_m: f64, s: f64, f: f64, alpha: i32, d: i32) -> f64 {
let first = {
let z = greenhall_sz(0.0, f, alpha, d);
z * z
};
let second = {
let z = greenhall_sz(j / s, f, alpha, d);
(1.0 - j / big_m) * z * z
};
let mut third = 0.0;
let j_int = j as i64;
for jj in 1..j_int {
let z = greenhall_sz(jj as f64 / s, f, alpha, d);
third += 2.0 * (1.0 - jj as f64 / big_m) * z * z;
}
first + second + third
}
fn binom(n: i32, k: i32) -> f64 {
if k < 0 || k > n {
return 0.0;
}
let k = k.min(n - k);
let mut acc = 1.0_f64;
for i in 0..k {
acc = acc * (n - i) as f64 / (i + 1) as f64;
}
acc
}
#[must_use]
pub fn chi2_inv(p: f64, dof: f64) -> f64 {
if p <= 0.0 {
return 0.0;
}
if p >= 1.0 {
return f64::INFINITY;
}
let z = norm_inv(p);
let t = 2.0 / (9.0 * dof);
let base = 1.0 - t + z * t.sqrt();
let mut x = dof * base * base * base;
if x <= 0.0 || !x.is_finite() {
x = 0.5 * dof.max(1e-3);
}
let a = 0.5 * dof;
for _ in 0..64 {
let cdf = reg_lower_gamma(a, 0.5 * x);
let pdf = chi2_pdf(x, dof);
if pdf <= 0.0 || !pdf.is_finite() {
break;
}
let dx = (cdf - p) / pdf;
let mut xn = x - dx;
if xn <= 0.0 {
xn = 0.5 * x;
}
let converged = (xn - x).abs() <= 1e-12 * x.max(1.0);
x = xn;
if converged {
break;
}
}
x
}
fn chi2_pdf(x: f64, dof: f64) -> f64 {
if x <= 0.0 {
return 0.0;
}
let a = 0.5 * dof;
let ln_pdf = (a - 1.0) * x.ln() - 0.5 * x - a * core::f64::consts::LN_2 - ln_gamma(a);
ln_pdf.exp()
}
fn ln_gamma(x: f64) -> f64 {
const COF: [f64; 6] = [
76.180_091_729_471_46,
-86.505_320_329_416_77,
24.014_098_240_830_91,
-1.231_739_572_450_155,
0.120_865_097_386_617_9e-2,
-0.539_523_938_495_3e-5,
];
let mut y = x;
let tmp = (x + 5.5) - (x + 0.5) * (x + 5.5).ln();
let mut ser = 1.000_000_000_190_015;
for &c in &COF {
y += 1.0;
ser += c / y;
}
-tmp + (2.506_628_274_631_000_5 * ser / x).ln()
}
fn reg_lower_gamma(a: f64, x: f64) -> f64 {
if x <= 0.0 {
return 0.0;
}
if x < a + 1.0 {
let mut ap = a;
let mut del = 1.0 / a;
let mut sum = del;
for _ in 0..1000 {
ap += 1.0;
del *= x / ap;
sum += del;
if del.abs() < sum.abs() * 1e-15 {
break;
}
}
sum * (-x + a * x.ln() - ln_gamma(a)).exp()
} else {
const TINY: f64 = 1e-300;
let mut b = x + 1.0 - a;
let mut c = 1.0 / TINY;
let mut d = 1.0 / b;
let mut h = d;
for i in 1..1000 {
let an = -(i as f64) * (i as f64 - a);
b += 2.0;
d = an * d + b;
if d.abs() < TINY {
d = TINY;
}
c = b + an / c;
if c.abs() < TINY {
c = TINY;
}
d = 1.0 / d;
let del = d * c;
h *= del;
if (del - 1.0).abs() < 1e-15 {
break;
}
}
let q = (-x + a * x.ln() - ln_gamma(a)).exp() * h;
1.0 - q
}
}
fn norm_inv(p: f64) -> f64 {
const A: [f64; 6] = [
-3.969683028665376e+01,
2.209460984245205e+02,
-2.759285104469687e+02,
1.38357751867269e+02,
-3.066479806614716e+01,
2.506628277459239e+00,
];
const B: [f64; 5] = [
-5.447609879822406e+01,
1.615858368580409e+02,
-1.556989798598866e+02,
6.680131188771972e+01,
-1.328068155288572e+01,
];
const C: [f64; 6] = [
-7.784894002430293e-03,
-3.223964580411365e-01,
-2.400758277161838e+00,
-2.549732539343734e+00,
4.374664141464968e+00,
2.938163982698783e+00,
];
const D: [f64; 4] = [
7.784695709041462e-03,
3.224671290700398e-01,
2.445134137142996e+00,
3.754408661907416e+00,
];
const P_LOW: f64 = 0.02425;
let p_high = 1.0 - P_LOW;
if p <= 0.0 {
return f64::NEG_INFINITY;
}
if p >= 1.0 {
return f64::INFINITY;
}
if p < P_LOW {
let q = (-2.0 * p.ln()).sqrt();
(((((C[0] * q + C[1]) * q + C[2]) * q + C[3]) * q + C[4]) * q + C[5])
/ ((((D[0] * q + D[1]) * q + D[2]) * q + D[3]) * q + 1.0)
} else if p <= p_high {
let q = p - 0.5;
let r = q * q;
(((((A[0] * r + A[1]) * r + A[2]) * r + A[3]) * r + A[4]) * r + A[5]) * q
/ (((((B[0] * r + B[1]) * r + B[2]) * r + B[3]) * r + B[4]) * r + 1.0)
} else {
let q = (-2.0 * (1.0 - p).ln()).sqrt();
-(((((C[0] * q + C[1]) * q + C[2]) * q + C[3]) * q + C[4]) * q + C[5])
/ ((((D[0] * q + D[1]) * q + D[2]) * q + D[3]) * q + 1.0)
}
}
#[must_use]
pub fn confidence_interval(variance: f64, edf: f64, p_lower: f64, p_upper: f64) -> (f64, f64) {
(
variance * edf / chi2_inv(p_upper, edf),
variance * edf / chi2_inv(p_lower, edf),
)
}
#[cfg(test)]
mod tests {
use super::*;
fn rel(a: f64, b: f64) -> f64 {
((a - b) / b).abs()
}
#[test]
fn chi2_inv_upper_tail() {
assert!(rel(chi2_inv(0.975, 10.0), 20.483) < 2e-2);
}
#[test]
fn chi2_inv_lower_tail() {
assert!(rel(chi2_inv(0.025, 10.0), 3.247) < 2e-2);
}
#[test]
fn chi2_inv_low_dof_k1() {
assert!(rel(chi2_inv(0.95, 1.0), 3.841459) < 2e-2);
}
#[test]
fn chi2_inv_low_dof_k2() {
assert!(rel(chi2_inv(0.95, 2.0), 5.991465) < 2e-2);
}
#[test]
fn chi2_inv_fractional_low_dof() {
assert!(rel(chi2_inv(0.95, 2.5), 6.928076) < 2e-2);
}
#[test]
fn edf_white_pm_kat() {
assert!(rel(edf(NoiseType::WhitePM, 1000, 10, VarType::Allan), 495.4444) < 2e-2);
}
#[test]
fn edf_white_fm_kat() {
assert!(rel(edf(NoiseType::WhiteFM, 1000, 1, VarType::Allan), 665.113) < 2e-2);
}
#[test]
fn edf_rw_fm_kat() {
assert!(
rel(
edf(NoiseType::RandomWalkFM, 1000, 10, VarType::Allan),
97.23
) < 2e-2
);
}
#[test]
fn confidence_interval_brackets_estimate() {
let nu = edf(NoiseType::WhiteFM, 1000, 1, VarType::Allan);
let s2 = 1.0e-24;
let (lo, hi) = confidence_interval(s2, nu, 0.025, 0.975);
assert!(lo < s2 && s2 < hi, "CI must bracket the point estimate");
assert!(lo > 0.0);
}
}