#[inline]
pub fn fwht(x: &mut [f32]) {
let n = x.len();
assert!(n.is_power_of_two(), "FWHT length must be a power of two");
let mut h = 1;
while h < n {
let mut i = 0;
while i < n {
for j in i..i + h {
let a = x[j];
let b = x[j + h];
x[j] = a + b;
x[j + h] = a - b;
}
i += h << 1;
}
h <<= 1;
}
}
#[inline]
pub fn fwht_normalized(x: &mut [f32]) {
let n = x.len();
fwht(x);
let inv = 1.0 / crate::math::sqrt(n as f32);
for v in x.iter_mut() {
*v *= inv;
}
}
#[inline]
pub fn hada_n(d_model: usize) -> usize {
debug_assert!(d_model > 0);
d_model.next_power_of_two()
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
use alloc::vec::Vec;
fn walsh_dense(n: usize) -> Vec<Vec<f32>> {
let mut h = vec![vec![1.0f32]];
while h.len() < n {
let m = h.len();
let mut next = vec![vec![0.0f32; m * 2]; m * 2];
for r in 0..m {
for c in 0..m {
let v = h[r][c];
next[r][c] = v;
next[r][c + m] = v;
next[r + m][c] = v;
next[r + m][c + m] = -v;
}
}
h = next;
}
h
}
#[test]
fn fwht_matches_dense_matmul_exactly() {
for &n in &[1usize, 2, 4, 8, 16, 128, 512, 1024] {
let w = walsh_dense(n);
let x: Vec<f32> = (0..n)
.map(|i| ((i * 7 + i / 5) % 19) as f32 - 9.0)
.collect();
let mut got = x.clone();
fwht(&mut got);
for r in 0..n {
let want: f32 = (0..n).map(|c| w[r][c] * x[c]).sum();
assert_eq!(got[r], want, "n={n} row={r}");
}
}
}
#[test]
fn fwht_matches_dense_matmul_on_fractional_input() {
for &n in &[8usize, 128, 512] {
let w = walsh_dense(n);
let x: Vec<f32> = (0..n)
.map(|i| (i as f32 * 0.37).sin() + 0.3 * (i as f32 * 0.11).cos())
.collect();
let l1: f32 = x.iter().map(|v| v.abs()).sum();
let mut got = x.clone();
fwht(&mut got);
for r in 0..n {
let want: f32 = (0..n).map(|c| w[r][c] * x[c]).sum();
assert!(
(got[r] - want).abs() <= 1e-5 * l1,
"n={n} row={r}: got {} want {want} (l1={l1})",
got[r]
);
}
}
}
#[test]
fn normalized_is_involutive() {
let n = 128;
let x: Vec<f32> = (0..n).map(|i| (i as f32 * 0.11).cos()).collect();
let mut y = x.clone();
fwht_normalized(&mut y);
fwht_normalized(&mut y);
for i in 0..n {
assert!((y[i] - x[i]).abs() < 1e-4, "i={i}: {} vs {}", y[i], x[i]);
}
}
#[test]
fn hada_n_matches_bit_length_formula() {
fn py(d: usize) -> usize {
let bit_length = usize::BITS - (d - 1).leading_zeros();
1usize << bit_length
}
for d in 1..=2048usize {
assert_eq!(hada_n(d), py(d), "d_model={d}");
}
assert_eq!(hada_n(512), 512);
assert_eq!(hada_n(768), 1024);
}
}