use crate::distributions::Cdf;
use crate::distributions::NormalDistribution;
use crate::error::{Error, Result};
use crate::tests_stat::TestResult;
use crate::tests_stat::parametric::{len_f64, mean, variance};
const AVALS_NORM: [f64; 5] = [0.561, 0.631, 0.752, 0.873, 1.035];
pub fn anderson_darling(sample: &[f64]) -> Result<TestResult> {
let n = sample.len();
if n < 2 {
return Err(Error::InsufficientData);
}
let var = variance(sample);
if var <= 0.0 {
return Err(Error::DegenerateInput("zero-variance sample".to_owned()));
}
let mu = mean(sample);
let sd = var.sqrt();
let standard = NormalDistribution {
mean: 0.0,
standard_deviation: 1.0,
..Default::default()
};
let mut sorted = sample.to_vec();
sorted.sort_by(f64::total_cmp);
let n_f = len_f64(n);
let mut acc = 0.0;
for i in 0..n {
let zi = (sorted.get(i).copied().unwrap_or(f64::NAN) - mu) / sd;
let z_tail = (sorted.get(n - 1 - i).copied().unwrap_or(f64::NAN) - mu) / sd;
let ln_cdf = standard.cdf(zi).ln();
let ln_sf = (1.0 - standard.cdf(z_tail)).ln();
let weight = len_f64(2 * i + 1) / n_f;
acc += weight * (ln_cdf + ln_sf);
}
let statistic = -n_f - acc;
Ok(TestResult {
statistic,
p_value: f64::NAN,
log_p_value: None,
df: None,
effect_size: None,
})
}
#[must_use]
pub fn anderson_normal_critical_values(n: usize) -> Vec<f64> {
let n_f = len_f64(n.max(1));
let factor = 2.25f64.mul_add(1.0 / (n_f * n_f), 0.75f64.mul_add(1.0 / n_f, 1.0));
AVALS_NORM
.iter()
.map(|&base| round3(base / factor))
.collect()
}
fn round3(x: f64) -> f64 {
(x * 1000.0).round() / 1000.0
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn zero_variance_is_degenerate() {
let sample = [3.0, 3.0, 3.0];
assert!(matches!(
anderson_darling(&sample),
Err(Error::DegenerateInput(_))
));
}
#[test]
fn critical_values_are_ascending() {
let cv = anderson_normal_critical_values(30);
assert_eq!(
cv.len(),
5,
"expected five critical values, got {}",
cv.len()
);
for pair in cv.windows(2) {
if let [lo, hi] = pair {
assert!(lo < hi, "critical values must ascend: {lo} !< {hi}");
}
}
}
}