use crate::criteria::{ic_order, Criterion};
use crate::dist::{df_tau_c_critical, kpss_critical};
use crate::linalg::ols;
use crate::{check_input, Error, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UnitRootTest {
Kpss,
Adf,
}
#[derive(Debug, Clone, Copy)]
pub struct IntegrationConfig {
pub max_d: usize,
pub alpha: Option<f64>,
pub test: UnitRootTest,
}
impl Default for IntegrationConfig {
fn default() -> Self {
IntegrationConfig {
max_d: 2,
alpha: None,
test: UnitRootTest::Kpss,
}
}
}
#[derive(Debug, Clone)]
pub struct IntegrationResult {
pub d: usize,
pub stats: Vec<f64>,
pub crits: Vec<f64>,
pub alpha: f64,
pub test: UnitRootTest,
}
pub fn integration_order(x: &[f64], config: IntegrationConfig) -> Result<IntegrationResult> {
let n = x.len();
check_input(x, 20 + 4 * config.max_d)?;
let alpha = config
.alpha
.unwrap_or(0.05 * 50f64.ln() / (n as f64).ln().max(1.0));
let mut series: Vec<f64> = x.to_vec();
let mut stats = Vec::new();
let mut crits = Vec::new();
let mut d = config.max_d;
for stage in 0..=config.max_d {
let (stat, crit, unit_root) = match config.test {
UnitRootTest::Kpss => {
let s = kpss_stat(&series)?;
let c = kpss_critical(alpha);
(s, c, s > c) }
UnitRootTest::Adf => {
let s = adf_stat(&series)?;
let c = df_tau_c_critical(alpha);
(s, c, s >= c) }
};
stats.push(stat);
crits.push(crit);
if !unit_root {
d = stage;
break;
}
if stage < config.max_d {
series = series.windows(2).map(|w| w[1] - w[0]).collect();
}
}
Ok(IntegrationResult {
d,
stats,
crits,
alpha,
test: config.test,
})
}
fn kpss_stat(x: &[f64]) -> Result<f64> {
let t = x.len();
let mean = x.iter().sum::<f64>() / t as f64;
let e: Vec<f64> = x.iter().map(|v| v - mean).collect();
let mut s = 0.0;
let mut sum_s2 = 0.0;
for v in &e {
s += v;
sum_s2 += s * s;
}
let bandwidth = (4.0 * (t as f64 / 100.0).powf(0.25)).floor() as usize;
let mut lr = e.iter().map(|v| v * v).sum::<f64>() / t as f64;
for j in 1..=bandwidth.min(t - 1) {
let w = 1.0 - j as f64 / (bandwidth as f64 + 1.0);
let g = (0..t - j).map(|i| e[i] * e[i + j]).sum::<f64>() / t as f64;
lr += 2.0 * w * g;
}
if lr <= 0.0 {
return Err(Error::Singular);
}
Ok(sum_s2 / (t as f64 * t as f64 * lr))
}
fn adf_stat(x: &[f64]) -> Result<f64> {
let t = x.len();
let dx: Vec<f64> = x.windows(2).map(|w| w[1] - w[0]).collect();
let k = ic_order(&dx, Criterion::Bic, None)?.order;
let start = k + 1; let rows = dx.len() - start;
let cols = 2 + k;
if rows <= cols + 2 {
return Err(Error::TooShort {
n: t,
needed: cols + start + 3,
});
}
let mut design = vec![0.0; rows * cols];
let mut y = vec![0.0; rows];
for r in 0..rows {
let ti = start + r; y[r] = dx[ti];
design[r * cols] = 1.0;
design[r * cols + 1] = x[ti];
for j in 0..k {
design[r * cols + 2 + j] = dx[ti - j - 1];
}
}
let (beta, _s2, se) = ols(&design, rows, cols, &y)?;
Ok(beta[1] / se[1])
}