System Identification
System Identification is how you build a math model of a system from measured data instead of deriving it from first principles. You poke the system with known inputs, record the outputs, and fit a model that reproduces that behavior. It matters because model-based controllers like MPC and LQR need a model — and sysid is how you get one when the physics are too messy to hand-derive.
The Recipe
- Excite the system with a rich input — a step, a chirp (sweeping frequency), or a PRBS (pseudo-random binary signal) that stirs up many dynamics at once.
- Log the input and output data, time-aligned.
- Pick a model structure — how many states, linear or nonlinear.
- Fit parameters to minimize prediction error (least squares is the workhorse).
- Validate on held-out data the model never saw during fitting.
Grey-box vs Black-box
- Grey-box: you know the structure from physics (e.g. a first-order motor model) and only fit the unknown constants — a time constant, a friction coefficient. Fewer parameters, more trustworthy extrapolation.
- Black-box: you let a generic model (ARX, a state-space form, or a neural net) learn the input→output mapping with no physical assumptions. More flexible, but easier to overfit and harder to interpret.
In robotics, sysid identifies motor and actuator models, measures friction and payload, calibrates a sim to match the real robot (the heart of Sim-to-Real Transfer), and supplies the dynamics model that MPC depends on.
Prerequisites
How It Works
known input REAL SYSTEM measured output u (step/chirp/PRBS) (unknown physics) y (with noise) ───────────────────► ┌──────────────────┐ ───────────────────► │ ??? motor + load │ └──────────────────┘ │ fit parameters to │ minimize ||y - ŷ||² ▼ ┌──────────────────┐ same input ────────► │ FITTED MODEL │ ─────► ŷ ≈ y │ ŷ[k+1]=a·y+b·u │ (predicted output) └──────────────────┘Runnable Example
Identify a first-order system y[k+1] = a·y[k] + b·u[k] from step-response data. We generate synthetic data from known a and b, add small noise, then recover them with numpy least squares.
import numpy as np
rng = np.random.default_rng(0)
# --- Ground truth (the "real system" we pretend not to know) ---a_true, b_true = 0.8, 0.5
# --- 1. Excite with a step input, simulate, add measurement noise ---N = 200u = np.zeros(N)u[10:] = 1.0 # step turns on at k=10y = np.zeros(N)for k in range(N - 1): y[k + 1] = a_true * y[k] + b_true * u[k]y_meas = y + rng.normal(0, 0.01, size=N) # noisy measurements
# --- 2. Build the regression: y[k+1] = [y[k], u[k]] @ [a, b] ---Y_next = y_meas[1:]Phi = np.column_stack([y_meas[:-1], u[:-1]])
# --- 3. Fit on the first 70%, validate on the unseen rest ---split = int(0.7 * (N - 1))theta, *_ = np.linalg.lstsq(Phi[:split], Y_next[:split], rcond=None)a_hat, b_hat = theta
# --- 4. Report recovered params and held-out error ---y_pred = Phi[split:] @ thetarmse = np.sqrt(np.mean((y_pred - Y_next[split:]) ** 2))print(f"a: true={a_true}, recovered={a_hat:.4f}")print(f"b: true={b_true}, recovered={b_hat:.4f}")print(f"validation RMSE (held-out): {rmse:.4f}")
# Output:# a: true=0.8, recovered=0.8001# b: true=0.5, recovered=0.4999# validation RMSE (held-out): 0.0118The recovered a and b match the true values to within noise, and the small held-out RMSE confirms the model generalizes rather than memorizing.
Related Terms
Sources
- System Identification Toolbox — MathWorks (R2026a, 2026) — Grey-box/black-box workflows, input design, and validation.
- Ljung, System Identification: Theory for the User, 2nd ed. (Prentice Hall, 1999) — The standard reference on model structures and prediction-error methods.
- scipy.signal & numpy.linalg.lstsq — SciPy/NumPy docs (v1.13, 2025) — Least-squares solver used in the example.