use crate::{
math::{igamc, ks_test},
result::TestResult,
};
const M: usize = 512; const YEAR: u32 = 1 << 24; const SAMPLES: usize = 500; const LAMBDA: f64 = 2.0;
pub fn birthday_spacings(words: &[u32]) -> TestResult {
if words.len() < 9 * SAMPLES * M {
return TestResult::insufficient("diehard::birthday_spacings", "not enough words");
}
let kmax = {
let mut k = 1usize;
while (SAMPLES as f64) * poisson_pmf(k, LAMBDA) > 5.0 {
k += 1;
}
k + 1
};
let mut p_values: Vec<f64> = Vec::with_capacity(9);
let mut word_iter = words.iter().copied();
for offset in 0..9usize {
let mut js = vec![0u32; kmax];
for _ in 0..SAMPLES {
let mut birthdays: Vec<u32> = (0..M)
.map(|_| (word_iter.next().expect("birthday_spacings: word iterator exhausted (precondition failed)") >> offset) & (YEAR - 1))
.collect();
birthdays.sort_unstable();
let mut sorted_intervals: Vec<u32> = Vec::with_capacity(M);
sorted_intervals.push(birthdays[0]);
for w in birthdays.windows(2) {
sorted_intervals.push(w[1] - w[0]);
}
sorted_intervals.sort_unstable();
let mut k = 0usize;
let mut m = 0usize;
while m + 1 < sorted_intervals.len() {
let mut mnext = m + 1;
while mnext < sorted_intervals.len()
&& sorted_intervals[m] == sorted_intervals[mnext]
{
if mnext == m + 1 {
k += 1;
}
mnext += 1;
}
m = if mnext != m + 1 { mnext } else { m + 1 };
}
if k < kmax {
js[k] += 1;
}
}
let chi_sq: f64 = js
.iter()
.enumerate()
.filter(|&(k, _)| (SAMPLES as f64) * poisson_pmf(k, LAMBDA) >= 5.0)
.map(|(k, &obs)| {
let exp = (SAMPLES as f64) * poisson_pmf(k, LAMBDA);
(obs as f64 - exp).powi(2) / exp
})
.sum();
let df = js
.iter()
.enumerate()
.filter(|&(k, _)| (SAMPLES as f64) * poisson_pmf(k, LAMBDA) >= 5.0)
.count()
.saturating_sub(1);
let p = igamc(df as f64 / 2.0, chi_sq / 2.0);
p_values.push(p);
}
let p_value = ks_test(&mut p_values);
TestResult::with_note(
"diehard::birthday_spacings",
p_value,
format!("m={M}, year=2^24, samples={SAMPLES}"),
)
}
fn poisson_pmf(k: usize, lambda: f64) -> f64 {
let mut term = (-lambda).exp();
for i in 1..=k {
term *= lambda / i as f64;
}
term
}