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:

  1. determine numerical rank with singular values or a rank-revealing factorization, not an exact determinant test;
  2. solve linear systems for \(\mathbf{T}\) instead of explicitly forming \(\mathcal{C}_{c}^{-1}\);
  3. verify residuals such as \(\|\mathbf{A}\mathbf{T}-\mathbf{T}\mathbf{A}_{c}\|\) and \(\|\mathbf{b}-\mathbf{T}\mathbf{b}_{c}\|\);
  4. compare transfer functions or frequency responses; and
  5. 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

  1. Kalman, R.E. (1960). Contributions to the theory of optimal control. Boletín de la Sociedad Matemática Mexicana, 5, 102–119.
  2. Gilbert, E.G. (1963). Controllability and observability in multivariable control systems. SIAM Journal on Control, 1(2), 128–151.
  3. Luenberger, D.G. (1967). Canonical forms for linear multivariable systems. IEEE Transactions on Automatic Control, 12(3), 290–293.
  4. Wonham, W.M. (1967). On pole assignment in multi-input controllable linear systems. IEEE Transactions on Automatic Control, 12(6), 660–665.
  5. Brunovský, P. (1970). A classification of linear controllable systems. Kybernetika, 6(3), 173–188.
  6. Ho, B.L., & Kalman, R.E. (1966). Effective construction of linear state-variable models from input/output functions. Regelungstechnik, 14(12), 545–548.
Support CaaT Academy

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.