use scirs2_core::ndarray::Array1;
use scirs2_core::random::{Random, rng};
use scirs2_core::random::distributions::{Normal, StandardNormal};
use sklears_core::error::{Result, SklearsError};
pub fn make_nonstationary_timeseries(
n_samples: usize,
non_stationary_type: &str,
initial_value: f64,
innovation_std: f64,
parameters: &[f64],
random_state: Option<u64>,
) -> Result<Array1<f64>> {
if n_samples == 0 {
return Err(SklearsError::InvalidInput(
"n_samples must be positive".to_string(),
));
}
let mut rng = Random::from_seed(random_state.unwrap_or_else(|| rng().gen()));
let mut y = Array1::zeros(n_samples);
y[0] = initial_value;
match non_stationary_type {
"random_walk" => {
let noise_dist = Normal::new(0.0, innovation_std).expect("operation should succeed");
for t in 1..n_samples {
let innovation: f64 = rng.sample(noise_dist);
y[t] = y[t - 1] + innovation;
}
}
"changing_variance" => {
if parameters.is_empty() {
return Err(SklearsError::InvalidInput(
"changing_variance requires persistence parameter".to_string(),
));
}
let persistence = parameters[0].clamp(0.0, 0.99);
let alpha0 = 0.1;
let alpha1 = 0.1;
let beta1 = persistence;
let mut variance = innovation_std * innovation_std;
let standard_normal = StandardNormal;
for t in 1..n_samples {
let z: f64 = rng.sample(standard_normal);
let innovation = variance.sqrt() * z;
y[t] = y[t - 1] + innovation;
variance = alpha0 + alpha1 * innovation * innovation + beta1 * variance;
}
}
"structural_break" => {
if parameters.len() < 2 {
return Err(SklearsError::InvalidInput(
"structural_break requires break_point and break_magnitude parameters"
.to_string(),
));
}
let break_point = ((parameters[0] * n_samples as f64) as usize).min(n_samples - 1);
let break_magnitude = parameters[1];
let noise_dist = Normal::new(0.0, innovation_std).expect("operation should succeed");
for t in 1..n_samples {
let innovation: f64 = rng.sample(noise_dist);
let mean_shift = if t >= break_point {
break_magnitude
} else {
0.0
};
y[t] = y[t - 1] + mean_shift + innovation;
}
}
"time_varying_ar" => {
if parameters.len() < 2 {
return Err(SklearsError::InvalidInput(
"time_varying_ar requires initial_ar and final_ar parameters".to_string(),
));
}
let initial_ar = parameters[0].clamp(-0.99, 0.99);
let final_ar = parameters[1].clamp(-0.99, 0.99);
let noise_dist = Normal::new(0.0, innovation_std).expect("operation should succeed");
for t in 1..n_samples {
let progress = t as f64 / (n_samples - 1) as f64;
let ar_coeff = initial_ar + progress * (final_ar - initial_ar);
let innovation: f64 = rng.sample(noise_dist);
y[t] = ar_coeff * y[t - 1] + innovation;
}
}
_ => {
return Err(SklearsError::InvalidInput(format!(
"Unknown non_stationary_type: {}",
non_stationary_type
)));
}
}
Ok(y)
}
pub fn make_stationary_arma(
n_samples: usize,
ar_coeffs: &[f64],
ma_coeffs: &[f64],
innovation_std: f64,
random_state: Option<u64>,
) -> Result<Array1<f64>> {
if n_samples == 0 {
return Err(SklearsError::InvalidInput(
"n_samples must be positive".to_string(),
));
}
if !ar_coeffs.is_empty() {
let sum_ar: f64 = ar_coeffs.iter().sum();
if sum_ar.abs() >= 1.0 {
return Err(SklearsError::InvalidInput(
"AR coefficients must satisfy stationarity conditions".to_string(),
));
}
}
let mut rng = Random::from_seed(random_state.unwrap_or_else(|| rng().gen()));
let noise_dist = Normal::new(0.0, innovation_std).expect("operation should succeed");
let p = ar_coeffs.len();
let q = ma_coeffs.len();
let burn_in = (p + q + 100).max(100); let total_samples = n_samples + burn_in;
let mut y = Array1::zeros(total_samples);
let mut innovations = Array1::zeros(total_samples);
for t in 0..total_samples {
innovations[t] = rng.sample(noise_dist);
}
for t in 1..total_samples {
let mut value = 0.0;
for j in 0..p {
if t > j {
value += ar_coeffs[j] * y[t - 1 - j];
}
}
for j in 0..=q {
if t > j {
let coeff = if j == 0 { 1.0 } else { ma_coeffs[j - 1] };
value += coeff * innovations[t - j];
}
}
y[t] = value;
}
Ok(y.slice(s![burn_in..]).to_owned())
}
use scirs2_core::ndarray::s;