use super::*;
use crate::primitives::Vector;
#[test]
fn falsify_arima_001_forecast_length() {
let data = Vector::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0]);
let mut arima = ARIMA::new(1, 0, 0);
arima.fit(&data).expect("fit");
let forecast = arima.forecast(5).expect("forecast");
assert_eq!(
forecast.len(),
5,
"FALSIFIED ARIMA-001: {} forecasts for 5 requested",
forecast.len()
);
}
#[test]
fn falsify_arima_002_finite_forecasts() {
let data = Vector::from_slice(&[10.0, 12.0, 11.0, 13.0, 12.5, 14.0, 13.5, 15.0, 14.5, 16.0]);
let mut arima = ARIMA::new(1, 0, 0);
arima.fit(&data).expect("fit");
let forecast = arima.forecast(3).expect("forecast");
for (i, &v) in forecast.as_slice().iter().enumerate() {
assert!(
v.is_finite(),
"FALSIFIED ARIMA-002: forecast[{i}] = {v} is not finite"
);
}
}
#[test]
fn falsify_arima_003_deterministic() {
let data = Vector::from_slice(&[1.0, 3.0, 2.0, 4.0, 3.0, 5.0, 4.0, 6.0, 5.0, 7.0]);
let mut arima = ARIMA::new(1, 0, 0);
arima.fit(&data).expect("fit");
let f1 = arima.forecast(3).expect("forecast 1");
let f2 = arima.forecast(3).expect("forecast 2");
for i in 0..3 {
assert_eq!(
f1[i], f2[i],
"FALSIFIED ARIMA-003: forecast differs at index {i}"
);
}
}
#[test]
fn falsify_arima_004_order_preserved() {
let arima = ARIMA::new(2, 1, 1);
let (p, d, q) = arima.order();
assert_eq!(p, 2, "FALSIFIED ARIMA-004: p={p}, expected 2");
assert_eq!(d, 1, "FALSIFIED ARIMA-004: d={d}, expected 1");
assert_eq!(q, 1, "FALSIFIED ARIMA-004: q={q}, expected 1");
}
mod arima_proptest_falsify {
use super::*;
use proptest::prelude::*;
proptest! {
#![proptest_config(ProptestConfig::with_cases(15))]
#[test]
fn falsify_arima_001_prop_forecast_length(
h in 1..=10usize,
seed in 0..200u32,
) {
let data: Vec<f64> = (0..20)
.map(|i| ((i as f64 + seed as f64) * 0.37).sin() * 10.0 + 20.0)
.collect();
let v = Vector::from_vec(data);
let mut arima = ARIMA::new(1, 0, 0);
arima.fit(&v).expect("fit");
let forecast = arima.forecast(h).expect("forecast");
prop_assert_eq!(
forecast.len(),
h,
"FALSIFIED ARIMA-001-prop: {} forecasts for {} requested",
forecast.len(), h
);
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(15))]
#[test]
fn falsify_arima_002_prop_finite_forecasts(
seed in 0..200u32,
) {
let data: Vec<f64> = (0..20)
.map(|i| ((i as f64 + seed as f64) * 0.37).sin() * 10.0 + 20.0)
.collect();
let v = Vector::from_vec(data);
let mut arima = ARIMA::new(1, 0, 0);
arima.fit(&v).expect("fit");
let forecast = arima.forecast(5).expect("forecast");
for (i, &val) in forecast.as_slice().iter().enumerate() {
prop_assert!(
val.is_finite(),
"FALSIFIED ARIMA-002-prop: forecast[{}]={} not finite",
i, val
);
}
}
}
}
#[test]
fn falsify_arima_integrate_d2_seeds_intermediate_difference() {
let data = Vector::from_slice(&[10.0, 20.0, 35.0, 55.0, 80.0]);
let mut arima = ARIMA::new(0, 2, 0);
arima.fit(&data).expect("fit");
let f = arima.forecast(1).expect("forecast");
assert!(
f[0] < 135.0,
"ARIMA(0,2,0) forecast {} overshot — reverse-differencing re-seeded with y[n] instead of the last 1st-difference (expected ~110, pre-fix ~165)",
f[0]
);
}
#[test]
fn falsify_arima_ar_centering_nonzero_mean_ar1() {
let series = [
50.0, 49.5341, 49.7999, 50.3074, 49.3648, 49.6845, 49.8413, 48.1659, 50.1006, 50.6508,
49.7, 49.6784, 50.3445, 49.9109, 49.7127, 48.4031, 49.7561, 50.0019, 50.2754, 48.6112,
50.9563, 50.6325, 49.9291, 51.9936, 50.9514, 49.025, 49.1073, 47.2653, 49.6821, 49.4246,
48.9697, 50.5573, 48.6276, 49.8492, 47.8602, 48.2679, 47.9297, 50.4269, 51.9796, 50.6604,
51.1709, 50.4055, 50.7708, 49.6326, 48.1079, 47.2509, 49.0086, 51.7519, 51.1453, 50.0481,
51.9361, 51.2053, 50.7041, 50.6046, 50.1699, 49.7755, 48.4528, 49.728, 49.7692, 51.0777,
];
let data = Vector::from_slice(&series);
let n = series.len() as f64;
let mean: f64 = series.iter().sum::<f64>() / n;
let range = series.iter().copied().fold(f64::NEG_INFINITY, f64::max)
- series.iter().copied().fold(f64::INFINITY, f64::min);
let mut arima = ARIMA::new(1, 0, 0);
arima.fit(&data).expect("fit");
let phi = arima.ar_coefficients().expect("ar coef present").as_slice()[0];
assert!(
(phi - 0.37).abs() < 0.05,
"FALSIFIED ARIMA-AR-CENTERING: AR(1) coef {phi} (expected demeaned phi_hat ~0.37, statsmodels 0.364). \
coef ~= 1.0 means AR was estimated on UNCENTERED levels."
);
assert!(
phi < 0.9,
"FALSIFIED ARIMA-AR-CENTERING: AR(1) coef {phi} collapsed toward 1.0 — AR estimated on uncentered data."
);
let fc = arima.forecast(1).expect("forecast")[0];
assert!(
(fc - mean).abs() < 0.5 * range,
"FALSIFIED ARIMA-AR-CENTERING: 1-step forecast {fc} diverged from level (mean {mean}, range {range}); \
band |fc - mean| < 0.5*range = {}. Pre-fix forecast ~100.93 (~2x level).",
0.5 * range
);
assert!(
(fc - 50.30).abs() < 0.5,
"FALSIFIED ARIMA-AR-CENTERING: 1-step forecast {fc} != statsmodels reference 50.30 (+/-0.5)."
);
}