use crate::{math::normal_cdf, result::TestResult};
pub fn cumulative_sums_forward(bits: &[u8]) -> TestResult {
cusum(bits, false, "nist::cumulative_sums_forward")
}
pub fn cumulative_sums_backward(bits: &[u8]) -> TestResult {
cusum(bits, true, "nist::cumulative_sums_backward")
}
fn cusum(bits: &[u8], reverse: bool, name: &'static str) -> TestResult {
let n = bits.len();
if n < 100 {
return TestResult::insufficient(name, "n < 100");
}
let seq: Vec<i64> = if reverse {
bits.iter()
.rev()
.map(|&b| if b == 1 { 1 } else { -1 })
.collect()
} else {
bits.iter().map(|&b| if b == 1 { 1 } else { -1 }).collect()
};
let mut running = 0i64;
let z = seq
.iter()
.map(|&x| {
running += x;
running.unsigned_abs()
})
.max()
.unwrap_or(0) as f64;
let p_value = cusum_pvalue(z, n);
TestResult::with_note(name, p_value, format!("n={n}, z={z}"))
}
fn cusum_pvalue(z: f64, n: usize) -> f64 {
let nf = n as f64;
let sqrt_n = nf.sqrt();
let k1_lo = ((-nf / z + 1.0) / 4.0).floor() as i64;
let k1_hi = ((nf / z - 1.0) / 4.0).floor() as i64;
let sum1: f64 = (k1_lo..=k1_hi)
.map(|k| {
let kf = k as f64;
normal_cdf((4.0 * kf + 1.0) * z / sqrt_n) - normal_cdf((4.0 * kf - 1.0) * z / sqrt_n)
})
.sum();
let k2_lo = ((-nf / z - 3.0) / 4.0).floor() as i64;
let k2_hi = ((nf / z - 1.0) / 4.0).floor() as i64;
let sum2: f64 = (k2_lo..=k2_hi)
.map(|k| {
let kf = k as f64;
normal_cdf((4.0 * kf + 3.0) * z / sqrt_n) - normal_cdf((4.0 * kf + 1.0) * z / sqrt_n)
})
.sum();
1.0 - sum1 + sum2
}