Quickstart#

Every model keeps its reservoirs fixed after random initialization and trains only a linear ridge readout. You feed a series, choose a horizon, and fit.

The class hierarchy at a glance#

BaseESN                    single reservoir + ridge readout
MultiESN                   N reservoirs, one input each   -> fit([X1, ..., Xn], y)
  └─ DoubleReservoirESN    fixed to two reservoirs        -> fit([X1, X2], y)
       ├─ fESN    memory reservoir <- ((1-B)^d - 1) u(t)
       └─ wESN    memory reservoir <- ((1-B)^d - 1) MODWT(u)

fESN, fractional memory#

import numpy as np
from memory_esn import fESN

u = np.cumsum(np.random.randn(600)).reshape(-1, 1) * 0.1     # raw series u(t)
Y = np.column_stack([np.roll(u[:, 0], -h) for h in (1, 2, 3)])   # H = 3 horizons

fesn = fESN(
    n_reservoir=(200, 150),   # (vanilla p, memory q)
    d=0.4, K=80,              # fractional order, filter lags
    spectral_radius=0.9,      # echo-state property (< 1)
    sparsity=0.9,             # 90% sparse reservoirs
    noise=(0.0, 0.01),        # optional per-reservoir state noise sigma
    random_state=0,
)
fesn.fit(u, Y, washout=100)
preds = fesn.predict(u, continuation=True)     # (T, H)

d may be a list, several differencing orders stacked as a multivariate memory input:

fESN(d=[0.3, 0.5, 0.8])   # 3 memory channels per feature

wESN, wavelet-smoothed memory#

from memory_esn import wESN

wesn = wESN(
    n_reservoir=(200, 150),
    d=0.25, K=100,
    wavelet="db4", wavelet_level=2,        # MODWT smoothing
    wavelet_components="smooth",           # which components feed reservoir 2
    wavelet_norm="modwt",                  # 'modwt' (Percival-Walden) | 'classic'
    random_state=0,
)
wesn.fit(u, Y, washout=100)
preds = wesn.predict(u, continuation=True)

Select any detail level(s) and/or the smooth; multiple selections stack into a multivariate input:

wESN(wavelet_level=3, wavelet_components=[1, 2, "smooth"])   # D1, D2, A3

Alignment & continuation#

The fractional filter needs K past samples. With skip_initial=True (default) and continuation=False, predictions are K steps shorter than the input. With continuation=True the model prepends stored history so the output matches the input length. This is the normal mode for streaming / iterative forecasting. wESN.predict uses continuation=True by default (it also re-smooths using training history to avoid wavelet boundary effects).

Persistence#

fesn.save("model.joblib")
from memory_esn import fESN
fesn = fESN.load("model.joblib")

Data splitting#

TimeSeriesDataset builds sliding-window train/val/test splits with leakage-free scaling (scaling parameters are fit on the train split only):

from memory_esn import TimeSeriesDataset

ds = TimeSeriesDataset(series, lookback=20, lookahead=5, test_size=50, scaling="standard")
X_train, y_train = ds.get_full_train_data()
X_test,  y_test  = ds.get_test_data()