The decomposition#
The decomposition is the product of the model, so the result carries every path the paper’s figures are drawn from, as arrays. This page lists them, shows how they combine, and says which scale each is on. The model page defines the quantities; here they are read.
Throughout, \(r\) is the number of path rows: the sample length
\(n\) under the filtered alignment, \(n - 1\) under the
predicted one. observed is the slice of the series the rows align
with.
The paths#
Attribute |
Shape |
Meaning |
|---|---|---|
|
|
The series rows the paths describe. |
|
|
\(\hat y_t\), the synthesized series (Fig. 1 of IEEE-ORSS). |
|
|
\(B_t\) (Fig. 3). |
|
|
|
|
|
\(A_{t,k}\), the softplus amplitude before the gate. |
|
|
\(g_{t,k} A_{t,k}\), the gated amplitude (Fig. 4). |
|
|
\(|w_k|\, g_{t,k} A_{t,k}\), the amplitude of each contribution. |
|
|
\(\omega_{t,k}\), radians per unit time (Fig. 5). |
|
|
\(2\pi / \omega_{t,k}\), in time units. |
|
|
\(\theta_{t,k}\), the cumulative phase. |
|
|
\(\phi_{t,k}\). |
|
|
\(g_{t,k}\) (Fig. 6); ones when |
|
|
\(w_k\, g_{t,k} A_{t,k} \sin(\theta_{t,k} + \phi_{t,k})\), each summand. |
|
|
\(h_t\). |
|
|
\(w\), the mixing vector. |
|
|
Each component’s share of mean contribution power over the sample. |
Fields are arrays; period, effective_amplitude, components,
oscillation, power_share and observed are properties computed
from them, so they cost a little each call and are always consistent
with the fields.
How they add up#
The synthesis equation is an identity on the result:
import numpy as np
from ns_sdn.spectral_decomposition import NSSDN, Stage
t = np.arange(240.0)
y = 1.0 + 0.5 * np.sin(0.3 * t) + 0.3 * np.sin(0.08 * t)
res = NSSDN(y, k=2, state_dim=8, schedule=(Stage(1e-2, 40),), seed=0).fit()
summands = res.mix * res.amplitude * np.sin(res.phase + res.phase_offset)
print(bool(np.allclose(summands, res.components)))
print(bool(np.allclose(res.trend + res.components.sum(axis=1), res.emission, atol=1e-5)))
print(bool(np.allclose(res.oscillation, res.components.sum(axis=1), atol=1e-5)))
print(bool(np.allclose(np.abs(res.mix) * res.amplitude, res.effective_amplitude)))
True
True
True
True
Reading a single component means reading the row of components it
contributes, the amplitude that bounds it, the frequency that
paces it, and the gate that switches it. A component whose gate sits
near zero over a stretch is silent there whatever its raw amplitude
says, which is why the figures plot the gated amplitude.
Two scales#
The network trains on a copy of the series min-max scaled to
\([-1, 1]\) on the fitted sample (scaling="minmax"; "none"
trains on the raw series). Every array on the result is mapped back to
the original scale: emission, trend and observed through the
full affine map, the amplitudes and components through its slope alone,
since they are differences rather than levels. frequency, phase,
phase_offset, gate and state are scale-free.
The paper’s figures are drawn in training units. decompose(scaled=True)
returns the same paths without the inverse map, and the scale field
holds the map itself:
(240,) True
True
A new sample#
decompose(endog) runs the trained network over another series under
teacher forcing from the learned initial state and returns its paths,
on the original scale of the fitted series’ map. This is how the
paper’s figures are drawn over the full sample after fitting on the
training segment, and how a decomposition is read off out-of-sample
data without refitting:
longer = np.concatenate([y, 1.0 + 0.5 * np.sin(0.3 * np.arange(240.0, 300.0))])
paths = res.decompose(longer)
print(paths["emission"].shape, paths["components"].shape)
(300,) (300, 2)
A fit with covariates needs exog again here, with one row per
observation of the new series, and time takes the new grid.
As a dataframe#
to_dict() returns the observed series, the emission, the trend, the
oscillation and every per-component path as a flat mapping with
one-based component suffixes; to_pandas(index=...) is the same
mapping as a frame, with your dates attached if you pass them:
16 ['observed', 'emission', 'trend', 'oscillation'] ['amplitude_1', 'frequency_1', 'phase_1', 'phase_offset_1', 'gate_1', 'component_1']
The flat layout is for plotting and export. For analysis, the (r, k)
arrays on the result are the better handle, and the
spectral state is the better summary.
Reading the figures#
The IEEE-ORSS figures correspond to the paths as follows, each drawn in training units over the full DGS10 sample with the holdout boundary marked:
Forecast versus actual:
observedandemission(training segment) withpredictover the holdout.Squared error:
resid ** 2from the one-step fit.Learned trend:
trend.Amplitude paths: columns of
amplitudefor a few components.Instantaneous frequency paths: columns of
frequency.Gating paths: columns of
gate.
The DGS10 example draws them, and the reproduction example writes the full set.