use crate::criteria::{ic_order, Criterion};
use crate::levinson::levinson_from_series;
use crate::linalg::ols;
use crate::{check_input, Result};
#[derive(Debug, Clone, Copy)]
pub struct HrConfig {
pub max_p: usize,
pub max_q: usize,
pub long_ar: Option<usize>,
pub erratum_pass: bool,
}
impl Default for HrConfig {
fn default() -> Self {
HrConfig {
max_p: 5,
max_q: 5,
long_ar: None,
erratum_pass: true,
}
}
}
#[derive(Debug, Clone)]
pub struct HrResult {
pub p: usize,
pub q: usize,
pub phi: Vec<f64>,
pub theta: Vec<f64>,
pub sigma2: f64,
pub long_ar: usize,
}
pub fn hannan_rissanen(x: &[f64], config: HrConfig) -> Result<HrResult> {
let n = x.len();
let h = match config.long_ar {
Some(h) => h.max(1),
None => ic_order(x, Criterion::Aic, None)?.order.max(1),
};
check_input(x, 2 * (h + config.max_p.max(config.max_q)) + 2)?;
let mean = x.iter().sum::<f64>() / n as f64;
let xc: Vec<f64> = x.iter().map(|v| v - mean).collect();
let l = levinson_from_series(&xc, h)?;
let eps: Vec<f64> = (h..n)
.map(|t| {
let mut e = xc[t];
for (j, a) in l.phi_last.iter().enumerate() {
e -= a * xc[t - j - 1];
}
e
})
.collect();
let m = h + config.max_p.max(config.max_q);
let rows = n - m;
if rows < 10 * (config.max_p + config.max_q + 1) {
return Err(crate::Error::TooShort {
n,
needed: m + 10 * (config.max_p + config.max_q + 1),
});
}
let (p, q, beta, sigma2) = stage2(&xc, &eps, h, m, config)?;
let (p, q, beta, sigma2) = if config.erratum_pass {
let (phi, theta) = (&beta[..p], &beta[p..]);
let start = p.max(q);
let mut eps2 = vec![0.0; n];
for t in start..n {
let mut e = xc[t];
for (j, ph) in phi.iter().enumerate() {
e -= ph * xc[t - j - 1];
}
for (j, th) in theta.iter().enumerate() {
e -= th * eps2[t - j - 1];
}
eps2[t] = e;
}
stage2(&xc, &eps2, 0, m, config)?
} else {
(p, q, beta, sigma2)
};
Ok(HrResult {
p,
q,
phi: beta[..p].to_vec(),
theta: beta[p..].to_vec(),
sigma2,
long_ar: h,
})
}
fn stage2(
xc: &[f64],
eps: &[f64],
offset: usize,
m: usize,
config: HrConfig,
) -> Result<(usize, usize, Vec<f64>, f64)> {
let n = xc.len();
let rows = n - m;
let tn = rows as f64;
let mut best: Option<(f64, usize, usize, Vec<f64>, f64)> = None;
for p in 0..=config.max_p {
for q in 0..=config.max_q {
let cols = p + q;
let (beta, sigma2) = if cols == 0 {
let s2 = (0..rows).map(|t| xc[m + t].powi(2)).sum::<f64>() / tn;
(Vec::new(), s2)
} else {
let mut design = vec![0.0; rows * cols];
let mut y = vec![0.0; rows];
for t in 0..rows {
y[t] = xc[m + t];
for j in 0..p {
design[t * cols + j] = xc[m + t - j - 1];
}
for j in 0..q {
design[t * cols + p + j] = eps[m + t - j - 1 - offset];
}
}
match ols(&design, rows, cols, &y) {
Ok((beta, _s2, _se)) => {
let mut rss = 0.0;
for t in 0..rows {
let fit: f64 = (0..cols).map(|c| design[t * cols + c] * beta[c]).sum();
rss += (y[t] - fit).powi(2);
}
(beta, rss / tn)
}
Err(_) => continue,
}
};
let crit = sigma2.max(1e-300).ln() + (p + q) as f64 * tn.ln() / tn;
if best.as_ref().map_or(true, |(b, ..)| crit < *b) {
best = Some((crit, p, q, beta, sigma2));
}
}
}
let (_, p, q, beta, sigma2) = best.ok_or(crate::Error::Singular)?;
Ok((p, q, beta, sigma2))
}