use num_traits::Float;
pub fn toeplitz_matvec<T: Float>(kernel: &[T], x: &[T]) -> Vec<T> {
let n = x.len();
assert_eq!(kernel.len(), n, "kernel and x must be the same length");
(0..n)
.map(|i| {
(0..=i).fold(T::zero(), |acc, j| acc + kernel[i - j] * x[j])
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
fn dense_reference(kernel: &[f64], x: &[f64]) -> Vec<f64> {
let n = x.len();
let mut y = vec![0.0; n];
for i in 0..n {
for j in 0..=i {
y[i] += kernel[i - j] * x[j];
}
}
y
}
#[test]
fn matches_dense_reference() {
let n = 8;
let kernel: Vec<f64> = (0..n).map(|i| (i as f64 * 0.41).cos() * 0.8_f64.powi(i as i32)).collect();
let x: Vec<f64> = (0..n).map(|i| (i as f64 * 0.23).sin()).collect();
let expected = dense_reference(&kernel, &x);
let actual = toeplitz_matvec(&kernel, &x);
for (e, got) in expected.iter().zip(actual.iter()) {
assert!((e - got).abs() < 1e-9, "expected {e}, got {got}");
}
}
#[test]
fn impulse_kernel_passes_signal_through() {
let n = 5;
let mut kernel = vec![0.0; n];
kernel[0] = 1.0;
let x: Vec<f64> = (0..n).map(|i| i as f64 + 1.0).collect();
assert_eq!(toeplitz_matvec(&kernel, &x), x);
}
}