#[derive(Debug, Clone, Copy)]
pub struct KupiecTest {
pub exceptions: usize,
pub observations: usize,
pub expected: f64,
pub lr_statistic: f64,
pub rejected: bool,
}
pub fn kupiec_pof(exceptions: usize, observations: usize, confidence: f64) -> KupiecTest {
assert!(observations > 0 && exceptions <= observations);
assert!(confidence > 0.5 && confidence < 1.0);
let p = 1.0 - confidence;
let n = observations as f64;
let x = exceptions as f64;
let expected = p * n;
let ll = |prob: f64| -> f64 {
let mut v = 0.0;
if x > 0.0 {
v += x * prob.ln();
}
if x < n {
v += (n - x) * (1.0 - prob).ln();
}
v
};
let observed_rate = (x / n).clamp(1e-12, 1.0 - 1e-12);
let lr = -2.0 * (ll(p) - ll(observed_rate));
KupiecTest {
exceptions,
observations,
expected,
lr_statistic: lr,
rejected: lr > 3.841,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn correct_coverage_passes_and_bad_coverage_fails() {
let ok = kupiec_pof(10, 1000, 0.99);
assert!(!ok.rejected, "{ok:?}");
assert!(ok.lr_statistic < 0.1);
let bad = kupiec_pof(30, 1000, 0.99);
assert!(bad.rejected, "{bad:?}");
let conservative = kupiec_pof(0, 1000, 0.99);
assert!(conservative.rejected, "{conservative:?}");
}
}