use crate::{math::igamc, result::TestResult};
const SIZE: usize = 32;
const N_TRIALS: usize = 100_000;
const CYCLES: usize = 4;
const START_VAL: usize = SIZE / 2 - 1;
const TARGET_DATA: [f64; 20] = [
0.0, 0.0, 0.0, 0.0, 0.13333333, 0.20000000, 0.20634921, 0.17857143, 0.13007085, 0.08183633, 0.04338395, 0.01851828, 0.00617270, 0.00151193, 0.00023520, 0.00001680, 0.0, 0.0, 0.0, 0.0, ];
const TARGET_LEN: usize = TARGET_DATA.len();
pub fn fill_tree_both(words: &[u32]) -> Vec<TestResult> {
if words.len() < N_TRIALS * SIZE * 2 {
return vec![
TestResult::insufficient("dieharder::fill_tree_count", "not enough words"),
TestResult::insufficient("dieharder::fill_tree_position", "not enough words"),
];
}
let n_f = N_TRIALS as f64;
let expected_fill: Vec<f64> = TARGET_DATA.iter().map(|&p| p * n_f).collect();
let mut start_idx = 0usize;
let mut end_idx = 0usize;
let mut found_end = false;
for (i, &val) in expected_fill.iter().enumerate().take(TARGET_LEN) {
if val < 4.0 {
if !found_end {
start_idx = i;
}
} else if val > 4.0 {
end_idx = i;
found_end = true;
}
}
start_idx += 1;
let mut fill_counts = [0u32; TARGET_LEN];
let mut position_counts = [0u32; SIZE / 2];
let mut word_idx = 0usize;
let mut rot_amount = 0u32;
for j in 0..N_TRIALS {
let mut array = [0.0f64; SIZE];
let mut word_count = 0usize;
let fail_pos = loop {
if word_idx >= words.len() {
return vec![
TestResult::insufficient("dieharder::fill_tree_count", "ran out of words"),
TestResult::insufficient("dieharder::fill_tree_position", "ran out of words"),
];
}
let v = words[word_idx];
word_idx += 1;
word_count += 1;
let rotated = if rot_amount == 0 {
v
} else {
v.rotate_left(rot_amount)
};
let x = rotated as f64 / u32::MAX as f64;
if word_count > SIZE * 2 {
break 0;
}
if let Some(pos) = tree_insert(x, &mut array) {
break pos;
}
};
let count_idx = (word_count - 1).min(TARGET_LEN - 1);
fill_counts[count_idx] += 1;
position_counts[fail_pos / 2] += 1;
if j % (N_TRIALS / CYCLES) == 0 {
rot_amount = (rot_amount + 1) % 32;
}
}
let chi_fill: f64 = (start_idx..=end_idx)
.map(|i| {
let e = expected_fill[i];
let o = fill_counts[i] as f64;
(o - e).powi(2) / e
})
.sum();
let df_fill = (end_idx - start_idx).saturating_sub(1);
let p_fill = igamc(df_fill as f64 / 2.0, chi_fill / 2.0);
let expected_pos = N_TRIALS as f64 / (SIZE / 2) as f64;
let chi_pos: f64 = position_counts
.iter()
.map(|&c| (c as f64 - expected_pos).powi(2) / expected_pos)
.sum();
let df_pos = SIZE / 2 - 1;
let p_pos = igamc(df_pos as f64 / 2.0, chi_pos / 2.0);
vec![
TestResult::with_note(
"dieharder::fill_tree_count",
p_fill,
format!("trials={N_TRIALS}, χ²={chi_fill:.4}, start={start_idx}, end={end_idx}"),
),
TestResult::with_note(
"dieharder::fill_tree_position",
p_pos,
format!("trials={N_TRIALS}, χ²={chi_pos:.4}"),
),
]
}
pub fn fill_tree(words: &[u32]) -> TestResult {
let mut results = fill_tree_both(words);
if results.iter().any(TestResult::skipped) {
return results.remove(0);
}
let p_fill = results[0].p_value;
let p_pos = results[1].p_value;
TestResult::with_note(
"dieharder::fill_tree",
p_fill.min(p_pos),
format!("p_fill={p_fill:.4}, p_pos={p_pos:.4}"),
)
}
fn tree_insert(x: f64, array: &mut [f64; SIZE]) -> Option<usize> {
let mut i = START_VAL;
let mut d = START_VAL.div_ceil(2); while d > 0 {
if array[i] == 0.0 {
array[i] = x;
return None; }
if array[i] < x {
i += d;
} else {
i -= d;
}
d /= 2;
}
Some(i) }