zilla_muf/structured/
toeplitz.rs1use num_traits::Float;
2
3pub fn toeplitz_matvec<T: Float>(kernel: &[T], x: &[T]) -> Vec<T> {
13 let n = x.len();
14 assert_eq!(kernel.len(), n, "kernel and x must be the same length");
15
16 (0..n)
21 .map(|i| {
22 (0..=i).fold(T::zero(), |acc, j| acc + kernel[i - j] * x[j])
23 })
24 .collect()
25}
26
27#[cfg(test)]
28mod tests {
29 use super::*;
30
31 fn dense_reference(kernel: &[f64], x: &[f64]) -> Vec<f64> {
35 let n = x.len();
36 let mut y = vec![0.0; n];
37 for i in 0..n {
38 for j in 0..=i {
39 y[i] += kernel[i - j] * x[j];
40 }
41 }
42 y
43 }
44
45 #[test]
46 fn matches_dense_reference() {
47 let n = 8;
48 let kernel: Vec<f64> = (0..n).map(|i| (i as f64 * 0.41).cos() * 0.8_f64.powi(i as i32)).collect();
49 let x: Vec<f64> = (0..n).map(|i| (i as f64 * 0.23).sin()).collect();
50
51 let expected = dense_reference(&kernel, &x);
52 let actual = toeplitz_matvec(&kernel, &x);
53 for (e, got) in expected.iter().zip(actual.iter()) {
54 assert!((e - got).abs() < 1e-9, "expected {e}, got {got}");
55 }
56 }
57
58 #[test]
59 fn impulse_kernel_passes_signal_through() {
60 let n = 5;
62 let mut kernel = vec![0.0; n];
63 kernel[0] = 1.0;
64 let x: Vec<f64> = (0..n).map(|i| i as f64 + 1.0).collect();
65 assert_eq!(toeplitz_matvec(&kernel, &x), x);
66 }
67}