fdars_core/fts/mod.rs
1//! Functional time series serial-dependence diagnostics.
2//!
3//! # R baselines
4//!
5//! * [`functional_acf`] / [`functional_pacf`] — `fdaACF::facf`
6//! (Mestre et al. 2021, *Computational Statistics & Data Analysis*).
7//! * [`stationarity_test`] — `ftsa::T_stationary`
8//! (Horváth, Kokoszka, Rice 2014, *Journal of Econometrics* 179:66–82).
9//! * [`long_run_covariance`] — `ftsa::long_run_covariance_estimation`
10//! (Bartlett HAC kernel-sandwich estimator).
11//! * [`functional_difference`] — `ftsa::diff.fts` (functional first-difference).
12//!
13//! # Conventions
14//!
15//! Entry points take an explicit deterministic `seed` (`StdRng::seed_from_u64(seed)`)
16//! and default Monte-Carlo replications of 999. All public functions return
17//! `Result<_, FdarError>` and validate inputs at entry. Result structs derive
18//! `Debug, Clone, PartialEq` and are serde-gated.
19
20mod acf;
21mod forecast;
22mod spectral;
23
24pub use acf::{
25 functional_acf, functional_difference, functional_pacf, long_run_covariance, stationarity_test,
26};
27pub use forecast::{fplsr, ftsm, ftsm_forecast, ftsm_forecast_multistep, ftsm_update};
28pub use spectral::{dpca, dpca_reconstruct, spectral_density};
29
30use crate::matrix::FdMatrix;
31
32/// Result of the spectral density operator estimator.
33///
34/// Produced by [`spectral_density`] (plan 41-01, FTS-03-01). Holds the complex
35/// m×m spectral density operator at each of the `n_curves` Fourier frequencies
36/// `θ_j = 2πj/N`. The operator at frequency `k` is stored as separate real
37/// (`re[k]`) and imaginary (`im[k]`) flat column-major m×m matrices — element
38/// `(j1, j2)` lives at index `j1 + j2 * m`. The operator is Hermitian:
39/// `im[k][j1 + j2*m] == -im[k][j2 + j1*m]`.
40///
41/// # Divergence from `freqdom`
42///
43/// The `1/2π` pre-factor is omitted (matching the crate's `long_run_covariance`,
44/// which does not divide by `2π`), so eigenvalues are `2π` larger than `freqdom`
45/// output. Filter shapes (eigenvectors) are unaffected.
46#[derive(Debug, Clone, PartialEq)]
47#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
48#[non_exhaustive]
49pub struct SpectralDensityResult {
50 /// Fourier frequencies `θ_j = 2πj/N`, length `n_curves`.
51 pub freqs: Vec<f64>,
52 /// Real part of the operator at each frequency; `re[k]` is a flat column-major m×m matrix.
53 pub re: Vec<Vec<f64>>,
54 /// Imaginary part of the operator at each frequency; `im[k]` is a flat column-major m×m matrix.
55 pub im: Vec<Vec<f64>>,
56 /// Grid dimension m (each `re[k]`/`im[k]` is m×m).
57 pub m: usize,
58 /// Number of curves N (= number of Fourier frequencies).
59 pub n_curves: usize,
60 /// Bartlett lag-window bandwidth used.
61 pub bandwidth: usize,
62}
63
64/// Result of dynamic functional PCA (DPCA).
65///
66/// Produced by [`dpca`] (plan 41-01, FTS-03-02). Holds the time-domain dynamic
67/// eigen-filters and the dynamic score series.
68///
69/// # Divergence from `freqdom`
70///
71/// Eigenvectors are computed from `Re(f̂(θ))` via [`nalgebra::SymmetricEigen`]
72/// (nalgebra 0.33 has no stable complex Hermitian path without `faer`); this is
73/// exact for the leading dynamic subspace of a Bartlett-windowed estimator.
74/// Filters and scores use the Simpson-weighted L2 inner product consistently,
75/// so the estimator/score/reconstruction metric matches.
76#[derive(Debug, Clone, PartialEq)]
77#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
78#[non_exhaustive]
79pub struct DpcaResult {
80 /// Dynamic eigen-filters, one per component. `filters[c]` is a `(2L+1) × m`
81 /// [`FdMatrix`]: row `l_idx` holds the filter tap at lag `l_idx - L`, column `j`
82 /// is the grid point.
83 pub filters: Vec<FdMatrix>,
84 /// Dynamic scores, shape `(N - 2L) × ncomp` (interior time points only).
85 pub scores: FdMatrix,
86 /// Per-component eigenvalue trajectory across frequencies: `eigenvalues[c]`
87 /// has length `n_freqs` (negative finite-sample eigenvalues clipped to 0).
88 pub eigenvalues: Vec<Vec<f64>>,
89 /// Number of Fourier frequencies N.
90 pub n_freqs: usize,
91 /// Filter lag support L (window is `[-L, L]`).
92 pub filter_lag: usize,
93 /// Number of retained dynamic components.
94 pub ncomp: usize,
95 /// Inclusive interior time range `(L, N-1-L)` for which scores are defined.
96 pub valid_range: (usize, usize),
97}
98
99/// Result of DPCA curve reconstruction from dynamic scores.
100///
101/// Produced by [`dpca_reconstruct`] (plan 41-01, FTS-03-03). The
102/// `reconstruction_error` is monotone non-increasing in the number of retained
103/// components (integrated-L2 error over the fully-defined interior).
104#[derive(Debug, Clone, PartialEq)]
105#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
106#[non_exhaustive]
107pub struct DpcaReconstruction {
108 /// Reconstructed curves over the interior, shape `(N - 2L) × m`.
109 pub fitted: FdMatrix,
110 /// Integrated-L2 reconstruction error using K = 1..=ncomp components
111 /// (`reconstruction_error[K-1]`); monotone non-increasing in K.
112 pub reconstruction_error: Vec<f64>,
113 /// Inclusive interior time range `(L, N-1-L)` matching the source [`DpcaResult`].
114 pub valid_range: (usize, usize),
115}
116
117/// Result of functional ACF/PACF estimation.
118///
119/// Produced by [`functional_acf`] and [`functional_pacf`].
120/// Lag values run from 1 to `max_lag`; `acf`, `pacf`, and `upper_band`
121/// all have length `max_lag`.
122#[derive(Debug, Clone, PartialEq)]
123#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
124#[non_exhaustive]
125pub struct FacfResult {
126 /// Lag values (1..=max_lag).
127 pub lags: Vec<u32>,
128 /// Functional autocorrelation ρ_h at each lag (L2-norm, fdaACF convention).
129 pub acf: Vec<f64>,
130 /// Functional partial autocorrelation (scalar Durbin-Levinson approximation).
131 pub pacf: Vec<f64>,
132 /// Upper confidence band under the strong-white-noise null (Monte-Carlo quantile).
133 pub upper_band: Vec<f64>,
134}
135
136/// Result of the functional stationarity test.
137///
138/// Produced by `stationarity_test` (plan 34-02).
139#[derive(Debug, Clone, PartialEq)]
140#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
141#[non_exhaustive]
142pub struct StationarityResult {
143 /// Test statistic T (KPSS-style partial-sum L2 norm).
144 pub statistic: f64,
145 /// Monte-Carlo permutation p-value.
146 pub p_value: f64,
147 /// Number of permutations used.
148 pub n_perm: usize,
149}
150
151/// Result of the Bartlett kernel-sandwich long-run covariance estimator.
152///
153/// Produced by `long_run_covariance` (plan 34-02).
154#[derive(Debug, Clone, PartialEq)]
155#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
156#[non_exhaustive]
157pub struct LongRunCovResult {
158 /// Estimated m×m long-run covariance matrix (column-major flat Vec).
159 pub cov_matrix: Vec<f64>,
160 /// Grid dimension m (cov_matrix is m×m).
161 pub m: usize,
162 /// Bandwidth used.
163 pub bandwidth: usize,
164 /// Number of curves N.
165 pub n_curves: usize,
166}
167
168/// Diagnostics for a single fitted FPC-score AR(p) model.
169///
170/// One per retained component in [`FtsmResult::ar_models`]. Produced by [`ftsm`].
171#[derive(Debug, Clone, PartialEq)]
172#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
173#[non_exhaustive]
174pub struct ArModelResult {
175 /// Selected AR order p (0 = white noise), chosen by AIC.
176 pub order: usize,
177 /// AR coefficients phi_1..phi_p (0-indexed: `phi[0]` is the lag-1 coefficient).
178 pub phi: Vec<f64>,
179 /// Innovation (residual) variance from the Yule-Walker fit.
180 pub sigma2: f64,
181}
182
183/// Result of fitting the FPCA-based functional time-series model.
184///
185/// Produced by [`ftsm`]. Carries the FPCA decomposition (mean, loadings,
186/// score-time-series, reconstructed fitted curves, integration weights) plus the
187/// per-component AR-model diagnostics used for forecasting.
188#[derive(Debug, Clone, PartialEq)]
189#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
190#[non_exhaustive]
191pub struct FtsmResult {
192 /// Mean curve μ(u), length m.
193 pub mean: Vec<f64>,
194 /// FPC loadings φ_k, shape m × ncomp.
195 pub rotation: crate::matrix::FdMatrix,
196 /// FPC score time-series β_{t,k}, shape n × ncomp.
197 pub scores: crate::matrix::FdMatrix,
198 /// Reconstructed fitted curves, shape n × m.
199 pub fitted: crate::matrix::FdMatrix,
200 /// Simpson integration weights, length m.
201 pub weights: Vec<f64>,
202 /// Effective number of retained components (clamped to min(ncomp, n, m)).
203 pub ncomp: usize,
204 /// Per-component fitted AR-model diagnostics (length = ncomp).
205 pub ar_models: Vec<ArModelResult>,
206}
207
208/// Result of an FPC-score-AR curve forecast.
209///
210/// Produced by [`ftsm_forecast`]. `forecast` is an h × m matrix whose row `i`
211/// holds the forecast curve for horizon `i + 1`.
212#[derive(Debug, Clone, PartialEq)]
213#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
214#[non_exhaustive]
215pub struct FtsmForecastResult {
216 /// Forecast curves, shape h × m (row i = horizon i+1).
217 pub forecast: crate::matrix::FdMatrix,
218 /// Forecast horizon (number of steps ahead).
219 pub h: usize,
220}
221
222/// Result of the functional PLS forecasting variant.
223///
224/// Produced by [`fplsr`]. A lag-1 PLS design (predictor = current curve,
225/// response = next curve) yields a one-step-ahead forecast curve plus the
226/// in-sample lag-1 fitted curves.
227#[derive(Debug, Clone, PartialEq)]
228#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
229#[non_exhaustive]
230pub struct FplsrResult {
231 /// One-step-ahead forecast of the next curve, shape 1 × m.
232 pub forecast: crate::matrix::FdMatrix,
233 /// In-sample lag-1 fitted curves, shape (n-1) × m.
234 pub fitted: crate::matrix::FdMatrix,
235 /// Number of PLS components used (clamped to min(ncomp, n-1, m)).
236 pub ncomp: usize,
237}