chc.koopman¶
Stability tier: experimental.
Koopman dynamics + LQR: lift a nonlinear system to a global-linear one, then control it.
The Koopman idea: in a lifted feature space phi(x) the dynamics are approximately linear,
phi(x') ~ A phi(x) + B u. Fit A, B from transitions by least squares (EDMD); the nonlinear
system becomes linear, so control is a fast discrete LQR in the lifted space, not a
gradient-descent MPC. The LQR is exact for the linear surrogate, not for the plant: through a
nonlinear dictionary the input of even a control-affine plant can enter the lift with a
state-dependent gain, so a linear lift is misspecified there in general, and LQR or MPC on it
carries no closed-loop guarantee for the plant (Strässer et al. 2026, Annual Reviews in Control
61). This mirrors the CHC hybrid philosophy (a structured global model plus a correction); the
modern, Residual-Koopman-MPC-lineage control backend. The dictionary is polynomial; the first
state_dim features are the raw state, decoding is a slice.
KoopmanModel
dataclass
¶
Koopman model phi(x') ~ A phi(x) + B u fit by least squares on a polynomial lift.
ridge is the Tikhonov term on each coefficient, in units of its column's mean square, so
the fit reads the same in any units of the state and the action. Added as a constant, it
outweighed an action logged in millionths of its units and set B near zero.
form="bilinear" fits phi(x') ~ A phi(x) + B u + sum_j u_j H_j phi(x). A control-affine
plant moves each nonlinear feature of the lift by a term that is the action times a function of
the state, so its linear lift is misspecified however rich the dictionary; the bilinear one
carries that term (Strässer et al., Annual Reviews in Control 61, 2026). The default
form="linear" fits as before, bit for bit.
fit reports closure_residuals: for each feature of the lift, the root mean square of
its one-step error on transitions held out of the fit, over the root mean square of the
feature at the next state. The transitions are cut into five contiguous blocks, each predicted
by a model fit on the other four; the model kept is fit on all of them. A lift whose span the
dynamics map into itself reads 0 on noise-free data (ResDMD's residual, Colbrook and
Townsend, on the whole dictionary); one whose span they leave reads the share of each feature
it does not predict. The ratio is read feature by feature, so it reads the same in any units of
the state and the action.
koopman_lqr_gain(model, q_state, r, *, x_target=None, max_residual=0.5)
¶
Infinite-horizon discrete-LQR gain in the lifted space (state cost via the decode slice).
The gain is the lifted model's, and it is refused where the lift does not close: where a
feature's held-out closure residual exceeds max_residual, or was not read. The default of
0.5 refuses a lift that predicts less than three quarters of some feature's mean square on
transitions it was not fit on. A bilinear model's channel depends on the state: its gain is
the LQR of the model linearised at x_target, which it then requires. A linear model's gain
does not depend on x_target.
Raises:
| Type | Description |
|---|---|
RuntimeError
|
before the model is fit. |
ValueError
|
where the lift does not close, or a bilinear model is given no |
koopman_controller(model, gain, x_target)
¶
Feedback law u = -G (phi(x) - phi(x_target)) regulating the system to x_target.