Chapter 17: Observable and Modal Canonical Forms
Lesson 2: Relationship Between CCF and OCF
This lesson establishes the exact algebraic relationship between controllable canonical form (CCF) and observable canonical form (OCF). The central idea is duality: the observable canonical realization of a SISO transfer function is obtained by transposing the controllable canonical realization and interchanging the roles of the input and output vectors.
1. Conceptual Overview
In Chapter 16, CCF was introduced as a realization where the input enters a companion chain. In Lesson 1 of this chapter, OCF was introduced as the corresponding structure where the output row is fixed and the numerator coefficients move to the input vector. The relationship is exact, not mnemonic.
For a strictly proper SISO transfer function with monic denominator,
\[ G(s)=\frac{b_{n-1}s^{n-1}+b_{n-2}s^{n-2}+\cdots+b_1s+b_0} {s^n+a_{n-1}s^{n-1}+a_{n-2}s^{n-2}+\cdots+a_1s+a_0}. \]
If \( (\mathbf A_c,\mathbf B_c,\mathbf C_c,D) \) is the CCF realization, then one consistent OCF realization is
\[ \boxed{\mathbf A_o=\mathbf A_c^T,\qquad \mathbf B_o=\mathbf C_c^T,\qquad \mathbf C_o=\mathbf B_c^T.} \]
flowchart TD
TF["Transfer function G(s)"] --> CCF["CCF: Ac, Bc, Cc"]
CCF --> CT["Controllability matrix: B, AB, ..., A^(n-1)B"]
CCF --> DUAL["Transpose duality"]
DUAL --> OCF["OCF: Ao = Ac^T, Bo = Cc^T, Co = Bc^T"]
OCF --> OB["Observability matrix: C; CA; ...; CA^(n-1)"]
CT --> RANK["same rank after transpose"]
OB --> RANK
RANK --> SAME["same scalar transfer function"]
2. Controllable Canonical Form Structure
Using the coefficient ordering \( a_0,a_1,\dots,a_{n-1} \) and \( b_0,b_1,\dots,b_{n-1} \), the CCF realization is
\[ \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},\qquad \mathbf C_c= \begin{bmatrix} b_0 & b_1 & b_2 & \cdots & b_{n-1} \end{bmatrix}. \]
Direct solution of the resolvent equation gives
\[ (s\mathbf I-\mathbf A_c)^{-1}\mathbf B_c = \frac{1}{p(s)} \begin{bmatrix} 1\\s\\s^2\\\vdots\\s^{n-1} \end{bmatrix},\qquad p(s)=s^n+a_{n-1}s^{n-1}+\cdots+a_1s+a_0. \]
Therefore,
\[ \mathbf C_c(s\mathbf I-\mathbf A_c)^{-1}\mathbf B_c = \frac{b_0+b_1s+b_2s^2+\cdots+b_{n-1}s^{n-1} }{p(s)}=G(s). \]
3. Observable Canonical Form as the Dual of CCF
Applying the transpose-dual construction gives
\[ \mathbf A_o=\mathbf A_c^T= \begin{bmatrix} 0 & 0 & \cdots & 0 & -a_0 \\ 1 & 0 & \cdots & 0 & -a_1 \\ 0 & 1 & \cdots & 0 & -a_2 \\ \vdots & \vdots & \ddots & \vdots & \vdots \\ 0 & 0 & \cdots & 1 & -a_{n-1} \end{bmatrix},\qquad \mathbf B_o= \begin{bmatrix} b_0\\b_1\\b_2\\\vdots\\b_{n-1} \end{bmatrix},\qquad \mathbf C_o= \begin{bmatrix} 0 & 0 & \cdots & 0 & 1 \end{bmatrix}. \]
Thus, CCF places numerator coefficients in \( \mathbf C_c \), while OCF places the same coefficients in \( \mathbf B_o \). The denominator coefficients remain in the companion matrix.
4. Proof of Transfer-Function Equivalence
The CCF transfer function is
\[ G_c(s)=\mathbf C_c(s\mathbf I-\mathbf A_c)^{-1}\mathbf B_c+D. \]
Since this expression is scalar, it equals its transpose. Hence,
\[ \begin{aligned} G_c(s) &=\left[\mathbf C_c(s\mathbf I-\mathbf A_c)^{-1}\mathbf B_c+D\right]^T \\ &=\mathbf B_c^T\left[(s\mathbf I-\mathbf A_c)^{-1}\right]^T\mathbf C_c^T+D \\ &=\mathbf B_c^T(s\mathbf I-\mathbf A_c^T)^{-1}\mathbf C_c^T+D \\ &=\mathbf C_o(s\mathbf I-\mathbf A_o)^{-1}\mathbf B_o+D \\ &=G_o(s). \end{aligned} \]
Therefore,
\[ \boxed{G_c(s)=G_o(s)}. \]
5. Controllability and Observability Matrices Under Duality
The CCF controllability matrix and OCF observability matrix are
\[ \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},\qquad \mathcal O_o= \begin{bmatrix} \mathbf C_o \\ \mathbf C_o\mathbf A_o \\ \vdots \\ \mathbf C_o\mathbf A_o^{n-1} \end{bmatrix}. \]
Substituting \( \mathbf A_o=\mathbf A_c^T \) and \( \mathbf C_o=\mathbf B_c^T \) gives
\[ \mathcal O_o= \begin{bmatrix} \mathbf B_c^T\\ (\mathbf A_c\mathbf B_c)^T\\ \vdots\\ (\mathbf A_c^{n-1}\mathbf B_c)^T \end{bmatrix} =\mathcal C_c^T. \]
Since transpose preserves rank,
\[ \operatorname{rank}\mathcal O_o = \operatorname{rank}\mathcal C_c. \]
Hence, the CCF realization being controllable is equivalent to the dual OCF realization being observable.
6. Worked Third-Order Example
Consider
\[ G(s)=\frac{2s^2+5s+3}{s^3+4s^2+6s+4}. \]
The CCF realization is
\[ \mathbf A_c= \begin{bmatrix} 0&1&0\\ 0&0&1\\ -4&-6&-4 \end{bmatrix},\qquad \mathbf B_c= \begin{bmatrix} 0\\0\\1 \end{bmatrix},\qquad \mathbf C_c= \begin{bmatrix} 3&5&2 \end{bmatrix}. \]
The corresponding OCF realization is
\[ \mathbf A_o= \begin{bmatrix} 0&0&-4\\ 1&0&-6\\ 0&1&-4 \end{bmatrix},\qquad \mathbf B_o= \begin{bmatrix} 3\\5\\2 \end{bmatrix},\qquad \mathbf C_o= \begin{bmatrix} 0&0&1 \end{bmatrix}. \]
Both realizations satisfy
\[ \mathbf C_c(s\mathbf I-\mathbf A_c)^{-1}\mathbf B_c = \mathbf C_o(s\mathbf I-\mathbf A_o)^{-1}\mathbf B_o = \frac{2s^2+5s+3}{s^3+4s^2+6s+4}. \]
7. Coordinate and Convention Notes
Different textbooks may use reversed state ordering. Let \( \mathbf J \) denote the reversal matrix:
\[ \mathbf J= \begin{bmatrix} 0&0&\cdots&1\\ 0&0&1&0\\ \vdots&\vdots&\cdots&\vdots\\ 1&0&\cdots&0 \end{bmatrix},\qquad \mathbf J^T=\mathbf J,\qquad \mathbf J^2=\mathbf I. \]
If a realization is written in reversed coordinates, then the transformed matrices are
\[ \tilde{\mathbf A}=\mathbf J\mathbf A\mathbf J,\qquad \tilde{\mathbf B}=\mathbf J\mathbf B,\qquad \tilde{\mathbf C}=\mathbf C\mathbf J. \]
This is a similarity transformation by a permutation matrix, so the transfer function is unchanged:
\[ \tilde{\mathbf C}(s\mathbf I-\tilde{\mathbf A})^{-1} \tilde{\mathbf B}+D = \mathbf C(s\mathbf I-\mathbf A)^{-1}\mathbf B+D. \]
8. Algorithmic Construction Workflow
The construction is simple enough to implement from scratch, but it is also supported by modern control libraries.
flowchart TD
A["Start with numerator and monic denominator"] --> B["Extract a0 ... a(n-1)"]
B --> C["Extract b0 ... b(n-1)"]
C --> D["Build CCF: Ac, Bc, Cc"]
D --> E["Set Ao = Ac transpose"]
E --> F["Set Bo = Cc transpose"]
F --> G["Set Co = Bc transpose"]
G --> H["Verify rank and transfer equality"]
9. Python Implementation
Useful Python libraries include NumPy, SciPy,
python-control, and SymPy. The following
implementation uses only NumPy.
File: Chapter17_Lesson2.py
# Chapter17_Lesson2.py
# CCF-OCF duality for a strictly proper SISO transfer function.
# Requires: numpy
import numpy as np
def ctrb(A, B):
n = A.shape[0]
blocks, Ak = [], np.eye(n)
for _ in range(n):
blocks.append(Ak @ B)
Ak = Ak @ A
return np.hstack(blocks)
def obsv(A, C):
n = A.shape[0]
rows, Ak = [], np.eye(n)
for _ in range(n):
rows.append(C @ Ak)
Ak = Ak @ A
return np.vstack(rows)
def ccf_ocf(num_desc, den_desc):
# den_desc = [1, a_{n-1}, ..., a0]
# num_desc = [b_{n-1}, ..., b0], padded if necessary.
den_desc = np.asarray(den_desc, dtype=float)
num_desc = np.asarray(num_desc, dtype=float)
if abs(den_desc[0] - 1.0) > 1e-12:
num_desc = num_desc / den_desc[0]
den_desc = den_desc / den_desc[0]
n = len(den_desc) - 1
if len(num_desc) > n:
raise ValueError("This script assumes a strictly proper transfer function.")
a = den_desc[1:][::-1] # [a0,...,a_{n-1}]
b = np.pad(num_desc, (n-len(num_desc), 0))[::-1] # [b0,...,b_{n-1}]
Ac = np.zeros((n, n))
Ac[:-1, 1:] = np.eye(n - 1)
Ac[-1, :] = -a
Bc = np.zeros((n, 1))
Bc[-1, 0] = 1.0
Cc = b.reshape(1, n)
D = np.array([[0.0]])
Ao = Ac.T
Bo = Cc.T
Co = Bc.T
return Ac, Bc, Cc, D, Ao, Bo, Co, D.copy()
def H(A, B, C, D, s):
n = A.shape[0]
return (C @ np.linalg.solve(s*np.eye(n) - A, B) + D)[0, 0]
if __name__ == "__main__":
# G(s) = (2s^2 + 5s + 3)/(s^3 + 4s^2 + 6s + 4)
num = [2.0, 5.0, 3.0]
den = [1.0, 4.0, 6.0, 4.0]
Ac, Bc, Cc, Dc, Ao, Bo, Co, Do = ccf_ocf(num, den)
print("Ac =\n", Ac)
print("Bc =\n", Bc)
print("Cc =\n", Cc)
print("Ao = Ac.T =\n", Ao)
print("Bo = Cc.T =\n", Bo)
print("Co = Bc.T =\n", Co)
Qc = ctrb(Ac, Bc)
Oo = obsv(Ao, Co)
print("rank ctrb(Ac,Bc) =", np.linalg.matrix_rank(Qc))
print("rank obsv(Ao,Co) =", np.linalg.matrix_rank(Oo))
print("||Oo - Qc.T||_F =", np.linalg.norm(Oo - Qc.T))
for s in [0.5, 1.0, 2.0, 3.0]:
hc = H(Ac, Bc, Cc, Dc, s)
ho = H(Ao, Bo, Co, Do, s)
print(f"s={s:.1f}: H_CCF={hc:.10f}, H_OCF={ho:.10f}, error={abs(hc-ho):.2e}")
10. C++ Implementation
Useful C++ libraries for state-space computation include Eigen, Armadillo, Blaze, and LAPACK. This version uses Eigen for compact matrix operations.
File: Chapter17_Lesson2.cpp
// Chapter17_Lesson2.cpp
// CCF-OCF duality using Eigen.
// Compile example: g++ -std=c++17 Chapter17_Lesson2.cpp -I /path/to/eigen -o Chapter17_Lesson2
#include <Eigen/Dense>
#include <iostream>
#include <vector>
using Eigen::MatrixXd;
using Eigen::VectorXd;
MatrixXd ctrb(const MatrixXd& A, const MatrixXd& B) {
int n = A.rows();
MatrixXd Q(n, n);
MatrixXd Ak = MatrixXd::Identity(n, n);
for (int k = 0; k < n; ++k) {
Q.col(k) = Ak * B;
Ak = Ak * A;
}
return Q;
}
MatrixXd obsv(const MatrixXd& A, const MatrixXd& C) {
int n = A.rows();
MatrixXd O(n, n);
MatrixXd Ak = MatrixXd::Identity(n, n);
for (int k = 0; k < n; ++k) {
O.row(k) = C * Ak;
Ak = Ak * A;
}
return O;
}
double transferValue(const MatrixXd& A, const MatrixXd& B,
const MatrixXd& C, double D, double s) {
int n = A.rows();
MatrixXd M = s * MatrixXd::Identity(n, n) - A;
MatrixXd x = M.fullPivLu().solve(B);
return (C * x)(0, 0) + D;
}
int main() {
// G(s) = (2s^2 + 5s + 3)/(s^3 + 4s^2 + 6s + 4)
int n = 3;
VectorXd a(n); a << 4.0, 6.0, 4.0; // [a0,a1,a2]
VectorXd b(n); b << 3.0, 5.0, 2.0; // [b0,b1,b2]
MatrixXd Ac = MatrixXd::Zero(n, n);
Ac.block(0, 1, n-1, n-1) = MatrixXd::Identity(n-1, n-1);
Ac.row(n-1) = -a.transpose();
MatrixXd Bc = MatrixXd::Zero(n, 1);
Bc(n-1, 0) = 1.0;
MatrixXd Cc(1, n);
Cc = b.transpose();
MatrixXd Ao = Ac.transpose();
MatrixXd Bo = Cc.transpose();
MatrixXd Co = Bc.transpose();
std::cout << "Ac =\n" << Ac << "\n\n";
std::cout << "Ao = Ac^T =\n" << Ao << "\n\n";
MatrixXd Qc = ctrb(Ac, Bc);
MatrixXd Oo = obsv(Ao, Co);
std::cout << "rank ctrb(Ac,Bc) = " << Qc.fullPivLu().rank() << "\n";
std::cout << "rank obsv(Ao,Co) = " << Oo.fullPivLu().rank() << "\n";
std::cout << "||Oo - Qc^T||_F = " << (Oo - Qc.transpose()).norm() << "\n\n";
for (double s : {0.5, 1.0, 2.0, 3.0}) {
double hc = transferValue(Ac, Bc, Cc, 0.0, s);
double ho = transferValue(Ao, Bo, Co, 0.0, s);
std::cout << "s=" << s << " H_CCF=" << hc
<< " H_OCF=" << ho << " error=" << std::abs(hc-ho) << "\n";
}
}
11. Java Implementation
Useful Java numerical libraries include EJML, Apache Commons Math, ojAlgo, and ND4J. This version uses plain Java arrays so that the canonical-form logic is visible.
File: Chapter17_Lesson2.java
// Chapter17_Lesson2.java
// CCF-OCF duality using plain Java arrays.
// Compile: javac Chapter17_Lesson2.java
// Run: java Chapter17_Lesson2
public class Chapter17_Lesson2 {
static double[][] zeros(int r, int c) { return new double[r][c]; }
static double[][] eye(int n) {
double[][] I = zeros(n, n);
for (int i = 0; i < n; i++) I[i][i] = 1.0;
return I;
}
static double[][] transpose(double[][] A) {
double[][] T = zeros(A[0].length, A.length);
for (int i = 0; i < A.length; i++)
for (int j = 0; j < A[0].length; j++)
T[j][i] = A[i][j];
return T;
}
static double[][] multiply(double[][] A, double[][] B) {
double[][] C = zeros(A.length, B[0].length);
for (int i = 0; i < A.length; i++)
for (int k = 0; k < A[0].length; k++)
for (int j = 0; j < B[0].length; j++)
C[i][j] += A[i][k] * B[k][j];
return C;
}
static double[][] ctrb(double[][] A, double[][] B) {
int n = A.length;
double[][] Q = zeros(n, n);
double[][] Ak = eye(n);
for (int k = 0; k < n; k++) {
double[][] col = multiply(Ak, B);
for (int i = 0; i < n; i++) Q[i][k] = col[i][0];
Ak = multiply(Ak, A);
}
return Q;
}
static double[][] obsv(double[][] A, double[][] C) {
int n = A.length;
double[][] O = zeros(n, n);
double[][] Ak = eye(n);
for (int k = 0; k < n; k++) {
double[][] row = multiply(C, Ak);
for (int j = 0; j < n; j++) O[k][j] = row[0][j];
Ak = multiply(Ak, A);
}
return O;
}
static int rank(double[][] input) {
double[][] A = copy(input);
int m = A.length, n = A[0].length, r = 0;
for (int col = 0; col < n && r < m; col++) {
int p = r;
for (int i = r+1; i < m; i++)
if (Math.abs(A[i][col]) > Math.abs(A[p][col])) p = i;
if (Math.abs(A[p][col]) < 1e-10) continue;
double[] tmp = A[r]; A[r] = A[p]; A[p] = tmp;
double pivot = A[r][col];
for (int j = col; j < n; j++) A[r][j] /= pivot;
for (int i = 0; i < m; i++) {
if (i == r) continue;
double f = A[i][col];
for (int j = col; j < n; j++) A[i][j] -= f * A[r][j];
}
r++;
}
return r;
}
static double[][] copy(double[][] A) {
double[][] B = zeros(A.length, A[0].length);
for (int i = 0; i < A.length; i++)
System.arraycopy(A[i], 0, B[i], 0, A[0].length);
return B;
}
static double[] solve(double[][] A0, double[] b0) {
double[][] A = copy(A0);
double[] b = b0.clone();
int n = b.length;
for (int k = 0; k < n; k++) {
int p = k;
for (int i = k+1; i < n; i++)
if (Math.abs(A[i][k]) > Math.abs(A[p][k])) p = i;
double[] tr = A[k]; A[k] = A[p]; A[p] = tr;
double tb = b[k]; b[k] = b[p]; b[p] = tb;
double pivot = A[k][k];
for (int j = k; j < n; j++) A[k][j] /= pivot;
b[k] /= pivot;
for (int i = 0; i < n; i++) {
if (i == k) continue;
double f = A[i][k];
for (int j = k; j < n; j++) A[i][j] -= f*A[k][j];
b[i] -= f*b[k];
}
}
return b;
}
static double H(double[][] A, double[][] B, double[][] C, double D, double s) {
int n = A.length;
double[][] M = zeros(n, n);
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
M[i][j] = (i == j ? s : 0.0) - A[i][j];
double[] b = new double[n];
for (int i = 0; i < n; i++) b[i] = B[i][0];
double[] x = solve(M, b);
double y = D;
for (int i = 0; i < n; i++) y += C[0][i] * x[i];
return y;
}
public static void main(String[] args) {
int n = 3;
double[] a = {4.0, 6.0, 4.0}; // [a0,a1,a2]
double[] b = {3.0, 5.0, 2.0}; // [b0,b1,b2]
double[][] Ac = zeros(n, n);
for (int i = 0; i < n-1; i++) Ac[i][i+1] = 1.0;
for (int j = 0; j < n; j++) Ac[n-1][j] = -a[j];
double[][] Bc = zeros(n, 1);
Bc[n-1][0] = 1.0;
double[][] Cc = zeros(1, n);
for (int j = 0; j < n; j++) Cc[0][j] = b[j];
double[][] Ao = transpose(Ac);
double[][] Bo = transpose(Cc);
double[][] Co = transpose(Bc);
System.out.println("rank ctrb(Ac,Bc) = " + rank(ctrb(Ac, Bc)));
System.out.println("rank obsv(Ao,Co) = " + rank(obsv(Ao, Co)));
for (double s : new double[]{0.5, 1.0, 2.0, 3.0}) {
double hc = H(Ac, Bc, Cc, 0.0, s);
double ho = H(Ao, Bo, Co, 0.0, s);
System.out.printf("s=%.1f H_CCF=%.10f H_OCF=%.10f error=%.2e%n",
s, hc, ho, Math.abs(hc-ho));
}
}
}
12. MATLAB and Simulink Implementation
MATLAB Control System Toolbox supports ss, tf,
ctrb, and obsv. The optional Simulink section
creates two State-Space blocks for direct response comparison.
File: Chapter17_Lesson2.m
% Chapter17_Lesson2.m
% CCF-OCF duality in MATLAB/Simulink.
% Requires Control System Toolbox for ss, tf, ctrb, obsv.
% Optional Simulink section requires Simulink.
clear; clc;
% G(s) = (2s^2 + 5s + 3)/(s^3 + 4s^2 + 6s + 4)
den = [1 4 6 4];
num = [2 5 3];
n = length(den) - 1;
a = fliplr(den(2:end)); % [a0 a1 ... a_{n-1}]
b = fliplr([zeros(1, n-length(num)) num]); % [b0 ... b_{n-1}]
Ac = [zeros(n-1,1) eye(n-1); -a];
Bc = [zeros(n-1,1); 1];
Cc = b;
Dc = 0;
Ao = Ac.';
Bo = Cc.';
Co = Bc.';
Do = Dc;
disp('A_c ='); disp(Ac);
disp('A_o = A_c^T ='); disp(Ao);
Qc = ctrb(Ac, Bc);
Oo = obsv(Ao, Co);
fprintf('rank ctrb(Ac,Bc) = %d\n', rank(Qc));
fprintf('rank obsv(Ao,Co) = %d\n', rank(Oo));
fprintf('norm(Oo - Qc'') = %.3e\n', norm(Oo - Qc.', 'fro'));
sysC = ss(Ac, Bc, Cc, Dc);
sysO = ss(Ao, Bo, Co, Do);
Gc = minreal(tf(sysC));
Go = minreal(tf(sysO));
disp('Transfer function from CCF:'); Gc
disp('Transfer function from OCF:'); Go
disp('Difference tf:'); minreal(Gc - Go)
for s = [0.5 1.0 2.0 3.0]
Hc = Cc*((s*eye(n) - Ac)\Bc) + Dc;
Ho = Co*((s*eye(n) - Ao)\Bo) + Do;
fprintf('s = %.1f, H_CCF = %.10f, H_OCF = %.10f, error = %.2e\n', ...
s, Hc, Ho, abs(Hc-Ho));
end
% Optional Simulink model generation.
createSimulinkModel = false;
if createSimulinkModel
model = 'Chapter17_Lesson2_Simulink';
if bdIsLoaded(model)
close_system(model, 0);
end
new_system(model);
open_system(model);
add_block('simulink/Sources/Step', [model '/Step'], ...
'Position', [50 80 80 110]);
add_block('simulink/Continuous/State-Space', [model '/CCF_StateSpace'], ...
'A', mat2str(Ac), 'B', mat2str(Bc), 'C', mat2str(Cc), 'D', mat2str(Dc), ...
'Position', [150 40 310 120]);
add_block('simulink/Continuous/State-Space', [model '/OCF_StateSpace'], ...
'A', mat2str(Ao), 'B', mat2str(Bo), 'C', mat2str(Co), 'D', mat2str(Do), ...
'Position', [150 160 310 240]);
add_block('simulink/Sinks/Scope', [model '/Scope'], ...
'Position', [390 90 430 210]);
add_line(model, 'Step/1', 'CCF_StateSpace/1');
add_line(model, 'Step/1', 'OCF_StateSpace/1');
add_line(model, 'CCF_StateSpace/1', 'Scope/1');
add_line(model, 'OCF_StateSpace/1', 'Scope/2');
save_system(model);
end
13. Wolfram Mathematica Implementation
Mathematica is useful for symbolic verification because it simplifies the two rational transfer functions exactly.
File: Chapter17_Lesson2.nb
(* Chapter17_Lesson2.nb *)
(* CCF-OCF duality in Wolfram Mathematica. *)
ClearAll["Global`*"];
den = {1, 4, 6, 4};
num = {2, 5, 3};
n = Length[den] - 1;
a = Reverse[Rest[den]]; (* {a0,a1,...,a_{n-1} } *)
b = Reverse[PadLeft[num, n]]; (* {b0,b1,...,b_{n-1} } *)
Ac = Join[
ArrayFlatten[{ {ConstantArray[0, {n - 1, 1}], IdentityMatrix[n - 1]} }],
{-a}
];
Bc = Transpose[{Join[ConstantArray[0, n - 1], {1}]}];
Cc = {b};
Dc = { {0} };
Ao = Transpose[Ac];
Bo = Transpose[Cc];
Co = Transpose[Bc];
Do = Dc;
controllabilityMatrix[A_, B_] :=
ArrayFlatten[{Table[MatrixPower[A, k].B, {k, 0, n - 1}]}];
observabilityMatrix[A_, C_] :=
Join @@ Table[C.MatrixPower[A, k], {k, 0, n - 1}];
Qc = controllabilityMatrix[Ac, Bc];
Oo = observabilityMatrix[Ao, Co];
{MatrixRank[Qc], MatrixRank[Oo], Norm[Oo - Transpose[Qc], "Frobenius"]}
s =.;
Gc = Simplify[Cc.Inverse[s IdentityMatrix[n] - Ac].Bc + Dc][[1, 1]];
Go = Simplify[Co.Inverse[s IdentityMatrix[n] - Ao].Bo + Do][[1, 1]];
{Gc, Go, Simplify[Gc - Go]}
StateSpaceModel[{Ac, Bc, Cc, Dc}]
StateSpaceModel[{Ao, Bo, Co, Do}]
TransferFunctionModel[StateSpaceModel[{Ac, Bc, Cc, Dc}], s]
TransferFunctionModel[StateSpaceModel[{Ao, Bo, Co, Do}], s]
14. Problems and Solutions
Problem 1: For \( G(s)=\frac{s+4}{s^2+3s+2} \), construct a CCF realization and then obtain OCF by duality.
Solution: Here \( a_1=3,\;a_0=2,\;b_1=1,\;b_0=4 \). Therefore,
\[ \mathbf A_c= \begin{bmatrix} 0&1\\-2&-3 \end{bmatrix},\quad \mathbf B_c= \begin{bmatrix} 0\\1 \end{bmatrix},\quad \mathbf C_c= \begin{bmatrix} 4&1 \end{bmatrix}. \]
\[ \mathbf A_o= \begin{bmatrix} 0&-2\\1&-3 \end{bmatrix},\quad \mathbf B_o= \begin{bmatrix} 4\\1 \end{bmatrix},\quad \mathbf C_o= \begin{bmatrix} 0&1 \end{bmatrix}. \]
Problem 2: Prove that the realizations in Problem 1 have the same transfer function.
Solution: For CCF,
\[ (s\mathbf I-\mathbf A_c)^{-1}\mathbf B_c = \frac{1}{s^2+3s+2} \begin{bmatrix} 1\\s \end{bmatrix}. \]
Therefore,
\[ \mathbf C_c(s\mathbf I-\mathbf A_c)^{-1}\mathbf B_c = \frac{4+s}{s^2+3s+2} = \frac{s+4}{s^2+3s+2}. \]
The OCF result follows by the transpose proof in Section 4.
Problem 3: Show that \( \mathcal O_o=\mathcal C_c^T \).
Solution:
\[ \mathcal O_o= \begin{bmatrix} \mathbf C_o\\\mathbf C_o\mathbf A_o\\\cdots\\ \mathbf C_o\mathbf A_o^{n-1} \end{bmatrix} = \begin{bmatrix} \mathbf B_c^T\\(\mathbf A_c\mathbf B_c)^T\\\cdots\\ (\mathbf A_c^{n-1}\mathbf B_c)^T \end{bmatrix} =\mathcal C_c^T. \]
Problem 4: Explain why reversed companion-form conventions do not change the transfer function.
Solution: Reversing state order is a similarity transformation by \( \mathbf J \). The transformed realization \( (\mathbf J\mathbf A\mathbf J,\mathbf J\mathbf B,\mathbf C\mathbf J,D) \) has the same transfer function because \( \mathbf J^{-1}=\mathbf J \).
Problem 5: Why do numerator coefficients appear in \( \mathbf C_c \) for CCF but in \( \mathbf B_o \) for OCF?
Solution: OCF is defined by \( \mathbf B_o=\mathbf C_c^T \). Therefore the row of numerator coefficients in CCF becomes the column of numerator coefficients in OCF.
15. Summary
CCF and OCF are dual canonical realizations of the same SISO transfer function. The transformation \( \mathbf A_o=\mathbf A_c^T \), \( \mathbf B_o=\mathbf C_c^T \), and \( \mathbf C_o=\mathbf B_c^T \) preserves the scalar transfer function and maps the CCF controllability matrix to the transpose of the OCF observability matrix.
16. References
- 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.
- 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.
- Kalman, R.E. (1962). Canonical structure of linear dynamical systems. Proceedings of the National Academy of Sciences of the United States of America, 48(4), 596–600.
- Ho, B.L., & Kalman, R.E. (1966). Effective construction of linear state-variable models from input/output functions. Regelungstechnik, 14(12), 545–548.
- Luenberger, D.G. (1967). Canonical forms for linear multivariable systems. IEEE Transactions on Automatic Control, 12(3), 290–293.
- Silverman, L.M. (1966). Realization of linear dynamical systems. IEEE Transactions on Automatic Control, 11(3), 462–463.
- Rosenbrock, H.H. (1962). Distinctive problems of process control. Chemical Engineering Progress, 58(9), 43–50.
- Wonham, W.M. (1968). On pole assignment in multi-input controllable linear systems. IEEE Transactions on Automatic Control, 12(6), 660–665.