Chapter 14: Observability Tests and Duality

Lesson 3: Duality Between Controllability and Observability

This lesson proves the fundamental duality between controllability and observability. The central idea is that the controllability of \( (A,B) \) is equivalent to the observability of \( (A^T,B^T) \), while the observability of \( (A,C) \) is equivalent to the controllability of \( (A^T,C^T) \). This transpose symmetry explains why Kalman rank tests, PBH tests, subspace tests, and Gramian tests appear in paired controllability/observability forms.

1. Conceptual Role of Duality

Consider the continuous-time LTI state-space model

\[ \dot{\mathbf{x} }(t)=A\mathbf{x}(t)+B\mathbf{u}(t),\quad \mathbf{y}(t)=C\mathbf{x}(t)+D\mathbf{u}(t). \]

Controllability is an input-to-state property: it asks whether the input matrix \( B \), together with the internal propagation generated by \( A \), can span the full state space. Observability is a state-to-output property: it asks whether the output matrix \( C \), together with the internal propagation generated by \( A \), can reveal all state components.

The dual system associated with \( (A,B,C) \) is

\[ \dot{\mathbf{z} }(t)=A^T\mathbf{z}(t)+C^T\mathbf{v}(t),\quad \mathbf{w}(t)=B^T\mathbf{z}(t). \]

Thus the original sensor matrix \( C \) becomes the dual actuator matrix \( C^T \), and the original actuator matrix \( B \) becomes the dual sensor matrix \( B^T \). The state matrix is transposed.

2. Duality Map Between Original and Dual Systems

Duality reverses the direction of linear maps. A column-space spanning condition for controllability becomes a row-space distinguishing condition for observability.

flowchart TD
  O["Original system: \nxdot = A x + B u, y = C x + D u"] --> C1["Controllability of (A,B)"]
  O --> O1["Observability of (A,C)"]
  D["Dual system: \nzdot = A^T z + C^T v, w = B^T z"] --> D1["Observability of (A^T,B^T)"]
  D --> D2["Controllability of (A^T,C^T)"]
  C1 --> D1
  O1 --> D2
  D1 --> R1["Same rank after transpose"]
  D2 --> R2["Same rank after transpose"]
        

3. Kalman Matrix Duality

For an \( n \)-state system, the controllability matrix is

\[ \mathcal{C}(A,B)= \begin{bmatrix}B&AB&A^2B&\cdots&A^{n-1}B\end{bmatrix}. \]

The observability matrix is

\[ \mathcal{O}(A,C)= \begin{bmatrix} C\\ CA\\ CA^2\\ \vdots\\ CA^{n-1} \end{bmatrix}. \]

Compute the controllability matrix of the dual pair \( (A^T,C^T) \):

\[ \mathcal{C}(A^T,C^T)= \begin{bmatrix} C^T&A^TC^T&(A^T)^2C^T&\cdots&(A^T)^{n-1}C^T \end{bmatrix}. \]

Taking the transpose gives

\[ \mathcal{C}(A^T,C^T)^T= \begin{bmatrix} C\\ CA\\ CA^2\\ \vdots\\ CA^{n-1} \end{bmatrix} =\mathcal{O}(A,C). \]

Therefore

\[ \boxed{\operatorname{rank}\mathcal{O}(A,C) =\operatorname{rank}\mathcal{C}(A^T,C^T)}. \]

Similarly,

\[ \boxed{\mathcal{O}(A^T,B^T)=\mathcal{C}(A,B)^T},\quad \boxed{\operatorname{rank}\mathcal{C}(A,B) =\operatorname{rank}\mathcal{O}(A^T,B^T)}. \]

Hence the two main duality equivalences are

\[ \boxed{(A,B)\;\text{controllable} \iff (A^T,B^T)\;\text{observable} },\\ \boxed{(A,C)\;\text{observable} \iff (A^T,C^T)\;\text{controllable} }. \]

4. PBH Duality

The PBH controllability condition for \( (A,B) \) is

\[ (A,B)\;\text{controllable} \iff \operatorname{rank}\begin{bmatrix}\lambda I-A&B\end{bmatrix}=n \quad \text{for every } \lambda\in\sigma(A). \]

Equivalently, there is no nonzero left eigenvector \( \mathbf{q}^T \) of \( A \) that is orthogonal to the input directions:

\[ \mathbf{q}^TA=\lambda\mathbf{q}^T,\quad \mathbf{q}^TB=\mathbf{0} \quad \text{has no nonzero solution } \mathbf{q}. \]

The PBH observability condition for \( (A,C) \) is

\[ (A,C)\;\text{observable} \iff \operatorname{rank}\begin{bmatrix}\lambda I-A\\C\end{bmatrix}=n \quad \text{for every } \lambda\in\sigma(A). \]

Equivalently, there is no nonzero right eigenvector \( \mathbf{x} \) of \( A \) hidden from the output:

\[ A\mathbf{x}=\lambda\mathbf{x},\quad C\mathbf{x}=\mathbf{0} \quad \text{has no nonzero solution } \mathbf{x}. \]

Apply the controllability PBH condition to \( (A^T,C^T) \):

\[ \operatorname{rank} \begin{bmatrix}\lambda I-A^T&C^T\end{bmatrix}=n. \]

Since transposition preserves rank,

\[ \operatorname{rank} \begin{bmatrix}\lambda I-A^T&C^T\end{bmatrix} = \operatorname{rank} \begin{bmatrix}\lambda I-A\\C\end{bmatrix}. \]

Therefore PBH controllability of \( (A^T,C^T) \) is exactly PBH observability of \( (A,C) \).

5. Subspace Duality and Orthogonality

The reachable subspace of \( (A,B) \) is

\[ \mathcal{R}(A,B)= \operatorname{span}\left\{\operatorname{im}B, \operatorname{im}AB,\dots, \operatorname{im}A^{n-1}B\right\}. \]

The unobservable subspace of \( (A,C) \) is

\[ \mathcal{N}_o(A,C)= \bigcap_{k=0}^{n-1}\ker(CA^k). \]

The key orthogonality identity is

\[ \boxed{\mathcal{N}_o(A,C) =\mathcal{R}(A^T,C^T)^\perp}. \]

Proof. A vector \( \mathbf{x} \) belongs to \( \mathcal{R}(A^T,C^T)^\perp \) exactly when it is orthogonal to every column of \( (A^T)^kC^T \), for \( k=0,1,\dots,n-1 \). Hence

\[ \mathbf{x}^T(A^T)^kC^T=\mathbf{0}^T \iff C A^k\mathbf{x}=\mathbf{0}. \]

Satisfying this for every \( k \) is exactly the definition of the unobservable subspace. Thus the identity is proved.

Similarly,

\[ \boxed{\mathcal{N}_o(A^T,B^T) =\mathcal{R}(A,B)^\perp}. \]

6. Gramian Duality

From Chapter 12, the finite-horizon controllability Gramian is

\[ W_c(T)=\int_0^T e^{A\tau}BB^T e^{A^T\tau}\,d\tau. \]

The controllability Gramian of the dual pair \( (A^T,C^T) \) is

\[ W_c^d(T)=\int_0^T e^{A^T\tau}C^TC e^{A\tau}\,d\tau. \]

This matrix is the finite-horizon observability Gramian associated with \( (A,C) \):

\[ \boxed{W_o(A,C,T)=W_c(A^T,C^T,T)}. \]

Thus output-energy observability in the original system is the same quadratic object as input-energy reachability in the dual system.

\[ (A,C)\;\text{observable on }[0,T] \iff W_o(A,C,T)\;\text{is positive definite} \\ \iff W_c(A^T,C^T,T)\;\text{is positive definite}. \]

7. Computational Workflow and Numerical Notes

Numerically, build both primal and dual rank-test matrices and compare their ranks using a singular-value tolerance. In exact symbolic calculations, ordinary rank is enough. In floating-point calculations, nearly zero singular values can cause rank decisions to be sensitive.

flowchart TD
  A["Start with A, B, C"] --> B["Build Ctrb(A,B) and Obsv(A,C)"]
  B --> C["Build dual matrices: A^T, C^T, B^T"]
  C --> D["Check Obsv(A,C) = Ctrb(A^T,C^T)^T"]
  C --> E["Check Ctrb(A,B)^T = Obsv(A^T,B^T)"]
  D --> F["Compare ranks with tolerance"]
  E --> F
  F --> G["Conclude controllability and observability dual properties"]
        

Useful libraries are NumPy, SciPy, and python-control in Python; Eigen or Armadillo in C++; EJML or Apache Commons Math in Java; Control System Toolbox in MATLAB; and symbolic MatrixRank and MatrixPower in Wolfram Mathematica.

8. Python Implementation

File: Chapter14_Lesson3.py

# Chapter14_Lesson3.py
# Duality Between Controllability and Observability
# Requires: numpy

import numpy as np


def controllability_matrix(A: np.ndarray, B: np.ndarray) -> np.ndarray:
    """Return Ctrb(A,B) = [B, AB, ..., A^(n-1)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 observability_matrix(A: np.ndarray, C: np.ndarray) -> np.ndarray:
    """Return Obsv(A,C) = [C; CA; ...; CA^(n-1)]."""
    n = A.shape[0]
    blocks = []
    Ak = np.eye(n)
    for _ in range(n):
        blocks.append(C @ Ak)
        Ak = Ak @ A
    return np.vstack(blocks)


def rank(M: np.ndarray, tol: float = 1e-10) -> int:
    """Numerical rank from singular values."""
    s = np.linalg.svd(M, compute_uv=False)
    return int(np.sum(s > tol))


def report_duality(A: np.ndarray, B: np.ndarray, C: np.ndarray) -> None:
    n = A.shape[0]

    Ctrb = controllability_matrix(A, B)
    Obsv = observability_matrix(A, C)

    Ctrb_dual = controllability_matrix(A.T, C.T)
    Obsv_dual = observability_matrix(A.T, B.T)

    print("rank Ctrb(A,B) =", rank(Ctrb), "of", n)
    print("rank Obsv(A,C) =", rank(Obsv), "of", n)

    print("\nDuality checks:")
    print("Obsv(A,C) == Ctrb(A.T,C.T).T ?",
          np.allclose(Obsv, Ctrb_dual.T))
    print("Ctrb(A,B).T == Obsv(A.T,B.T) ?",
          np.allclose(Ctrb.T, Obsv_dual))

    print("\nrank Ctrb(A.T,C.T) =", rank(Ctrb_dual), "of", n)
    print("rank Obsv(A.T,B.T) =", rank(Obsv_dual), "of", n)


if __name__ == "__main__":
    A = np.array([[0.0, 1.0, 0.0],
                  [0.0, 0.0, 1.0],
                  [-2.0, -3.0, -4.0]])
    B = np.array([[0.0],
                  [0.0],
                  [1.0]])
    C = np.array([[1.0, 0.0, 0.0]])

    report_duality(A, B, C)
      

9. C++ and Java Implementations

The following implementations construct controllability and observability matrices from scratch and compute rank by Gaussian elimination. For production numerical control work, replace the scratch rank function with Eigen, Armadillo, EJML, or Apache Commons Math.

File: Chapter14_Lesson3.cpp

// Chapter14_Lesson3.cpp
// Duality Between Controllability and Observability
// Compile: g++ -std=c++17 Chapter14_Lesson3.cpp -o Chapter14_Lesson3

#include <cmath>
#include <iomanip>
#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>

using Matrix = std::vector<std::vector<double>>;

Matrix zeros(int r, int c) {
    return Matrix(r, std::vector<double>(c, 0.0));
}

Matrix identity(int n) {
    Matrix I = zeros(n, n);
    for (int i = 0; i < n; ++i) I[i][i] = 1.0;
    return I;
}

Matrix multiply(const Matrix& A, const Matrix& B) {
    int r = static_cast<int>(A.size());
    int k = static_cast<int>(A[0].size());
    int c = static_cast<int>(B[0].size());
    if (static_cast<int>(B.size()) != k) {
        throw std::runtime_error("Dimension mismatch in multiply.");
    }
    Matrix M = zeros(r, c);
    for (int i = 0; i < r; ++i)
        for (int j = 0; j < c; ++j)
            for (int t = 0; t < k; ++t)
                M[i][j] += A[i][t] * B[t][j];
    return M;
}

Matrix transpose(const Matrix& A) {
    int r = static_cast<int>(A.size());
    int c = static_cast<int>(A[0].size());
    Matrix T = zeros(c, r);
    for (int i = 0; i < r; ++i)
        for (int j = 0; j < c; ++j)
            T[j][i] = A[i][j];
    return T;
}

Matrix hstack(const std::vector<Matrix>& blocks) {
    int rows = static_cast<int>(blocks[0].size());
    int totalCols = 0;
    for (const auto& B : blocks) totalCols += static_cast<int>(B[0].size());
    Matrix H = zeros(rows, totalCols);
    int col0 = 0;
    for (const auto& B : blocks) {
        int cols = static_cast<int>(B[0].size());
        for (int i = 0; i < rows; ++i)
            for (int j = 0; j < cols; ++j)
                H[i][col0 + j] = B[i][j];
        col0 += cols;
    }
    return H;
}

Matrix vstack(const std::vector<Matrix>& blocks) {
    int cols = static_cast<int>(blocks[0][0].size());
    int totalRows = 0;
    for (const auto& B : blocks) totalRows += static_cast<int>(B.size());
    Matrix V = zeros(totalRows, cols);
    int row0 = 0;
    for (const auto& B : blocks) {
        int rows = static_cast<int>(B.size());
        for (int i = 0; i < rows; ++i)
            for (int j = 0; j < cols; ++j)
                V[row0 + i][j] = B[i][j];
        row0 += rows;
    }
    return V;
}

int rank(Matrix M, double tol = 1e-10) {
    int rows = static_cast<int>(M.size());
    int cols = static_cast<int>(M[0].size());
    int r = 0;
    for (int c = 0; c < cols && r < rows; ++c) {
        int pivot = r;
        for (int i = r + 1; i < rows; ++i)
            if (std::fabs(M[i][c]) > std::fabs(M[pivot][c])) pivot = i;

        if (std::fabs(M[pivot][c]) <= tol) continue;
        std::swap(M[pivot], M[r]);

        double div = M[r][c];
        for (int j = c; j < cols; ++j) M[r][j] /= div;

        for (int i = 0; i < rows; ++i) {
            if (i == r) continue;
            double factor = M[i][c];
            for (int j = c; j < cols; ++j)
                M[i][j] -= factor * M[r][j];
        }
        ++r;
    }
    return r;
}

Matrix controllabilityMatrix(const Matrix& A, const Matrix& B) {
    int n = static_cast<int>(A.size());
    std::vector<Matrix> blocks;
    Matrix Ak = identity(n);
    for (int k = 0; k < n; ++k) {
        blocks.push_back(multiply(Ak, B));
        Ak = multiply(Ak, A);
    }
    return hstack(blocks);
}

Matrix observabilityMatrix(const Matrix& A, const Matrix& C) {
    int n = static_cast<int>(A.size());
    std::vector<Matrix> blocks;
    Matrix Ak = identity(n);
    for (int k = 0; k < n; ++k) {
        blocks.push_back(multiply(C, Ak));
        Ak = multiply(Ak, A);
    }
    return vstack(blocks);
}

int main() {
    Matrix A = { {0.0, 1.0, 0.0},
                {0.0, 0.0, 1.0},
                {-2.0, -3.0, -4.0} };
    Matrix B = { {0.0}, {0.0}, {1.0} };
    Matrix C = { {1.0, 0.0, 0.0} };

    int n = static_cast<int>(A.size());

    Matrix Ctrb = controllabilityMatrix(A, B);
    Matrix Obsv = observabilityMatrix(A, C);
    Matrix CtrbDual = controllabilityMatrix(transpose(A), transpose(C));
    Matrix ObsvDual = observabilityMatrix(transpose(A), transpose(B));

    std::cout << "rank Ctrb(A,B) = " << rank(Ctrb) << " of " << n << "\n";
    std::cout << "rank Obsv(A,C) = " << rank(Obsv) << " of " << n << "\n";
    std::cout << "rank Ctrb(A^T,C^T) = " << rank(CtrbDual) << " of " << n << "\n";
    std::cout << "rank Obsv(A^T,B^T) = " << rank(ObsvDual) << " of " << n << "\n";

    return 0;
}
      

File: Chapter14_Lesson3.java

// Chapter14_Lesson3.java
// Duality Between Controllability and Observability
// Compile: javac Chapter14_Lesson3.java
// Run:     java Chapter14_Lesson3

public class Chapter14_Lesson3 {
    static double[][] zeros(int r, int c) {
        return new double[r][c];
    }

    static double[][] identity(int n) {
        double[][] I = zeros(n, n);
        for (int i = 0; i < n; i++) I[i][i] = 1.0;
        return I;
    }

    static double[][] multiply(double[][] A, double[][] B) {
        int r = A.length;
        int k = A[0].length;
        int c = B[0].length;
        if (B.length != k) throw new IllegalArgumentException("Dimension mismatch.");
        double[][] M = zeros(r, c);
        for (int i = 0; i < r; i++)
            for (int j = 0; j < c; j++)
                for (int t = 0; t < k; t++)
                    M[i][j] += A[i][t] * B[t][j];
        return M;
    }

    static double[][] transpose(double[][] A) {
        int r = A.length;
        int c = A[0].length;
        double[][] T = zeros(c, r);
        for (int i = 0; i < r; i++)
            for (int j = 0; j < c; j++)
                T[j][i] = A[i][j];
        return T;
    }

    static double[][] hstack(double[][][] blocks) {
        int rows = blocks[0].length;
        int totalCols = 0;
        for (double[][] B : blocks) totalCols += B[0].length;
        double[][] H = zeros(rows, totalCols);
        int col0 = 0;
        for (double[][] B : blocks) {
            int cols = B[0].length;
            for (int i = 0; i < rows; i++)
                for (int j = 0; j < cols; j++)
                    H[i][col0 + j] = B[i][j];
            col0 += cols;
        }
        return H;
    }

    static double[][] vstack(double[][][] blocks) {
        int cols = blocks[0][0].length;
        int totalRows = 0;
        for (double[][] B : blocks) totalRows += B.length;
        double[][] V = zeros(totalRows, cols);
        int row0 = 0;
        for (double[][] B : blocks) {
            int rows = B.length;
            for (int i = 0; i < rows; i++)
                for (int j = 0; j < cols; j++)
                    V[row0 + i][j] = B[i][j];
            row0 += rows;
        }
        return V;
    }

    static int rank(double[][] input, double tol) {
        int rows = input.length;
        int cols = input[0].length;
        double[][] M = new double[rows][cols];
        for (int i = 0; i < rows; i++)
            System.arraycopy(input[i], 0, M[i], 0, cols);

        int r = 0;
        for (int c = 0; c < cols && r < rows; c++) {
            int pivot = r;
            for (int i = r + 1; i < rows; i++)
                if (Math.abs(M[i][c]) > Math.abs(M[pivot][c])) pivot = i;

            if (Math.abs(M[pivot][c]) <= tol) continue;

            double[] temp = M[pivot];
            M[pivot] = M[r];
            M[r] = temp;

            double div = M[r][c];
            for (int j = c; j < cols; j++) M[r][j] /= div;

            for (int i = 0; i < rows; i++) {
                if (i == r) continue;
                double factor = M[i][c];
                for (int j = c; j < cols; j++)
                    M[i][j] -= factor * M[r][j];
            }
            r++;
        }
        return r;
    }

    static double[][] controllabilityMatrix(double[][] A, double[][] B) {
        int n = A.length;
        double[][][] blocks = new double[n][][];
        double[][] Ak = identity(n);
        for (int k = 0; k < n; k++) {
            blocks[k] = multiply(Ak, B);
            Ak = multiply(Ak, A);
        }
        return hstack(blocks);
    }

    static double[][] observabilityMatrix(double[][] A, double[][] C) {
        int n = A.length;
        double[][][] blocks = new double[n][][];
        double[][] Ak = identity(n);
        for (int k = 0; k < n; k++) {
            blocks[k] = multiply(C, Ak);
            Ak = multiply(Ak, A);
        }
        return vstack(blocks);
    }

    public static void main(String[] args) {
        double[][] A = {
            {0.0, 1.0, 0.0},
            {0.0, 0.0, 1.0},
            {-2.0, -3.0, -4.0}
        };
        double[][] B = { {0.0}, {0.0}, {1.0} };
        double[][] C = { {1.0, 0.0, 0.0} };

        int n = A.length;

        double[][] Ctrb = controllabilityMatrix(A, B);
        double[][] Obsv = observabilityMatrix(A, C);
        double[][] CtrbDual = controllabilityMatrix(transpose(A), transpose(C));
        double[][] ObsvDual = observabilityMatrix(transpose(A), transpose(B));

        System.out.println("rank Ctrb(A,B) = " + rank(Ctrb, 1e-10) + " of " + n);
        System.out.println("rank Obsv(A,C) = " + rank(Obsv, 1e-10) + " of " + n);
        System.out.println("rank Ctrb(A^T,C^T) = " + rank(CtrbDual, 1e-10) + " of " + n);
        System.out.println("rank Obsv(A^T,B^T) = " + rank(ObsvDual, 1e-10) + " of " + n);
    }
}
      

10. MATLAB/Simulink and Wolfram Mathematica Implementations

MATLAB users can compare the scratch implementation with ctrb(A,B) and obsv(A,C) from Control System Toolbox. In Simulink, use two State-Space blocks: the original block with \( (A,B,C,D) \), and the dual block with \( (A^T,C^T,B^T,0) \).

File: Chapter14_Lesson3.m

% Chapter14_Lesson3.m
% Duality Between Controllability and Observability
% MATLAB / GNU Octave compatible scratch implementation.

clear; clc;

A = [0 1 0;
     0 0 1;
    -2 -3 -4];

B = [0; 0; 1];
C = [1 0 0];

Ctrb = local_ctrb(A, B);
Obsv = local_obsv(A, C);

Ctrb_dual = local_ctrb(A.', C.');
Obsv_dual = local_obsv(A.', B.');

fprintf('rank Ctrb(A,B)       = %d of %d\n', rank(Ctrb), size(A,1));
fprintf('rank Obsv(A,C)       = %d of %d\n', rank(Obsv), size(A,1));
fprintf('rank Ctrb(A'',C'')    = %d of %d\n', rank(Ctrb_dual), size(A,1));
fprintf('rank Obsv(A'',B'')    = %d of %d\n', rank(Obsv_dual), size(A,1));

fprintf('\nDuality identities:\n');
fprintf('norm(Obsv - Ctrb_dual'') = %.3e\n', norm(Obsv - Ctrb_dual.', 'fro'));
fprintf('norm(Ctrb'' - Obsv_dual) = %.3e\n', norm(Ctrb.' - Obsv_dual, 'fro'));

% Simulink note:
% Original State-Space block: A, B, C, D=0.
% Dual State-Space block: A_dual=A.', B_dual=C.', C_dual=B.', D_dual=0.

function Ctrb = local_ctrb(A, B)
    n = size(A, 1);
    Ctrb = [];
    Ak = eye(n);
    for k = 0:n-1
        Ctrb = [Ctrb, Ak * B]; %#ok<AGROW>
        Ak = Ak * A;
    end
end

function Obsv = local_obsv(A, C)
    n = size(A, 1);
    Obsv = [];
    Ak = eye(n);
    for k = 0:n-1
        Obsv = [Obsv; C * Ak]; %#ok<AGROW>
        Ak = Ak * A;
    end
end
      

File: Chapter14_Lesson3.nb

(* Chapter14_Lesson3.nb *)
ClearAll[ctrb, obsv, rankReport];

ctrb[A_, B_] := ArrayFlatten[
  {Table[MatrixPower[A, k].B, {k, 0, Length[A] - 1}]}
];

obsv[A_, C_] := Join @@ Table[C.MatrixPower[A, k], {k, 0, Length[A] - 1}];

A = { {0, 1, 0}, {0, 0, 1}, {-2, -3, -4} };
B = { {0}, {0}, {1} };
Cmat = { {1, 0, 0} };

Ctrb = ctrb[A, B];
Obsv = obsv[A, Cmat];

CtrbDual = ctrb[Transpose[A], Transpose[Cmat]];
ObsvDual = obsv[Transpose[A], Transpose[B]];

rankReport = <|
  "rank Ctrb(A,B)" -> MatrixRank[Ctrb],
  "rank Obsv(A,C)" -> MatrixRank[Obsv],
  "rank Ctrb(A^T,C^T)" -> MatrixRank[CtrbDual],
  "rank Obsv(A^T,B^T)" -> MatrixRank[ObsvDual],
  "Obsv == Transpose[CtrbDual]" -> (Obsv == Transpose[CtrbDual]),
  "Transpose[Ctrb] == ObsvDual" -> (Transpose[Ctrb] == ObsvDual)
|>;

rankReport // MatrixForm
      

11. Problems and Solutions

Problem 1 (Kalman Matrix Identity): Prove that \( \mathcal{O}(A,C)=\mathcal{C}(A^T,C^T)^T \).

Solution:

\[ \mathcal{C}(A^T,C^T)= \begin{bmatrix} C^T&A^TC^T&\cdots&(A^T)^{n-1}C^T \end{bmatrix}. \]

Transpose the block matrix:

\[ \mathcal{C}(A^T,C^T)^T= \begin{bmatrix} C\\ CA\\ \vdots\\ CA^{n-1} \end{bmatrix} =\mathcal{O}(A,C). \]

Since rank is invariant under transpose, \( \operatorname{rank}\mathcal{O}(A,C) =\operatorname{rank}\mathcal{C}(A^T,C^T) \).

Problem 2 (PBH Duality): Show that \( (A,C) \) is observable if and only if \( (A^T,C^T) \) is controllable.

Solution: PBH observability gives

\[ \operatorname{rank} \begin{bmatrix}\lambda I-A\\C\end{bmatrix}=n \quad \text{for every }\lambda\in\sigma(A). \]

PBH controllability of \( (A^T,C^T) \) gives

\[ \operatorname{rank} \begin{bmatrix}\lambda I-A^T&C^T\end{bmatrix}=n. \]

These matrices are transposes of each other, so their ranks are identical. Also \( A \) and \( A^T \) have the same eigenvalues. Therefore the two PBH conditions are equivalent.

Problem 3 (Numerical Rank Test): Let

\[ A=\begin{bmatrix}0&1&0\\0&0&1\\-2&-3&-4\end{bmatrix}, \quad B=\begin{bmatrix}0\\0\\1\end{bmatrix}, \quad C=\begin{bmatrix}1&0&0\end{bmatrix}. \]

Determine whether \( (A,B) \) is controllable and whether \( (A,C) \) is observable. Then state the dual conclusions.

Solution:

\[ \mathcal{C}(A,B)= \begin{bmatrix} 0&0&1\\ 0&1&-4\\ 1&-4&13 \end{bmatrix},\quad \mathcal{O}(A,C)= \begin{bmatrix} 1&0&0\\ 0&1&0\\ 0&0&1 \end{bmatrix}. \]

Both matrices have rank \( 3 \). Therefore \( (A,B) \) is controllable and \( (A,C) \) is observable. By duality, \( (A^T,B^T) \) is observable and \( (A^T,C^T) \) is controllable.

Problem 4 (Subspace Orthogonality): Prove that \( \mathcal{N}_o(A,C)=\mathcal{R}(A^T,C^T)^\perp \).

Solution:

\[ \mathbf{x}\in\mathcal{R}(A^T,C^T)^\perp \iff \mathbf{x}^T(A^T)^kC^T=\mathbf{0}^T \quad \text{for }k=0,1,\dots,n-1. \]

Taking transposes inside the scalar products gives

\[ \mathbf{x}^T(A^T)^kC^T=\mathbf{0}^T \iff CA^k\mathbf{x}=\mathbf{0}. \]

This must hold for every \( k \), which is exactly \( \mathbf{x}\in\mathcal{N}_o(A,C) \).

Problem 5 (Gramian Duality): Given \( W_c(A,B,T) \), derive the controllability Gramian of \( (A^T,C^T) \) and identify it.

Solution:

\[ W_c(A^T,C^T,T)= \int_0^T e^{A^T\tau}C^TCe^{A\tau}\,d\tau. \]

This is the observability Gramian of \( (A,C) \). Therefore output-energy observability in the original system is controllability-energy reachability in the dual system.

12. Summary

Duality is the transpose symmetry connecting the two central state properties of modern control. The observability matrix of \( (A,C) \) is the transpose of the controllability matrix of \( (A^T,C^T) \), while the controllability matrix of \( (A,B) \) is the transpose of the observability matrix of \( (A^T,B^T) \). Consequently, rank tests, PBH tests, subspace decompositions, and Gramian tests all occur in controllability/observability pairs.

13. 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. Journal of the Society for Industrial and Applied Mathematics, Series A: Control, 1(2), 128–151.
  3. 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.
  4. Hautus, M.L.J. (1969). Controllability and observability conditions of linear autonomous systems. Indagationes Mathematicae, 31, 443–448.
  5. Silverman, L.M., & Meadows, H.E. (1967). Controllability and observability in time-variable linear systems. SIAM Journal on Control, 5(1), 64–73.
  6. Wonham, W.M. (1979). Geometric state-space theory in linear multivariable control. Automatica, 15(2), 133–147.