use crate::{check_input, Error, Result};
#[derive(Debug, Clone)]
pub struct Levinson {
pub pacf: Vec<f64>,
pub sigma2: Vec<f64>,
pub phi_last: Vec<f64>,
}
pub fn levinson(r: &[f64]) -> Result<Levinson> {
if r.len() < 2 {
return Err(Error::TooShort {
n: r.len(),
needed: 2,
});
}
let k_max = r.len() - 1;
let mut phi: Vec<f64> = Vec::with_capacity(k_max);
let mut pacf = Vec::with_capacity(k_max);
let mut sigma2 = Vec::with_capacity(k_max + 1);
let mut sig = r[0];
if sig <= 0.0 {
return Err(Error::Singular);
}
sigma2.push(sig);
for k in 1..=k_max {
let mut num = r[k];
for j in 1..k {
num -= phi[j - 1] * r[k - j];
}
let kk = num / sig;
if !kk.is_finite() {
return Err(Error::Singular);
}
let prev = phi.clone();
for j in 1..k {
phi[j - 1] = prev[j - 1] - kk * prev[k - j - 1];
}
phi.push(kk);
sig *= 1.0 - kk * kk;
if sig <= 0.0 {
sig = f64::EPSILON * r[0];
}
pacf.push(kk);
sigma2.push(sig);
}
Ok(Levinson {
pacf,
sigma2,
phi_last: phi,
})
}
pub fn levinson_from_series(x: &[f64], k_max: usize) -> Result<Levinson> {
check_input(x, k_max + 2)?;
let r = crate::acov::autocovariance(x, k_max)?;
levinson(&r)
}
pub fn burg_from_series(x: &[f64], k_max: usize) -> Result<Levinson> {
check_input(x, k_max + 2)?;
let n = x.len();
let mean = x.iter().sum::<f64>() / n as f64;
let xc: Vec<f64> = x.iter().map(|v| v - mean).collect();
let mut sig = xc.iter().map(|v| v * v).sum::<f64>() / n as f64;
if sig <= 0.0 {
return Err(Error::Singular);
}
let r0 = sig;
let mut f = xc.clone();
let mut b = xc;
let mut phi: Vec<f64> = Vec::with_capacity(k_max);
let mut pacf = Vec::with_capacity(k_max);
let mut sigma2 = Vec::with_capacity(k_max + 1);
sigma2.push(sig);
for k in 1..=k_max {
let mut num = 0.0;
let mut den = 0.0;
for t in k..n {
num += f[t] * b[t - 1];
den += f[t] * f[t] + b[t - 1] * b[t - 1];
}
if den <= 0.0 || !den.is_finite() {
return Err(Error::Singular);
}
let kk = 2.0 * num / den;
for t in (k..n).rev() {
let fo = f[t];
let bo = b[t - 1];
f[t] = fo - kk * bo;
b[t] = bo - kk * fo;
}
let prev = phi.clone();
for j in 1..k {
phi[j - 1] = prev[j - 1] - kk * prev[k - j - 1];
}
phi.push(kk);
sig *= 1.0 - kk * kk;
if sig <= 0.0 {
sig = f64::EPSILON * r0;
}
pacf.push(kk);
sigma2.push(sig);
}
Ok(Levinson {
pacf,
sigma2,
phi_last: phi,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ar1_exact_autocovariance_recovers_phi() {
let phi: f64 = 0.6;
let r: Vec<f64> = (0..6).map(|k| phi.powi(k) / (1.0 - phi * phi)).collect();
let l = levinson(&r).unwrap();
assert!((l.pacf[0] - phi).abs() < 1e-12);
for k in 1..5 {
assert!(l.pacf[k].abs() < 1e-10, "pacf[{k}] = {}", l.pacf[k]);
}
assert!((l.sigma2[1] - 1.0).abs() < 1e-10);
}
}