use super::types::{FlatCirculant, FlatToeplitz};
use crate::error::{LinalgError, LinalgResult};
impl FlatToeplitz {
pub fn matvec(&self, x: &[f64]) -> LinalgResult<Vec<f64>> {
let n = self.n;
if x.len() != n {
return Err(LinalgError::ShapeError(format!(
"x length {} ≠ matrix dim {}",
x.len(),
n
)));
}
let two_n = 2 * n;
let mut c_circ = vec![0.0_f64; two_n];
c_circ[..n].copy_from_slice(&self.first_col[..n]);
for i in 1..n {
c_circ[two_n - i] = self.first_row[i];
}
let circ = FlatCirculant::new(c_circ)?;
let mut x_pad = vec![0.0_f64; two_n];
x_pad[..n].copy_from_slice(&x[..n]);
let y_full = circ.matvec(&x_pad)?;
Ok(y_full[..n].to_vec())
}
pub fn solve_symmetric(&self, b: &[f64]) -> LinalgResult<Vec<f64>> {
let n = self.n;
if b.len() != n {
return Err(LinalgError::ShapeError(format!(
"b length {} ≠ matrix dim {}",
b.len(),
n
)));
}
levinson_durbin(&self.first_row, &self.first_col, b)
}
}
pub fn levinson_durbin(t_row: &[f64], _t_col: &[f64], b: &[f64]) -> LinalgResult<Vec<f64>> {
levinson_clean(t_row, b)
}
fn levinson_clean(t: &[f64], b: &[f64]) -> LinalgResult<Vec<f64>> {
let n = t.len();
if t[0].abs() < 1e-15 {
return Err(LinalgError::SingularMatrixError(
"Levinson: zero diagonal".into(),
));
}
let mut x = vec![b[0] / t[0]];
let mut a: Vec<f64> = Vec::new();
let mut alpha = t[0];
for k in 0..n - 1 {
let inner: f64 = (0..k).map(|j| t[j + 1] * a[k - 1 - j]).sum();
let rho = -(t[k + 1] + inner) / alpha;
let alpha_new = alpha * (1.0 - rho * rho);
if alpha_new.abs() < 1e-15 {
return Err(LinalgError::SingularMatrixError(format!(
"Levinson: near-zero pivot at step {}",
k
)));
}
let a_old = a.clone();
let mut a_new: Vec<f64> = (0..k).map(|j| a_old[j] + rho * a_old[k - 1 - j]).collect();
a_new.push(rho);
alpha = alpha_new;
let lambda: f64 = b[k + 1] - (0..=k).map(|j| t[k + 1 - j] * x[j]).sum::<f64>();
let mu = lambda / alpha_new;
let x_old = x.clone();
let mut x_new: Vec<f64> = (0..=k).map(|j| x_old[j] + mu * a_new[k - j]).collect();
x_new.push(mu);
a = a_new;
x = x_new;
}
Ok(x)
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
fn mat_vec_explicit(a: &[f64], n: usize, x: &[f64]) -> Vec<f64> {
let mut y = vec![0.0_f64; n];
for i in 0..n {
for j in 0..n {
y[i] += a[i * n + j] * x[j];
}
}
y
}
#[test]
fn test_toeplitz_matvec_vs_dense() {
let row = vec![4.0_f64, 3.0, 2.0, 1.0];
let col = row.clone(); let t = FlatToeplitz::new(row.clone(), col).expect("failed");
let x = vec![1.0_f64, 2.0, 3.0, 4.0];
let y_struct = t.matvec(&x).expect("matvec failed");
let dense = t.to_dense();
let y_dense = mat_vec_explicit(&dense, 4, &x);
for i in 0..4 {
assert_relative_eq!(y_struct[i], y_dense[i], epsilon = 1e-8);
}
}
#[test]
fn test_toeplitz_matvec_asymmetric() {
let row = vec![1.0_f64, 2.0, 3.0];
let col = vec![1.0_f64, 4.0, 5.0];
let t = FlatToeplitz::new(row, col).expect("failed");
let x = vec![1.0_f64, 0.0, 0.0];
let y = t.matvec(&x).expect("matvec failed");
assert_relative_eq!(y[0], 1.0, epsilon = 1e-8);
assert_relative_eq!(y[1], 4.0, epsilon = 1e-8);
assert_relative_eq!(y[2], 5.0, epsilon = 1e-8);
}
#[test]
fn test_levinson_durbin_roundtrip() {
let row = vec![4.0_f64, 1.0, 0.5, 0.1];
let col = row.clone();
let t = FlatToeplitz::new(row.clone(), col.clone()).expect("failed");
let b = vec![1.0_f64, 2.0, 3.0, 4.0];
let x = t.solve_symmetric(&b).expect("solve failed");
let y = t.matvec(&x).expect("matvec failed");
for i in 0..4 {
assert_relative_eq!(y[i], b[i], epsilon = 1e-7);
}
}
#[test]
fn test_levinson_durbin_1x1() {
let row = vec![2.0_f64];
let col = vec![2.0_f64];
let b = vec![6.0_f64];
let x = levinson_durbin(&row, &col, &b).expect("solve failed");
assert_relative_eq!(x[0], 3.0, epsilon = 1e-12);
}
}