use crate::error::SpectralError;
use crate::transform::{fwht_f64, is_power_of_two_len};
use sim_lib_discrete_algebra::{AlgebraLimits, Matrix, RealF64};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct HadamardView {
pub n: usize,
}
impl HadamardView {
pub fn matvec(&self, v: &[f64]) -> Result<Vec<f64>, SpectralError> {
if v.len() != self.n {
return Err(SpectralError::ShapeMismatch(format!(
"matvec: vector len {} != view dimension {}",
v.len(),
self.n
)));
}
Ok(fwht_f64(v)?.values)
}
pub fn materialize(&self, limits: AlgebraLimits) -> Result<Matrix<RealF64>, SpectralError> {
if !is_power_of_two_len(self.n) {
return Err(SpectralError::NonPowerOfTwoLength { len: self.n });
}
if self.n > limits.max_dim {
return Err(SpectralError::LimitExceeded(format!(
"materialize: dimension {} exceeds max_dim {}",
self.n, limits.max_dim
)));
}
let n = self.n;
let mut m = Matrix::filled(n, n, RealF64(0.0));
for i in 0..n {
for j in 0..n {
let sign = if (i & j).count_ones() % 2 == 0 {
1.0
} else {
-1.0
};
m.data[i * n + j] = RealF64(sign);
}
}
Ok(m)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn matvec_matches_materialized_matrix() {
for &n in &[2usize, 4, 8] {
let v: Vec<f64> = (0..n).map(|i| i as f64 + 1.0).collect();
let view = HadamardView { n };
let mv = view.matvec(&v).unwrap();
let h = view.materialize(AlgebraLimits::default()).unwrap();
let col = Matrix::from_rows(v.iter().map(|x| vec![RealF64(*x)]).collect()).unwrap();
let prod = h.matmul(&col).unwrap();
for (p, m) in prod.data.iter().zip(&mv) {
assert!((p.0 - m).abs() < 1e-9, "n={n}");
}
}
}
#[test]
fn oversize_materialization_fails_by_limit() {
let view = HadamardView { n: 128 };
let limited = AlgebraLimits { max_dim: 16 };
assert!(matches!(
view.materialize(limited),
Err(SpectralError::LimitExceeded(_))
));
}
#[test]
fn materialized_is_an_involution_scaled() {
let h = HadamardView { n: 2 }
.materialize(AlgebraLimits::default())
.unwrap();
let h2 = h.matmul(&h).unwrap();
assert_eq!(h2.data[0], RealF64(2.0));
assert_eq!(h2.data[1], RealF64(0.0));
}
}