A 1 kg puck on an air table, pushed by two thrusters and watched by a noisy overhead camera. An LQR controller steers it from a Kalman filter estimate. Both gains come from Riccati equations solved live in your browser. Click or drag on the table to move the target.
State is position and velocity, input is thrust, and the camera sees position only. Drag c slows the puck, and random force w (air currents, a bumpy table) pushes it around.
Zero-order hold over one sample Δt, computed from one matrix exponential:
Minimise the infinite-horizon quadratic cost of tracking error and thrust:
The optimal cost-to-go is x̃⊤Px̃, where P solves the discrete algebraic Riccati equation (DARE):
The feedforward uff = m aref + c vref carries moving targets. Thrust is clipped at ±Fmax per axis, which LQR does not know about.
Predict with the model, then correct with the camera:
The prior covariance converges to the solution of the dual DARE, swapping A→A⊤, B→C⊤, Q→W, R→V:
A consistent filter has mean normalised innovation squared ν⊤S−1ν = 2 (two measured axes). Detune the model noise to see it drift away from 2.
The DARE is solved with the structure-preserving doubling algorithm. With G = BR−1B⊤, each step doubles the Riccati horizon:
Starting from A0 = A, G0 = G, H0 = Q, Hj → P. For comparison, the continuous ARE is solved with the matrix sign function of the Hamiltonian:
As Δt → 0 the sampled gain K approaches Kc. Make Δt large and watch them part.