use crate::{math::igamc, result::TestResult};
use std::f64::consts::PI;
const NTUPLE: usize = 256;
const TSAMPLES: usize = 5_000;
const RMAX_BITS: u32 = 32;
pub fn dct(words: &[u32]) -> TestResult {
let needed = TSAMPLES * NTUPLE;
if words.len() < needed {
return TestResult::insufficient("dieharder::dct", "not enough words");
}
let v = 1u64 << (RMAX_BITS - 1);
let mean_dc = NTUPLE as f64 * (v as f64 - 0.5);
let mut position_counts = vec![0u64; NTUPLE];
let scale = PI / NTUPLE as f64;
let cos_table: Vec<f64> = (0..NTUPLE)
.flat_map(|k| (0..NTUPLE).map(move |j| ((j as f64 + 0.5) * k as f64 * scale).cos()))
.collect();
for j in 0..TSAMPLES {
let rot_amount = ((j / (TSAMPLES / 4)) as u32 * (RMAX_BITS / 4)) % RMAX_BITS;
let block = &words[j * NTUPLE..(j + 1) * NTUPLE];
let mut dct_vals = dct_ii_u32(block, rot_amount, &cos_table);
dct_vals[0] -= mean_dc;
dct_vals[0] /= 2f64.sqrt();
let max_pos = dct_vals
.iter()
.enumerate()
.max_by(|(_, a), (_, b)| a.abs().partial_cmp(&b.abs()).unwrap())
.map(|(i, _)| i)
.unwrap_or(0);
position_counts[max_pos] += 1;
}
let expected = TSAMPLES as f64 / NTUPLE as f64;
let chi_sq: f64 = position_counts
.iter()
.map(|&c| (c as f64 - expected).powi(2) / expected)
.sum();
let df = NTUPLE - 1;
let p_value = igamc(df as f64 / 2.0, chi_sq / 2.0);
TestResult::with_note(
"dieharder::dct",
p_value,
format!("ntuple={NTUPLE}, tsamples={TSAMPLES}, χ²={chi_sq:.4}"),
)
}
fn dct_ii_u32(words: &[u32], rot_amount: u32, cos_table: &[f64]) -> Vec<f64> {
let n = words.len();
let x: Vec<f64> = words
.iter()
.map(|&w| {
let rotated = if rot_amount == 0 {
w
} else {
w.rotate_left(rot_amount)
};
rotated as f64
})
.collect();
(0..n)
.map(|k| {
let row = &cos_table[k * n..(k + 1) * n];
x.iter().zip(row).map(|(&xj, &c)| xj * c).sum()
})
.collect()
}