use crate::core::{Forecast, TimeSeries};
use crate::error::Result;
use crate::models::exponential::{AutoETS, AutoETSConfig, ETSSpec, ModelPool, ETS};
use crate::models::mfles::MFLES;
use crate::models::Forecaster;
use chrono::{Duration, TimeZone, Utc};
#[cfg(feature = "parallel")]
use rayon::prelude::*;
fn make_timestamps(n: usize) -> Vec<chrono::DateTime<Utc>> {
(0..n)
.map(|i| Utc.with_ymd_and_hms(2020, 1, 1, 0, 0, 0).unwrap() + Duration::days(i as i64))
.collect()
}
pub fn auto_ets(
values: &[Vec<f64>],
period: usize,
horizon: Option<usize>,
pool: Option<ModelPool>,
) -> Vec<Result<(Forecast, ETSSpec)>> {
let h = horizon.unwrap_or(period);
let pool = pool.unwrap_or(ModelPool::Complete);
let fit_one = |series_values: &Vec<f64>| -> Result<(Forecast, ETSSpec)> {
let n = series_values.len();
let ts = TimeSeries::univariate(make_timestamps(n), series_values.clone())?;
let config = AutoETSConfig::with_period(period).with_model_pool(pool);
let mut model = AutoETS::with_config(config);
model.fit(&ts)?;
let fc = model.predict(h)?;
let spec = model.selected_spec().unwrap_or(ETSSpec::ann());
Ok((fc, spec))
};
#[cfg(feature = "parallel")]
{
values.par_iter().map(fit_one).collect()
}
#[cfg(not(feature = "parallel"))]
{
values.iter().map(fit_one).collect()
}
}
pub fn ets(
values: &[Vec<f64>],
spec: ETSSpec,
period: usize,
horizon: Option<usize>,
) -> Vec<Result<Forecast>> {
let h = horizon.unwrap_or(period);
let fit_one = |series_values: &Vec<f64>| -> Result<Forecast> {
let n = series_values.len();
let ts = TimeSeries::univariate(make_timestamps(n), series_values.clone())?;
let mut model = ETS::new(spec, period);
model.fit(&ts)?;
model.predict(h)
};
#[cfg(feature = "parallel")]
{
values.par_iter().map(fit_one).collect()
}
#[cfg(not(feature = "parallel"))]
{
values.iter().map(fit_one).collect()
}
}
pub fn mfles(values: &[Vec<f64>], period: usize, horizon: Option<usize>) -> Vec<Result<Forecast>> {
let h = horizon.unwrap_or(period);
if values.is_empty() {
return vec![];
}
let n = values[0].len();
let fourier_order = MFLES::default_fourier_order(period);
let fourier_series = MFLES::build_fourier_series(n, period, fourier_order);
let k = fourier_series.len();
let shared_cholesky = if k > 0 {
let mut xtx = vec![vec![0.0; k]; k];
for i in 0..k {
for j in i..k {
let dot: f64 = fourier_series[i]
.iter()
.zip(fourier_series[j].iter())
.map(|(a, b)| a * b)
.sum();
xtx[i][j] = dot;
xtx[j][i] = dot;
}
}
for i in 0..k {
xtx[i][i] += 1e-8;
}
MFLES::compute_cholesky_factor(&xtx)
} else {
None
};
let fit_one = |series_values: &Vec<f64>| -> Result<Forecast> {
let sn = series_values.len();
let ts = TimeSeries::univariate(make_timestamps(sn), series_values.clone())?;
let mut model = MFLES::new(vec![period]);
if sn == n {
model.set_shared_cholesky(shared_cholesky.clone());
}
model.fit(&ts)?;
model.predict(h)
};
#[cfg(feature = "parallel")]
{
values.par_iter().map(fit_one).collect()
}
#[cfg(not(feature = "parallel"))]
{
values.iter().map(fit_one).collect()
}
}