Reference
Metrics¶
Forecast-quality and dynamical metrics for reservoir models, with first-class support for reporting horizons in Lyapunov times — the natural clock of a chaotic attractor — rather than raw integration steps. A valid-prediction time in Lyapunov times (\(\text{VPT}_{\Lambda t} = \text{steps} \cdot dt \cdot \lambda_\mathrm{max}\)) is hardware- and integration-step-independent, so it is comparable across systems and papers.
The conversions, the canonical \(\lambda_\mathrm{max}\) tables, valid_prediction_time,
and reservoir_lyapunov are pure PyTorch/NumPy and always available. The
series- and System-based paths of largest_lyapunov need the optional
resdag[dynamics] extra (tsdynamics, Python ≥ 3.12) — see
Work · Lyapunov time for the prose walkthrough.
Forecast accuracy¶
metrics
¶
Reservoir-computing metrics¶
Forecast-quality and dynamical metrics for reservoir models, with first-class support for reporting horizons in Lyapunov times — the natural clock of a chaotic attractor — instead of raw integration steps.
Lyapunov bookkeeping
lyapunov_time, steps_per_lyapunov_time, steps_to_lyapunov, lyapunov_to_steps
Pure, dependency-free conversions between step counts and Lyapunov times.
CANONICAL_LAMBDA_MAX, MACKEY_GLASS_LAMBDA
Literature largest-Lyapunov-exponent tables for resdag's built-in
generators.
largest_lyapunov
Estimate the largest Lyapunov exponent of a generator name (canonical),
a measured series, or a tsdynamics System.
reservoir_lyapunov
Native-torch Benettin/QR estimate of a trained reservoir's own maximal
Lyapunov exponent (an edge-of-chaos / Echo-State-Property diagnostic).
Forecast accuracy
valid_prediction_time Contiguous valid forecast horizon (NRMSE below a threshold), reportable in steps or Lyapunov times.
Optional dependency
The series- and System-based Lyapunov estimators require the optional
tsdynamics package (pip install resdag[dynamics], Python >= 3.12);
every tsdynamics import is lazy and guarded. Everything else — the
conversions, the canonical constants, the reservoir Jacobian estimate, and the
forecast metrics — is pure PyTorch/NumPy and always available.
Examples:
>>> from resdag.metrics import steps_to_lyapunov, valid_prediction_time
>>> round(steps_to_lyapunov(357, dt=0.02, lambda_max=0.9056), 2)
6.47
valid_prediction_time
¶
valid_prediction_time(pred: Tensor, truth: Tensor, *, threshold: float = 0.3, metric: str = 'nrmse', units: str = 'steps', dt: float | None = None, lambda_max: float | None = None, dataset: str | None = None, **kw: float) -> float
Valid Prediction Time: contiguous steps a forecast stays accurate.
Per-step error is the NRMSE — the RMSE across feature channels, each
channel normalised by the per-dimension standard deviation of truth over
the (batch, time) axes — reduced across the batch with the median.
The VPT is the number of steps, counting from the start, whose error stays
strictly below threshold (0 if the very first step is already over).
This matches :func:resdag.hpo.losses.forecast_horizon exactly (that copy
is kept out of the optional-HPO import path); see the module docstring.
| PARAMETER | DESCRIPTION |
|---|---|
pred
|
Forecast and ground truth of shape
TYPE:
|
truth
|
Forecast and ground truth of shape
TYPE:
|
threshold
|
NRMSE threshold below which a step counts as "valid".
TYPE:
|
metric
|
Per-step error metric. Only NRMSE is implemented (the reservoir-computing standard); the argument exists for forward compatibility.
TYPE:
|
units
|
Return the horizon in raw steps, or converted to Lyapunov times
(
TYPE:
|
dt
|
Integration step, required for
TYPE:
|
lambda_max
|
Largest Lyapunov exponent, required for
TYPE:
|
dataset
|
A built-in generator name (
TYPE:
|
**kw
|
Extra keys forwarded to inference;
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
float
|
The VPT: an integer step count as a |
| RAISES | DESCRIPTION |
|---|---|
ValueError
|
If |
Examples:
>>> import torch
>>> pred = torch.zeros(1, 100, 3)
>>> truth = torch.zeros(1, 100, 3)
>>> valid_prediction_time(pred, truth) # identical -> full horizon
100.0
Source code in src/resdag/metrics/forecast.py
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 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 | |
Lyapunov-time conversions¶
metrics
¶
Reservoir-computing metrics¶
Forecast-quality and dynamical metrics for reservoir models, with first-class support for reporting horizons in Lyapunov times — the natural clock of a chaotic attractor — instead of raw integration steps.
Lyapunov bookkeeping
lyapunov_time, steps_per_lyapunov_time, steps_to_lyapunov, lyapunov_to_steps
Pure, dependency-free conversions between step counts and Lyapunov times.
CANONICAL_LAMBDA_MAX, MACKEY_GLASS_LAMBDA
Literature largest-Lyapunov-exponent tables for resdag's built-in
generators.
largest_lyapunov
Estimate the largest Lyapunov exponent of a generator name (canonical),
a measured series, or a tsdynamics System.
reservoir_lyapunov
Native-torch Benettin/QR estimate of a trained reservoir's own maximal
Lyapunov exponent (an edge-of-chaos / Echo-State-Property diagnostic).
Forecast accuracy
valid_prediction_time Contiguous valid forecast horizon (NRMSE below a threshold), reportable in steps or Lyapunov times.
Optional dependency
The series- and System-based Lyapunov estimators require the optional
tsdynamics package (pip install resdag[dynamics], Python >= 3.12);
every tsdynamics import is lazy and guarded. Everything else — the
conversions, the canonical constants, the reservoir Jacobian estimate, and the
forecast metrics — is pure PyTorch/NumPy and always available.
Examples:
>>> from resdag.metrics import steps_to_lyapunov, valid_prediction_time
>>> round(steps_to_lyapunov(357, dt=0.02, lambda_max=0.9056), 2)
6.47
lyapunov_time
¶
Return the Lyapunov time :math:t_\lambda = 1 / \lambda_\mathrm{max}.
One Lyapunov time is the characteristic timescale over which infinitesimally
close trajectories of a chaotic system separate by a factor of :math:e.
| PARAMETER | DESCRIPTION |
|---|---|
lambda_max
|
Largest Lyapunov exponent (must be strictly positive for a chaotic system).
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
float
|
The Lyapunov time :math: |
| RAISES | DESCRIPTION |
|---|---|
ValueError
|
If |
Source code in src/resdag/metrics/lyapunov.py
steps_per_lyapunov_time
¶
Return the number of integration steps in one Lyapunov time.
A convenience for turning a horizon-in-steps into a horizon-in-Lyapunov-times (divide the step count by this) and vice versa.
| PARAMETER | DESCRIPTION |
|---|---|
dt
|
Integration step (time between consecutive samples).
TYPE:
|
lambda_max
|
Largest Lyapunov exponent (strictly positive).
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
float
|
|
| RAISES | DESCRIPTION |
|---|---|
ValueError
|
If |
Source code in src/resdag/metrics/lyapunov.py
steps_to_lyapunov
¶
Convert a step count to Lyapunov times :math:\Lambda t.
This is the headline conversion: VPT_in_Lyapunov = steps * dt *
lambda_max.
| PARAMETER | DESCRIPTION |
|---|---|
steps
|
Number of integration steps (e.g. a valid-prediction time).
TYPE:
|
dt
|
Integration step.
TYPE:
|
lambda_max
|
Largest Lyapunov exponent (strictly positive).
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
float
|
The horizon expressed in Lyapunov times, |
| RAISES | DESCRIPTION |
|---|---|
ValueError
|
If |
Examples:
A Lorenz-63 valid-prediction time of 357 steps at dt = 0.02:
Source code in src/resdag/metrics/lyapunov.py
lyapunov_to_steps
¶
Convert a horizon in Lyapunov times back to a whole number of steps.
The inverse of :func:steps_to_lyapunov, rounded to the nearest integer
step (horizons are discrete step counts).
| PARAMETER | DESCRIPTION |
|---|---|
lyap
|
Horizon in Lyapunov times :math:
TYPE:
|
dt
|
Integration step.
TYPE:
|
lambda_max
|
Largest Lyapunov exponent (strictly positive).
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
int
|
|
| RAISES | DESCRIPTION |
|---|---|
ValueError
|
If |
Source code in src/resdag/metrics/lyapunov.py
Canonical exponents¶
metrics
¶
Reservoir-computing metrics¶
Forecast-quality and dynamical metrics for reservoir models, with first-class support for reporting horizons in Lyapunov times — the natural clock of a chaotic attractor — instead of raw integration steps.
Lyapunov bookkeeping
lyapunov_time, steps_per_lyapunov_time, steps_to_lyapunov, lyapunov_to_steps
Pure, dependency-free conversions between step counts and Lyapunov times.
CANONICAL_LAMBDA_MAX, MACKEY_GLASS_LAMBDA
Literature largest-Lyapunov-exponent tables for resdag's built-in
generators.
largest_lyapunov
Estimate the largest Lyapunov exponent of a generator name (canonical),
a measured series, or a tsdynamics System.
reservoir_lyapunov
Native-torch Benettin/QR estimate of a trained reservoir's own maximal
Lyapunov exponent (an edge-of-chaos / Echo-State-Property diagnostic).
Forecast accuracy
valid_prediction_time Contiguous valid forecast horizon (NRMSE below a threshold), reportable in steps or Lyapunov times.
Optional dependency
The series- and System-based Lyapunov estimators require the optional
tsdynamics package (pip install resdag[dynamics], Python >= 3.12);
every tsdynamics import is lazy and guarded. Everything else — the
conversions, the canonical constants, the reservoir Jacobian estimate, and the
forecast metrics — is pure PyTorch/NumPy and always available.
Examples:
Estimators¶
metrics
¶
Reservoir-computing metrics¶
Forecast-quality and dynamical metrics for reservoir models, with first-class support for reporting horizons in Lyapunov times — the natural clock of a chaotic attractor — instead of raw integration steps.
Lyapunov bookkeeping
lyapunov_time, steps_per_lyapunov_time, steps_to_lyapunov, lyapunov_to_steps
Pure, dependency-free conversions between step counts and Lyapunov times.
CANONICAL_LAMBDA_MAX, MACKEY_GLASS_LAMBDA
Literature largest-Lyapunov-exponent tables for resdag's built-in
generators.
largest_lyapunov
Estimate the largest Lyapunov exponent of a generator name (canonical),
a measured series, or a tsdynamics System.
reservoir_lyapunov
Native-torch Benettin/QR estimate of a trained reservoir's own maximal
Lyapunov exponent (an edge-of-chaos / Echo-State-Property diagnostic).
Forecast accuracy
valid_prediction_time Contiguous valid forecast horizon (NRMSE below a threshold), reportable in steps or Lyapunov times.
Optional dependency
The series- and System-based Lyapunov estimators require the optional
tsdynamics package (pip install resdag[dynamics], Python >= 3.12);
every tsdynamics import is lazy and guarded. Everything else — the
conversions, the canonical constants, the reservoir Jacobian estimate, and the
forecast metrics — is pure PyTorch/NumPy and always available.
Examples:
>>> from resdag.metrics import steps_to_lyapunov, valid_prediction_time
>>> round(steps_to_lyapunov(357, dt=0.02, lambda_max=0.9056), 2)
6.47
largest_lyapunov
¶
largest_lyapunov(source: Any, *, dt: float | None = None, from_: str | None = None, **kw: Any) -> float
Estimate the largest Lyapunov exponent of source.
A single entry point that dispatches on the type of source:
- generator name (:class:
str, e.g."lorenz","mackey_glass") — returns the canonical literature value from :data:CANONICAL_LAMBDA_MAX/ :data:MACKEY_GLASS_LAMBDA(keyed bytau=for Mackey-Glass). No dependency. - time series (:class:
numpy.ndarray/ :class:torch.Tensor) — a diagnostic estimate viatsdynamics.lyapunov_from_data. Data-driven exponents are noisy; you must opt in withfrom_="data"so a series is never silently mistaken for accurate. Needsresdag[dynamics]. - tsdynamics System — a high-accuracy Benettin estimate via
tsdynamics.lyapunov_spectrum. Needsresdag[dynamics].
| PARAMETER | DESCRIPTION |
|---|---|
source
|
What to estimate the exponent of; see above.
TYPE:
|
dt
|
Integration step. Required for the series path; forwarded to the System path if given. Ignored for a generator name.
TYPE:
|
from_
|
Set to
TYPE:
|
**kw
|
Extra arguments:
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
float
|
The estimated largest Lyapunov exponent (inverse natural-time units). |
| RAISES | DESCRIPTION |
|---|---|
ImportError
|
If a |
TypeError
|
If an array is passed without |
KeyError
|
If a generator name has no canonical value. |
Examples:
Source code in src/resdag/metrics/lyapunov.py
350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 | |
reservoir_lyapunov
¶
reservoir_lyapunov(source: Any, warmup_inputs: Tensor | tuple[Tensor, ...], *, horizon: int = 1000, k: int = 1, transient: int = 100, seed: int | None = 0) -> float | ndarray
Largest (or top-k) Lyapunov exponent of a driven ESN reservoir.
Estimates the maximal Lyapunov exponent of the reservoir's own dynamics via Benettin's algorithm with QR reorthonormalisation of the analytic tangent map, iterated along an input-driven reservoir trajectory. This is a standard reservoir-computing quality / edge-of-chaos diagnostic: a healthy ESN sits just below the edge, with a maximal exponent that is negative but close to zero (the Echo State Property requires the driven dynamics to contract, so trajectories from different initial states converge).
The reservoir map is the leaky-ESN update
.. math::
h_t = (1-\alpha) h_{t-1} + \alpha\, f(W_\mathrm{fb} x_t
+ W_\mathrm{in} u_t + W_\mathrm{hh} h_{t-1} + b),
whose Jacobian with respect to the state is
.. math::
J_t = (1-\alpha) I + \alpha\, \mathrm{diag}\!\big(f'(\mathrm{pre}_t)\big)\, W_\mathrm{hh},
with :math:f' = 1 - \tanh^2 for the (default) tanh activation. The
driving inputs come from warmup_inputs; the trajectory is looped over
them if horizon exceeds their length, so a short synchronisation window
still yields a long tangent-space average.
| PARAMETER | DESCRIPTION |
|---|---|
source
|
The reservoir to analyse, or a model containing exactly one ESN reservoir.
TYPE:
|
warmup_inputs
|
Driving inputs of shape
TYPE:
|
horizon
|
Number of tangent-map iterations (after the transient). Longer gives a tighter estimate.
TYPE:
|
k
|
Number of leading exponents to return.
TYPE:
|
transient
|
Leading steps whose tangent growth is discarded so the estimate reflects the attractor rather than the initial state. Clamped so at least a few accumulation steps remain.
TYPE:
|
seed
|
Seed for the random initial tangent frame. Defaults to
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
float or ndarray
|
The maximal Lyapunov exponent ( |
| RAISES | DESCRIPTION |
|---|---|
ValueError
|
If |
NotImplementedError
|
If the reservoir activation is not |
Notes
Exponents are per reservoir step. Multiply by 1/dt to compare with
a natural-time attractor exponent from :data:CANONICAL_LAMBDA_MAX.
Examples:
>>> import torch
>>> from resdag.layers import ESNLayer
>>> res = ESNLayer(100, feedback_size=3, spectral_radius=0.9, seed=0)
>>> feedback = torch.randn(1, 300, 3)
>>> lam = reservoir_lyapunov(res, feedback, horizon=500)
>>> lam < 0.0 # a stable, ESP-satisfying reservoir contracts
True
Source code in src/resdag/metrics/lyapunov.py
476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 | |