Operator identification

Identification is an opt-in closed-form update of a discrete dense per-node \(K\). Default fit(..., identification=None) remains the Adam path and does not import koopman_graph.identification.

What ships

IdentificationConfig selects ridge, TLS, or constrained least squares. Pass it to fit() to alternate a frozen-encoder \(K\) write with encoder/decoder Adam steps. IdentificationReport records latent one-step / short-rollout mean squared error (MSE) and \(\rho(K)\). Those scalars are not Haseli–Cortés, certified ResDMD, or stability certificates.

Related helpers (all off root __all__):

  • identify_operator() — closed-form solve on frozen LatentPairs

  • subspace_invariance_report() — finite-sample projection leakage \(\eta\) (not a Haseli–Cortés certificate). fit does not populate the report invariance block; use evaluate(..., include_invariance=True) or the helper

  • select_resdmd_gated() — drop pre-scored dictionaries whose max finite-dictionary ResDMD residual exceeds DEFAULT_RESDMD_GATE_TOLERANCE (\(10^{-2}\)). IdentificationConfig.gate_resdmd=True fills the report spectral block and does not abort fit. ResDMDFitCallback mode="gate" may raise at fit end

  • identify_sparse_graph_factors() — sparse \(K_{\mathrm{self}}\) / \(K_{\mathrm{nbr}}\) on frozen encodings (not latent SINDy; not KoopmanSparsityLoss)

  • select_latent_rank() — truncated-SVD rank grid on frozen encodings (VAMP-2, ResDMD elbow, or stability-penalized held-out MSE). Not Ray Tune AutoML for encoder latent_dim

Graph, hetero, continuous, and controlled operators raise. solver="varpro" is not implemented.

How to use it

from koopman_graph.identification import (
    IdentificationConfig,
    LatentPairs,
    build_identification_report,
    identify_operator,
    select_resdmd_gated,
)

pairs = LatentPairs(z_t=z_t, z_next=z_next)
snapshot = identify_operator(
    pairs, IdentificationConfig(solver="ridge", ridge=0.0)
)
report = build_identification_report(pairs, snapshot, gate_resdmd=True)
residual_max = report.spectral.residual_max

A teaching walkthrough that populates report fields, rejects a polluted RMSE-only dictionary, and reads finite-sample leakage \(\eta\) on a rank-deficient line is examples/48_identification_invariance.ipynb. Rank selection on a linear Gaussian oracle is examples/53_latent_rank_selection.ipynb.

Ceilings

  • Discrete dense per-node \(K\) only. Networked / continuous maps stay on Adam (or a later increment).

  • Closed-form ridge / TLS / constrained least squares is not ResKoopNet residual training (Xu2025ResKoopNet).

  • Reports are finite-sample MSE and \(\rho(K)\), not certificates.

  • select_latent_rank() does not train an encoder per candidate and does not set GraphKoopmanModel latent_dim. koopman_graph.tuning remains caller-owned HPO.

See FAQ and troubleshooting (identification versus Adam; rank versus Ray Tune), Scope and limitations (Identification), and API Reference.