cultivars.experimental.spectral#

Non-Stationary Spectral Decomposition Network, re-exported from ns-sdn.

The NS-SDN (Sunder and Leon, 2026) is a neural state-space model of a scalar series as a trend plus \(K\) sinusoids whose amplitude, instantaneous frequency, phase offset and participation evolve with a latent state,

\[\hat y_t = B_t + \sum_{k=1}^{K} w_k\, g_{t,k}\, A_{t,k}\, \sin(\theta_{t,k} + \phi_{t,k}), \qquad \theta_{t,k} = \theta_{t-1,k} + \omega_{t,k}\,\Delta t_t, \qquad h_t = F_\psi(h_{t-1}, x_t, \Delta t_t),\]

trained as a one-step forecaster and read as a time-varying spectral decomposition: the result carries the trend, the per-component amplitude, frequency, phase and gate paths, and the spectral-state coordinates (power-weighted centroid, dispersion, effective rank and Wasserstein motion) that summarize where the power sits and how it moves. It answers the question cultivars.spectral answers for stationary systems – at which frequencies, with what power – for a series whose frequencies drift.

The model lives in its own package, ns-sdn, because it is estimated by gradient descent in torch rather than on numpy and scipy, which is the contract the rest of cultivars keeps. This module is the bridge: every public name of ns_sdn is re-exported here under an NSSDN prefix, so a cultivars user reaches the model through one import path and never confuses its records with the package’s own. NSSDNResult is not a cultivars result and NSSDNSummaryTable is not cultivars.summary.SummaryTable: the two packages share the model-fit-result design and the exception hierarchy by construction, but their records are distinct types, and a consumer written against cultivars protocols (identification, backtests, spectral views) does not accept an NSSDNResult. What does carry across is the shape of the error handling: NSSDNDimensionError, NSSDNSpecificationError and NSSDNNumericalError derive from ValueError and RuntimeError exactly as their cultivars namesakes do, so one except clause covers both packages.

Held to the cultivars.experimental rules. The package is pinned to one pre-release version in pyproject.toml, the API may change in any minor release, and the model is promoted into cultivars.spectral once the replication fixture against the published DGS10 run lands and the surface has held for two minor releases. The defaults reproduce that run – sixteen components, a 128-dimensional GRU state, the filtered alignment, min-max scaling and a two-stage Adam schedule of 3,000 steps – so every exploratory fit should pass a shorter schedule of NSSDNStage records.

Layout. NSSDN is the specification and NSSDNResult the frozen record fit() returns, with decompose, forecast, predict and spectral_state; NSSDNStage and NSSDNPenalties are the schedule records the constructor takes; NSSDNSpectralState is the measure-valued reading returned by spectral_state(); NSSDNSummaryTable and NSSDNInformationCriteria are the reporting records; NSSDNAlignment, NSSDNObjective, NSSDNScaling and NSSDNTransition are the categorical option aliases; and the four NSSDN...Error names are the error hierarchy. The ns_sdn.engine layers are private and are not re-exported.

References

Sunder, N., & Leon, M. (2026). Non-Stationary Spectral Decomposition Network: Adaptive Spectral Emission Heads and Frequency Modulation. IEEE ORSS.

Sunder, N. (2026). Non-Stationary Spectral Decomposition Network for Econometric Time Series Forecasting. FLAIRS-39.

Example

A chirp with a step in amplitude and a linear trend, fitted with a short schedule. The decomposition has one row per observation; the one-step fit has one fewer:

>>> import numpy as np
>>> from cultivars.experimental.spectral import NSSDN, NSSDNStage
>>> t = np.arange(300.0)
>>> amplitude = 0.4 + 0.3 * (t > 150)
>>> phase = np.cumsum(0.15 + 0.0004 * t)
>>> y = 0.002 * t + amplitude * np.sin(phase)
>>> res = NSSDN(y, k=3, state_dim=16, schedule=(NSSDNStage(1e-2, 150),), seed=0).fit()
>>> res.emission.shape, res.amplitude.shape, res.fittedvalues.shape
((300,), (300, 3), (299,))
>>> bool(np.allclose(res.trend + res.components.sum(axis=1), res.emission, atol=1e-5))
True
>>> res.forecast(5).shape
(5,)

Classes#

NSSDN

Non-Stationary Spectral Decomposition Network specification.

NSSDNInformationCriteria

NSSDNPenalties

NSSDNResult

A fitted spectral decomposition network.

NSSDNSpectralState

NSSDNStage

NSSDNSummaryTable

Exceptions#

NSSDNDimensionError

NSSDNError

Base class for every exception raised by ns-sdn.

NSSDNNumericalError

NSSDNSpecificationError

Type aliases and constants#