Chapter 16: Controllable Canonical Form
Lesson 3: Properties of CCF for Analysis and Design
This lesson studies the algebraic properties that make controllable canonical form useful for analysis and controller design. We prove that the companion realization has a prescribed characteristic polynomial, is always controllable, gives a direct transfer-function interpretation, and permits pole placement by coefficient matching.
1. Why CCF Is Useful
For a SISO strictly proper transfer function with monic denominator,
\[ G(s)=\frac{N(s)}{D(s)},\quad D(s)=s^n+a_{n-1}s^{n-1}+\cdots+a_1s+a_0, \]
\[ N(s)=b_{n-1}s^{n-1}+b_{n-2}s^{n-2}+\cdots+b_1s+b_0, \]
the controllable canonical form places all denominator coefficients in the last row of the state matrix:
\[ \dot{\mathbf{x}}=\mathbf{A}_c\mathbf{x}+\mathbf{B}_c u,\quad y=\mathbf{C}_c\mathbf{x}+Du, \]
\[ \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}, \quad \mathbf{B}_c= \begin{bmatrix} 0\\0\\ \vdots\\0\\1 \end{bmatrix}, \quad \mathbf{C}_c= \begin{bmatrix} b_0&b_1&\cdots&b_{n-1} \end{bmatrix}. \]
flowchart TD
A["Transfer function data: denominator and numerator"] --> B["Build CCF matrices A_c, B_c, C_c, D"]
B --> C["Analysis property: \ndet(sI-A_c) equals D(s)"]
B --> D["Controllability property: \nrank of controllability matrix equals n"]
C --> E["Design property: \nfeedback changes only last row"]
D --> E
E --> F["Pole placement by coefficient matching"]
F --> G["Closed-loop polynomial equals desired polynomial"]
2. Characteristic Polynomial Property
The first key property is that the eigenvalues of \( \mathbf{A}_c \) are exactly the roots of the denominator polynomial \( D(s) \).
\[ \boxed{ \det(s\mathbf{I}-\mathbf{A}_c) = s^n+a_{n-1}s^{n-1}+\cdots+a_1s+a_0 = D(s) } \]
Proof sketch. Let
\[ \mathbf{v}(s)= \begin{bmatrix} 1&s&s^2&\cdots&s^{n-1} \end{bmatrix}^{T}. \]
The shift structure in the first \( n-1 \) rows of \( \mathbf{A}_c \) enforces the recurrence \( v_{k+1}=s v_k \). The last row gives
\[ -a_0-a_1s-\cdots-a_{n-1}s^{n-1}=s^n. \]
Rearranging yields \( D(s)=0 \). Therefore, the values of \( s \) for which \( s\mathbf{I}-\mathbf{A}_c \) loses rank are exactly the roots of the denominator polynomial.
3. Controllability Property
The controllability matrix of the CCF pair is
\[ \mathcal{C}_c= \begin{bmatrix} \mathbf{B}_c&\mathbf{A}_c\mathbf{B}_c& \mathbf{A}_c^2\mathbf{B}_c&\cdots& \mathbf{A}_c^{n-1}\mathbf{B}_c \end{bmatrix}. \]
For controllable canonical form,
\[ \boxed{ \operatorname{rank}(\mathcal{C}_c)=n } \]
In fact, the determinant is nonzero for every choice of denominator coefficients:
\[ \det(\mathcal{C}_c)=(-1)^{\frac{n(n-1)}{2}}. \]
Thus controllability does not depend on the numerical values of \( a_0,\dots,a_{n-1} \). This is why the form is called controllable canonical form.
4. Transfer-Function and Minimality Properties
The transfer function of the CCF realization is
\[ G(s)=\mathbf{C}_c(s\mathbf{I}-\mathbf{A}_c)^{-1}\mathbf{B}_c+D. \]
For the strictly proper part,
\[ \mathbf{C}_c(s\mathbf{I}-\mathbf{A}_c)^{-1}\mathbf{B}_c = \frac{b_{n-1}s^{n-1}+\cdots+b_1s+b_0} {s^n+a_{n-1}s^{n-1}+\cdots+a_1s+a_0}. \]
If the original transfer function is proper but not strictly proper, first separate the direct term:
\[ G(s)=d+\frac{N_s(s)}{D(s)}. \]
Then use \( D=d \) as the direct feedthrough and use \( N_s(s) \) to form \( \mathbf{C}_c \).
Observability depends on the numerator. For an eigenvalue \( \lambda \) satisfying \( D(\lambda)=0 \), define
\[ \mathbf{v}(\lambda)= \begin{bmatrix} 1&\lambda&\lambda^2&\cdots&\lambda^{n-1} \end{bmatrix}^{T}. \]
Then \( \mathbf{A}_c\mathbf{v}(\lambda)=\lambda \mathbf{v}(\lambda) \) and \( \mathbf{C}_c\mathbf{v}(\lambda)=N(\lambda) \). Hence an eigenmode is unobservable exactly when the numerator and denominator share the same root.
\[ \boxed{ \text{CCF realization is minimal} \iff \gcd(N(s),D(s))=1. } \]
flowchart TD
A["CCF is always controllable"] --> B["Check numerator denominator common roots"]
B -->|no common root| C["Observable and controllable"]
C --> D["Minimal realization"]
B -->|common root exists| E["Unobservable pole-zero cancellation"]
E --> F["Controllable but nonminimal"]
5. State-Feedback Design by Coefficient Matching
Let the feedback law be
\[ u=-\mathbf{K}\mathbf{x}+r,\quad \mathbf{K}= \begin{bmatrix} k_1&k_2&\cdots&k_n \end{bmatrix}. \]
Since \( \mathbf{B}_c \) is the last unit vector, feedback changes only the last row of the matrix:
\[ \mathbf{A}_{cl} = \mathbf{A}_c-\mathbf{B}_c\mathbf{K}. \]
Therefore the closed-loop characteristic polynomial is
\[ \det(s\mathbf{I}-\mathbf{A}_{cl}) = s^n+(a_{n-1}+k_n)s^{n-1}+\cdots+ (a_1+k_2)s+(a_0+k_1). \]
If the desired polynomial is
\[ D_d(s)=s^n+\alpha_{n-1}s^{n-1}+\cdots+\alpha_1s+\alpha_0, \]
then direct coefficient matching gives
\[ \boxed{ \mathbf{K}= \begin{bmatrix} \alpha_0-a_0& \alpha_1-a_1& \cdots& \alpha_{n-1}-a_{n-1} \end{bmatrix}. } \]
6. Software Libraries Used for This Lesson
The implementations below use simple matrix operations so students can see the mechanics of the CCF construction. In practice, larger systems should be analyzed with numerical linear algebra libraries.
- Python: NumPy for matrices; SciPy, python-control, and Slycot for state-space and transfer-function workflows.
- C++: Eigen, Armadillo, and SLICOT-style routines for robust matrix computations.
- Java: EJML, Apache Commons Math, and ojAlgo for matrix operations.
-
MATLAB/Simulink: Control System Toolbox functions
such as
ss,tf,ctrb,place, andacker; Simulink State-Space block. -
Wolfram Mathematica: built-in symbolic linear
algebra,
StateSpaceModel, andTransferFunctionModel.
7. Python Implementation
Chapter16_Lesson3.py
import numpy as np
def ccf_matrices(a, b):
a = np.asarray(a, dtype=float).ravel()
b = np.asarray(b, dtype=float).ravel()
n = len(a)
A = np.zeros((n, n))
A[:-1, 1:] = np.eye(n - 1)
A[-1, :] = -a
B = np.zeros((n, 1))
B[-1, 0] = 1.0
C = b.reshape(1, -1)
D = np.array([[0.0]])
return A, B, C, D
def controllability_matrix(A, B):
n = A.shape[0]
cols = []
v = B.copy()
for _ in range(n):
cols.append(v)
v = A @ v
return np.hstack(cols)
def desired_coefficients_from_roots(roots):
p = np.poly(np.asarray(roots, dtype=float))
return p[:0:-1]
def pole_placement_gain_ccf(a, alpha):
return np.asarray(alpha, dtype=float) - np.asarray(a, dtype=float)
# D(s)=s^4+6s^3+11s^2+6s+2
# N(s)=3s^2+2s+1
a = np.array([2.0, 6.0, 11.0, 6.0])
b = np.array([1.0, 2.0, 3.0, 0.0])
A, B, C, D = ccf_matrices(a, b)
Wc = controllability_matrix(A, B)
print("A =\n", A)
print("B =\n", B)
print("C =\n", C)
print("rank(Wc) =", np.linalg.matrix_rank(Wc))
print("det(Wc) =", round(np.linalg.det(Wc)))
desired_roots = [-2, -3, -4, -5]
alpha = desired_coefficients_from_roots(desired_roots)
K = pole_placement_gain_ccf(a, alpha)
Acl = A - B @ K.reshape(1, -1)
print("alpha =", alpha)
print("K =", K)
print("closed-loop eigenvalues =", np.sort_complex(np.linalg.eigvals(Acl)))
8. C++ Implementation
Chapter16_Lesson3.cpp
#include <cmath>
#include <iomanip>
#include <iostream>
#include <vector>
using Matrix = std::vector<std::vector<double>>;
using Vector = std::vector<double>;
Matrix zeros(int r, int c) {
return Matrix(r, Vector(c, 0.0));
}
Vector matVec(const Matrix& A, const Vector& x) {
int n = A.size();
Vector y(n, 0.0);
for (int i = 0; i < n; ++i)
for (int j = 0; j < (int)x.size(); ++j)
y[i] += A[i][j] * x[j];
return y;
}
Matrix buildAccf(const Vector& a) {
int n = a.size();
Matrix A = zeros(n, n);
for (int i = 0; i < n - 1; ++i) A[i][i + 1] = 1.0;
for (int j = 0; j < n; ++j) A[n - 1][j] = -a[j];
return A;
}
Vector buildBccf(int n) {
Vector B(n, 0.0);
B[n - 1] = 1.0;
return B;
}
Matrix controllabilityMatrix(const Matrix& A, const Vector& B) {
int n = A.size();
Matrix W = zeros(n, n);
Vector v = B;
for (int col = 0; col < n; ++col) {
for (int row = 0; row < n; ++row) W[row][col] = v[row];
v = matVec(A, v);
}
return W;
}
double determinant(Matrix M) {
int n = M.size();
double det = 1.0;
int sign = 1;
const double eps = 1e-12;
for (int k = 0; k < n; ++k) {
int pivot = k;
for (int i = k + 1; i < n; ++i)
if (std::fabs(M[i][k]) > std::fabs(M[pivot][k])) pivot = i;
if (std::fabs(M[pivot][k]) < eps) return 0.0;
if (pivot != k) {
std::swap(M[pivot], M[k]);
sign *= -1;
}
double p = M[k][k];
det *= p;
for (int i = k + 1; i < n; ++i) {
double factor = M[i][k] / p;
for (int j = k; j < n; ++j)
M[i][j] -= factor * M[k][j];
}
}
return sign * det;
}
void printMatrix(const Matrix& M, const std::string& name) {
std::cout << name << " =\n";
for (const auto& row : M) {
for (double v : row) std::cout << std::setw(12) << v << " ";
std::cout << "\n";
}
}
int main() {
Vector a = {2.0, 6.0, 11.0, 6.0};
Matrix A = buildAccf(a);
Vector B = buildBccf(a.size());
Matrix Wc = controllabilityMatrix(A, B);
printMatrix(A, "A");
printMatrix(Wc, "Wc");
std::cout << "det(Wc) = " << determinant(Wc) << "\n";
Vector alpha = {120.0, 154.0, 71.0, 14.0};
Vector K(a.size());
for (int i = 0; i < (int)a.size(); ++i)
K[i] = alpha[i] - a[i];
std::cout << "K = [ ";
for (double v : K) std::cout << v << " ";
std::cout << "]\n";
return 0;
}
9. Java Implementation
Chapter16_Lesson3.java
import java.util.Arrays;
public class Chapter16_Lesson3 {
static double[][] buildAccf(double[] a) {
int n = a.length;
double[][] A = new double[n][n];
for (int i = 0; i < n - 1; i++)
A[i][i + 1] = 1.0;
for (int j = 0; j < n; j++)
A[n - 1][j] = -a[j];
return A;
}
static double[] buildBccf(int n) {
double[] B = new double[n];
B[n - 1] = 1.0;
return B;
}
static double[] matVec(double[][] A, double[] x) {
int n = A.length;
double[] y = new double[n];
for (int i = 0; i < n; i++)
for (int j = 0; j < x.length; j++)
y[i] += A[i][j] * x[j];
return y;
}
static double[][] controllabilityMatrix(double[][] A, double[] B) {
int n = A.length;
double[][] W = new double[n][n];
double[] v = B.clone();
for (int col = 0; col < n; col++) {
for (int row = 0; row < n; row++)
W[row][col] = v[row];
v = matVec(A, v);
}
return W;
}
static double[] polePlacementGain(double[] a, double[] alpha) {
double[] K = new double[a.length];
for (int i = 0; i < a.length; i++)
K[i] = alpha[i] - a[i];
return K;
}
public static void main(String[] args) {
double[] a = {2.0, 6.0, 11.0, 6.0};
double[] alpha = {120.0, 154.0, 71.0, 14.0};
double[][] A = buildAccf(a);
double[] B = buildBccf(a.length);
double[][] Wc = controllabilityMatrix(A, B);
double[] K = polePlacementGain(a, alpha);
System.out.println("A =");
for (double[] row : A)
System.out.println(Arrays.toString(row));
System.out.println("B = " + Arrays.toString(B));
System.out.println("Wc =");
for (double[] row : Wc)
System.out.println(Arrays.toString(row));
System.out.println("K = " + Arrays.toString(K));
}
}
10. MATLAB and Simulink Implementation
Chapter16_Lesson3.m
% Chapter16_Lesson3.m
clear; clc;
% D(s) = s^4 + 6s^3 + 11s^2 + 6s + 2
% N(s) = 3s^2 + 2s + 1
a = [2 6 11 6];
b = [1 2 3 0];
n = numel(a);
A = zeros(n,n);
A(1:n-1,2:n) = eye(n-1);
A(n,:) = -a;
B = zeros(n,1);
B(n) = 1;
C = b;
D = 0;
Wc = zeros(n,n);
Apow = eye(n);
for k = 1:n
Wc(:,k) = Apow * B;
Apow = A * Apow;
end
disp('A ='); disp(A);
disp('B ='); disp(B);
disp('C ='); disp(C);
disp('Wc ='); disp(Wc);
fprintf('rank(Wc) = %d\n', rank(Wc));
fprintf('det(Wc) = %.0f\n', det(Wc));
desired_poles = [-2 -3 -4 -5];
p_des = poly(desired_poles);
alpha = fliplr(p_des(2:end));
K = alpha - a;
Acl = A - B*K;
disp('alpha ='); disp(alpha);
disp('K ='); disp(K);
disp('eig(A-BK) ='); disp(eig(Acl).');
% Control System Toolbox alternatives:
% sys = ss(A,B,C,D);
% tf(sys)
% place(A,B,desired_poles)
% acker(A,B,desired_poles)
% Simulink note:
% Use a State-Space block with matrices A, B, C, D.
% Implement u = r - K*x using a Gain block and Sum block.
11. Wolfram Mathematica Implementation
Chapter16_Lesson3.nb
ClearAll["Global`*"];
a = {2, 6, 11, 6};
b = {1, 2, 3, 0};
n = Length[a];
A = ConstantArray[0, {n, n}];
Do[
A[[i, i + 1]] = 1,
{i, 1, n - 1}
];
A[[n, All]] = -a;
B = UnitVector[n, n];
Cmat = {b};
Dmat = {{0}};
Wc = Transpose[
Table[MatrixPower[A, k].B, {k, 0, n - 1}]
];
desiredPoles = {-2, -3, -4, -5};
pdes = CoefficientList[
Expand[Times @@ (s - # & /@ desiredPoles)],
s
];
alpha = Most[pdes];
K = alpha - a;
Acl = A - KroneckerProduct[B, K];
Print["A = ", MatrixForm[A]];
Print["B = ", MatrixForm[B]];
Print["C = ", MatrixForm[Cmat]];
Print["Wc = ", MatrixForm[Wc]];
Print["Rank[Wc] = ", MatrixRank[Wc]];
Print["Det[Wc] = ", Det[Wc]];
Print["K = ", K];
Print["Eigenvalues[A - B K] = ", Eigenvalues[Acl]];
G = FullSimplify[
Cmat . Inverse[s IdentityMatrix[n] - A] . B + Dmat
];
Print["G(s) = ", G[[1, 1]]];
12. Problems and Solutions
Problem 1: For \( D(s)=s^3+4s^2+5s+2 \), construct \( \mathbf{A}_c \) and \( \mathbf{B}_c \).
Solution: Here \( a_0=2 \), \( a_1=5 \), and \( a_2=4 \). Therefore
\[ \mathbf{A}_c= \begin{bmatrix} 0&1&0\\ 0&0&1\\ -2&-5&-4 \end{bmatrix}, \quad \mathbf{B}_c= \begin{bmatrix} 0\\0\\1 \end{bmatrix}. \]
Problem 2: Prove that the system in Problem 1 is controllable.
Solution: Compute
\[ \mathcal{C}_c= \begin{bmatrix} \mathbf{B}_c&\mathbf{A}_c\mathbf{B}_c&\mathbf{A}_c^2\mathbf{B}_c \end{bmatrix} = \begin{bmatrix} 0&0&1\\ 0&1&-4\\ 1&-4&11 \end{bmatrix}. \]
\[ \det(\mathcal{C}_c)=-1. \]
Since the determinant is nonzero, \( \operatorname{rank}(\mathcal{C}_c)=3 \), so the system is controllable.
Problem 3: For the system in Problem 1, design \( \mathbf{K} \) so that the desired poles are \( -1,-2,-3 \).
Solution: The desired polynomial is
\[ D_d(s)=(s+1)(s+2)(s+3)=s^3+6s^2+11s+6. \]
Hence \( \alpha_0=6 \), \( \alpha_1=11 \), and \( \alpha_2=6 \). Since \( a_0=2,a_1=5,a_2=4 \),
\[ \mathbf{K}= \begin{bmatrix} 6-2&11-5&6-4 \end{bmatrix} = \begin{bmatrix} 4&6&2 \end{bmatrix}. \]
Problem 4: Let \( N(s)=s+1 \) and \( D(s)=s^3+4s^2+5s+2 \). Is the CCF realization minimal?
Solution: Factor the denominator:
\[ D(s)=s^3+4s^2+5s+2=(s+1)^2(s+2). \]
Since \( N(s)=s+1 \) shares the root \( s=-1 \) with \( D(s) \), the realization is controllable but not observable. Therefore it is not minimal.
Problem 5: Explain why CCF is convenient for pole placement but may be numerically sensitive for high-order systems.
Solution: CCF is convenient because \( \mathbf{B}_c \) changes only the last row under state feedback, so pole placement reduces to polynomial coefficient matching. However, polynomial coefficients can be poorly conditioned: small coefficient errors may create large root errors. Therefore, high-order practical systems often use numerically balanced, modal, or staircase forms instead of raw companion form.
13. Summary
Controllable canonical form encodes the denominator polynomial directly in the state matrix, guarantees controllability, and makes state-feedback pole placement especially transparent. Its transfer-function numerator determines observability and minimality: common roots between numerator and denominator correspond to hidden pole-zero cancellations. The main design advantage is coefficient matching, while the main practical drawback is numerical sensitivity for high-order systems.
14. References
- Kalman, R.E. (1960). Contributions to the theory of optimal control. Boletín de la Sociedad Matemática Mexicana, 5, 102–119.
- Kalman, R.E. (1963). Mathematical description of linear dynamical systems. Journal of the Society for Industrial and Applied Mathematics, Series A: Control, 1(2), 152–192.
- Kalman, R.E. (1963). Controllability of linear dynamical systems. Contributions to Differential Equations, 1, 190–213.
- Gilbert, E.G. (1963). Controllability and observability in multivariable control systems. Journal of the Society for Industrial and Applied Mathematics, Series A: Control, 1(2), 128–151.
- Luenberger, D.G. (1967). Canonical forms for linear multivariable systems. IEEE Transactions on Automatic Control, AC-12(3), 290–293.
- Brunovský, P. (1970). A classification of linear controllable systems. Kybernetika, 6(3), 173–188.
- Wonham, W.M. (1967). On pole assignment in multi-input controllable linear systems. IEEE Transactions on Automatic Control, AC-12(6), 660–665.
- Rosenbrock, H.H. (1970). State-space and multivariable theory. Control-theoretic monograph, Wiley.