use ndarray::Array1;
use stochastic_rs_core::simd_rng::Deterministic;
use stochastic_rs_core::simd_rng::SeedExt;
use stochastic_rs_core::simd_rng::Unseeded;
use stochastic_rs_distributions::normal::SimdNormal;
use crate::traits::FloatExt;
use crate::traits::ProcessExt;
#[derive(Debug, Clone)]
pub struct Sarima<T: FloatExt, S: SeedExt = Unseeded> {
pub non_seasonal_ar_coefs: Array1<T>,
pub non_seasonal_ma_coefs: Array1<T>,
pub seasonal_ar_coefs: Array1<T>,
pub seasonal_ma_coefs: Array1<T>,
pub d: usize,
pub D: usize,
pub s: usize,
pub sigma: T,
pub n: usize,
pub seed: S,
}
impl<T: FloatExt> Sarima<T> {
pub fn new(
non_seasonal_ar_coefs: Array1<T>,
non_seasonal_ma_coefs: Array1<T>,
seasonal_ar_coefs: Array1<T>,
seasonal_ma_coefs: Array1<T>,
d: usize,
D: usize,
s: usize,
sigma: T,
n: usize,
) -> Self {
assert!(sigma > T::zero(), "Sarima requires sigma > 0");
assert!(s > 0, "Sarima requires season length s > 0");
Sarima {
non_seasonal_ar_coefs,
non_seasonal_ma_coefs,
seasonal_ar_coefs,
seasonal_ma_coefs,
d,
D,
s,
sigma,
n,
seed: Unseeded,
}
}
}
impl<T: FloatExt> Sarima<T, Deterministic> {
#[allow(non_snake_case)]
pub fn seeded(
non_seasonal_ar_coefs: Array1<T>,
non_seasonal_ma_coefs: Array1<T>,
seasonal_ar_coefs: Array1<T>,
seasonal_ma_coefs: Array1<T>,
d: usize,
D: usize,
s: usize,
sigma: T,
n: usize,
seed: u64,
) -> Self {
assert!(sigma > T::zero(), "Sarima requires sigma > 0");
assert!(s > 0, "Sarima requires season length s > 0");
Sarima {
non_seasonal_ar_coefs,
non_seasonal_ma_coefs,
seasonal_ar_coefs,
seasonal_ma_coefs,
d,
D,
s,
sigma,
n,
seed: Deterministic::new(seed),
}
}
}
impl<T: FloatExt, S: SeedExt> ProcessExt<T> for Sarima<T, S> {
type Output = Array1<T>;
fn sample(&self) -> Self::Output {
let mut noise = Array1::<T>::zeros(self.n);
if self.n > 0 {
let slice = noise.as_slice_mut().expect("contiguous");
let normal = SimdNormal::<T>::from_seed_source(T::zero(), self.sigma, &self.seed);
normal.fill_slice_fast(slice);
}
let ar_lags =
Self::multiply_ar_polynomials(&self.non_seasonal_ar_coefs, &self.seasonal_ar_coefs, self.s);
let ma_lags =
Self::multiply_ma_polynomials(&self.non_seasonal_ma_coefs, &self.seasonal_ma_coefs, self.s);
let mut sarma_series = Array1::<T>::zeros(self.n);
for t in 0..self.n {
let mut val = noise[t];
for &(lag, coef) in &ar_lags {
if t >= lag {
val += coef * sarma_series[t - lag];
}
}
for &(lag, coef) in &ma_lags {
if t >= lag {
val += coef * noise[t - lag];
}
}
sarma_series[t] = val;
}
let mut integrated = sarma_series;
for _ in 0..self.D {
integrated = Self::inverse_seasonal_difference(&integrated, self.s);
}
for _ in 0..self.d {
integrated = Self::inverse_difference(&integrated);
}
integrated
}
}
impl<T: FloatExt, S: SeedExt> Sarima<T, S> {
fn multiply_ar_polynomials(
non_seasonal: &Array1<T>,
seasonal: &Array1<T>,
s: usize,
) -> Vec<(usize, T)> {
let p = non_seasonal.len();
let big_p = seasonal.len();
let max_lag = p + big_p * s;
let mut combined = vec![T::zero(); max_lag + 1];
for i in 0..p {
combined[i + 1] += non_seasonal[i];
}
for j in 0..big_p {
let lag_j = (j + 1) * s;
combined[lag_j] += seasonal[j];
}
for i in 0..p {
for j in 0..big_p {
let lag = (i + 1) + (j + 1) * s;
combined[lag] -= non_seasonal[i] * seasonal[j];
}
}
combined
.into_iter()
.enumerate()
.filter(|&(lag, _)| lag > 0)
.filter(|&(_, c)| c != T::zero())
.collect()
}
fn multiply_ma_polynomials(
non_seasonal: &Array1<T>,
seasonal: &Array1<T>,
s: usize,
) -> Vec<(usize, T)> {
let q = non_seasonal.len();
let big_q = seasonal.len();
let max_lag = q + big_q * s;
let mut combined = vec![T::zero(); max_lag + 1];
for i in 0..q {
combined[i + 1] += non_seasonal[i];
}
for j in 0..big_q {
let lag_j = (j + 1) * s;
combined[lag_j] += seasonal[j];
}
for i in 0..q {
for j in 0..big_q {
let lag = (i + 1) + (j + 1) * s;
combined[lag] += non_seasonal[i] * seasonal[j];
}
}
combined
.into_iter()
.enumerate()
.filter(|&(lag, _)| lag > 0)
.filter(|&(_, c)| c != T::zero())
.collect()
}
fn inverse_difference(y: &Array1<T>) -> Array1<T> {
let n = y.len();
if n == 0 {
return y.clone();
}
let mut x = Array1::<T>::zeros(n);
x[0] = y[0];
for t in 1..n {
x[t] = x[t - 1] + y[t];
}
x
}
fn inverse_seasonal_difference(y: &Array1<T>, s: usize) -> Array1<T> {
let n = y.len();
if n == 0 || s == 0 {
return y.clone();
}
let mut x = Array1::<T>::zeros(n);
for t in 0..s.min(n) {
x[t] = y[t];
}
for t in s..n {
x[t] = x[t - s] + y[t];
}
x
}
}
py_process_1d!(PySarima, Sarima,
sig: (non_seasonal_ar_coefs, non_seasonal_ma_coefs, seasonal_ar_coefs, seasonal_ma_coefs, d, d_seasonal, s, sigma, n, seed=None, dtype=None),
params: (non_seasonal_ar_coefs: Vec<f64>, non_seasonal_ma_coefs: Vec<f64>, seasonal_ar_coefs: Vec<f64>, seasonal_ma_coefs: Vec<f64>, d: usize, d_seasonal: usize, s: usize, sigma: f64, n: usize)
);