tsorder_core/arma.rs
1//! Hannan–Rissanen (1982) recursive ARMA order selection and initial
2//! coefficient estimation (their stages 1–2; the efficient third stage is
3//! left to the bindings/estimation layer).
4//!
5//! Stage 1 fits a long Yule–Walker autoregression (order chosen by AIC, their
6//! practical recommendation) and extracts innovation estimates ε̂. Stage 2
7//! regresses `x_t` on `p` lags of `x` and `q` lags of `ε̂` for every
8//! candidate `(p, q)` on a common sample and minimises
9//! `log σ̃²_{p,q} + (p + q) log T / T` (their BIC choice, eq. 1.11).
10//!
11//! Their 1983 Biometrika correction (70, p. 303) reports that Theorem 3's
12//! proof is in error and the stage-2 selection as published can converge to
13//! values *larger* than `(p₀, q₀)`, typically when the MA polynomial has
14//! zeros near the unit circle; the fix they give — repeating stage 2 with the
15//! residuals `ε̃(t)` of eq. (1.12) in place of ε̂ — restores convergence and
16//! is applied here by default (`erratum_pass`). The common-factor degeneracy
17//! makes the stage-2 design matrix singular exactly when `p > p₀` and
18//! `q > q₀` — such candidates are skipped when the Cholesky fails, which is
19//! itself the paper's monitoring advice.
20
21use crate::criteria::{ic_order, Criterion};
22use crate::levinson::levinson_from_series;
23use crate::linalg::ols;
24use crate::{check_input, Result};
25
26/// Configuration of [`hannan_rissanen`].
27#[derive(Debug, Clone, Copy)]
28pub struct HrConfig {
29 /// Candidate grid `0..=max_p × 0..=max_q` (defaults 5 × 5).
30 pub max_p: usize,
31 pub max_q: usize,
32 /// Long-AR order of stage 1; `None` → AIC choice (clamped to ≥ 1).
33 pub long_ar: Option<usize>,
34 /// Repeat stage 2 with the eq. (1.12) residuals of the first-pass model
35 /// (the 1983 erratum's consistency fix). Default true.
36 pub erratum_pass: bool,
37}
38
39impl Default for HrConfig {
40 fn default() -> Self {
41 HrConfig {
42 max_p: 5,
43 max_q: 5,
44 long_ar: None,
45 erratum_pass: true,
46 }
47 }
48}
49
50/// Output of [`hannan_rissanen`].
51#[derive(Debug, Clone)]
52pub struct HrResult {
53 pub p: usize,
54 pub q: usize,
55 /// AR coefficients in the `x_t = Σ φ_j x_{t−j} + ε_t + Σ θ_j ε_{t−j}` convention.
56 pub phi: Vec<f64>,
57 pub theta: Vec<f64>,
58 /// Stage-2 residual variance at the selected order.
59 pub sigma2: f64,
60 /// Long-AR order used in stage 1.
61 pub long_ar: usize,
62}
63
64/// Hannan–Rissanen stages 1–2: select `(p, q)` and return the (consistent,
65/// not efficient) stage-2 coefficient estimates.
66pub fn hannan_rissanen(x: &[f64], config: HrConfig) -> Result<HrResult> {
67 let n = x.len();
68 let h = match config.long_ar {
69 Some(h) => h.max(1),
70 None => ic_order(x, Criterion::Aic, None)?.order.max(1),
71 };
72 check_input(x, 2 * (h + config.max_p.max(config.max_q)) + 2)?;
73 let mean = x.iter().sum::<f64>() / n as f64;
74 let xc: Vec<f64> = x.iter().map(|v| v - mean).collect();
75 // Stage 1: Yule–Walker long AR, innovations ε̂(t) for t = h..n.
76 let l = levinson_from_series(&xc, h)?;
77 let eps: Vec<f64> = (h..n)
78 .map(|t| {
79 let mut e = xc[t];
80 for (j, a) in l.phi_last.iter().enumerate() {
81 e -= a * xc[t - j - 1];
82 }
83 e
84 })
85 .collect();
86 // Stage 2 on the common sample t = m..n with m = h + max(max_p, max_q):
87 // eps index offset: ε̂(t) lives at eps[t - h].
88 let m = h + config.max_p.max(config.max_q);
89 let rows = n - m;
90 if rows < 10 * (config.max_p + config.max_q + 1) {
91 return Err(crate::Error::TooShort {
92 n,
93 needed: m + 10 * (config.max_p + config.max_q + 1),
94 });
95 }
96 let (p, q, beta, sigma2) = stage2(&xc, &eps, h, m, config)?;
97 let (p, q, beta, sigma2) = if config.erratum_pass {
98 // 1983 erratum fix: recompute residuals through the eq. (1.12)
99 // recursion of the first-pass model and repeat stage 2.
100 let (phi, theta) = (&beta[..p], &beta[p..]);
101 let start = p.max(q);
102 let mut eps2 = vec![0.0; n];
103 for t in start..n {
104 let mut e = xc[t];
105 for (j, ph) in phi.iter().enumerate() {
106 e -= ph * xc[t - j - 1];
107 }
108 for (j, th) in theta.iter().enumerate() {
109 e -= th * eps2[t - j - 1];
110 }
111 eps2[t] = e;
112 }
113 stage2(&xc, &eps2, 0, m, config)?
114 } else {
115 (p, q, beta, sigma2)
116 };
117 Ok(HrResult {
118 p,
119 q,
120 phi: beta[..p].to_vec(),
121 theta: beta[p..].to_vec(),
122 sigma2,
123 long_ar: h,
124 })
125}
126
127/// The stage-2 grid: regress `xc[t]` on `p` lags of `xc` and `q` lags of the
128/// innovation proxy (`eps[t - offset]` holds ε(t)), over the common sample
129/// `t = m..n`, and minimise `log σ̃² + (p+q) log T / T`.
130fn stage2(
131 xc: &[f64],
132 eps: &[f64],
133 offset: usize,
134 m: usize,
135 config: HrConfig,
136) -> Result<(usize, usize, Vec<f64>, f64)> {
137 let n = xc.len();
138 let rows = n - m;
139 let tn = rows as f64;
140 let mut best: Option<(f64, usize, usize, Vec<f64>, f64)> = None;
141 for p in 0..=config.max_p {
142 for q in 0..=config.max_q {
143 let cols = p + q;
144 let (beta, sigma2) = if cols == 0 {
145 let s2 = (0..rows).map(|t| xc[m + t].powi(2)).sum::<f64>() / tn;
146 (Vec::new(), s2)
147 } else {
148 let mut design = vec![0.0; rows * cols];
149 let mut y = vec![0.0; rows];
150 for t in 0..rows {
151 y[t] = xc[m + t];
152 for j in 0..p {
153 design[t * cols + j] = xc[m + t - j - 1];
154 }
155 for j in 0..q {
156 design[t * cols + p + j] = eps[m + t - j - 1 - offset];
157 }
158 }
159 match ols(&design, rows, cols, &y) {
160 Ok((beta, _s2, _se)) => {
161 // σ̃² with the paper's 1/T normalisation on the common sample.
162 let mut rss = 0.0;
163 for t in 0..rows {
164 let fit: f64 = (0..cols).map(|c| design[t * cols + c] * beta[c]).sum();
165 rss += (y[t] - fit).powi(2);
166 }
167 (beta, rss / tn)
168 }
169 // Common-factor singularity (p > p0 and q > q0): skip.
170 Err(_) => continue,
171 }
172 };
173 let crit = sigma2.max(1e-300).ln() + (p + q) as f64 * tn.ln() / tn;
174 if best.as_ref().map_or(true, |(b, ..)| crit < *b) {
175 best = Some((crit, p, q, beta, sigma2));
176 }
177 }
178 }
179 let (_, p, q, beta, sigma2) = best.ok_or(crate::Error::Singular)?;
180 Ok((p, q, beta, sigma2))
181}