Skip to content
Menu

9. Controllability and Observability

Can the right control input be found from measured outputs? Controllability and observability answer this from opposite directions, and together they determine what’s fundamentally achievable with state feedback and estimation.

PowerPoint handouts: Dark · Light

link to download code

For a 4-state discrete-time system built from two identical, decoupled 2-state blocks, this builds the controllability matrix P = [B, AB, A^2 B, A^3 B] and checks its rank. Since the two blocks are identical, their contributions to P are linearly dependent, so P only reaches rank 2 out of 4 states — confirming the system is uncontrollable, exactly as the hand computation in the text finds.

import numpy as np
import control as ct
A = np.array([[0.4, 0.4, 0, 0], [-0.9, -0.07, 0, 0], [0, 0, 0.4, 0.4], [0, 0, -0.9, -0.07]])
B = np.array([[0.3], [0.4], [0.3], [0.4]])
P = ct.ctrb(A, B)
rankP = np.linalg.matrix_rank(P)
print("Controllability matrix P has rank", rankP, "out of", A.shape[0], "states")
print("-> the system is uncontrollable" if rankP < A.shape[0] else "-> the system is controllable")

Controllability matrix rank (mcimp/codes/controllability_observability/controllability_matrix.py)

Click "Run" to execute.

Controllability/observability under series and parallel connections

Section titled “Controllability/observability under series and parallel connections”
link to download code

Two small systems are each checked individually: System 1 is controllable but not observable (rank(P1)=2, rank(Q1)=1); System 2 is both controllable and observable (both rank 2).

Connecting them in series gives a 4-state system that’s controllable (rank 4) but not observable (rank 3) — and connecting them in parallel instead gives a system that’s neither controllable nor observable (ranks 3 and 2). Combining two ‘good’ systems doesn’t automatically preserve controllability or observability — how they’re interconnected matters.

import control
import numpy as np
A1 = [[0, 1], [-2, -3]]
B1 = [[0], [1]]
C1 = [[2, 1]]
sys1 = control.ss(A1, B1, C1, 0)
P1 = control.ctrb(A1, B1)
Q1 = control.obsv(A1, C1)
print("System 1: rank(P1) =", np.linalg.matrix_rank(P1), ", rank(Q1) =", np.linalg.matrix_rank(Q1))
A2 = [[-1, 0], [0, -3]]
B2 = [[1], [1]]
C2 = [[1, 1]]
sys2 = control.ss(A2, B2, C2, 0)
P2 = control.ctrb(A2, B2)
Q2 = control.obsv(A2, C2)
print("System 2: rank(P2) =", np.linalg.matrix_rank(P2), ", rank(Q2) =", np.linalg.matrix_rank(Q2))
# Now connect the two (each individually controllable and observable) in
# series and in parallel, and re-check
sys_s = control.series(sys1, sys2)
sys_p = control.parallel(sys1, sys2)
Ps = control.ctrb(sys_s.A, sys_s.B)
Qs = control.obsv(sys_s.A, sys_s.C)
print("\nSeries connection: rank(Ps) =", np.linalg.matrix_rank(Ps), ", rank(Qs) =", np.linalg.matrix_rank(Qs))
Pp = control.ctrb(sys_p.A, sys_p.B)
Qp = control.obsv(sys_p.A, sys_p.C)
print("Parallel connection: rank(Pp) =", np.linalg.matrix_rank(Pp), ", rank(Qp) =", np.linalg.matrix_rank(Qp))

Controllability/observability under series and parallel connections (mcimp/codes/controllability_observability/connectedsys.py)

Click "Run" to execute.

← All chapters