1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
//! Distributional forecasting shell (alpha, `distributional` feature).
//!
//! Inspired by [`microprediction/skaters`](https://github.com/microprediction/skaters):
//! streaming leaves, per-observation likelihood-weighted mixture, per-horizon
//! [`GaussianMixture`] output. Default leaf set: EMA, drift, AR(1);
//! [`LaplaceForecaster::with_holt`], [`LaplaceForecaster::with_ar2`], and
//! [`LaplaceForecaster::with_seasonal`] each add an opt-in leaf. The full
//! skaters ensemble (OU, fractional-differencing, Yeo-Johnson, CRPS-tuned
//! terminal leaf) is deferred.
//!
//! Attribution: skaters is MIT-licensed. See `THIRD_PARTY_NOTICES.md` at
//! the repository root for full attribution and license text.
//!
//! # Example
//! ```ignore
//! use anofox_forecast::models::laplace::{LaplaceForecaster, DistributionalForecaster};
//! use anofox_forecast::models::Forecaster;
//!
//! let mut f = LaplaceForecaster::new();
//! f.fit(&ts)?;
//! let dists = f.forecast_dist(5)?; // Vec<GaussianMixture>
//! let intervals = f.predict_with_intervals(5, 0.9)?; // point + P05/P95
//! ```
//!
//! The distributional surface is exposed via the [`DistributionalForecaster`]
//! trait; the point-forecast surface via the existing
//! [`Forecaster`](crate::models::Forecaster) trait. The mixture parameters
//! are also reachable via [`Explanation::Laplace`](crate::models::Explanation).
//!
//! # Choosing a selector (empirical cross-panel guidance)
//!
//! The stack ships three zero-config selectors. Which one wins depends
//! on the panel type. Full benchmark in `examples/fev_benchmark.rs`
//! covers 27 fev / Chronos-benchmark classical datasets; the M5-specific
//! benchmark in `examples/skaters_m5_full_auto.rs` covers full retail
//! demand.
//!
//! | panel | domain | best selector |
//! |-------|--------|---------------|
//! | M5 full 30k | retail counts (all intermittent) | [`LaplaceForecaster::auto_aid`] — median MAE within 0.8 % of AutoETS, ~42× faster |
//! | M4 hourly, tourism monthly | seasonal with adequate history | [`LaplaceForecaster::auto`] — competitive with classical |
//! | m1/m3/m4 yearly, tourism_yearly | short-history (N < 50) | `AutoTheta` — Laplace's streaming leaves haven't warmed up |
//! | M3 monthly, M4 daily | economic continuous, adequate history | [`LaplaceForecaster::auto`] — close to `AutoTheta`, +5-15 % gap |
//! | dominick, m5-style retail | retail SKU counts | [`LaplaceForecaster::auto_aid`] — Croston-family family selection |
//!
//! ## Rules of thumb
//!
//! - **Retail SKU / demand data (counts, intermittency)** → use
//! [`LaplaceForecaster::new().auto_aid()`](LaplaceForecaster::auto_aid)
//! or [`SmartForecaster`](crate::models::SmartForecaster). AID's
//! distribution-family classification is designed for this segment;
//! [Poisson](leaves::PoissonLeaf) / [Negative-Binomial](leaves::NegativeBinomialLeaf)
//! / [seasonal-Croston](leaves::SeasonalIntermittentLeaf) are the right
//! leaves.
//!
//! - **Economic / financial / continuous non-demand series** → use
//! [`LaplaceForecaster::new().auto()`](LaplaceForecaster::auto) (no
//! AID). On M3 monthly `auto_aid` **regresses ~7% median MAE vs plain
//! auto** because AID picks distribution families (usually LogNormal)
//! whose Gaussian moment-match doesn't fit smooth continuous data.
//!
//! - **Not sure which** → benchmark both on a held-out window of your
//! own data before deciding. A single `.fit()` + `.predict()` per
//! selector is a few milliseconds.
//!
//! **The [`SmartForecaster`](crate::models::SmartForecaster) route is
//! specifically demand-focused.** It commits to a single Laplace
//! distribution-family configuration based on AID's classification and
//! is not designed to be a general-purpose replacement for `AutoETS` /
//! `AutoTheta` on economic panels. On M3 monthly it regresses ~14% vs.
//! plain `auto()`.
//!
//! # Warmup requirement — training length matters
//!
//! `LaplaceForecaster` is a **streaming per-observation** design.
//! Each leaf (`EmaLeaf`, `Ar1Leaf`, `SeasonalEmaLeaf`, `HoltLeaf`, ...)
//! maintains state that converges as it processes observations. The leaf
//! softmax also needs several observations to reweight from uniform
//! toward the leaves that fit the series.
//!
//! **Consequence:** on short-history panels the leaf state and softmax
//! weights are still warming up when the forecast is requested. Classical
//! closed-form fitters (`AutoETS`, `AutoTheta`) that directly optimize
//! their parameters over the full training window do not share this
//! penalty and are consistently better on panels where `N < 60`.
//!
//! Empirical rule of thumb from the fev-benchmark evaluation
//! (`examples/fev_benchmark.rs`):
//!
//! | training length | Laplace vs. AutoTheta MASE gap |
//! |-----------------|---------------------------------|
//! | `N > 300` (m3_monthly, m4_daily, hospital, nn5_weekly, fred_md) | within ±5 %, competitive |
//! | `N = 100–300` (m4_weekly, tourism_monthly, cif_2016) | +5 to +25 % |
//! | `N = 50–100` (m4_hourly, m3_quarterly, tourism_quarterly) | +25 to +80 % |
//! | `N < 50` (m1_yearly, m3_yearly, tourism_yearly) | +15 to +30 %, cold start dominates |
//!
//! This is a **fundamental architectural property**, not a bug or a
//! configuration issue. If your data has short histories, prefer
//! `AutoTheta` / `AutoETS` (available directly in
//! `crate::models::exponential`, `crate::models::theta`). Reach for
//! `LaplaceForecaster` when either:
//!
//! - The series is long enough for the streaming leaves to converge
//! (`N ≥ 100`, ideally ≥ 300); or
//! - You need the distributional output (mixture density, quantiles,
//! per-h calibration) that classical forecasters don't provide; or
//! - You're on retail / demand data where AID's family classification
//! provides the largest win (via `.auto_aid()` / `SmartForecaster`).
pub use ;
pub use LaplaceForecaster;
pub use ;
pub use ;
pub use HierarchicalLaplace;
pub use Leaf;
pub use MultiScaleLaplace;
use crateResult;
/// Trait for models that emit per-horizon predictive densities.
///
/// Sibling to [`Forecaster`](crate::models::Forecaster) — implementers must
/// also implement `Forecaster` (point forecast = mixture mean). Object-safe:
/// `Box<dyn DistributionalForecaster>` works.