Chapter 7: MRAC for General SISO Systems in Canonical Forms
Lesson 1: Controllable Canonical Form for SISO Plants (From Modern Control)
This lesson develops the controller companion, or controllable canonical, realization of a continuous-time single-input single-output linear plant. We derive the realization from a transfer function, prove its input-output equivalence, obtain the similarity transformation from an arbitrary controllable state-space model, and explain why this coordinate structure is useful before introducing general-order MRAC laws in later lessons.
1. Learning Objectives and Scope
After completing this lesson, a student should be able to:
- test controllability of a SISO realization using its controllability matrix;
- construct a controller companion realization from denominator and numerator coefficients;
- prove that the canonical realization produces the prescribed transfer function;
- compute the similarity transformation between an arbitrary controllable realization and the chosen canonical realization;
- distinguish controllability, observability, minimality, and numerical conditioning; and
- implement and verify the transformation in Python, C++, Java, MATLAB, Simulink, and Wolfram Mathematica.
The lesson uses results from linear control: state-space models, characteristic polynomials, controllability, transfer functions, similarity transformations, and the Cayley-Hamilton theorem. No adaptation law is introduced yet. The purpose is to establish the plant coordinates used in the next lessons.
2. SISO LTI Plant and Controllability
Consider an order-\(n\), continuous-time, real SISO plant
\[ \dot{\mathbf{x} }(t)=\mathbf{A}\mathbf{x}(t)+\mathbf{b}u(t), \qquad y(t)=\mathbf{c}^{T}\mathbf{x}(t)+d\,u(t), \]
where \(\mathbf{x}\in\mathbb{R}^{n}\), \(u,y\in\mathbb{R}\), \(\mathbf{A}\in\mathbb{R}^{n\times n}\), \(\mathbf{b},\mathbf{c}\in\mathbb{R}^{n}\), and \(d\in\mathbb{R}\). The controllability matrix is
\[ \mathcal{C}(\mathbf{A},\mathbf{b}) = \begin{bmatrix} \mathbf{b} & \mathbf{A}\mathbf{b} & \mathbf{A}^{2}\mathbf{b} & \cdots & \mathbf{A}^{n-1}\mathbf{b} \end{bmatrix}. \]
The pair \((\mathbf{A},\mathbf{b})\) is controllable precisely when
\[ \operatorname{rank}\mathcal{C}(\mathbf{A},\mathbf{b})=n. \]
For a single-input system, this condition means that the ordered vectors \(\mathbf{b},\mathbf{A}\mathbf{b},\ldots, \mathbf{A}^{n-1}\mathbf{b}\) form a basis of the state space. Canonical coordinates are obtained by replacing this basis with a standardized basis that exposes an integrator-chain structure.
Controllability is invariant under any nonsingular state transformation. If \(\mathbf{x}=\mathbf{T}\mathbf{z}\), then
\[ \mathbf{A}_{z}=\mathbf{T}^{-1}\mathbf{A}\mathbf{T}, \qquad \mathbf{b}_{z}=\mathbf{T}^{-1}\mathbf{b}, \qquad \mathbf{c}_{z}^{T}=\mathbf{c}^{T}\mathbf{T}, \qquad d_{z}=d. \]
The transfer function and all zero-initial-condition input-output behavior remain unchanged because only the internal state coordinates have changed.
3. Transfer-Function Coefficients and the Chosen Convention
Let the monic denominator polynomial be
\[ a(s)=s^{n}+a_{n-1}s^{n-1}+\cdots+a_{1}s+a_{0}. \]
Every proper transfer function with this denominator can be written as
\[ G(s)=d+\frac{\bar{b}_{n-1}s^{n-1}+\cdots+\bar{b}_{1}s+\bar{b}_{0} } {a(s)}. \]
If the original numerator has the same degree as the denominator,
\[ \beta(s)=\beta_{n}s^{n}+\beta_{n-1}s^{n-1}+\cdots+\beta_{0}, \]
polynomial division gives
\[ d=\beta_{n}, \qquad \bar{b}_{i}=\beta_{i}-d\,a_{i}, \quad i=0,\ldots,n-1. \]
In this course, the phrase controllable canonical form refers to the following controller companion convention:
\[ \mathbf{A}_{c}= \begin{bmatrix} 0 & 1 & 0 & \cdots & 0\\ 0 & 0 & 1 & \cdots & 0\\ \vdots & \vdots & \vdots & \ddots & \vdots\\ 0 & 0 & 0 & \cdots & 1\\ -a_{0} & -a_{1} & -a_{2} & \cdots & -a_{n-1} \end{bmatrix}, \qquad \mathbf{b}_{c}= \begin{bmatrix} 0\\0\\\vdots\\0\\1 \end{bmatrix}, \]
\[ \mathbf{c}_{c}^{T}= \begin{bmatrix} \bar{b}_{0} & \bar{b}_{1} & \cdots & \bar{b}_{n-1} \end{bmatrix}, \qquad d_{c}=d. \]
Some books and software reverse the state order, place the input in the first state equation, or use the transpose companion matrix. Those conventions are equivalent through a permutation or similarity transformation. Coefficients must never be copied between conventions without checking the state order.
4. Canonical State Equations and Integrator Chain
The canonical model \(\dot{\mathbf{z} }=\mathbf{A}_{c}\mathbf{z} +\mathbf{b}_{c}u\) is equivalent to
\[ \begin{aligned} \dot{z}_{1} &= z_{2},\\ \dot{z}_{2} &= z_{3},\\ &\ \vdots\\ \dot{z}_{n-1} &= z_{n},\\ \dot{z}_{n} &= -a_{0}z_{1}-a_{1}z_{2}-\cdots-a_{n-1}z_{n}+u,\\ y &= \bar{b}_{0}z_{1}+\bar{b}_{1}z_{2}+\cdots+ \bar{b}_{n-1}z_{n}+d\,u. \end{aligned} \]
flowchart TD
U["Input u"] --> SUM["Last-state equation: zdot_n = u - a0 z1 - ... - a(n-1) zn"]
SUM --> IN["Integrator"]
IN --> ZN["State zn"]
ZN --> I2["Integrator chain"]
I2 --> Z2["States z(n-1), ..., z2"]
Z2 --> I1["Final integrator"]
I1 --> Z1["State z1"]
Z1 --> FB["Coefficient feedback a0, ..., a(n-1)"]
ZN --> FB
Z2 --> FB
FB --> SUM
Z1 --> OUT["Output combination with b0, ..., b(n-1), and d"]
Z2 --> OUT
ZN --> OUT
U --> OUT
For a strictly proper transfer function, elimination of the internal states produces the input-output differential equation
\[ y^{(n)}+a_{n-1}y^{(n-1)}+\cdots+a_{1}\dot{y}+a_{0}y = \bar{b}_{n-1}u^{(n-1)}+\cdots+\bar{b}_{1}\dot{u} +\bar{b}_{0}u. \]
However, the canonical states are not generally \(y,\dot{y},\ldots,y^{(n-1)}\). They are internal coordinates of a realization. They coincide with simple output derivatives only for special output matrices.
5. Proof of the Canonical Transfer Function
We prove directly that the realization in Section 3 has transfer function \(G(s)\). Define
\[ \mathbf{v}(s)=(s\mathbf{I}-\mathbf{A}_{c})^{-1}\mathbf{b}_{c}. \]
The first \(n-1\) rows of \((s\mathbf{I}-\mathbf{A}_{c})\mathbf{v} =\mathbf{b}_{c}\) give
\[ sv_{1}-v_{2}=0,\quad sv_{2}-v_{3}=0,\quad\ldots,\quad sv_{n-1}-v_{n}=0. \]
Therefore,
\[ v_{k}=s^{k-1}v_{1}, \qquad k=1,\ldots,n. \]
The last row becomes
\[ a_{0}v_{1}+a_{1}v_{2}+\cdots+a_{n-2}v_{n-1} +(s+a_{n-1})v_{n}=1. \]
Substitution of \(v_{k}=s^{k-1}v_{1}\) yields
\[ a(s)v_{1}=1. \]
Consequently,
\[ (s\mathbf{I}-\mathbf{A}_{c})^{-1}\mathbf{b}_{c} = \frac{1}{a(s)} \begin{bmatrix} 1 & s & s^{2} & \cdots & s^{n-1} \end{bmatrix}^{T}. \]
The transfer function is therefore
\[ \begin{aligned} G_{c}(s) &= \mathbf{c}_{c}^{T} (s\mathbf{I}-\mathbf{A}_{c})^{-1}\mathbf{b}_{c}+d\\ &= \frac{\bar{b}_{0}+\bar{b}_{1}s+\cdots+ \bar{b}_{n-1}s^{n-1} }{a(s)}+d, \end{aligned} \]
which is exactly the prescribed transfer function. This proof also explains why the numerator coefficients appear in ascending order in \(\mathbf{c}_{c}^{T}\) for this convention.
6. Similarity Transformation from an Arbitrary Realization
Suppose \((\mathbf{A},\mathbf{b})\) is controllable and its characteristic polynomial is
\[ \det(s\mathbf{I}-\mathbf{A}) = s^{n}+a_{n-1}s^{n-1}+\cdots+a_{0}. \]
Construct \((\mathbf{A}_{c},\mathbf{b}_{c})\) from these coefficients. Define
\[ \mathcal{C}= \begin{bmatrix} \mathbf{b} & \mathbf{A}\mathbf{b} & \cdots & \mathbf{A}^{n-1}\mathbf{b} \end{bmatrix}, \qquad \mathcal{C}_{c}= \begin{bmatrix} \mathbf{b}_{c} & \mathbf{A}_{c}\mathbf{b}_{c} & \cdots & \mathbf{A}_{c}^{n-1}\mathbf{b}_{c} \end{bmatrix}. \]
Both matrices are nonsingular. Under \(\mathbf{x}=\mathbf{T}\mathbf{z}\), their columns satisfy
\[ \mathcal{C}=\mathbf{T}\mathcal{C}_{c}. \]
Hence the required transformation is
\[ \boxed{ \mathbf{T}=\mathcal{C}\mathcal{C}_{c}^{-1} }. \]
In computation, solve \(\mathbf{T}\mathcal{C}_{c}=\mathcal{C}\) rather than forming an explicit inverse.
6.1 Proof that the Formula Produces the Similarity Relation
By construction,
\[ \mathbf{T}\mathbf{A}_{c}^{k}\mathbf{b}_{c} = \mathbf{A}^{k}\mathbf{b}, \qquad k=0,\ldots,n-1. \]
For \(k=0,\ldots,n-2\),
\[ \mathbf{A}\mathbf{T}\mathbf{A}_{c}^{k}\mathbf{b}_{c} = \mathbf{A}^{k+1}\mathbf{b} = \mathbf{T}\mathbf{A}_{c}^{k+1}\mathbf{b}_{c}. \]
For the final basis vector, the Cayley-Hamilton theorem gives
\[ \mathbf{A}^{n} = -a_{n-1}\mathbf{A}^{n-1}-\cdots-a_{1}\mathbf{A}-a_{0}\mathbf{I}, \]
and the identical polynomial relation holds for \(\mathbf{A}_{c}\). Therefore, \(\mathbf{A}\mathbf{T}=\mathbf{T}\mathbf{A}_{c}\) on every vector of the canonical controllability basis. Since that basis spans \(\mathbb{R}^{n}\),
\[ \mathbf{A}_{c}=\mathbf{T}^{-1}\mathbf{A}\mathbf{T}, \qquad \mathbf{b}_{c}=\mathbf{T}^{-1}\mathbf{b}. \]
The output matrices follow from the coordinate substitution:
\[ \mathbf{c}_{c}^{T}=\mathbf{c}^{T}\mathbf{T}, \qquad d_{c}=d. \]
flowchart TD
A["Start with A, b, c, d"] --> P["Compute characteristic polynomial coefficients"]
A --> W["Build controllability matrix C"]
W --> R["Check rank(C) = n"]
R -->|no| STOP["No full-order controllable \ncompanion similarity"]
R -->|yes| CC["Construct Ac, bc and \ncanonical controllability matrix Cc"]
P --> CC
CC --> T["Solve T Cc = C"]
T --> V["Verify A T = T Ac and b = T bc"]
V --> O["Compute cc = c T and dc = d"]
O --> E["Compare transfer functions and trajectories"]
7. Structural Properties
7.1 Characteristic Polynomial
The companion matrix is constructed so that
\[ \det(s\mathbf{I}-\mathbf{A}_{c})=a(s). \]
Thus its eigenvalues are precisely the poles represented by the denominator polynomial, including algebraic multiplicities.
7.2 Automatic Controllability
The canonical controllability matrix has an anti-triangular unit structure. Its determinant is
\[ \det\mathcal{C}_{c} = (-1)^{n(n-1)/2}, \]
independently of the denominator coefficients. Hence it is always nonsingular.
7.3 Minimality Is a Separate Question
The pair \((\mathbf{A}_{c},\mathbf{b}_{c})\) is always controllable, but the complete realization is minimal only if \((\mathbf{c}_{c}^{T},\mathbf{A}_{c})\) is observable. If numerator and denominator polynomials share a factor, pole-zero cancellation causes nonminimal input-output behavior even though the state-input pair remains controllable.
7.4 Similarity Invariants
Similarity preserves the characteristic polynomial, eigenvalues, minimal polynomial, controllability rank, observability rank, transfer function, and zero-state input-output map. It does not preserve the numerical values or physical interpretation of individual state components.
7.5 Uniqueness Relative to a Convention
Once the coefficient ordering, state ordering, input location, and relation \(\mathbf{x}=\mathbf{T}\mathbf{z}\) are fixed, the transformation \(\mathbf{T}=\mathcal{C}\mathcal{C}_{c}^{-1}\) is unique. A different canonical convention produces a different but equivalent transformation.
8. Worked Third-Order Example
Consider the transfer function
\[ G(s)= \frac{2s^{2}+3s+1}{s^{3}+4s^{2}+5s+2}. \]
The controller companion realization is
\[ \mathbf{A}_{c}= \begin{bmatrix} 0 & 1 & 0\\ 0 & 0 & 1\\ -2 & -5 & -4 \end{bmatrix}, \qquad \mathbf{b}_{c}= \begin{bmatrix} 0\\0\\1 \end{bmatrix}, \qquad \mathbf{c}_{c}^{T}= \begin{bmatrix} 1 & 3 & 2 \end{bmatrix}, \qquad d=0. \]
Now suppose the same plant is supplied in the noncanonical realization
\[ \mathbf{A}= \begin{bmatrix} 0 & 1 & 0\\ -2 & -3 & 0\\ -1 & -3 & -1 \end{bmatrix}, \quad \mathbf{b}= \begin{bmatrix} 0\\1\\1 \end{bmatrix}, \quad \mathbf{c}^{T}= \begin{bmatrix} 1 & 2 & 0 \end{bmatrix}. \]
Its controllability matrix is
\[ \mathcal{C}= \begin{bmatrix} 0 & 1 & -3\\ 1 & -3 & 7\\ 1 & -4 & 12 \end{bmatrix}, \qquad \det\mathcal{C}=-2. \]
The canonical controllability matrix is
\[ \mathcal{C}_{c}= \begin{bmatrix} 0 & 0 & 1\\ 0 & 1 & -4\\ 1 & -4 & 11 \end{bmatrix}, \qquad \det\mathcal{C}_{c}=-1. \]
Therefore,
\[ \mathbf{T}=\mathcal{C}\mathcal{C}_{c}^{-1} = \begin{bmatrix} 1 & 1 & 0\\ 0 & 1 & 1\\ 1 & 0 & 1 \end{bmatrix}. \]
Direct verification gives
\[ \mathbf{T}^{-1}\mathbf{A}\mathbf{T}=\mathbf{A}_{c}, \qquad \mathbf{T}^{-1}\mathbf{b}=\mathbf{b}_{c}, \qquad \mathbf{c}^{T}\mathbf{T}= \begin{bmatrix} 1 & 3 & 2 \end{bmatrix}. \]
If the original initial state is \(\mathbf{x}(0)=\mathbf{x}_{0}\), the equivalent canonical initial state is \(\mathbf{z}(0)=\mathbf{T}^{-1}\mathbf{x}_{0}\). Using the same input and these transformed initial conditions produces identical outputs.
9. Why Canonical Coordinates Matter for General-Order MRAC
The controller companion form isolates the denominator coefficients in the last state equation:
\[ \dot{z}_{n} = -\begin{bmatrix} a_{0} & a_{1} & \cdots & a_{n-1} \end{bmatrix} \mathbf{z}+u. \]
This is a linear parameterization in the denominator coefficients. The state chain also separates the known shift structure in the first \(n-1\) equations from the coefficient-dependent final equation. These features are useful when a later controller is parameterized with adjustable gains.
This observation does not by itself establish an adaptive controller. General-order MRAC additionally requires a control-law structure, matching conditions, an error model, knowledge of the relevant gain sign under the assumptions of the design, and a stability-based update law. Those topics are developed in Lessons 2 through 4.
10. Numerical Conditioning and Implementation Discipline
Canonical forms are mathematically exact but may be numerically unattractive. Powers \(\mathbf{A}^{k}\mathbf{b}\) can differ greatly in scale, especially for high-order plants, widely separated poles, or poorly scaled states. Then \(\mathcal{C}\) may have a large condition number even though it has full rank.
Reliable implementation should therefore:
- determine numerical rank with singular values or a rank-revealing factorization, not an exact determinant test;
- solve linear systems for \(\mathbf{T}\) instead of explicitly forming \(\mathcal{C}_{c}^{-1}\);
- verify residuals such as \(\|\mathbf{A}\mathbf{T}-\mathbf{T}\mathbf{A}_{c}\|\) and \(\|\mathbf{b}-\mathbf{T}\mathbf{b}_{c}\|\);
- compare transfer functions or frequency responses; and
- compare trajectories after transforming the initial condition.
For high-order production software, orthogonal controllability staircase forms are often preferable to raw power-basis canonical transformations. The companion form remains extremely valuable for proofs, symbolic derivations, low-order models, and the structural development of adaptive controllers.
11. Python Implementation
The Python implementation uses NumPy for matrix operations, SciPy for state-space and integration checks, and optionally the Python Control Systems Library for an independent reachable-form comparison. The from-scratch transformation is retained so the basis construction is explicit.
Chapter7_Lesson1.py
"""
Chapter7_Lesson1.py
Controllable canonical form for continuous-time SISO LTI systems.
Dependencies:
Required: numpy, scipy
Optional: control (Python Control Systems Library)
Install:
python -m pip install numpy scipy control
"""
from __future__ import annotations
import numpy as np
from numpy.typing import NDArray
from scipy.integrate import solve_ivp
from scipy.signal import ss2tf
FloatMatrix = NDArray[np.float64]
def controllability_matrix(A: FloatMatrix, B: FloatMatrix) -> FloatMatrix:
"""Return [B, AB, ..., A^(n-1)B] for a single-input system."""
A = np.asarray(A, dtype=float)
B = np.asarray(B, dtype=float).reshape(-1, 1)
n = A.shape[0]
if A.shape != (n, n) or B.shape != (n, 1):
raise ValueError("A must be n-by-n and B must be n-by-1.")
columns = [B]
for _ in range(1, n):
columns.append(A @ columns[-1])
return np.hstack(columns)
def controllable_companion(
denominator_ascending: FloatMatrix,
numerator_ascending: FloatMatrix,
direct_term: float = 0.0,
) -> tuple[FloatMatrix, FloatMatrix, FloatMatrix, FloatMatrix]:
"""
Construct the controller companion realization.
denominator_ascending = [a0, a1, ..., a_(n-1)] represents
s^n + a_(n-1)s^(n-1) + ... + a1 s + a0.
numerator_ascending = [b0, b1, ..., b_(n-1)] represents the
strictly proper remainder
b_(n-1)s^(n-1) + ... + b1 s + b0.
"""
a = np.asarray(denominator_ascending, dtype=float).reshape(-1)
b = np.asarray(numerator_ascending, dtype=float).reshape(-1)
n = a.size
if b.size != n:
raise ValueError("The numerator remainder must contain n coefficients.")
A_c = np.zeros((n, n), dtype=float)
if n > 1:
A_c[:-1, 1:] = np.eye(n - 1)
A_c[-1, :] = -a
B_c = np.zeros((n, 1), dtype=float)
B_c[-1, 0] = 1.0
C_c = b.reshape(1, -1)
D_c = np.array([[float(direct_term)]])
return A_c, B_c, C_c, D_c
def transform_to_controllable_companion(
A: FloatMatrix,
B: FloatMatrix,
C: FloatMatrix,
D: FloatMatrix,
) -> tuple[FloatMatrix, FloatMatrix, FloatMatrix, FloatMatrix, FloatMatrix]:
"""
Transform a controllable SISO realization into controller companion form.
Coordinates are related by x = T z, so:
A_c = T^{-1} A T
B_c = T^{-1} B
C_c = C T
"""
A = np.asarray(A, dtype=float)
B = np.asarray(B, dtype=float).reshape(-1, 1)
C = np.asarray(C, dtype=float).reshape(1, -1)
D = np.asarray(D, dtype=float).reshape(1, 1)
n = A.shape[0]
W = controllability_matrix(A, B)
if np.linalg.matrix_rank(W) != n:
raise ValueError("The pair (A, B) is not controllable.")
# np.poly(A) returns [1, a_(n-1), ..., a0].
characteristic_descending = np.poly(A)
a_ascending = characteristic_descending[1:][::-1]
A_c, B_c, _, _ = controllable_companion(
a_ascending, np.zeros(n), float(D[0, 0])
)
W_c = controllability_matrix(A_c, B_c)
# T = W W_c^{-1}, evaluated without forming an explicit inverse.
T = np.linalg.solve(W_c.T, W.T).T
T_inv = np.linalg.inv(T)
A_check = T_inv @ A @ T
B_check = T_inv @ B
C_c = C @ T
if not np.allclose(A_check, A_c, atol=1e-10):
raise RuntimeError("A transformation check failed.")
if not np.allclose(B_check, B_c, atol=1e-10):
raise RuntimeError("B transformation check failed.")
return A_c, B_c, C_c, D.copy(), T
def simulate(
A: FloatMatrix,
B: FloatMatrix,
C: FloatMatrix,
D: FloatMatrix,
x0: FloatMatrix,
final_time: float = 8.0,
) -> tuple[FloatMatrix, FloatMatrix]:
"""Simulate the response to u(t) = sin(0.7t) + 0.25."""
Bv = B.reshape(-1)
Cv = C.reshape(-1)
d = float(D[0, 0])
def input_signal(t: float) -> float:
return float(np.sin(0.7 * t) + 0.25)
def rhs(t: float, x: FloatMatrix) -> FloatMatrix:
return A @ x + Bv * input_signal(t)
t_eval = np.linspace(0.0, final_time, 801)
solution = solve_ivp(
rhs,
(0.0, final_time),
np.asarray(x0, dtype=float).reshape(-1),
t_eval=t_eval,
rtol=1e-10,
atol=1e-12,
)
if not solution.success:
raise RuntimeError(solution.message)
u = np.array([input_signal(t) for t in solution.t])
y = Cv @ solution.y + d * u
return solution.t, y
def main() -> None:
np.set_printoptions(precision=8, suppress=True)
# Canonical target:
# G(s) = (2 s^2 + 3 s + 1)/(s^3 + 4 s^2 + 5 s + 2)
a_ascending = np.array([2.0, 5.0, 4.0])
b_ascending = np.array([1.0, 3.0, 2.0])
A_c_expected, B_c_expected, C_c_expected, D_c_expected = (
controllable_companion(a_ascending, b_ascending)
)
# A noncanonical realization generated by x = T_true z.
A = np.array(
[
[0.0, 1.0, 0.0],
[-2.0, -3.0, 0.0],
[-1.0, -3.0, -1.0],
]
)
B = np.array([[0.0], [1.0], [1.0]])
C = np.array([[1.0, 2.0, 0.0]])
D = np.array([[0.0]])
A_c, B_c, C_c, D_c, T = transform_to_controllable_companion(A, B, C, D)
print("A_c =\n", A_c)
print("B_c =\n", B_c)
print("C_c =\n", C_c)
print("D_c =\n", D_c)
print("T (x = T z) =\n", T)
print("rank C(A,B) =", np.linalg.matrix_rank(controllability_matrix(A, B)))
print("cond C(A,B) =", np.linalg.cond(controllability_matrix(A, B)))
assert np.allclose(A_c, A_c_expected)
assert np.allclose(B_c, B_c_expected)
assert np.allclose(C_c, C_c_expected)
assert np.allclose(D_c, D_c_expected)
# Transfer-function verification using SciPy.
numerator, denominator = ss2tf(A_c, B_c, C_c, D_c)
print("SciPy numerator coefficients (descending) =", numerator[0])
print("SciPy denominator coefficients (descending) =", denominator)
# Coordinate-equivalent simulations.
x0 = np.array([0.4, -0.2, 0.1])
z0 = np.linalg.solve(T, x0)
t_original, y_original = simulate(A, B, C, D, x0)
t_canonical, y_canonical = simulate(A_c, B_c, C_c, D_c, z0)
assert np.allclose(t_original, t_canonical)
print("max |y_original - y_canonical| =",
np.max(np.abs(y_original - y_canonical)))
# Optional validation with the Python Control Systems Library.
try:
import control as ct
original_system = ct.ss(A, B, C, D)
reachable_system, coordinate_map = ct.reachable_form(original_system)
print("python-control reachable A =\n", reachable_system.A)
print("python-control coordinate map =\n", coordinate_map)
except ImportError:
print("Optional package 'control' is not installed; validation skipped.")
if __name__ == "__main__":
main()
12. C++ Implementation with Eigen
Eigen supplies dense matrix factorizations and singular values. The characteristic polynomial coefficients are computed with the Faddeev-LeVerrier recurrence, while the canonical transformation is implemented directly from the two controllability matrices.
Chapter7_Lesson1.cpp
/*
Chapter7_Lesson1.cpp
Controllable canonical form for continuous-time SISO LTI systems.
Library:
Eigen 3 (header-only linear algebra)
Example compilation:
g++ -std=c++17 -O2 Chapter7_Lesson1.cpp \
-I /path/to/eigen -o Chapter7_Lesson1
*/
#include <Eigen/Dense>
#include <cmath>
#include <iomanip>
#include <iostream>
#include <stdexcept>
#include <vector>
using Eigen::MatrixXd;
using Eigen::VectorXd;
MatrixXd controllabilityMatrix(const MatrixXd& A, const VectorXd& B) {
const Eigen::Index n = A.rows();
if (A.cols() != n || B.size() != n) {
throw std::invalid_argument("A must be n-by-n and B must have n entries.");
}
MatrixXd W(n, n);
VectorXd column = B;
for (Eigen::Index k = 0; k < n; ++k) {
W.col(k) = column;
column = A * column;
}
return W;
}
/*
Faddeev-LeVerrier:
returns [c1, c2, ..., cn] for
det(lambda I - A) = lambda^n + c1 lambda^(n-1) + ... + cn.
*/
VectorXd characteristicCoefficients(const MatrixXd& A) {
const Eigen::Index n = A.rows();
if (A.cols() != n) {
throw std::invalid_argument("A must be square.");
}
MatrixXd Bk = MatrixXd::Identity(n, n);
const MatrixXd I = MatrixXd::Identity(n, n);
VectorXd coefficients(n);
for (Eigen::Index k = 1; k <= n; ++k) {
const double ck = -(A * Bk).trace() / static_cast<double>(k);
coefficients(k - 1) = ck;
Bk = A * Bk + ck * I;
}
return coefficients;
}
struct CanonicalModel {
MatrixXd A;
VectorXd B;
Eigen::RowVectorXd C;
double D;
};
CanonicalModel controllableCompanion(
const VectorXd& denominatorAscending,
const VectorXd& numeratorAscending,
double directTerm = 0.0
) {
const Eigen::Index n = denominatorAscending.size();
if (numeratorAscending.size() != n) {
throw std::invalid_argument(
"The numerator remainder must contain n coefficients."
);
}
CanonicalModel model{
MatrixXd::Zero(n, n),
VectorXd::Zero(n),
numeratorAscending.transpose(),
directTerm
};
for (Eigen::Index i = 0; i < n - 1; ++i) {
model.A(i, i + 1) = 1.0;
}
model.A.row(n - 1) = -denominatorAscending.transpose();
model.B(n - 1) = 1.0;
return model;
}
struct TransformationResult {
CanonicalModel canonical;
MatrixXd T; // x = T z
};
TransformationResult transformToControllableCompanion(
const MatrixXd& A,
const VectorXd& B,
const Eigen::RowVectorXd& C,
double D
) {
const Eigen::Index n = A.rows();
const MatrixXd W = controllabilityMatrix(A, B);
Eigen::FullPivLU<MatrixXd> rankTest(W);
if (rankTest.rank() != n) {
throw std::runtime_error("The pair (A, B) is not controllable.");
}
const VectorXd descending = characteristicCoefficients(A);
VectorXd ascending(n);
for (Eigen::Index i = 0; i < n; ++i) {
ascending(i) = descending(n - 1 - i);
}
CanonicalModel canonical =
controllableCompanion(ascending, VectorXd::Zero(n), D);
const MatrixXd Wc =
controllabilityMatrix(canonical.A, canonical.B);
// T = W Wc^{-1}; solve the transposed system instead of inverting Wc.
const MatrixXd T =
Wc.transpose().fullPivLu().solve(W.transpose()).transpose();
const MatrixXd Acheck = T.fullPivLu().solve(A * T);
const VectorXd Bcheck = T.fullPivLu().solve(B);
canonical.C = C * T;
const double tolerance = 1.0e-10;
if ((Acheck - canonical.A).norm() > tolerance ||
(Bcheck - canonical.B).norm() > tolerance) {
throw std::runtime_error("Similarity-transformation verification failed.");
}
return {canonical, T};
}
int main() {
std::cout << std::fixed << std::setprecision(8);
MatrixXd A(3, 3);
A << 0.0, 1.0, 0.0,
-2.0, -3.0, 0.0,
-1.0, -3.0, -1.0;
VectorXd B(3);
B << 0.0, 1.0, 1.0;
Eigen::RowVectorXd C(3);
C << 1.0, 2.0, 0.0;
const double D = 0.0;
const TransformationResult result =
transformToControllableCompanion(A, B, C, D);
const MatrixXd W = controllabilityMatrix(A, B);
Eigen::JacobiSVD<MatrixXd> svd(W);
const auto singularValues = svd.singularValues();
const double conditionNumber =
singularValues(0) / singularValues(singularValues.size() - 1);
std::cout << "A_c =\n" << result.canonical.A << "\n\n";
std::cout << "B_c =\n" << result.canonical.B << "\n\n";
std::cout << "C_c =\n" << result.canonical.C << "\n\n";
std::cout << "D_c =\n" << result.canonical.D << "\n\n";
std::cout << "T (x = T z) =\n" << result.T << "\n\n";
std::cout << "rank C(A,B) = " << W.fullPivLu().rank() << "\n";
std::cout << "cond C(A,B) = " << conditionNumber << "\n";
VectorXd expectedA(3);
expectedA << 2.0, 5.0, 4.0;
VectorXd expectedB(3);
expectedB << 1.0, 3.0, 2.0;
const CanonicalModel expected =
controllableCompanion(expectedA, expectedB);
if ((result.canonical.A - expected.A).norm() > 1.0e-10 ||
(result.canonical.B - expected.B).norm() > 1.0e-10) {
std::cerr << "Unexpected canonical matrices.\n";
return 1;
}
if ((result.canonical.C - expected.C).norm() > 1.0e-10) {
std::cerr << "Unexpected canonical output matrix.\n";
return 1;
}
std::cout << "Numerator remainder C_c =\n"
<< result.canonical.C << "\n";
return 0;
}
13. Java Implementation with EJML
The Java implementation uses EJML's
SimpleMatrix interface. It performs the same rank check,
coefficient recovery, basis transformation, and residual verification as
the C++ implementation.
Chapter7_Lesson1.java
/*
Chapter7_Lesson1.java
Controllable canonical form for continuous-time SISO LTI systems.
Library:
EJML SimpleMatrix
Maven dependency:
<dependency>
<groupId>org.ejml</groupId>
<artifactId>ejml-simple</artifactId>
<version>0.43.1</version>
</dependency>
Compile/run with the EJML jars on the classpath.
*/
import org.ejml.simple.SimpleMatrix;
public final class Chapter7_Lesson1 {
private Chapter7_Lesson1() {
// Utility class.
}
static SimpleMatrix controllabilityMatrix(SimpleMatrix A, SimpleMatrix B) {
int n = A.numRows();
if (A.numCols() != n || B.numRows() != n || B.numCols() != 1) {
throw new IllegalArgumentException(
"A must be n-by-n and B must be n-by-1."
);
}
SimpleMatrix W = new SimpleMatrix(n, n);
SimpleMatrix column = B.copy();
for (int k = 0; k < n; k++) {
W.insertIntoThis(0, k, column);
column = A.mult(column);
}
return W;
}
/*
* Faddeev-LeVerrier:
* returns [c1, ..., cn] for
* det(lambda I - A) = lambda^n + c1 lambda^(n-1) + ... + cn.
*/
static double[] characteristicCoefficients(SimpleMatrix A) {
int n = A.numRows();
if (A.numCols() != n) {
throw new IllegalArgumentException("A must be square.");
}
SimpleMatrix identity = SimpleMatrix.identity(n);
SimpleMatrix bk = identity.copy();
double[] coefficients = new double[n];
for (int k = 1; k <= n; k++) {
double ck = -A.mult(bk).trace() / k;
coefficients[k - 1] = ck;
bk = A.mult(bk).plus(identity.scale(ck));
}
return coefficients;
}
static final class CanonicalModel {
final SimpleMatrix A;
final SimpleMatrix B;
final SimpleMatrix C;
final double D;
CanonicalModel(
SimpleMatrix aMatrix,
SimpleMatrix bMatrix,
SimpleMatrix cMatrix,
double dValue
) {
A = aMatrix;
B = bMatrix;
C = cMatrix;
D = dValue;
}
}
static CanonicalModel controllableCompanion(
double[] denominatorAscending,
double[] numeratorAscending,
double directTerm
) {
int n = denominatorAscending.length;
if (numeratorAscending.length != n) {
throw new IllegalArgumentException(
"The numerator remainder must contain n coefficients."
);
}
SimpleMatrix ac = new SimpleMatrix(n, n);
for (int i = 0; i < n - 1; i++) {
ac.set(i, i + 1, 1.0);
}
for (int j = 0; j < n; j++) {
ac.set(n - 1, j, -denominatorAscending[j]);
}
SimpleMatrix bc = new SimpleMatrix(n, 1);
bc.set(n - 1, 0, 1.0);
SimpleMatrix cc = new SimpleMatrix(1, n);
for (int j = 0; j < n; j++) {
cc.set(0, j, numeratorAscending[j]);
}
return new CanonicalModel(ac, bc, cc, directTerm);
}
static final class TransformationResult {
final CanonicalModel canonical;
final SimpleMatrix T; // x = T z
TransformationResult(CanonicalModel model, SimpleMatrix transformation) {
canonical = model;
T = transformation;
}
}
static TransformationResult transformToControllableCompanion(
SimpleMatrix A,
SimpleMatrix B,
SimpleMatrix C,
double D
) {
int n = A.numRows();
SimpleMatrix w = controllabilityMatrix(A, B);
if (w.rank() != n) {
throw new IllegalArgumentException(
"The pair (A, B) is not controllable."
);
}
double[] descending = characteristicCoefficients(A);
double[] ascending = new double[n];
for (int i = 0; i < n; i++) {
ascending[i] = descending[n - 1 - i];
}
CanonicalModel base = controllableCompanion(
ascending, new double[n], D
);
SimpleMatrix wc = controllabilityMatrix(base.A, base.B);
// T = W Wc^{-1}; solve Wc^T T^T = W^T.
SimpleMatrix t = wc.transpose().solve(w.transpose()).transpose();
SimpleMatrix aCheck = t.solve(A.mult(t));
SimpleMatrix bCheck = t.solve(B);
SimpleMatrix cc = C.mult(t);
double tolerance = 1.0e-10;
if (aCheck.minus(base.A).normF() > tolerance
|| bCheck.minus(base.B).normF() > tolerance) {
throw new IllegalStateException(
"Similarity-transformation verification failed."
);
}
CanonicalModel result =
new CanonicalModel(base.A, base.B, cc, D);
return new TransformationResult(result, t);
}
public static void main(String[] args) {
SimpleMatrix A = new SimpleMatrix(
new double[][] {
{ 0.0, 1.0, 0.0},
{-2.0, -3.0, 0.0},
{-1.0, -3.0, -1.0}
}
);
SimpleMatrix B = new SimpleMatrix(
new double[][] {
{0.0},
{1.0},
{1.0}
}
);
SimpleMatrix C = new SimpleMatrix(
new double[][] {
{1.0, 2.0, 0.0}
}
);
double D = 0.0;
TransformationResult result =
transformToControllableCompanion(A, B, C, D);
System.out.println("A_c =");
result.canonical.A.print();
System.out.println("B_c =");
result.canonical.B.print();
System.out.println("C_c =");
result.canonical.C.print();
System.out.println("D_c = " + result.canonical.D);
System.out.println("T (x = T z) =");
result.T.print();
SimpleMatrix w = controllabilityMatrix(A, B);
System.out.println("rank C(A,B) = " + w.rank());
System.out.println("condition_2 C(A,B) = " + w.conditionP2());
CanonicalModel expected = controllableCompanion(
new double[] {2.0, 5.0, 4.0},
new double[] {1.0, 3.0, 2.0},
0.0
);
double tolerance = 1.0e-10;
if (result.canonical.A.minus(expected.A).normF() > tolerance
|| result.canonical.B.minus(expected.B).normF() > tolerance
|| result.canonical.C.minus(expected.C).normF() > tolerance) {
throw new IllegalStateException(
"Unexpected canonical realization."
);
}
System.out.println("All canonical-form checks passed.");
}
}
14. MATLAB and Simulink Implementation
MATLAB is used both for a from-scratch construction and for validation with Control System Toolbox. The optional final section programmatically creates a Simulink model containing a Sine Wave source, a State-Space block configured with the canonical matrices, a Scope, and a workspace output.
Chapter7_Lesson1.m
%% Chapter7_Lesson1.m
% Controllable canonical form for continuous-time SISO LTI systems.
%
% Toolboxes:
% Required for ss/tf/lsim: Control System Toolbox
% Optional model generation: Simulink
%
% The example realizes
% G(s) = (2 s^2 + 3 s + 1)/(s^3 + 4 s^2 + 5 s + 2).
clear;
clc;
format short g;
%% 1. Construct the controller companion realization from coefficients
% Ascending coefficient order:
% aAscending = [a0, a1, ..., a_(n-1)]
% bAscending = [b0, b1, ..., b_(n-1)]
aAscending = [2, 5, 4];
bAscending = [1, 3, 2];
n = numel(aAscending);
AcExpected = [zeros(n-1, 1), eye(n-1); -aAscending];
BcExpected = [zeros(n-1, 1); 1];
CcExpected = bAscending;
DcExpected = 0;
disp('Expected controller companion realization:');
disp('AcExpected ='); disp(AcExpected);
disp('BcExpected ='); disp(BcExpected);
disp('CcExpected ='); disp(CcExpected);
%% 2. Start with a noncanonical but equivalent realization
A = [ 0, 1, 0;
-2, -3, 0;
-1, -3, -1];
B = [0; 1; 1];
C = [1, 2, 0];
D = 0;
W = ctrb_from_scratch(A, B);
assert(rank(W) == n, 'The pair (A,B) is not controllable.');
% Characteristic polynomial:
% poly(A) = [1, a_(n-1), ..., a0].
characteristicDescending = poly(A);
aRecoveredAscending = fliplr(characteristicDescending(2:end));
Ac = [zeros(n-1, 1), eye(n-1); -aRecoveredAscending];
Bc = [zeros(n-1, 1); 1];
Wc = ctrb_from_scratch(Ac, Bc);
% x = T z and W = T Wc, hence T = W Wc^{-1}.
% MATLAB right division avoids explicitly forming inv(Wc).
T = W / Wc;
AcCheck = T \ (A * T);
BcCheck = T \ B;
Cc = C * T;
Dc = D;
assert(norm(AcCheck - Ac, 'fro') < 1e-10);
assert(norm(BcCheck - Bc, 'fro') < 1e-10);
assert(norm(Ac - AcExpected, 'fro') < 1e-10);
assert(norm(Bc - BcExpected, 'fro') < 1e-10);
assert(norm(Cc - CcExpected, 'fro') < 1e-10);
disp('Recovered transformation x = T z:');
disp(T);
disp('Recovered canonical matrices:');
disp('Ac ='); disp(Ac);
disp('Bc ='); disp(Bc);
disp('Cc ='); disp(Cc);
fprintf('rank C(A,B) = %d\n', rank(W));
fprintf('cond C(A,B) = %.8g\n', cond(W));
%% 3. Validate transfer functions and coordinate-equivalent responses
sysOriginal = ss(A, B, C, D);
sysCanonical = ss(Ac, Bc, Cc, Dc);
GOriginal = minreal(tf(sysOriginal));
GCanonical = minreal(tf(sysCanonical));
disp('Transfer function from the original realization:');
GOriginal
disp('Transfer function from the canonical realization:');
GCanonical
t = linspace(0, 8, 801).';
u = sin(0.7*t) + 0.25;
x0 = [0.4; -0.2; 0.1];
z0 = T \ x0;
yOriginal = lsim(sysOriginal, u, t, x0);
yCanonical = lsim(sysCanonical, u, t, z0);
fprintf('max |yOriginal-yCanonical| = %.3e\n', ...
max(abs(yOriginal-yCanonical)));
figure('Name', 'Chapter 7 Lesson 1: Coordinate-equivalent outputs');
plot(t, yOriginal, '-', t, yCanonical, '--', 'LineWidth', 1.2);
grid on;
xlabel('Time (s)');
ylabel('Output');
legend('Original realization', 'Controllable canonical realization', ...
'Location', 'best');
title('Input-output invariance under x = Tz');
%% 4. Optional Simulink model generation
% Running this section creates Chapter7_Lesson1_CCF_Model.slx.
if license('test', 'Simulink')
modelName = 'Chapter7_Lesson1_CCF_Model';
if bdIsLoaded(modelName)
close_system(modelName, 0);
end
if exist([modelName, '.slx'], 'file')
delete([modelName, '.slx']);
end
new_system(modelName);
open_system(modelName);
add_block('simulink/Sources/Sine Wave', ...
[modelName, '/Input'], ...
'Amplitude', '1', ...
'Frequency', '0.7', ...
'Bias', '0.25', ...
'Position', [40, 70, 110, 100]);
add_block('simulink/Continuous/State-Space', ...
[modelName, '/Controller Companion Plant'], ...
'A', mat2str(Ac), ...
'B', mat2str(Bc), ...
'C', mat2str(Cc), ...
'D', mat2str(Dc), ...
'X0', mat2str(z0), ...
'Position', [170, 55, 330, 115]);
add_block('simulink/Sinks/Scope', ...
[modelName, '/Output Scope'], ...
'Position', [400, 50, 440, 90]);
add_block('simulink/Sinks/To Workspace', ...
[modelName, '/Output To Workspace'], ...
'VariableName', 'yCanonicalSimulink', ...
'SaveFormat', 'Structure With Time', ...
'Position', [380, 120, 500, 150]);
add_line(modelName, 'Input/1', ...
'Controller Companion Plant/1', 'autorouting', 'on');
add_line(modelName, 'Controller Companion Plant/1', ...
'Output Scope/1', 'autorouting', 'on');
add_line(modelName, 'Controller Companion Plant/1', ...
'Output To Workspace/1', 'autorouting', 'on');
set_param(modelName, ...
'StopTime', '8', ...
'Solver', 'ode45', ...
'SaveOutput', 'on');
save_system(modelName);
fprintf('Created %s.slx\n', modelName);
else
disp('Simulink is unavailable; model-generation section skipped.');
end
%% Local function
function W = ctrb_from_scratch(A, B)
%CTRB_FROM_SCRATCH Return [B, AB, ..., A^(n-1)B] for a SISO system.
n = size(A, 1);
if size(A, 2) ~= n || ~isequal(size(B), [n, 1])
error('A must be n-by-n and B must be n-by-1.');
end
W = zeros(n, n);
column = B;
for k = 1:n
W(:, k) = column;
column = A * column;
end
end
15. Wolfram Mathematica Implementation
The notebook uses exact arithmetic for the matrix derivation, Wolfram Language control-system functions for verification, and numerical differential-equation solutions for the trajectory comparison.
Chapter7_Lesson1.nb
Notebook[{
Cell["Chapter 7, Lesson 1: Controllable Canonical Form for SISO Plants", "Title"],
Cell["This notebook constructs the controller companion realization, recovers it from an arbitrary controllable realization, and verifies transfer-function and trajectory equivalence.", "Text"],
Cell[BoxData["ClearAll[\"Global`*\"];"], "Input"],
Cell[BoxData["aAscending = {2, 5, 4}; bAscending = {1, 3, 2}; n = Length[aAscending];
AcExpected = Join[
ArrayFlatten[{{ConstantArray[0, {n - 1, 1}], IdentityMatrix[n - 1]}}],
{-aAscending}
];
BcExpected = Join[ConstantArray[0, n - 1], {1}];
CcExpected = {bAscending}; DcExpected = {{0}};"], "Input"],
Cell[BoxData["A = {{0, 1, 0}, {-2, -3, 0}, {-1, -3, -1}};
B = {{0}, {1}, {1}}; C = {{1, 2, 0}}; D = {{0}};
ssOriginal = StateSpaceModel[{A, B, C, D}];
W = ControllabilityMatrix[ssOriginal];
{MatrixRank[W], MatrixForm[W]}"], "Input"],
Cell[BoxData["characteristicPolynomial = CharacteristicPolynomial[A, s] // Expand;
descending = Rest[CoefficientList[characteristicPolynomial, s] // Reverse];
aRecoveredAscending = Reverse[descending];
Ac = Join[
ArrayFlatten[{{ConstantArray[0, {n - 1, 1}], IdentityMatrix[n - 1]}}],
{-aRecoveredAscending}
];
Bc = List /@ Join[ConstantArray[0, n - 1], {1}];
controllabilityFromScratch[m_, b_] :=
Transpose[NestList[m.# &, Flatten[b], Length[m] - 1]];
Wc = controllabilityFromScratch[Ac, Bc];
T = Simplify[W . Inverse[Wc]];
AcCheck = Simplify[Inverse[T].A.T];
BcCheck = Simplify[Inverse[T].B];
Cc = Simplify[C.T];
Dc = D;
{MatrixForm[AcCheck], MatrixForm[BcCheck],
MatrixForm[Cc], MatrixForm[T]}"], "Input"],
Cell[BoxData["verification = {
Simplify[AcCheck == AcExpected],
Simplify[BcCheck == List /@ BcExpected],
Simplify[Cc == CcExpected],
Simplify[Det[W] != 0],
Simplify[A.T == T.AcExpected],
Simplify[B == T.(List /@ BcExpected)]
};
verification"], "Input"],
Cell[BoxData["ssCanonical = StateSpaceModel[
{AcExpected, List /@ BcExpected, CcExpected, DcExpected}
];
tfOriginal = TransferFunctionModel[ssOriginal, s] // Simplify;
tfCanonical = TransferFunctionModel[ssCanonical, s] // Simplify;
{tfOriginal, tfCanonical,
Simplify[tfOriginal == tfCanonical]}"], "Input"],
Cell[BoxData["x0 = {0.4, -0.2, 0.1};
z0 = LinearSolve[T, x0];
u[t_] := Sin[0.7 t] + 0.25;
originalSolution = NDSolveValue[
{
x'[t] == A.x[t] + Flatten[B] u[t],
x[0] == x0
},
x,
{t, 0, 8}
];
canonicalSolution = NDSolveValue[
{
z'[t] == AcExpected.z[t] + BcExpected u[t],
z[0] == z0
},
z,
{t, 0, 8}
];
yOriginal[t_] :=
First[C.originalSolution[t]] + D[[1, 1]] u[t];
yCanonical[t_] :=
First[CcExpected.canonicalSolution[t]] +
DcExpected[[1, 1]] u[t];
maximumOutputError = Max@Table[
Abs[yOriginal[t] - yCanonical[t]],
{t, 0, 8, 0.01}
];
Plot[
Evaluate[{yOriginal[t], yCanonical[t]}],
{t, 0, 8},
PlotLegends -> {"Original", "Controllable canonical"},
AxesLabel -> {"t", "y"},
PlotLabel -> Row[{
"Maximum sampled output error = ",
ScientificForm[maximumOutputError]
}]
]"], "Input"],
Cell[BoxData["(* Built-in controllable-companion realization for comparison. *)
tfm = TransferFunctionModel[
{{{2 s^2 + 3 s + 1}}, s^3 + 4 s^2 + 5 s + 2},
s
];
builtInCompanion = StateSpaceModel[
tfm,
StateSpaceRealization -> \"ControllableCompanion\"
];
{
ControllableModelQ[builtInCompanion],
MatrixForm[ControllabilityMatrix[builtInCompanion]],
builtInCompanion
}"], "Input"]
},
WindowTitle -> "Chapter7_Lesson1",
StyleDefinitions -> "Default.nb"
]
16. Problems and Solutions
Problem 1: Constructing a Fourth-Order Canonical Realization
Construct the controller companion realization for
\[ G(s)= \frac{3s^{3}-2s^{2}+5s+7} {s^{4}+6s^{3}+11s^{2}+6s+4}. \]
Solution:
Reading the denominator in ascending coefficient order gives \((a_{0},a_{1},a_{2},a_{3})=(4,6,11,6)\). The numerator remainder gives \((\bar b_{0},\bar b_{1},\bar b_{2},\bar b_{3}) =(7,5,-2,3)\). Thus
\[ \mathbf{A}_{c}= \begin{bmatrix} 0 & 1 & 0 & 0\\ 0 & 0 & 1 & 0\\ 0 & 0 & 0 & 1\\ -4 & -6 & -11 & -6 \end{bmatrix}, \quad \mathbf{b}_{c}= \begin{bmatrix} 0\\0\\0\\1 \end{bmatrix}, \quad \mathbf{c}_{c}^{T}= \begin{bmatrix} 7 & 5 & -2 & 3 \end{bmatrix}, \quad d=0. \]
Problem 2: Handling a Nonzero Direct Term
Put the proper transfer function
\[ G(s)= \frac{3s^{3}+7s^{2}+8s+4} {s^{3}+4s^{2}+5s+2} \]
into the form \(d+r(s)/a(s)\), then give the canonical output matrices.
Solution:
The leading-coefficient ratio is \(d=3\). Subtracting \(3a(s)\) from the numerator gives
\[ r(s)= (3s^{3}+7s^{2}+8s+4) -3(s^{3}+4s^{2}+5s+2) = -5s^{2}-7s-2. \]
Therefore,
\[ \mathbf{c}_{c}^{T}= \begin{bmatrix} -2 & -7 & -5 \end{bmatrix}, \qquad d=3, \]
while \(\mathbf{A}_{c}\) and \(\mathbf{b}_{c}\) are obtained from \((a_{0},a_{1},a_{2})=(2,5,4)\).
Problem 3: Computing a Similarity Transformation
For
\[ \mathbf{A}= \begin{bmatrix} 1 & 2\\ -3 & -4 \end{bmatrix}, \qquad \mathbf{b}= \begin{bmatrix} 1\\0 \end{bmatrix}, \]
compute the controller companion pair and the matrix \(\mathbf{T}\) satisfying \(\mathbf{x}=\mathbf{T}\mathbf{z}\).
Solution:
The characteristic polynomial is
\[ \det(s\mathbf{I}-\mathbf{A})=s^{2}+3s+2. \]
Hence
\[ \mathbf{A}_{c}= \begin{bmatrix} 0 & 1\\ -2 & -3 \end{bmatrix}, \qquad \mathbf{b}_{c}= \begin{bmatrix} 0\\1 \end{bmatrix}. \]
The two controllability matrices are
\[ \mathcal{C}= \begin{bmatrix} 1 & 1\\ 0 & -3 \end{bmatrix}, \qquad \mathcal{C}_{c}= \begin{bmatrix} 0 & 1\\ 1 & -3 \end{bmatrix}. \]
Thus
\[ \mathbf{T}=\mathcal{C}\mathcal{C}_{c}^{-1} = \begin{bmatrix} 4 & 1\\ -3 & 0 \end{bmatrix}. \]
Substitution confirms \(\mathbf{T}^{-1}\mathbf{A}\mathbf{T} =\mathbf{A}_{c}\) and \(\mathbf{T}^{-1}\mathbf{b}=\mathbf{b}_{c}\).
Problem 4: Proving Canonical Controllability
Prove that the controller companion pair is controllable for every choice of denominator coefficients.
Solution:
Starting from \(\mathbf{b}_{c}=\mathbf{e}_{n}\), the vectors \(\mathbf{b}_{c},\mathbf{A}_{c}\mathbf{b}_{c},\ldots, \mathbf{A}_{c}^{n-1}\mathbf{b}_{c}\) introduce, successively, a unit entry in rows \(n,n-1,\ldots,1\). Entries affected by the denominator coefficients occur only on and below that anti-diagonal ordering. Therefore, the controllability matrix has anti-diagonal entries equal to one. Reversing its columns converts it to a triangular matrix with unit diagonal. Column reversal contributes the sign \((-1)^{n(n-1)/2}\), so
\[ \det\mathcal{C}_{c}=(-1)^{n(n-1)/2}\neq 0. \]
The pair is therefore controllable.
Problem 5: Why an Uncontrollable Plant Cannot Use the Formula
Consider
\[ \mathbf{A}= \begin{bmatrix} -1 & 0\\ 0 & -2 \end{bmatrix}, \qquad \mathbf{b}= \begin{bmatrix} 1\\0 \end{bmatrix}. \]
Explain why no nonsingular full-order transformation to a controllable companion pair exists.
Solution:
The controllability matrix is
\[ \mathcal{C}= \begin{bmatrix} 1 & -1\\ 0 & 0 \end{bmatrix}, \qquad \operatorname{rank}\mathcal{C}=1. \]
Similarity transformations preserve controllability rank. Every controller companion pair has controllability rank \(2\), so a rank-one pair cannot be similar to it. The uncontrollable mode must first be separated using a controllability decomposition; only the controllable subsystem can be put into a controllable companion form.
17. Summary
A controllable SISO realization can be represented in controller companion coordinates. The denominator coefficients occupy the last row of \(\mathbf{A}_{c}\), the input acts through \(\mathbf{b}_{c}=\mathbf{e}_{n}\), and the numerator remainder coefficients form \(\mathbf{c}_{c}^{T}\). Direct solution of \((s\mathbf{I}-\mathbf{A}_{c})^{-1}\mathbf{b}_{c}\) proves transfer-function equivalence. For an arbitrary controllable realization, the coordinate matrix is \(\mathbf{T}=\mathcal{C}\mathcal{C}_{c}^{-1}\), with \(\mathbf{x}=\mathbf{T}\mathbf{z}\). These coordinates provide the structured plant model required for the controller parameterization developed in Lesson 2.
18. References
- Kalman, R.E. (1960). Contributions to the theory of optimal control. Boletín de la Sociedad Matemática Mexicana, 5, 102–119.
- Gilbert, E.G. (1963). Controllability and observability in multivariable control systems. SIAM Journal on Control, 1(2), 128–151.
- Luenberger, D.G. (1967). Canonical forms for linear multivariable systems. IEEE Transactions on Automatic Control, 12(3), 290–293.
- Wonham, W.M. (1967). On pole assignment in multi-input controllable linear systems. IEEE Transactions on Automatic Control, 12(6), 660–665.
- Brunovský, P. (1970). A classification of linear controllable systems. Kybernetika, 6(3), 173–188.
- Ho, B.L., & Kalman, R.E. (1966). Effective construction of linear state-variable models from input/output functions. Regelungstechnik, 14(12), 545–548.
Help keep these engineering tutorials free and growing
If these lessons, examples, and project pages help you, a small donation supports the continued creation and improvement of free control, robotics, software, and engineering education resources.
Created and maintained by Abolfazl Mohammadijoo.