REN / R2DN Models¶
Sequence models with certificates that hold at every value of their free parameters.
Two architectures from the same lineage, both feedback interconnections of a linear system with a nonlinearity, and both certified by construction rather than by projection, penalty or post-hoc verification:
- :mod:
.core— the recurrent equilibrium network (:class:~.core.REN), whose nonlinearity is a layer of slope-restricted neurons with feedback onto itself. Contracting, Lipschitz and(Q,S,R)-dissipative variants. - :mod:
.r2dn— the robust recurrent deep network (:class:~.r2dn.R2DN), which drops that feedback and replaces the neurons with a 1-Lipschitz network (:mod:.lbdn). Same certificates, no equilibrium solve, and a certificate matrix whose size no longer grows with nonlinear capacity.
The static specs and the explicit-realization containers live in :mod:.common.
Both are laid out around one seam: the certificate construction runs once per forward and
produces a bundle of plain tensors, and the sequential rollout reads nothing else. The
(Q,S,R) variants and the fused rollout kernels attach on opposite sides of it without
meeting — a kernel that gets ∂L/∂A right needs no opinion about ∂L/∂X, and autograd
carries the rest.
The REN's rollout is sequential twice over, along the sequence and along the n_nl neurons,
so the naive loop is dispatch-bound by two orders of magnitude. The fused backends collapse a
whole rollout into one launch with a hand-derived BPTT backward (see MATH_REN.md):
- :mod:
.backend_c: generic scalar-templated C++ (float and double), batch-parallel; the fp64 gradcheck vehicle and the fast CPU path. - :mod:
.backend_triton: persistent per-trajectory GPU kernel, float32, within the config caps; the fast CUDA training path.
Both run behind the tsfast::ren_rollout / tsfast::ren_rollout_train /
tsfast::ren_rollout_bwd custom ops registered in :mod:.core and are selected through
:class:~.core.REN's backend argument (or the process-wide preference from
:func:tsfast.models.set_backend); each backend reports its own applicability via
supports(spec, u, x0).
The R2DN gets the same treatment in :mod:.r2dn_backend_triton (MATH_R2DN.md,
tsfast::r2dn_rollout*). Deleting the sweep makes its step cheap but leaves the launches
untouched, so eager it is dispatch-bound just as the REN is; fused, its cost is flat in
nonlinear capacity where the REN's still grows with n_nl, which is where the
architecture's scalability claim finally shows.
RENParameterization ¶
RENParameterization(spec: RENSpec, gamma: float = 1.0, qsr: tuple[Tensor, Tensor, Tensor] | None = None, eps: float = _EPS, polar: bool = True, init: str = 'long_memory')
Bases: Module
Free parameters of a REN and the direct construction of its explicit realization.
Every parameter here is unconstrained: the certificate is a property of the
construction, not of where the optimizer happens to be. forward runs the whole
construction — the XᵀX product, the partition of H, and the E⁻¹/Λ⁻¹
solves — and returns plain tensors.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
spec
|
RENSpec
|
static architecture description. |
required |
gamma
|
float
|
certified incremental |
1.0
|
qsr
|
tuple[Tensor, Tensor, Tensor] | None
|
|
None
|
eps
|
float
|
regularization floor on |
_EPS
|
polar
|
bool
|
use the polar parameterization |
True
|
init
|
str
|
|
'long_memory'
|
Source code in tsfast/models/architectures/ren/core.py
forward ¶
Build the explicit realization from the current free parameters.
Source code in tsfast/models/architectures/ren/core.py
hmatrix ¶
The certificate matrix H, positive definite for any parameter values.
Exposed because it is the guarantee itself: the storage matrix P = H33 and the
contraction LMI are read off its blocks.
Source code in tsfast/models/architectures/ren/core.py
qsr ¶
Supply-rate matrices (Q, S, R) in effect.
variant="lipschitz" is the special case Q = -I/γ, S = 0, R = γI, so
the certified gain is a runtime scalar rather than a stored matrix.
Source code in tsfast/models/architectures/ren/core.py
cache_key ¶
Identity of the current parameter values, for the inference-mode explicit cache.
RENCore ¶
Bases: Module
Explicit realization plus the sequential rollout over an input sequence.
Holds the free parameters (in :attr:parameterization) but evaluates only through
:class:~.common.ExplicitREN tensors, which is the seam a fused kernel would attach
to: the rollout has no opinion about how the matrices were certified.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
spec
|
RENSpec
|
static architecture description. |
required |
**kwargs
|
Any
|
forwarded to :class: |
{}
|
Source code in tsfast/models/architectures/ren/core.py
explicit ¶
The explicit realization, rebuilt on demand and cached while gradients are off.
The construction costs a few matrix products and two solves on (2nx+nv)-sized
matrices — irrelevant next to an L-step rollout during training, but worth
caching for repeated inference from fixed weights.
Source code in tsfast/models/architectures/ren/core.py
rollout ¶
Run the realization over u [B, L, n_input] from x0 [B, n_state].
Returns:
| Type | Description |
|---|---|
tuple[Tensor, Tensor]
|
|
Source code in tsfast/models/architectures/ren/core.py
REN ¶
REN(n_input: int, n_output: int, n_state: int = 8, n_nl: int = 32, variant: str = 'contracting', alpha: float = 1.0, gamma: float = 1.0, qsr: tuple[Tensor, Tensor, Tensor] | None = None, act: str = 'tanh', eps: float = _EPS, polar: bool = True, init: str = 'long_memory', backend: str = 'auto', return_state: bool = False)
Bases: Module
Recurrent equilibrium network: a sequence model that is contracting by construction.
Trained with plain SGD from any initialization, the model satisfies its certificate at every step of training, because the certificate is built into the map from free parameters to model matrices rather than enforced on top of it.
Three variants, differing only in how the certificate matrix is assembled:
"contracting": two trajectories under the same input converge at ratealpha."lipschitz": additionally‖y(u) - y(ũ)‖ ≤ gamma ‖u - ũ‖in truncatedℓ2from a common initial state.gammais a runtime scalar and may be reassigned."dissipative": additionally satisfies the incremental IQC given byqsr.
What the Lipschitz certificate buys, precisely: an input perturbation of energy δ
moves the output by at most gamma·δ, measured in ℓ2 over the horizon and
starting from the same state. It says nothing about model-vs-plant error — a REN with
gamma = 1 can be an arbitrarily bad model of a system, certified smooth and stable
rather than correct.
The rollout is irreducibly sequential twice over — once along the sequence, once along
the n_nl neurons of the equilibrium layer — so a naive Python loop is dispatch-bound
by a wide margin. Several backends implement the identical recurrence:
"eager": nested Python loop — the reference implementation, any device and dtype."c": generated C++ rollout with a fused BPTT backward, batch-parallel via the ATen thread pool — float32 and float64 on CPU, and thegradcheckvehicle."triton": persistent per-trajectory GPU kernel with a fused BPTT backward — float32 on CUDA, within the size caps itsfitsreports."compiled":torch.compileover the unrolled loop. Only usable on short sequences — the graph hasseq * n_nlnodes and compiles at roughly 0.2 s per node — so it is never selected implicitly."auto": defers to the process-wide preference (tsfast.models.set_backend/use_backend); under an"auto"preference pickstritonwhere it applies and eager elsewhere (select"c"explicitly to trade a one-time compilation for much faster CPU training). A"reference"preference forces the eager path everywhere.
All backends share the same parameters, so the backend can be switched at any time via
the backend attribute. The fused backends run as registered torch.library custom
ops with analytic-BPTT backward ops (MATH_REN.md), so they are loss-agnostic and
compose with torch.compile. They consume the explicit realization as plain tensors
and know nothing about the certificate, which is built in ordinary autograd above them.
Contraction is a prior about the plant, not free insurance, and it fits some systems
badly. On benchmarks/gate_ren.py at matched parameter count it wins clearly on
CascadedTanks (0.37 NRMSE against 0.60 for a GRU, and 0.10 against 0.23 with both
under FranSys), ties on Silverbox, and loses by roughly a quarter on WH —
where :class:~.r2dn.R2DN reaches 0.037 at the same budget and closes the gap, so that
loss is the equilibrium layer's rather than contraction's. It loses badly on EMPS, by
4.2x under FranSys — that plant is friction-dominated, and stick-slip is exactly the
behaviour a contraction certificate excludes, since trajectories in a stick phase do not
converge. Do not reach for this model on stick-slip or hysteretic systems; the
guarantee it offers is one those dynamics cannot satisfy in the first place.
gamma is not a free addition either. It costs nothing on WH (0.037, matching the
contracting model) but a third of the accuracy on CascadedTanks (0.43 against 0.37),
where the gain budget binds against dynamics that need the range. What it buys is a
certificate that is tight on trained models — gamma_empirical/gamma_certified in
0.37-0.84 across the suite — rather than the orders-of-magnitude slack typical of
post-hoc bounds on freely-trained networks.
Contraction makes the initial state self-correcting at rate alpha, so x0=None
(zeros) plus n_skip is usually enough. But the forgetting time ≈ 1/(1-alpha)
is the longest time constant the model can represent, so no alpha both forgets
x0 quickly and represents an integrator. For integrating plants (position from
velocity, tank level, thermal accumulation) use return_state=True and compose with
:class:~tsfast.prediction.fransys.FranSys, which estimates x0 from an (u, y)
window instead of asking the dynamics to forget it. Note that the Lipschitz bound is
stated for a fixed initial state and does not survive that composition.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n_input
|
int
|
exogenous input dimension. |
required |
n_output
|
int
|
observed output dimension. |
required |
n_state
|
int
|
state dimension. |
8
|
n_nl
|
int
|
neurons in the equilibrium layer; the model's nonlinear capacity. |
32
|
variant
|
str
|
|
'contracting'
|
alpha
|
float
|
contraction rate in |
1.0
|
gamma
|
float
|
certified incremental |
1.0
|
qsr
|
tuple[Tensor, Tensor, Tensor] | None
|
|
None
|
act
|
str
|
equilibrium-layer activation, one of |
'tanh'
|
eps
|
float
|
regularization floor on the certificate matrix. |
_EPS
|
polar
|
bool
|
use the polar parameterization of |
True
|
init
|
str
|
|
'long_memory'
|
backend
|
str
|
execution backend, see above. |
'auto'
|
return_state
|
bool
|
if |
False
|
Source code in tsfast/models/architectures/ren/core.py
gamma
property
writable
¶
Certified incremental ℓ2 gain; reassign to retune the certificate.
forward ¶
forward(u: Tensor, x0: Tensor | None = None, state: dict | None = None) -> Tensor | tuple[Tensor, dict]
Roll the certified dynamics over the input sequence.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
u
|
Tensor
|
input sequence |
required |
x0
|
Tensor | None
|
initial state |
None
|
state
|
dict | None
|
carried state |
None
|
Returns:
| Type | Description |
|---|---|
Tensor | tuple[Tensor, dict]
|
Output sequence |
Tensor | tuple[Tensor, dict]
|
or |
Source code in tsfast/models/architectures/ren/core.py
ExplicitSandwich
dataclass
¶
The tensors a sandwich layer evaluates, once the Cayley transform has been taken.
A and psi are absent on the output layer, which is a plain norm-bounded linear
map::
hidden: h ↦ √2 · A Ψ σ(√2 Ψ⁻¹ B h + bias) with ‖[Aᵀ; Bᵀ]‖ an isometry
output: h ↦ B h + bias with ‖B‖ ≤ 1
SandwichLayer ¶
Bases: Module
A layer that is 1-Lipschitz at every value of its free parameters.
The weights come from a Cayley transform of one unconstrained matrix, which makes
[Aᵀ; Bᵀ] an isometry, and the activation is sandwiched between Ψ and Ψ⁻¹.
The bound then follows from the activation being slope-restricted to [0, 1] rather
than from any norm product, so Ψ is free to rescale the units without spending gain
budget — which is what keeps a deep stack expressive.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n_in
|
int
|
input features. |
required |
n_out
|
int
|
output features. |
required |
act
|
str
|
activation name; must be monotone and slope-restricted to |
'relu'
|
is_output
|
bool
|
build the norm-bounded linear form instead, with no activation and no
|
False
|
Source code in tsfast/models/architectures/ren/lbdn.py
explicit ¶
Take the Cayley transform of the free parameters.
Source code in tsfast/models/architectures/ren/lbdn.py
forward ¶
Map h [..., n_in] to [..., n_out], from a prebuilt realization if given.
Source code in tsfast/models/architectures/ren/lbdn.py
LBDN ¶
LBDN(n_input: int, n_output: int, hidden: tuple[int, ...] = (64, 64), act: str = 'relu', gamma: float = 1.0)
Bases: Module
Feedforward network with a certified Lipschitz bound of gamma.
A drop-in replacement for an MLP whose incremental gain is a design parameter:
‖f(u) - f(ũ)‖ ≤ gamma ‖u - ũ‖ holds for every value of the free parameters, so
ordinary SGD cannot break it. The bound is on the map, not a bound on how well it fits.
gamma is a runtime scalar — reassign it and the next forward re-derives the network,
since it only rescales the two ends of the stack.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n_input
|
int
|
input features. |
required |
n_output
|
int
|
output features. |
required |
hidden
|
tuple[int, ...]
|
width of each hidden layer; its length is the number of nonlinear layers. |
(64, 64)
|
act
|
str
|
activation, one of |
'relu'
|
gamma
|
float
|
certified Lipschitz bound. |
1.0
|
Source code in tsfast/models/architectures/ren/lbdn.py
explicit ¶
Per-layer realizations, in evaluation order.
Identically shaped hidden layers — the usual case, and the only one a uniform width produces — take their Cayley transforms as a single batched call. The output layer has a different form and is always built on its own.
Source code in tsfast/models/architectures/ren/lbdn.py
forward ¶
Map h [..., n_input] to [..., n_output], from a prebuilt realization if given.
Source code in tsfast/models/architectures/ren/lbdn.py
R2DNSpec
dataclass
¶
R2DNSpec(n_state: int, n_input: int, n_output: int, n_nl: int, hidden: tuple[int, ...], variant: str, alpha: float, act: str)
Static description of an R2DN.
The certified gain gamma is deliberately not a field: like the REN's it is a runtime
scalar, so retuning it must not invalidate anything specialized on the spec.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n_state
|
int
|
state dimension |
required |
n_input
|
int
|
exogenous input dimension |
required |
n_output
|
int
|
observed output dimension |
required |
n_nl
|
int
|
width of the interconnection, i.e. of both |
required |
hidden
|
tuple[int, ...]
|
hidden widths of the 1-Lipschitz network; its length is the network's depth. |
required |
variant
|
str
|
|
required |
alpha
|
float
|
contraction rate |
required |
act
|
str
|
activation name; must be monotone and slope-restricted to |
required |
n_h
property
¶
Side length of the certificate matrix H: 2*n_state.
Independent of n_nl and of the network's depth, which is the scalability claim —
the REN's H is (2*n_state + n_nl)² and grows with nonlinear capacity.
ExplicitR2DN
dataclass
¶
ExplicitR2DN(A: Tensor, B1: Tensor, B2: Tensor, C1: Tensor, C2: Tensor, D12: Tensor, D21: Tensor, D22: Tensor, bx: Tensor, bv: Tensor, by: Tensor, net: tuple[ExplicitSandwich, ...])
The realization the rollout actually runs, as plain tensors.
Per sample, with φ the 1-Lipschitz network held in :attr:net::
w = φ(x C1ᵀ + u D12ᵀ + bv)
x⁺ = x Aᵀ + w B1ᵀ + u B2ᵀ + bx
y = x C2ᵀ + w D21ᵀ + u D22ᵀ + by
y observes the state before the update, so a rollout of length L from x0
returns y_0 .. y_{L-1} and carries x_L.
tensors
property
¶
The linear part, flattened in field order; excludes :attr:net.
R2DNParameterization ¶
R2DNParameterization(spec: R2DNSpec, gamma: float = 1.0, eps: float = _EPS, polar: bool = True, init: str = 'long_memory')
Bases: Module
Free parameters of an R2DN and the direct construction of its explicit realization.
Every parameter is unconstrained. The construction places the certificate's given
terms into H = XᵀX + εI + (given) so that the dissipation LMI it has to satisfy
reduces to XᵀX + εI ≻ 0 — true for any X — and then reads the realization off the
blocks of H. The free B1, B2 are the implicit input maps E B1, E B2,
since the LMI constrains those rather than the explicit ones.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
spec
|
R2DNSpec
|
static architecture description. |
required |
gamma
|
float
|
certified incremental |
1.0
|
eps
|
float
|
regularization floor on |
_EPS
|
polar
|
bool
|
use the polar parameterization |
True
|
init
|
str
|
|
'long_memory'
|
Source code in tsfast/models/architectures/ren/r2dn.py
forward ¶
Build the explicit realization from the current free parameters.
Source code in tsfast/models/architectures/ren/r2dn.py
hmatrix ¶
The certificate matrix H, positive definite for any parameter values.
Exposed because it is the guarantee itself: the storage matrix P = H22 and the
dissipation LMI are read off its blocks.
Source code in tsfast/models/architectures/ren/r2dn.py
cache_key ¶
Identity of the current parameter values, for the inference-mode explicit cache.
R2DNCore ¶
Bases: Module
Explicit realization plus the sequential rollout over an input sequence.
Holds the free parameters (in :attr:parameterization) but evaluates only through
:class:ExplicitR2DN tensors: the rollout has no opinion about how the matrices were
certified, and the 1-Lipschitz network's Cayley transforms are taken once per rollout
rather than once per timestep.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
spec
|
R2DNSpec
|
static architecture description. |
required |
**kwargs
|
Any
|
forwarded to :class: |
{}
|
Source code in tsfast/models/architectures/ren/r2dn.py
explicit ¶
The explicit realization, rebuilt on demand and cached while gradients are off.
The construction costs a few matrix products, one solve on a 2*n_state matrix and
one Cayley transform per network layer — irrelevant next to an L-step rollout
during training, but worth caching for repeated inference from fixed weights.
Source code in tsfast/models/architectures/ren/r2dn.py
rollout ¶
Run the realization over u [B, L, n_input] from x0 [B, n_state].
Returns:
| Type | Description |
|---|---|
tuple[Tensor, Tensor]
|
|
Source code in tsfast/models/architectures/ren/r2dn.py
R2DN ¶
R2DN(n_input: int, n_output: int, n_state: int = 8, n_nl: int = 32, depth: int = 2, variant: str = 'contracting', alpha: float = 1.0, gamma: float = 1.0, act: str = 'relu', eps: float = _EPS, polar: bool = True, init: str = 'long_memory', backend: str = 'auto', return_state: bool = False)
Bases: Module
Robust recurrent deep network: contracting by construction, with a deep nonlinearity.
Trained with plain SGD from any initialization, the model satisfies its certificate at every step of training, because the certificate is built into the map from free parameters to model matrices rather than enforced on top of it.
Two variants, differing only in how the certificate matrix is assembled:
"contracting": two trajectories under the same input converge at ratealpha."lipschitz": additionally‖y(u) - y(ũ)‖ ≤ gamma ‖u - ũ‖in truncatedℓ2from a common initial state.gammais a runtime scalar and may be reassigned. The paper's parameterization of this case covers the subset withD22 = 0, so there is no direct feedthrough from input to output; the nonlinearity still provides a static path throughD12andD21.
What the Lipschitz certificate buys, precisely: an input perturbation of energy δ
moves the output by at most gamma·δ, measured in ℓ2 over the horizon and starting
from the same state. It says nothing about model-vs-plant error — an R2DN with
gamma = 1 can be an arbitrarily bad model of a system, certified smooth and stable
rather than correct.
Against :class:~.core.REN, which certifies the same properties: nonlinear capacity
here is depth in a 1-Lipschitz network instead of width in an equilibrium layer, so a
step costs depth small GEMMs rather than a sequential sweep over n_nl neurons, and
the certificate matrix stays 2·n_state square however large the nonlinearity grows.
The REN's parameterization is the more general one — its equilibrium layer contains
multi-layer networks as special cases — but the sweep is what makes it expensive.
Both models are dispatch-bound before they are fused, and both have a kernel that fixes
that, so the comparison is between the fused paths. There the scalability claim holds:
this rollout's cost is flat in nonlinear capacity while the REN's grows with n_nl, so
at matched parameter count the two cross over around n_nl ≈ 24 and the R2DN is several
times faster beyond it — and slower below, where the REN's sweep is only a few neurons
long. benchmarks/benchmark_r2dn.py times both at matched parameter count.
The rollout is sequential along the sequence, so a Python loop is dispatch-bound at short
n_state:
"eager": the loop — any device and dtype."triton": persistent per-trajectory GPU kernel with a fused BPTT backward (MATH_R2DN.md) — float32 on CUDA, within the size caps itsfitsreports."compiled":torch.compileover the unrolled loop. Only usable on short sequences (the graph holdsseqcopies of the network), so it is never selected implicitly."auto": defers to the process-wide preference (tsfast.models.set_backend/use_backend); under an"auto"preference pickstritonwhere it applies and eager elsewhere. A"reference"preference forces the eager path everywhere.
All backends share the same parameters, so the backend can be switched at any time via
the backend attribute. There is no CPU kernel: a "c" request selects eager.
Contraction is a prior about the plant, not free insurance, and it fits some systems
badly: on friction-dominated plants the stick-slip phases are exactly what a contraction
certificate excludes, since trajectories in a stick phase do not converge. On
benchmarks/gate_ren.py that shows up on EMPS, where the best certified model
trails a GRU by 2.3x under FranSys — better than the REN manages there, but the same
verdict. Do not reach for either model on stick-slip or hysteretic plants.
Against the REN at matched parameter count the two are at parity overall, which is what
the paper reports: this model wins on WH (0.037 NRMSE against 0.045, closing the one
benchmark the REN loses to a GRU) and on EMPS under FranSys (0.058 against 0.106),
ties on Silverbox and on CascadedTanks under FranSys (0.097 both), and loses
on both integrating plants standalone (0.43 against 0.37, 0.36 against 0.28). Prefer it
when the nonlinearity has to be large, where its cost is flat in capacity and the REN's
is not; prefer the REN at small n_nl.
gamma is not a free addition. It costs nothing on WH (0.038 against 0.037
contracting) but a great deal on the integrating plants (0.61 against 0.43 on
CascadedTanks), where the budget binds against dynamics that need the range. Prescribe
it when something downstream consumes the bound, not by default. What it does buy is
honest: on trained models the certificate is tight, gamma_empirical/gamma_certified
landing in 0.48-0.83 across the suite — the same range the REN reaches, and nothing like
the orders-of-magnitude slack typical of post-hoc bounds on freely-trained networks.
Contraction makes the initial state self-correcting at rate alpha, so x0=None
(zeros) plus n_skip is usually enough. But the forgetting time ≈ 1/(1-alpha) is
the longest time constant the model can represent, so no alpha both forgets x0
quickly and represents an integrator. For integrating plants (position from velocity,
tank level, thermal accumulation) use return_state=True and compose with
:class:~tsfast.prediction.fransys.FranSys, which estimates x0 from an (u, y)
window instead of asking the dynamics to forget it. Note that the Lipschitz bound is
stated for a fixed initial state and does not survive that composition.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n_input
|
int
|
exogenous input dimension. |
required |
n_output
|
int
|
observed output dimension. |
required |
n_state
|
int
|
state dimension. |
8
|
n_nl
|
int
|
width of the interconnection with the nonlinearity, and of its hidden layers. |
32
|
depth
|
int
|
number of nonlinear layers in the 1-Lipschitz network; at least |
2
|
variant
|
str
|
|
'contracting'
|
alpha
|
float
|
contraction rate in |
1.0
|
gamma
|
float
|
certified incremental |
1.0
|
act
|
str
|
activation of the 1-Lipschitz network, one of |
'relu'
|
eps
|
float
|
regularization floor on the certificate matrix. |
_EPS
|
polar
|
bool
|
use the polar parameterization of |
True
|
init
|
str
|
|
'long_memory'
|
backend
|
str
|
execution backend, see above. |
'auto'
|
return_state
|
bool
|
if |
False
|
Source code in tsfast/models/architectures/ren/r2dn.py
gamma
property
writable
¶
Certified incremental ℓ2 gain; reassign to retune the certificate.
forward ¶
forward(u: Tensor, x0: Tensor | None = None, state: dict | None = None) -> Tensor | tuple[Tensor, dict]
Roll the certified dynamics over the input sequence.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
u
|
Tensor
|
input sequence |
required |
x0
|
Tensor | None
|
initial state |
None
|
state
|
dict | None
|
carried state |
None
|
Returns:
| Type | Description |
|---|---|
Tensor | tuple[Tensor, dict]
|
Output sequence |
Tensor | tuple[Tensor, dict]
|
or |
Source code in tsfast/models/architectures/ren/r2dn.py
equilibrium_sweep ¶
Solve w = act(w D11ᵀ + b) by forward substitution over the neurons.
D11 is strictly lower triangular by construction, so neuron i depends only on
0 .. i-1 and the equilibrium resolves exactly in one sweep. Each step is a rank-1
update of the pending pre-activations, which is why the loop costs two tensor ops per
neuron rather than a growing matmul.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
b
|
Tensor
|
input-and-state part of the pre-activation |
required |
d11
|
Tensor
|
strictly lower triangular feedback |
required |
act
|
Callable[[Tensor], Tensor]
|
the activation, monotone and slope-restricted to |
required |
Source code in tsfast/models/architectures/ren/core.py
fused_rollout ¶
Run the rollout through the fused-kernel custom ops (autograd-capable).
Picks the training op (which stores the states and equilibrium activations for the analytic BPTT backward) when gradients are live, else the inference op, which keeps no intermediates.
Returns:
| Type | Description |
|---|---|
tuple[Tensor, Tensor]
|
|
Source code in tsfast/models/architectures/ren/core.py
lbdn_forward ¶
lbdn_forward(layers: tuple[ExplicitSandwich, ...], h: Tensor, act: Callable, gamma: float = 1.0) -> Tensor
Evaluate an LBDN from its explicit realization alone.
Separate from :class:LBDN so a caller that builds the realization once — a rollout
reusing it at every timestep — never pays for the Cayley transforms again, and so the
evaluation depends on plain tensors rather than on module state.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layers
|
tuple[ExplicitSandwich, ...]
|
per-layer realizations; all but the last are the nonlinear form. |
required |
h
|
Tensor
|
input |
required |
act
|
Callable
|
the activation, monotone and slope-restricted to |
required |
gamma
|
float
|
Lipschitz bound, applied as |
1.0
|
Source code in tsfast/models/architectures/ren/lbdn.py
folded_weights ¶
The network collapsed to one matrix and one bias per layer, in fused-kernel order.
A hidden layer's √2 A Ψ σ(√2 Ψ⁻¹ B h + c) is V σ(W h + c) with W = √2 Ψ⁻¹ B
and V = √2 A Ψ. Every factor there is constant across a rollout, so this folds them
once per call — and then folds each V into the following layer's W, since
W_{l+1}(V_l a_l) = (W_{l+1} V_l) a_l. What reaches a kernel is one matrix per layer:
half the register footprint and, more to the point, half the dependent cross-lane
reductions per timestep, which is what a sequential rollout is actually bound by
(MATH_R2DN.md §1). Autograd carries ∂L/∂B, ∂L/∂ψ and ∂L/∂A back through
the composition.
Returns:
| Type | Description |
|---|---|
list[Tensor]
|
|