use crate::{math::igamc, result::TestResult};
fn aperiodic_templates_9() -> Vec<Vec<u8>> {
let m = 9usize;
(0u16..512)
.filter(|&bits| {
let pattern: Vec<u8> = (0..m).map(|i| ((bits >> (m - 1 - i)) & 1) as u8).collect();
!(1..m).any(|p| (0..m - p).all(|i| pattern[i] == pattern[i + p]))
})
.map(|bits| (0..m).map(|i| ((bits >> (m - 1 - i)) & 1) as u8).collect())
.collect()
}
pub fn non_overlapping_all(bits: &[u8]) -> Vec<TestResult> {
aperiodic_templates_9()
.into_iter()
.map(|t| non_overlapping_template_raw(bits, &t))
.collect()
}
pub fn non_overlapping_template(bits: &[u8], m: usize) -> TestResult {
let mut template = vec![0u8; m];
template[m - 1] = 1;
non_overlapping_template_raw(bits, &template)
}
pub fn non_overlapping_template_raw(bits: &[u8], template: &[u8]) -> TestResult {
let n = bits.len();
let m = template.len();
let num_blocks: usize = 8;
let big_m = n / num_blocks;
if big_m < m + 1 {
return TestResult::insufficient(
"nist::non_overlapping_template",
"n too small — need M = n/8 > m",
);
}
let pow2m = (1u64 << m) as f64;
let mu = (big_m - m + 1) as f64 / pow2m;
let sigma2 = big_m as f64 * (1.0 / pow2m - (2 * m - 1) as f64 / (pow2m * pow2m));
let chi_sq: f64 = bits
.chunks_exact(big_m)
.take(num_blocks)
.map(|block| {
let w = count_non_overlapping(block, template);
(w as f64 - mu).powi(2) / sigma2
})
.sum();
let p_value = igamc(num_blocks as f64 / 2.0, chi_sq / 2.0);
let tmpl_str: String = template.iter().map(|b| b.to_string()).collect();
TestResult::with_note(
"nist::non_overlapping_template",
p_value,
format!("B={tmpl_str}, N={num_blocks}, M={big_m}, χ²={chi_sq:.4}"),
)
}
fn count_non_overlapping(block: &[u8], template: &[u8]) -> usize {
let m = template.len();
let mut count = 0usize;
let mut i = 0usize;
while i + m <= block.len() {
if &block[i..i + m] == template {
count += 1;
i += m; } else {
i += 1;
}
}
count
}
#[cfg(test)]
mod tests {
use super::aperiodic_templates_9;
#[test]
fn aperiodic_9bit_template_count() {
assert_eq!(aperiodic_templates_9().len(), 148);
}
}