DynoNet Models¶
dynoNet linear-dynamical-operator model, with its private fused all-pole kernel.
The model lives in :mod:.core; :mod:.allpole_triton is the fused all-pole IIR
denominator kernel it dispatches to directly.
LinearDynamicalOperator ¶
LinearDynamicalOperator(in_channels: int, out_channels: int, nb: int = 8, na: int = 2, backend: str = 'scan')
Bases: Module
MIMO bank of learnable rational transfer functions G(q) = B(q) / A(q) (dynoNet G-block).
Each (output, input) channel pair owns an independent SISO filter with nb numerator
taps b_0 .. b_{nb-1} and na monic-denominator coefficients a_1 .. a_na; output
channels sum the filtered contributions of all inputs. The numerator is a grouped causal
convolution; the denominator recurrence runs in state-space (companion) form through
linear_recurrence, so the whole operator is exact and sequence-parallel.
Coefficients are unconstrained as in Forgione & Piga (2021, arXiv:2006.02250; full
citation on DynoNet): b starts small and random, a starts at zero (all poles
at the origin — a pure FIR filter), so the operator is stable at initialization but
poles may leave the unit circle during training.
The internal pair flattening is input-major (index = j_in * out_channels + i_out),
forced by conv1d group semantics; every reshape below relies on this ordering.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
in_channels
|
int
|
number of input signals. |
required |
out_channels
|
int
|
number of output signals. |
required |
nb
|
int
|
number of numerator (FIR) taps per filter. |
8
|
na
|
int
|
denominator order per filter; |
2
|
backend
|
str
|
|
'scan'
|
Source code in tsfast/models/architectures/dynonet/core.py
forward ¶
forward(u: Tensor, state: dict | None = None, return_state: bool = False) -> torch.Tensor | tuple[torch.Tensor, dict]
Filter the input sequence through all channel pairs and sum over inputs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
u
|
Tensor
|
input sequence |
required |
state
|
dict | None
|
carried filter state |
None
|
return_state
|
bool
|
if |
False
|
Returns:
| Type | Description |
|---|---|
Tensor | tuple[Tensor, dict]
|
Output sequence |
Source code in tsfast/models/architectures/dynonet/core.py
DynoNet ¶
DynoNet(input_size: int, output_size: int, n_channels: int = 8, nb: int = 8, na: int = 2, hidden_size: int = 32, hidden_layers: int = 1, act: type[Module] = nn.Tanh, bypass: bool = True, backend: str = 'scan', return_state: bool = False)
Bases: Module
dynoNet: linear transfer-function blocks G interconnected with a static nonlinearity F.
Wiener-Hammerstein-like structure G1 -> F -> G2 with an optional parallel linear
bypass path, the canonical architecture of Forgione & Piga (2021). F is a pointwise
MLP (memoryless), so all dynamics live in the LinearDynamicalOperator blocks.
With return_state=True the model follows the stateful-model protocol
(forward(u, state=...) -> (out, state)); the carried state holds each G-block's FIR
tail and IIR states, so chunked rollouts are exactly equivalent to the full sequence and
TbpttLearner works unchanged. Initial conditions are zero unless state is passed.
References
M. Forgione and D. Piga, "dynoNet: A neural network architecture for learning dynamical systems," International Journal of Adaptive Control and Signal Processing, 35(4):612-626, 2021. arXiv:2006.02250.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
input_size
|
int
|
number of input signals. |
required |
output_size
|
int
|
number of output signals. |
required |
n_channels
|
int
|
signal width between the blocks. |
8
|
nb
|
int
|
numerator taps per filter in every G-block. |
8
|
na
|
int
|
denominator order per filter in every G-block. |
2
|
hidden_size
|
int
|
hidden width of the static nonlinearity MLP. |
32
|
hidden_layers
|
int
|
number of hidden layers of the static nonlinearity MLP. |
1
|
act
|
type[Module]
|
activation class of the static nonlinearity MLP. |
Tanh
|
bypass
|
bool
|
add a parallel linear path |
True
|
backend
|
str
|
execution backend of the G-blocks, see |
'scan'
|
return_state
|
bool
|
if |
False
|
Source code in tsfast/models/architectures/dynonet/core.py
forward ¶
Run the block interconnection over the input sequence.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
u
|
Tensor
|
input sequence |
required |
state
|
dict | None
|
carried state |
None
|
Returns:
| Type | Description |
|---|---|
Tensor | tuple[Tensor, dict]
|
Output sequence |
Tensor | tuple[Tensor, dict]
|
|
Source code in tsfast/models/architectures/dynonet/core.py
linear_recurrence ¶
Compute the linear recurrence x_t = A x_{t-1} + v_t with constant A via a log-doubling scan.
Because A is constant along the sequence, the recurrence is a prefix sum
x_t = A^t x_0 + sum_k A^(t-k) v_k that parallelizes exactly: each doubling step extends
the summation window by a factor of two using one batched matmul over the whole sequence,
so the sequential depth is ceil(log2(L)) instead of L. Exact for any spectral radius
of A. Gradients come from the analytic matrix adjoint (the reverse-time scan
G_t = A^T G_{t+1} + g_t) rather than autograd replay through the doubling levels, so
backward memory is O(L) instead of the O(L log L) the levels would retain. Real dtypes only.
Runs as the tsfast::linear_recurrence custom op, so it composes with torch.compile.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
A
|
Tensor
|
transition matrices |
required |
v
|
Tensor
|
input sequence |
required |
x0
|
Tensor | None
|
initial state |
None
|
Returns:
| Type | Description |
|---|---|
Tensor
|
States |