Skip to content
Menu

4. State-Space Description of a Dynamic System

Unlike a memoryless system, a dynamic system’s output depends on past inputs, not just the present one. State-space (modern control) representations capture exactly how much past information is needed, in a compact, general form.

PowerPoint handouts: State-Space Models (dark) · State-Space Models (light) · State-Space to Transfer Functions (dark) · State-Space to Transfer Functions (light)

link to download code

Builds the state-space model (A, B, C, D) for a mass-spring-damper system with m=1, k=2, b=1, then converts it to a transfer function via ss2tf() to confirm it matches the closed-form G(s) = (1/m) / (s^2 + (b/m)s + (k/m)) derived by hand — the same model in two equivalent representations.

import control as ct
import numpy as np
m = 1
k = 2
b = 1
A = np.array([[0, 1], [-k / m, -b / m]])
B = np.array([[0], [1 / m]])
C = np.array([1, 0])
D = np.array([0])
sys = ct.ss(A, B, C, D) # state-space representation
print(sys)
sys_tf = ct.ss2tf(sys)
print(sys_tf)

Mass-spring-damper state-space model (mcimp/codes/ssdescription/msd.py)

Click "Run" to execute.
link to download code

A bicycle-model of vehicle steering is nonlinear in general, but near a straight-line equilibrium it can be linearized into a simple state-space model. This simulates both the true nonlinear system and its linearization under a 1 Hz sinusoidal steering input of increasing magnitude, and plots them together.

At small input magnitudes the two curves are nearly indistinguishable — the linearization is accurate near the equilibrium point. As the magnitude grows, the nonlinear and linearized responses visibly diverge, illustrating that a linearized model is only a local approximation, valid in a neighborhood of the point it was linearized around. (The book’s original example also sweeps input frequency at fixed magnitude; that second sweep is omitted here to keep the demo focused, but follows the identical pattern.)

import numpy as np
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import control.matlab as ct
from scipy.integrate import odeint
import io, base64
# Vehicle steering: bicycle model -- accuracy of linearization
a = 1.799 / 2 # length from rear wheels to center of mass
b = 1.799 # length of the car
v_0 = 10 # initial velocity, m/s
def f(x, t, u):
y, theta = x
dydt = v_0 * np.sin(np.arctan(a * np.tan(u) / b) + theta)
dthetadt = v_0 * np.sin(np.arctan(a * np.tan(u) / b)) / a
return [dydt, dthetadt]
tspan = [0, 10]
# Linearized state-space model of the vehicle (output = position only)
A = np.array([[0, v_0], [0, 0]])
B = np.array([a * v_0 / b, v_0 / b]).reshape(-1, 1)
C = np.array([1, 0])
D = np.array([0])
# Reduced point count (vs. the book's 1000) keeps this fast enough to
# re-run in the browser while still showing the same trend clearly.
Ugain = [0.05, 0.1, 0.5, 1]
t = np.linspace(tspan[0], tspan[1], 300)
Y = np.zeros((len(t), len(Ugain)))
Ylinear = Y.copy()
for ii in range(len(Ugain)):
u = Ugain[ii] * np.sin(2 * np.pi * 1 * t)
x0 = [0, 0]
for jj in range(1, len(t)):
x = odeint(f, x0, [t[jj - 1], t[jj]], args=(u[jj],))
x0 = x[1]
Y[jj, ii] = x[1][0]
Ylinear[:, ii], _, _ = ct.lsim(ct.ss(A, B, C, D), u, t, [0, 0])
print(f"gain={Ugain[ii]}: nonlinear max |y| = {np.max(np.abs(Y[:, ii])):.3f}, "
f"linearized max |y| = {np.max(np.abs(Ylinear[:, ii])):.3f}")
fig, axs = plt.subplots(4, 1, figsize=(7, 9), sharex=True)
for ii in range(len(Ugain)):
axs[ii].plot(t, Y[:, ii], label="nonlinear model")
axs[ii].plot(t, Ylinear[:, ii], "r--", label="linearized model")
axs[ii].set_title(f"Input at 1 Hz, magnitude {Ugain[ii]} rad")
axs[ii].set_ylabel("Position y (m)")
axs[ii].grid(True)
axs[0].legend(loc="upper left")
axs[-1].set_xlabel("Time (s)")
plt.tight_layout()
buf = io.BytesIO()
plt.savefig(buf, format="png", dpi=90)
plot_b64 = base64.b64encode(buf.getvalue()).decode()

Vehicle steering: linearization accuracy (mcimp/codes/ssdescription/vehicle_steer_linear.py)

Click "Run" to execute.

← All chapters