Case Study 2 — Reachable States in Control Theory: The Column Space as What You Can Steer To
Field: control engineering / robotics. Concepts used: column space, span, rank, reachability. Why it matters: before you design a controller for a robot, a drone, or a spacecraft, you must answer a prior question — which states can the controls reach at all? That question is, exactly, "what is the column space of the controllability matrix?" States outside that column space are physically unreachable no matter how cleverly you steer.
A system you can push on
Picture a small cart on a track that also carries a thermometer. Its state has three numbers: position $p$, velocity $v$, and internal temperature $\theta$. At each time step the state evolves on its own — position drifts by the current velocity, velocity persists, temperature persists — and you get one control knob: a force $u$ that you can apply. The force changes the velocity but, being a mechanical push, does nothing to position directly and nothing at all to temperature. In the standard discrete linear form $\mathbf{x}_{k+1} = A\mathbf{x}_k + B u_k$, with state $\mathbf{x} = (p, v, \theta)$: $$A = \begin{bmatrix} 1 & 1 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \end{bmatrix}, \qquad B = \begin{bmatrix} 0 \\ 1 \\ 0 \end{bmatrix}.$$ The matrix $A$ says "new position = old position + old velocity; velocity and temperature carry over." The vector $B$ says "force adds to velocity only." Now the engineer's question: starting from rest at the origin, which states $(p, v, \theta)$ can I drive the cart to by choosing a sequence of forces?
Intuitively, you can speed the cart up and let velocity accumulate into position, so you should be able to reach a range of positions and velocities. But nothing you do touches temperature — there is no heater, and the dynamics never couple force to $\theta$. So you ought to be stuck at $\theta = 0$ forever. Linear algebra turns that intuition into a theorem, and the object that does it is a column space.
The reachable set is a column space
Run the dynamics forward from $\mathbf{x}_0 = \mathbf{0}$. After one step the state is $B u_0$. After two steps it is $A B u_0 + B u_1$. After three, $A^2 B u_0 + A B u_1 + B u_2$. In general, the state reachable in $n$ steps is a linear combination $$\mathbf{x}_n = B u_{n-1} + A B u_{n-2} + A^2 B u_{n-3} + \cdots + A^{n-1} B u_0,$$ with the chosen forces $u_0, \dots, u_{n-1}$ as the weights. That is exactly a linear combination of the vectors $B, AB, A^2B, \dots$ — so the set of all reachable states is their span, which is the column space of the matrix that stacks them side by side. This matrix has a name in control theory: the controllability matrix $$\mathcal{C} = \big[\, B \ \ A B \ \ A^2 B \ \ \cdots \ \ A^{n-1}B \,\big].$$ The reachable set from the origin is precisely $C(\mathcal{C})$. (The Cayley–Hamilton theorem [verify], which you will meet in Chapter 24, guarantees that powers beyond $A^{n-1}B$ add nothing new — so for an $n$-state system, the first $n$ terms already span everything reachable.) The question "where can I steer to?" has become "what is the column space of $\mathcal{C}$?"
Computing it: the reachable set is a plane, not all of space
For our cart, $\mathcal{C} = [\,B \ \ AB \ \ A^2B\,]$. Compute the columns: $B = (0,1,0)$; $AB = (1,1,0)$; and $A^2 B = A(AB) = (2,1,0)$. So $$\mathcal{C} = \begin{bmatrix} 0 & 1 & 2 \\ 1 & 1 & 1 \\ 0 & 0 & 0 \end{bmatrix}.$$ Every column has a $0$ in the third (temperature) slot — unsurprising, since nothing ever moves temperature. The column space therefore lies entirely in the plane $\{\theta = 0\}$. Row-reduce or count: the first two columns $(0,1,0)$ and $(1,1,0)$ are independent, and the third is their combination, so $\operatorname{rank}(\mathcal{C}) = 2$. The reachable set is a 2-dimensional plane inside $\mathbb{R}^3$ — the $(p, v)$-plane — and not all of $\mathbb{R}^3$. Let's verify.
# Reachable states = column space of the controllability matrix C = [B AB A^2B].
import numpy as np
A = np.array([[1, 1, 0],
[0, 1, 0],
[0, 0, 1]], dtype=float)
B = np.array([[0], [1], [0]], dtype=float)
C = np.hstack([B, A @ B, A @ A @ B])
print("controllability matrix:\n", C)
print("rank(C) =", np.linalg.matrix_rank(C)) # 2 -> reachable set is a plane, NOT R^3
# Is the pure-temperature state (0,0,1) reachable? Solve C w = (0,0,1) in least squares.
e_theta = np.array([0, 0, 1.0])
w, *_ = np.linalg.lstsq(C, e_theta, rcond=None)
print("best we can do toward (0,0,1):", C @ w, " residual =", round(np.linalg.norm(C @ w - e_theta), 6))
# A state in the plane, say (5, 3, 0), IS reachable:
target = np.array([5, 3, 0.0])
w2, *_ = np.linalg.lstsq(C, target, rcond=None)
print("reach (5,3,0):", C @ w2)
The output prints rank(C) = 2, the best approximation to $(0,0,1)$ as [0. 0. 0.] with residual = 1.0 (we cannot get anywhere near it), and reach (5,3,0) = [5. 3. 0.] exactly. The verdict is unambiguous: any state with $\theta = 0$ is reachable; no state with $\theta \neq 0$ is reachable, ever, by any sequence of forces. The temperature axis lives outside the column space $C(\mathcal{C})$, and §13.2's criterion applies — the steering equation $\mathcal{C}\mathbf{w} = \mathbf{b}$ is solvable iff $\mathbf{b} \in C(\mathcal{C})$.
"Controllable" means "the column space is everything"
A system is called controllable when you can steer it from the origin to any state — which, in the language of this chapter, means $C(\mathcal{C}) = \mathbb{R}^n$, i.e. the controllability matrix has full rank $n$. Our cart fails this: $\operatorname{rank}(\mathcal{C}) = 2 < 3$, so it is uncontrollable, and the rank deficiency of exactly $1$ matches the one dimension (temperature) we cannot influence. The famous Kalman rank condition of control theory is precisely this statement: a linear system is controllable if and only if its controllability matrix has rank equal to the number of states — a column-space full-rank test, nothing more. The entire theory of which systems can be driven where reduces to computing $\dim C(\mathcal{C})$ and comparing it to $n$.
The reachable subspace $C(\mathcal{C})$ answers "where can I go?"; the unreachable rest of $\mathbb{R}^n$ — everything outside the column space — answers "what is forever beyond my control?" A rank-deficient controllability matrix is not a numerical accident; it is a structural fact about the machine, telling you a real physical quantity that your actuators cannot touch.
Why an engineer computes this first
Knowing the reachable column space before designing a controller saves enormous wasted effort and prevents dangerous mistakes. If a state you care about — say the orientation of an underactuated drone, or a particular vibration mode of a flexible robot arm — lies outside $C(\mathcal{C})$, then no control law, however sophisticated, can regulate it; you must add an actuator (change $B$) or exploit nonlinear effects the linear model ignores. Conversely, if the system is controllable, theory guarantees a controller exists that can place the state anywhere, and you can proceed to design it. The column space is the feasibility check that gates the whole design.
The fix for our cart is instructive and is itself a column-space statement. Add a heater: give the force input a thermal channel, or add a second input column to $B$ that couples to temperature. The new controllability matrix gains a column with a nonzero third entry, its rank rises to $3$, the column space becomes all of $\mathbb{R}^3$, and every state — including any temperature — becomes reachable. "Add an actuator to control temperature" is, precisely, "add a column so that the column space fills $\mathbb{R}^3$." The geometry of reachability is the geometry of the column space.
The takeaway
Reachability in a linear control system is a column-space question through and through. The states you can steer to form the span of the controlled directions $B, AB, A^2B, \dots$ — the column space $C(\mathcal{C})$ of the controllability matrix — and a system is controllable exactly when that column space is the whole state space (full rank, the Kalman condition). Our cart's temperature, untouched by any force, sits outside the column space and is unreachable forever, a fact numpy confirms with a stubborn nonzero residual. This is the mirror image of Case Study 1: there the null space encoded what a transformation conserves; here the column space encodes what a system can produce. Together they are this chapter's two questions — what can $A\mathbf{x} = \mathbf{b}$ reach, and what does it leave fixed — answered for a machine you can actually push on.