Scientific ML · E40 · Implementation

Infer a state-space model’s poles before fitting its readout

Delay embeddings and a reduced eigensystem provide one route from observed dynamics to a compact recurrent feature bank.

PyTorch complex tensorsSVD / eigvalsRidge solve
Stable discrete-time poles determine recurrent modes. The pole locations shown are illustrative, not estimated values from a particular run.
Figure 1. Dynamics live in the poles. Stable discrete-time poles determine recurrent modes. The pole locations shown are illustrative, not estimated values from a particular run. Illustrative z-plane; not inferred poles. Original vector illustration.

Follow the information

From input to outcome

Training sequences determine the reduced dynamics and pole initialization. The recurrence then generates real/imaginary features for a fitted readout. It is not an exact identification guarantee for arbitrary nonlinear dynamics.

Training sequences determine the reduced dynamics and pole initialization. The recurrence then generates real/imaginary features for a fitted readout. It is not an exact identification guarantee for arbitrary nonlinear dynamics.
Figure 2. Information flow. Solid arrows carry observations, tensors or artifacts; other routes are explicitly labelled. Signal shapes, matrices and network icons are schematic, not measured samples or literal neuron counts. Open full-size SVG ↗ On narrow screens, scroll the diagram horizontally.

Read this alongside Figure 1: Stable discrete-time poles determine recurrent modes. The pole locations shown are illustrative, not estimated values from a particular run. The module map and layer-level figures below expand the operations in this route.

Infer a state-space model’s poles before fitting its readout: architectureTraining output sequences: Observed dynamics → Hankel snapshot pairs: Delayed X₀ / X₁ → Truncated SVD: Reduced coordinate system → DMD eigenvalues: Projected stable poles → Complex recurrence: Real / imaginary features → Ridge readout: Predict output channels. A high-level module map; comparison branches and training details are explained in the article.SCIENTIFIC ML / E40 / MODULE MAP01 INPUTTraining output sequencesObserved dynamics02 MODULEHankel snapshot pairsDelayed X₀ / X₁03 MODULETruncated SVDReduced coordinate system04 MODULEDMD eigenvaluesProjected stable poles05 MODULEComplex recurrenceReal / imaginary features06 OUTPUTRidge readoutPredict output channels
Source-grounded module map. Boxes summarize operations, not individual neurons; comparison arms and training paths are detailed below. On a small screen, scroll the diagram horizontally.
Training output sequences — Observed dynamics

The architecture in context

The system we are building

The state-space experiment represents memory through diagonal complex recurrences. Rather than choosing every pole randomly or learning all poles by gradient descent, one arm estimates them from delayed output snapshots using dynamic mode decomposition. The resulting recurrent features feed a ridge readout trained against the observed outputs.

Who does what in the stack

PyTorch complex tensors
Implement diagonal recurrent states.
SVD / eigvals
Identify a reduced dynamics operator.
Ridge solve
Fits output channels on a fixed recurrent representation.

The project connects output-based pole estimation to the same feature/readout interface used by generic and gradient-trained controls. Complex arithmetic represents oscillation and decay together. The fitted poles are inferred from training observations, not supplied as an oracle in this arm.

Framework responsibility map. Each row maps a library or custom component to its job; rows are not a sequential inference graph.
Framework responsibility map. Each row maps a library or custom component to its job; rows are not a sequential inference graph. Open full-size SVG ↗

From module map to executable structure

Inside Complex diagonal state-space model

Default 64 complex states, input 1/output 2, train length 96/test 192;1,500 training and 400 test sequences.

Layer-level implementation. B denotes batch size; parameter and shape conventions are expanded in the table.
Layer-level implementation. B denotes batch size; parameter and shape conventions are expanded in the table. Open full-size SVG ↗
Layer / tensor / operation ledger
Layer or branchOutput shapeImplementation detail
Input projectionB × N complexFixed projection P maps d input channels to N modes.
Diagonal recurrenceB × N complexh[t]=p*h[t−1]+u[t]P; one multiplier per mode.
Real feature vectorB × 2NConcatenate real and imaginary state components.
Ridge readoutB × doutFit linear output coefficients on training states.
Alternative pole estimatorsN complex polesFixed pole families, learned poles or Hankel/DMD inference are separate arms.

The diagonal transition replaces a dense N×N state multiply with N elementwise products. Complex conjugate structure or real/imaginary expansion represents damped oscillation. A state model can be cheap to advance and still be poorly identified; pole radius, input projection and observability determine whether the output is useful.

The equation and the update

ht=diag⁡(p)ht−1+PTut,y^t=WT[ℜht;ℑht]h_t=\operatorname{diag}(p)h_{t-1}+P^Tu_t,\qquad \widehat y_t=W^T[\Re h_t;\Im h_t]

Default learned-pole arm 600 Adam steps at 5e-3; three seeds. DMD and fixed-pole arms do not use that optimizer budget. Known-system poles in a matched arm supply privileged information.

Learning or solution path. A parameter-update path is different from the forward inference path; see text for target-network, frozen-feature and local-loss boundaries.
Learning or solution path. A parameter-update path is different from the forward inference path; see text for target-network, frozen-feature and local-loss boundaries. Open full-size SVG ↗

Implementation card / no invented benchmarks

Capacity, budget and execution evidence

Parameters / retained state
State is N complex values per sequence; readout width 2N before any bias convention. Pole-learning parameters are distinct from fixed projection and readout storage.
Duration and hardware evidence
No generic speedup is inferred from diagonal structure; training, identification and rollout need separate timings.
Source coordinates
E40 lines 65–155 and configuration 159–166; E41 metrics
Current reproduction context
Current workstation, supplied by the author: Apple M4, 128 GB unified RAM, 40 GPU cores and 16 CPU cores. This is context for prospective reproduction, not attribution of every archived run. Python and framework versions are not fully locked for these historical sources; declarations, when available, are identified separately.

Counts above are calculated from the stated layer shapes unless identified as saved measurements. They exclude optimizer state and nontrainable buffers. No archived training was rerun for this revision.

What these design choices change

Increasing N adds dynamical modes but may make the readout ill-conditioned. Longer Hankel windows change the identification problem, not just batch size. The source projects inferred eigenvalues inside radius .999; this avoids explosive autonomous modes but can bias nearly undamped dynamics.

Reproduction and measurement protocol

Check the impulse response of a single real pole and a conjugate pair against the recurrence. Reset state at sequence boundaries. Keep noisy observation targets separate from the clean reference used for evaluation, and report matched-pole arms as oracle-assisted rather than fully learned.

For a new run, save the resolved Python/framework versions, backend, dtype, seed, input shapes, batch size and exact source revision. Start with one batch and one update. Log training steps separately from epochs or environment steps. Do not equate the configured maximum with a completed budget or convergence.

Measure initialization/compilation, data preparation, warmed forward pass, training updates and evaluation separately. Synchronize accelerator work around timed regions using the chosen framework’s supported mechanism. Report peak process memory and framework allocation separately; parameter bytes exclude activations, gradients, optimizer state and input buffers. On a shared machine, begin with a single CPU worker and a small batch rather than claiming all available resources.

A closer look at the implementation

The code that carries the idea

The snippet forms paired Hankel matrices, truncates the SVD and computes a reduced transition matrix. Its eigenvalues become discrete-time poles. Radial projection caps magnitudes at 0.999 while preserving phase, preventing an estimated unstable pole from exploding during longer rollouts.

Python · file · lines 94–114
def infer_poles_dmd(y, N, delay=None):
    """Closed-form system-ID of the dynamics operator from observed output (Hankel delay-embedding + DMD). NO oracle, NO
    gradient. Returns up to N discrete poles (complex). This is the 'model the substrate' / OSNR operator-inference arm."""
    # average a few sequences' Hankel into one identification (use the first training seqs)
    yy = y[:64].reshape(y.shape[0] if y.shape[0] < 64 else 64, y.shape[1], -1).mean(-1)  # (b,T) collapse channels
    b, T = yy.shape
    L = delay or max(2 * N, 16)
    L = min(L, T - 2)
    # build Hankel snapshot pairs across all sequences
    X0, X1 = [], []
    for i in range(b):
        H = torch.stack([yy[i, k:k + L] for k in range(T - L)], 1)      # (L, T-L)
        X0.append(H[:, :-1]); X1.append(H[:, 1:])
    X0 = torch.cat(X0, 1); X1 = torch.cat(X1, 1)                        # (L, m)
    U, S, Vh = torch.linalg.svd(X0, full_matrices=False)
    r = min(N, (S > 1e-8 * S[0]).sum().item(), L)
    Ur, Sr, Vr = U[:, :r], S[:r], Vh[:r, :].conj().T
    Atil = Ur.conj().T @ X1 @ Vr @ torch.diag(1.0 / Sr)                 # reduced DMD operator
    ev = torch.linalg.eigvals(Atil)                                     # discrete poles
    mag = ev.abs()                                                      # ENFORCE STABILITY (physical prior): |pole| <= 0.999.
    ev = ev * (torch.clamp(mag, max=0.999) / (mag + 1e-12))            # project unstable modes onto the unit disk, keep phase

Verbatim archive excerpt from closed_form_neat_osnr_ssm.py. Context-dependent historical code, not a standalone runnable program. Comments retain their original wording; the article distinguishes implemented behavior from stale or overbroad comments.

The boundary that matters

Projection changes the estimate; it is a modeling prior, not proof that the identified system is correct. Collapsing output channels before identification can discard modes, and repeated-pole padding needs a shape test when too few eigenvalues are retained. Stability alone does not guarantee accurate extrapolation.

Keep building

Other posts of interest