use crate::binning::ensure_finite;
use crate::error::{DriftError, Result};
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct KsTestResult {
pub statistic: f64,
pub p_value: f64,
pub n_reference: usize,
pub n_live: usize,
}
pub fn ks_test(reference: &[f64], live: &[f64]) -> Result<KsTestResult> {
if reference.is_empty() || live.is_empty() {
return Err(DriftError::EmptyInput(
"KS test needs at least one sample in each group".into(),
));
}
ensure_finite(reference)?;
ensure_finite(live)?;
let mut a = reference.to_vec();
let mut b = live.to_vec();
a.sort_by(|x, y| x.partial_cmp(y).expect("finiteness checked"));
b.sort_by(|x, y| x.partial_cmp(y).expect("finiteness checked"));
let (n, m) = (a.len(), b.len());
let (en1, en2) = (n as f64, m as f64);
let mut i = 0usize;
let mut j = 0usize;
let mut fn1 = 0.0;
let mut fn2 = 0.0;
let mut d = 0.0f64;
while i < n && j < m {
let d1 = a[i];
let d2 = b[j];
if d1 <= d2 {
while i < n && a[i] == d1 {
i += 1;
}
fn1 = i as f64 / en1;
}
if d2 <= d1 {
while j < m && b[j] == d2 {
j += 1;
}
fn2 = j as f64 / en2;
}
let dt = (fn2 - fn1).abs();
if dt > d {
d = dt;
}
}
let en = (en1 * en2 / (en1 + en2)).sqrt();
let p_value = ks_prob((en + 0.12 + 0.11 / en) * d);
Ok(KsTestResult {
statistic: d,
p_value,
n_reference: n,
n_live: m,
})
}
fn ks_prob(lambda: f64) -> f64 {
const EPS1: f64 = 1e-6;
const EPS2: f64 = 1e-10;
if lambda <= 0.0 {
return 1.0;
}
let a2 = -2.0 * lambda * lambda;
let mut fac = 2.0;
let mut sum = 0.0;
let mut termbf = 0.0;
for k in 1..=100 {
let term = fac * (a2 * (k * k) as f64).exp();
sum += term;
if term.abs() <= EPS1 * termbf || term.abs() <= EPS2 * sum {
return sum.clamp(0.0, 1.0);
}
fac = -fac;
termbf = term.abs();
}
1.0
}