use anofox_forecast::core::TimeSeries;
use anofox_forecast::models::exponential::{
AutoETS, AutoETSConfig, ETSSeasonalType, ETSSpec, ErrorType, HoltLinearTrend, HoltWinters,
SeasonalType, SimpleExponentialSmoothing, TrendType, ETS,
};
use anofox_forecast::models::Forecaster;
use chrono::{Duration, TimeZone, Utc};
fn main() {
println!("=== Exponential Smoothing Models Example ===\n");
let timestamps: Vec<_> = (0..72)
.map(|i| Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap() + Duration::hours(i))
.collect();
let values: Vec<f64> = (0..72)
.map(|i| {
let base = 50.0;
let trend = 0.3 * i as f64;
let seasonal = 10.0 * (2.0 * std::f64::consts::PI * i as f64 / 12.0).sin();
let noise = 0.5 * ((i as f64 * 0.7).cos());
base + trend + seasonal + noise
})
.collect();
let ts = TimeSeries::univariate(timestamps.clone(), values.clone()).unwrap();
println!("Data: {} observations (6 seasons of period 12)\n", ts.len());
println!("--- Simple Exponential Smoothing (SES) ---");
let mut ses_fixed = SimpleExponentialSmoothing::new(0.3);
ses_fixed.fit(&ts).unwrap();
println!("Fixed alpha = 0.3");
println!("Final level: {:.4}", ses_fixed.level().unwrap());
let mut ses_auto = SimpleExponentialSmoothing::auto();
ses_auto.fit(&ts).unwrap();
println!("\nOptimized alpha: {:.4}", ses_auto.alpha().unwrap());
println!("Final level: {:.4}", ses_auto.level().unwrap());
let ses_forecast = ses_auto.predict(5).unwrap();
println!("\nSES Forecast (flat):");
for (i, pred) in ses_forecast.primary().iter().enumerate() {
println!(" h={}: {:.4}", i + 1, pred);
}
println!("\n--- Holt's Linear Trend ---");
let trend_values: Vec<f64> = (0..50)
.map(|i| 10.0 + 2.0 * i as f64 + (i as f64 * 0.2).sin())
.collect();
let trend_timestamps: Vec<_> = (0..50)
.map(|i| Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap() + Duration::hours(i))
.collect();
let trend_ts = TimeSeries::univariate(trend_timestamps, trend_values).unwrap();
let mut holt = HoltLinearTrend::auto();
holt.fit(&trend_ts).unwrap();
println!("Optimized parameters:");
println!(" alpha: {:.4}", holt.alpha().unwrap());
println!(" beta: {:.4}", holt.beta().unwrap());
println!(
"Level: {:.4}, Trend: {:.4}",
holt.level().unwrap(),
holt.trend().unwrap()
);
let holt_forecast = holt.predict(5).unwrap();
println!("\nHolt Forecast (trending):");
for (i, pred) in holt_forecast.primary().iter().enumerate() {
println!(" h={}: {:.4}", i + 1, pred);
}
println!("\n--- Holt's Damped Trend ---");
let mut holt_damped = HoltLinearTrend::auto_damped();
holt_damped.fit(&trend_ts).unwrap();
println!("Damping parameter phi: {:.4}", holt_damped.phi().unwrap());
let damped_forecast = holt_damped.predict(10).unwrap();
let undamped_forecast = holt.predict(10).unwrap();
println!("\nComparison at h=10:");
println!(" Undamped: {:.4}", undamped_forecast.primary()[9]);
println!(" Damped: {:.4}", damped_forecast.primary()[9]);
println!("\n--- Holt-Winters (Triple Exponential Smoothing) ---");
let mut hw_add = HoltWinters::auto(12, SeasonalType::Additive);
hw_add.fit(&ts).unwrap();
println!("Additive seasonality (period=12):");
println!(" alpha: {:.4}", hw_add.alpha().unwrap());
println!(" beta: {:.4}", hw_add.beta().unwrap());
println!(" gamma: {:.4}", hw_add.gamma().unwrap());
let mult_values: Vec<f64> = (0..72)
.map(|i| {
let base = 100.0 + 0.5 * i as f64;
let seasonal = 1.0 + 0.2 * (2.0 * std::f64::consts::PI * i as f64 / 12.0).sin();
base * seasonal
})
.collect();
let mult_ts = TimeSeries::univariate(timestamps.clone(), mult_values).unwrap();
let mut hw_mult = HoltWinters::auto(12, SeasonalType::Multiplicative);
hw_mult.fit(&mult_ts).unwrap();
println!("\nMultiplicative seasonality (period=12):");
println!(" alpha: {:.4}", hw_mult.alpha().unwrap());
println!(" beta: {:.4}", hw_mult.beta().unwrap());
println!(" gamma: {:.4}", hw_mult.gamma().unwrap());
println!("\nSeasonal indices (additive):");
if let Some(seasonals) = hw_add.seasonals() {
for (i, s) in seasonals.iter().enumerate() {
print!(" S{}: {:+.2}", i + 1, s);
if (i + 1) % 4 == 0 {
println!();
}
}
}
let hw_forecast = hw_add.predict_with_intervals(12, 0.95).unwrap();
println!("\nHolt-Winters Forecast (1 season ahead):");
let preds = hw_forecast.primary();
let lower = hw_forecast.lower_series(0).unwrap();
let upper = hw_forecast.upper_series(0).unwrap();
println!(
"{:>4} {:>10} {:>10} {:>10}",
"h", "Lower", "Forecast", "Upper"
);
println!("{:-<38}", "");
for i in 0..12 {
println!(
"{:>4} {:>10.2} {:>10.2} {:>10.2}",
i + 1,
lower[i],
preds[i],
upper[i]
);
}
println!("\n--- ETS State-Space Framework ---");
let spec = ETSSpec::new(
ErrorType::Additive,
TrendType::Additive,
ETSSeasonalType::Additive,
);
let mut ets = ETS::new(spec, 12);
ets.fit(&ts).unwrap();
println!("ETS(A,A,A) with period=12");
println!(" alpha: {:.4}", ets.alpha().unwrap_or(0.0));
println!(" beta: {:.4}", ets.beta().unwrap_or(0.0));
println!(" gamma: {:.4}", ets.gamma().unwrap_or(0.0));
println!("\n--- AutoETS (Automatic Model Selection) ---");
let config = AutoETSConfig::with_period(12);
let mut auto_ets = AutoETS::with_config(config);
auto_ets.fit(&ts).unwrap();
if let Some(spec) = auto_ets.selected_spec() {
println!(
"Selected model: ETS({:?}, {:?}, {:?})",
spec.error, spec.trend, spec.seasonal
);
}
println!("\nTop 5 models by AIC:");
for (i, (spec, aic)) in auto_ets.model_scores().iter().take(5).enumerate() {
println!(
" {}. ETS({:?},{:?},{:?}): AIC = {:.2}",
i + 1,
spec.error,
spec.trend,
spec.seasonal,
aic
);
}
let auto_forecast = auto_ets.predict(6).unwrap();
println!("\nAutoETS Forecast:");
for (i, pred) in auto_forecast.primary().iter().enumerate() {
println!(" h={}: {:.4}", i + 1, pred);
}
println!("\n--- Model Comparison ---");
println!("{:<25} {:>15}", "Model", "Last Prediction");
println!("{:-<42}", "");
let ses_pred = ses_auto.predict(1).unwrap().primary()[0];
let holt_pred = holt.predict(1).unwrap().primary()[0];
let hw_pred = hw_add.predict(1).unwrap().primary()[0];
let auto_pred = auto_ets.predict(1).unwrap().primary()[0];
println!("{:<25} {:>15.4}", "SES", ses_pred);
println!("{:<25} {:>15.4}", "Holt Linear", holt_pred);
println!("{:<25} {:>15.4}", "Holt-Winters", hw_pred);
println!("{:<25} {:>15.4}", "AutoETS", auto_pred);
println!(
"{:<25} {:>15.4}",
"Actual last value",
values.last().unwrap()
);
println!("\n=== Exponential Smoothing Example Complete ===");
}