Chapter 13: Observability and Detectability – Concepts

Lesson 5: Examples of Observable and Unobservable Systems

This lesson consolidates observability, unobservable subspaces, detectability, and sensor placement through explicit state-space examples. The goal is not only to decide whether a system is observable, but also to understand why some initial-state directions leave no trace in the measured output.

1. Setting of the Lesson

Consider the autonomous output experiment for a continuous-time LTI system

\[ \dot{\mathbf{x} }(t)=\mathbf{A}\mathbf{x}(t),\qquad \mathbf{y}(t)=\mathbf{C}\mathbf{x}(t),\qquad \mathbf{x}(0)=\mathbf{x}_0. \]

Two initial states \( \mathbf{x}_a \) and \( \mathbf{x}_b \) are experimentally indistinguishable on a time interval when they produce the same output trajectory. Their difference \( \boldsymbol{\eta}=\mathbf{x}_a-\mathbf{x}_b \) is an unobservable direction if

\[ \mathbf{C}e^{\mathbf{A}t}\boldsymbol{\eta}=\mathbf{0} \qquad \text{for all measured times } t. \]

For finite-dimensional LTI systems, the examples below use the finite derivative stack at \( t=0 \):

\[ \begin{aligned} \mathbf{y}(0)&=\mathbf{C}\mathbf{x}_0,\\ \dot{\mathbf{y} }(0)&=\mathbf{C}\mathbf{A}\mathbf{x}_0,\\ \ddot{\mathbf{y} }(0)&=\mathbf{C}\mathbf{A}^2\mathbf{x}_0,\\ &\vdots \end{aligned} \]

This gives the observation stack \( \mathcal{O}_n \), used here as an algebraic way to reconstruct or fail to reconstruct \( \mathbf{x}_0 \):

\[ \mathcal{O}_n= \begin{bmatrix} \mathbf{C}\\ \mathbf{C}\mathbf{A}\\ \mathbf{C}\mathbf{A}^2\\ \vdots\\ \mathbf{C}\mathbf{A}^{n-1} \end{bmatrix},\qquad \mathbf{z}_0= \begin{bmatrix} \mathbf{y}(0)\\ \dot{\mathbf{y} }(0)\\ \vdots\\ \mathbf{y}^{(n-1)}(0) \end{bmatrix} = \mathcal{O}_n\mathbf{x}_0. \]

If this linear algebraic map uniquely determines \( \mathbf{x}_0 \), the system is observable. If not, the null space of \( \mathcal{O}_n \) contains initial-state directions that cannot be seen by the output. The formal rank and PBH criteria will be developed in the next chapter; this lesson focuses on concrete examples and interpretation.

2. Diagnostic Flow for Worked Examples

In each example, we connect three viewpoints: output derivatives, unobservable directions, and physical sensor interpretation.

flowchart TD
  A["Start with A and C"] --> B["Write y(0), ydot(0), ..., y^(n-1)(0)"]
  B --> C["Stack equations into z0 = O_n x0"]
  C --> D["Check whether x0 is uniquely determined"]
  D -->|yes| E["Observable: \nno hidden initial direction"]
  D -->|no| F["Unobservable: \nfind null directions of O_n"]
  F --> G["Check hidden mode behavior"]
  G --> H["Stable hidden modes: \ndetectable"]
  G --> I["Unstable hidden modes: \nnot detectable"]
        

3. Example 1: Observable Second-Order System

Consider a two-state model whose measured output is the first state:

\[ \mathbf{A}= \begin{bmatrix} 0 & 1\\ -2 & -3 \end{bmatrix},\qquad \mathbf{C}=\begin{bmatrix}1 & 0\end{bmatrix},\qquad y=x_1. \]

From the output and its first derivative,

\[ y(0)=x_1(0),\qquad \dot{y}(0)=\dot{x}_1(0)=x_2(0). \]

Therefore the two initial coordinates are directly recovered:

\[ \mathbf{x}_0= \begin{bmatrix}x_1(0)\\x_2(0)\end{bmatrix} = \begin{bmatrix}y(0)\\\dot{y}(0)\end{bmatrix}. \]

Equivalently,

\[ \mathcal{O}_2= \begin{bmatrix} \mathbf{C}\\ \mathbf{C}\mathbf{A} \end{bmatrix} = \begin{bmatrix} 1 & 0\\ 0 & 1 \end{bmatrix}. \]

Since \( \mathcal{O}_2 \) is the identity matrix, no nonzero vector can be hidden:

\[ \mathcal{N}(\mathcal{O}_2)=\{\mathbf{0}\}. \]

Thus this system is observable. Physically, although only position is measured, velocity appears in the derivative of the measured position, so the sensor indirectly reveals the full state.

4. Example 2: Unobservable and Not Detectable

Now consider a system with two decoupled modes, only the first of which is measured:

\[ \mathbf{A}= \begin{bmatrix} -1 & 0\\ 0 & 2 \end{bmatrix},\qquad \mathbf{C}=\begin{bmatrix}1 & 0\end{bmatrix},\qquad y=x_1. \]

The state equations and output are

\[ \dot{x}_1=-x_1,\qquad \dot{x}_2=2x_2,\qquad y=x_1. \]

The exact solution is

\[ x_1(t)=e^{-t}x_1(0),\qquad x_2(t)=e^{2t}x_2(0),\qquad y(t)=e^{-t}x_1(0). \]

Hence \( y(t) \) contains no information about \( x_2(0) \). For any scalar \( \alpha \),

\[ \mathbf{x}_a(0)= \begin{bmatrix}x_1(0)\\0\end{bmatrix},\qquad \mathbf{x}_b(0)= \begin{bmatrix}x_1(0)\\\alpha\end{bmatrix} \quad \Longrightarrow \quad y_a(t)=y_b(t). \]

The observation stack confirms this:

\[ \mathcal{O}_2= \begin{bmatrix} \mathbf{C}\\ \mathbf{C}\mathbf{A} \end{bmatrix} = \begin{bmatrix} 1 & 0\\ -1 & 0 \end{bmatrix},\qquad \mathcal{N}(\mathcal{O}_2)= \operatorname{span}\left\{ \begin{bmatrix}0\\1\end{bmatrix} \right\}. \]

The unobservable direction is the second coordinate axis. Along that hidden direction,

\[ \begin{bmatrix}0\\1\end{bmatrix} \quad \mapsto \quad e^{2t}\begin{bmatrix}0\\1\end{bmatrix}. \]

The hidden mode grows because its eigenvalue is \( 2 \). Therefore the system is not only unobservable, but also not detectable: an unstable internal behavior can remain invisible at the output.

5. Example 3: Unobservable but Detectable

Replace the hidden unstable mode in the previous example by a stable hidden mode:

\[ \mathbf{A}= \begin{bmatrix} -1 & 0\\ 0 & -4 \end{bmatrix},\qquad \mathbf{C}=\begin{bmatrix}1 & 0\end{bmatrix}. \]

Again,

\[ \mathcal{O}_2= \begin{bmatrix} 1 & 0\\ -1 & 0 \end{bmatrix},\qquad \mathcal{N}(\mathcal{O}_2)= \operatorname{span}\left\{ \begin{bmatrix}0\\1\end{bmatrix} \right\}. \]

Thus the system is not observable. However, the hidden trajectory is now

\[ x_2(t)=e^{-4t}x_2(0). \]

This invisible component decays to zero. Consequently, every unobservable motion is internally stable, so the system is detectable. This distinction is essential for observer design: exact reconstruction of the initial condition is impossible, but asymptotic state estimation can still be possible because the non-reconstructible part vanishes.

The difference between Example 2 and Example 3 is not the sensor; the same output \( y=x_1 \) is used in both cases. The difference is the internal stability of the hidden mode.

6. Example 4: Sensor Placement Can Change Observability

Consider

\[ \mathbf{A}= \begin{bmatrix} 0 & 1\\ -6 & -5 \end{bmatrix}. \]

First, measure velocity:

\[ \mathbf{C}_v=\begin{bmatrix}0 & 1\end{bmatrix},\qquad \mathcal{O}_v= \begin{bmatrix} 0 & 1\\ -6 & -5 \end{bmatrix},\qquad \det(\mathcal{O}_v)=6. \]

Next, measure position:

\[ \mathbf{C}_p=\begin{bmatrix}1 & 0\end{bmatrix},\qquad \mathcal{O}_p= \begin{bmatrix} 1 & 0\\ 0 & 1 \end{bmatrix},\qquad \det(\mathcal{O}_p)=1. \]

Both sensors make this particular second-order system observable. The reason is that the internal dynamics couple the two coordinates: measuring one coordinate and differentiating the output reveals the other coordinate. In contrast, Example 2 failed because the unmeasured coordinate was dynamically disconnected from the measured output.

flowchart TD
  X1["state x1"] -->|"dynamic coupling"| X2["state x2"]
  X2 -->|"dynamic coupling"| X1
  X1 -->|"position sensor"| Y1["output y = x1"]
  X2 -->|"velocity sensor"| Y2["output y = x2"]
  Y1 --> R1["derivatives reveal both states"]
  Y2 --> R2["derivatives reveal both states"]
        

This illustrates the sensor-placement lesson: a sensor need not measure every state directly, but the measured coordinates must be dynamically connected to all state directions that must be reconstructed.

7. Example 5: A Repeated-Mode Cancellation Produces an Invisible Direction

Consider a diagonal system with two states having the same natural mode:

\[ \mathbf{A}= \begin{bmatrix} -2 & 0\\ 0 & -2 \end{bmatrix},\qquad \mathbf{C}=\begin{bmatrix}1 & -1\end{bmatrix}. \]

The output is the difference between the two states:

\[ y(t)=x_1(t)-x_2(t) =e^{-2t}\left(x_1(0)-x_2(0)\right). \]

Therefore only the difference \( x_1(0)-x_2(0) \) can be identified. The common component \( x_1(0)=x_2(0) \) is invisible because it cancels in the output:

\[ \mathcal{O}_2= \begin{bmatrix} 1 & -1\\ -2 & 2 \end{bmatrix},\qquad \mathcal{N}(\mathcal{O}_2)= \operatorname{span}\left\{ \begin{bmatrix}1\\1\end{bmatrix} \right\}. \]

The system is unobservable. Since the hidden common mode decays as \( e^{-2t} \), the system is detectable. This example is important because the unobservability is not caused by a missing sensor on a physically disconnected state; it is caused by a measurement combination that cancels a modal direction.

8. Mathematical Proofs from the Examples

Proof 1 (Hidden directions are null directions of the observation stack): Suppose \( \boldsymbol{\eta} \) is hidden, so \( \mathbf{C}e^{\mathbf{A}t}\boldsymbol{\eta}=\mathbf{0} \) for all measured times. Expanding the matrix exponential,

\[ \mathbf{C}e^{\mathbf{A}t}\boldsymbol{\eta} = \sum_{k=0}^{\infty} \frac{t^k}{k!}\mathbf{C}\mathbf{A}^k\boldsymbol{\eta}. \]

If this analytic function is identically zero, all its derivatives at \( t=0 \) are zero:

\[ \mathbf{C}\mathbf{A}^k\boldsymbol{\eta}=\mathbf{0}, \qquad k=0,1,2,\ldots. \]

In finite dimension, the Cayley-Hamilton theorem implies that powers \( \mathbf{A}^k \) for \( k\ge n \) are linear combinations of \( \mathbf{I},\mathbf{A},\ldots,\mathbf{A}^{n-1} \). Hence it is enough to check

\[ \mathcal{O}_n\boldsymbol{\eta}=\mathbf{0}. \]

Thus the unobservable subspace is precisely the null space of the finite observation stack used in this lesson.

Proof 2 (Observability is invariant under a change of coordinates): Let \( \mathbf{x}=\mathbf{T}\mathbf{z} \) with nonsingular \( \mathbf{T} \). Then

\[ \dot{\mathbf{z} }= \mathbf{T}^{-1}\mathbf{A}\mathbf{T}\mathbf{z},\qquad \mathbf{y}=\mathbf{C}\mathbf{T}\mathbf{z}. \]

The transformed observation stack is

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

Since multiplication by a nonsingular matrix does not change rank,

\[ \operatorname{rank}(\widetilde{\mathcal{O} }_n) = \operatorname{rank}(\mathcal{O}_n). \]

Therefore observability is a property of the input-output realization, not of the particular coordinates used to describe the state.

9. Computational Implementations

The following programs build \( \mathcal{O}_n=[\mathbf{C}^T,(\mathbf{C}\mathbf{A})^T,\ldots]^T \), compute its numerical rank, and classify the examples above. The Python and MATLAB versions also simulate indistinguishable outputs for the unobservable system.

Chapter13_Lesson5.py


# Chapter13_Lesson5.py
# Examples of observable and unobservable systems for continuous-time LTI models.
# Dependencies: numpy, scipy, matplotlib

import numpy as np
from numpy.linalg import matrix_rank
from scipy.linalg import expm
import matplotlib.pyplot as plt


def observability_matrix(A, C):
    """Build O = [C; C A; ...; C A^(n-1)]."""
    A = np.array(A, dtype=float)
    C = np.array(C, dtype=float)
    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 classify_system(name, A, C, stable_tol=1e-9):
    """Print rank, unobservable dimension, and a basic detectability indication."""
    A = np.array(A, dtype=float)
    C = np.array(C, dtype=float)
    O = observability_matrix(A, C)
    r = matrix_rank(O)
    n = A.shape[0]
    eigvals = np.linalg.eigvals(A)

    print("\n" + "=" * 72)
    print(name)
    print("A =\n", A)
    print("C =\n", C)
    print("Observability matrix O =\n", O)
    print(f"rank(O) = {r} out of n = {n}")
    print("eigenvalues(A) =", eigvals)

    # A simple numerical way to inspect unobservable directions:
    # right singular vectors associated with nearly zero singular values.
    U, s, Vh = np.linalg.svd(O)
    print("singular values(O) =", s)
    if r == n:
        print("Conclusion: observable.")
    else:
        null_vectors = Vh[r:, :].T
        print("Basis estimate for unobservable subspace columns =\n", null_vectors)
        # Detectability is exact only after isolating A-invariant unobservable modes.
        # For these examples, the null basis is one-dimensional and invariant.
        if null_vectors.shape[1] == 1:
            v = null_vectors[:, 0]
            Av = A @ v
            lam = (v @ Av) / (v @ v)
            residual = np.linalg.norm(Av - lam * v)
            print("candidate unobservable eigenvalue =", lam)
            print("invariance residual =", residual)
            if residual < 1e-7 and np.real(lam) < -stable_tol:
                print("Conclusion: unobservable but detectable.")
            elif residual < 1e-7:
                print("Conclusion: unobservable and not detectable.")
            else:
                print("Conclusion: unobservable; inspect invariant unobservable modes.")


def simulate_outputs(A, C, x0_list, t_grid):
    """Compare outputs from different initial states for u(t)=0."""
    A = np.array(A, dtype=float)
    C = np.array(C, dtype=float)
    outputs = []
    for x0 in x0_list:
        x0 = np.array(x0, dtype=float)
        y = []
        for t in t_grid:
            x = expm(A * t) @ x0
            y.append((C @ x).reshape(-1))
        outputs.append(np.array(y))
    return outputs


if __name__ == "__main__":
    # Example 1: observable second-order oscillator with position measurement.
    A1 = np.array([[0.0, 1.0],
                   [-2.0, -3.0]])
    C1 = np.array([[1.0, 0.0]])
    classify_system("Example 1: observable mass-spring-damper style system", A1, C1)

    # Example 2: unobservable decoupled state. x2 never affects x1 or y.
    A2 = np.array([[-1.0, 0.0],
                   [0.0, 2.0]])
    C2 = np.array([[1.0, 0.0]])
    classify_system("Example 2: unobservable and not detectable", A2, C2)

    # Example 3: unobservable but detectable. Hidden mode decays.
    A3 = np.array([[-1.0, 0.0],
                   [0.0, -4.0]])
    C3 = np.array([[1.0, 0.0]])
    classify_system("Example 3: unobservable but detectable", A3, C3)

    # Example 4: sensor placement changes observability.
    A4 = np.array([[0.0, 1.0],
                   [-6.0, -5.0]])
    C4_bad = np.array([[0.0, 1.0]])
    C4_good = np.array([[1.0, 0.0]])
    classify_system("Example 4a: velocity-only sensor for this model", A4, C4_bad)
    classify_system("Example 4b: position sensor for this model", A4, C4_good)

    # Output indistinguishability demonstration for Example 2:
    t_grid = np.linspace(0.0, 3.0, 200)
    x0_a = [1.0, 0.0]
    x0_b = [1.0, 5.0]
    y_a, y_b = simulate_outputs(A2, C2, [x0_a, x0_b], t_grid)

    plt.figure()
    plt.plot(t_grid, y_a[:, 0], label="x0 = [1, 0]")
    plt.plot(t_grid, y_b[:, 0], "--", label="x0 = [1, 5]")
    plt.xlabel("time")
    plt.ylabel("output y(t)")
    plt.title("Unobservable Example: Different Initial States, Same Output")
    plt.legend()
    plt.grid(True)
    plt.show()

      

Chapter13_Lesson5.cpp


// Chapter13_Lesson5.cpp
// Observability examples using only the C++ standard library.
// Compile: g++ -std=c++17 Chapter13_Lesson5.cpp -o Chapter13_Lesson5

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

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

Matrix multiply(const Matrix& A, const Matrix& B) {
    int m = static_cast<int>(A.size());
    int p = static_cast<int>(A[0].size());
    int n = static_cast<int>(B[0].size());
    Matrix R(m, std::vector<double>(n, 0.0));
    for (int i = 0; i < m; ++i) {
        for (int k = 0; k < p; ++k) {
            for (int j = 0; j < n; ++j) {
                R[i][j] += A[i][k] * B[k][j];
            }
        }
    }
    return R;
}

Matrix identity(int n) {
    Matrix I(n, std::vector<double>(n, 0.0));
    for (int i = 0; i < n; ++i) I[i][i] = 1.0;
    return I;
}

Matrix vstack(const std::vector<Matrix>& blocks) {
    Matrix R;
    for (const auto& B : blocks) {
        for (const auto& row : B) R.push_back(row);
    }
    return R;
}

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

void printMatrix(const Matrix& M, const std::string& name) {
    std::cout << name << " =\n";
    for (const auto& row : M) {
        for (double x : row) {
            std::cout << std::setw(12) << std::setprecision(6) << x << " ";
        }
        std::cout << "\n";
    }
}

int rankByGaussianElimination(Matrix M, double tol = 1e-10) {
    int rows = static_cast<int>(M.size());
    int cols = static_cast<int>(M[0].size());
    int rank = 0;

    for (int col = 0; col < cols && rank < rows; ++col) {
        int pivot = rank;
        for (int i = rank + 1; i < rows; ++i) {
            if (std::fabs(M[i][col]) > std::fabs(M[pivot][col])) pivot = i;
        }
        if (std::fabs(M[pivot][col]) <= tol) continue;

        std::swap(M[pivot], M[rank]);
        double div = M[rank][col];
        for (int j = col; j < cols; ++j) M[rank][j] /= div;

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

void classify(const std::string& name, const Matrix& A, const Matrix& C) {
    Matrix O = observabilityMatrix(A, C);
    int n = static_cast<int>(A.size());
    int r = rankByGaussianElimination(O);

    std::cout << "\n" << std::string(70, '=') << "\n";
    std::cout << name << "\n";
    printMatrix(A, "A");
    printMatrix(C, "C");
    printMatrix(O, "O");
    std::cout << "rank(O) = " << r << " out of n = " << n << "\n";
    if (r == n) std::cout << "Conclusion: observable.\n";
    else std::cout << "Conclusion: unobservable. Inspect hidden modes for detectability.\n";
}

int main() {
    Matrix A1 = { {0.0, 1.0}, {-2.0, -3.0} };
    Matrix C1 = { {1.0, 0.0} };
    classify("Example 1: observable second-order system", A1, C1);

    Matrix A2 = { {-1.0, 0.0}, {0.0, 2.0} };
    Matrix C2 = { {1.0, 0.0} };
    classify("Example 2: unobservable and not detectable", A2, C2);

    Matrix A3 = { {-1.0, 0.0}, {0.0, -4.0} };
    Matrix C3 = { {1.0, 0.0} };
    classify("Example 3: unobservable but detectable", A3, C3);

    Matrix A4 = { {0.0, 1.0}, {-6.0, -5.0} };
    Matrix C4a = { {0.0, 1.0} };
    Matrix C4b = { {1.0, 0.0} };
    classify("Example 4a: velocity sensor", A4, C4a);
    classify("Example 4b: position sensor", A4, C4b);

    return 0;
}

      

Chapter13_Lesson5.java


// Chapter13_Lesson5.java
// Observability examples using plain Java arrays.
// Compile: javac Chapter13_Lesson5.java
// Run:     java Chapter13_Lesson5

public class Chapter13_Lesson5 {
    static double[][] multiply(double[][] A, double[][] B) {
        int m = A.length;
        int p = A[0].length;
        int n = B[0].length;
        double[][] R = new double[m][n];

        for (int i = 0; i < m; ++i) {
            for (int k = 0; k < p; ++k) {
                for (int j = 0; j < n; ++j) {
                    R[i][j] += A[i][k] * B[k][j];
                }
            }
        }
        return R;
    }

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

    static double[][] observabilityMatrix(double[][] A, double[][] C) {
        int n = A.length;
        int p = C.length;
        double[][] O = new double[p * n][n];
        double[][] Ak = identity(n);

        for (int k = 0; k < n; ++k) {
            double[][] block = multiply(C, Ak);
            for (int i = 0; i < p; ++i) {
                for (int j = 0; j < n; ++j) {
                    O[k * p + i][j] = block[i][j];
                }
            }
            Ak = multiply(Ak, A);
        }
        return O;
    }

    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 col = 0; col < cols && r < rows; ++col) {
            int pivot = r;
            for (int i = r + 1; i < rows; ++i) {
                if (Math.abs(M[i][col]) > Math.abs(M[pivot][col])) pivot = i;
            }
            if (Math.abs(M[pivot][col]) <= tol) continue;

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

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

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

    static void printMatrix(String name, double[][] M) {
        System.out.println(name + " =");
        for (int i = 0; i < M.length; ++i) {
            for (int j = 0; j < M[0].length; ++j) {
                System.out.printf("%12.6f ", M[i][j]);
            }
            System.out.println();
        }
    }

    static void classify(String name, double[][] A, double[][] C) {
        double[][] O = observabilityMatrix(A, C);
        int r = rank(O, 1e-10);
        int n = A.length;

        System.out.println("\n======================================================================");
        System.out.println(name);
        printMatrix("A", A);
        printMatrix("C", C);
        printMatrix("O", O);
        System.out.println("rank(O) = " + r + " out of n = " + n);
        if (r == n) {
            System.out.println("Conclusion: observable.");
        } else {
            System.out.println("Conclusion: unobservable. Inspect hidden modes for detectability.");
        }
    }

    public static void main(String[] args) {
        double[][] A1 = { {0.0, 1.0}, {-2.0, -3.0} };
        double[][] C1 = { {1.0, 0.0} };
        classify("Example 1: observable second-order system", A1, C1);

        double[][] A2 = { {-1.0, 0.0}, {0.0, 2.0} };
        double[][] C2 = { {1.0, 0.0} };
        classify("Example 2: unobservable and not detectable", A2, C2);

        double[][] A3 = { {-1.0, 0.0}, {0.0, -4.0} };
        double[][] C3 = { {1.0, 0.0} };
        classify("Example 3: unobservable but detectable", A3, C3);

        double[][] A4 = { {0.0, 1.0}, {-6.0, -5.0} };
        double[][] C4a = { {0.0, 1.0} };
        double[][] C4b = { {1.0, 0.0} };
        classify("Example 4a: velocity sensor", A4, C4a);
        classify("Example 4b: position sensor", A4, C4b);
    }
}

      

Chapter13_Lesson5.m


% Chapter13_Lesson5.m
% Examples of observable and unobservable systems.
% Requires base MATLAB. The Control System Toolbox function obsv is optional.

clear; clc; close all;

examples = {};

examples{end+1}.name = 'Example 1: observable second-order system';
examples{end}.A = [0 1; -2 -3];
examples{end}.C = [1 0];

examples{end+1}.name = 'Example 2: unobservable and not detectable';
examples{end}.A = [-1 0; 0 2];
examples{end}.C = [1 0];

examples{end+1}.name = 'Example 3: unobservable but detectable';
examples{end}.A = [-1 0; 0 -4];
examples{end}.C = [1 0];

examples{end+1}.name = 'Example 4a: velocity sensor';
examples{end}.A = [0 1; -6 -5];
examples{end}.C = [0 1];

examples{end+1}.name = 'Example 4b: position sensor';
examples{end}.A = [0 1; -6 -5];
examples{end}.C = [1 0];

for k = 1:numel(examples)
    A = examples{k}.A;
    C = examples{k}.C;
    O = local_obsv(A, C);
    r = rank(O);
    n = size(A, 1);

    fprintf('\n===============================================================\n');
    fprintf('%s\n', examples{k}.name);
    disp('A ='); disp(A);
    disp('C ='); disp(C);
    disp('O ='); disp(O);
    fprintf('rank(O) = %d out of n = %d\n', r, n);
    disp('eig(A) ='); disp(eig(A).');

    if r == n
        fprintf('Conclusion: observable.\n');
    else
        fprintf('Conclusion: unobservable. Inspect hidden modes for detectability.\n');
    end
end

% Demonstrate indistinguishable outputs for Example 2.
A = [-1 0; 0 2];
C = [1 0];
t = linspace(0, 3, 250);
x0a = [1; 0];
x0b = [1; 5];
ya = zeros(size(t));
yb = zeros(size(t));

for i = 1:numel(t)
    ya(i) = C * expm(A * t(i)) * x0a;
    yb(i) = C * expm(A * t(i)) * x0b;
end

figure;
plot(t, ya, 'LineWidth', 1.5); hold on;
plot(t, yb, '--', 'LineWidth', 1.5);
grid on;
xlabel('time');
ylabel('output y(t)');
title('Unobservable Example: Different Initial States, Same Output');
legend('x0 = [1;0]', 'x0 = [1;5]', 'Location', 'best');

function O = local_obsv(A, C)
    n = size(A, 1);
    O = [];
    Ak = eye(n);
    for k = 1:n
        O = [O; C * Ak];
        Ak = Ak * A;
    end
end

      

Chapter13_Lesson5.nb


(* Chapter13_Lesson5.nb *)
(* Wolfram Mathematica code for observability examples. *)

ClearAll[ObservabilityMatrixCustom, ClassifySystem];

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

ClassifySystem[name_, A_, C_] := Module[
  {O, r, n, eig},
  O = ObservabilityMatrixCustom[A, C];
  r = MatrixRank[O];
  n = Length[A];
  eig = Eigenvalues[A];

  Print["\n==============================================================="];
  Print[name];
  Print["A = ", MatrixForm[A]];
  Print["C = ", MatrixForm[C]];
  Print["O = ", MatrixForm[O]];
  Print["rank(O) = ", r, " out of n = ", n];
  Print["eig(A) = ", eig];

  If[r == n,
    Print["Conclusion: observable."],
    Print["Conclusion: unobservable. Inspect hidden modes for detectability."]
  ];
];

A1 = { {0, 1}, {-2, -3} };
C1 = { {1, 0} };
ClassifySystem["Example 1: observable second-order system", A1, C1];

A2 = { {-1, 0}, {0, 2} };
C2 = { {1, 0} };
ClassifySystem["Example 2: unobservable and not detectable", A2, C2];

A3 = { {-1, 0}, {0, -4} };
C3 = { {1, 0} };
ClassifySystem["Example 3: unobservable but detectable", A3, C3];

A4 = { {0, 1}, {-6, -5} };
C4a = { {0, 1} };
C4b = { {1, 0} };
ClassifySystem["Example 4a: velocity sensor", A4, C4a];
ClassifySystem["Example 4b: position sensor", A4, C4b];

(* Output indistinguishability for Example 2 *)
x0a = {1, 0};
x0b = {1, 5};
ya[t_] := First[C2 . MatrixExp[A2 t] . x0a];
yb[t_] := First[C2 . MatrixExp[A2 t] . x0b];

Plot[
  {ya[t], yb[t]},
  {t, 0, 3},
  PlotLegends -> {"x0 = {1,0}", "x0 = {1,5}"},
  AxesLabel -> {"time", "y(t)"},
  PlotLabel -> "Unobservable Example: Different Initial States, Same Output"
]

      

10. Problems and Solutions

Problem 1 (Direct Reconstruction): For \( \mathbf{A}=\begin{bmatrix}0&1\\-4&-4\end{bmatrix} \) and \( \mathbf{C}=\begin{bmatrix}1&0\end{bmatrix} \), determine whether the initial state is observable by using \( y(0) \) and \( \dot{y}(0) \).

Solution: Since \( y=x_1 \), we have \( y(0)=x_1(0) \). Also \( \dot{y}(0)=\dot{x}_1(0)=x_2(0) \). Hence both components are reconstructed:

\[ \mathbf{x}_0= \begin{bmatrix}y(0)\\\dot{y}(0)\end{bmatrix}. \]

The system is observable.

Problem 2 (Unobservable Direction): Let \( \mathbf{A}=\begin{bmatrix}-3&0\\0&1\end{bmatrix} \) and \( \mathbf{C}=\begin{bmatrix}1&0\end{bmatrix} \). Find a nonzero initial state that produces identically zero output.

Solution: Choose \( \mathbf{x}_0=\begin{bmatrix}0\\1\end{bmatrix} \). Then \( x_1(t)=0 \) for all time and \( y(t)=x_1(t)=0 \), while \( x_2(t)=e^t \). Therefore the nonzero hidden initial state is unobservable. Because the hidden mode grows, the system is not detectable.

Problem 3 (Detectability): Repeat Problem 2 with \( \mathbf{A}=\begin{bmatrix}-3&0\\0&-1\end{bmatrix} \). Is the system observable? Is it detectable?

Solution: The same vector \( \begin{bmatrix}0&1\end{bmatrix}^T \) is unobservable, so the system is not observable. However, the hidden mode is \( e^{-t} \), which decays to zero. Therefore the system is detectable.

Problem 4 (Cancellation in the Output): For \( \mathbf{A}=-5\mathbf{I}_2 \) and \( \mathbf{C}=\begin{bmatrix}1&1\end{bmatrix} \), find the unobservable subspace.

Solution: The output is \( y(t)=e^{-5t}(x_1(0)+x_2(0)) \). Therefore only the sum of initial coordinates is visible. The hidden directions satisfy

\[ x_1(0)+x_2(0)=0. \]

Hence

\[ \mathcal{N}(\mathcal{O}_2)= \operatorname{span}\left\{ \begin{bmatrix}1\\-1\end{bmatrix} \right\}. \]

Because the hidden mode decays as \( e^{-5t} \), the system is detectable.

Problem 5 (Coordinate Change): Suppose a system is observable in coordinates \( \mathbf{x} \). Show that it remains observable after the coordinate transformation \( \mathbf{x}=\mathbf{T}\mathbf{z} \), where \( \mathbf{T} \) is nonsingular.

Solution: The transformed observation stack is \( \widetilde{\mathcal{O} }_n=\mathcal{O}_n\mathbf{T} \). Since \( \mathbf{T} \) is nonsingular, multiplication by \( \mathbf{T} \) preserves rank. Therefore the transformed stack has the same rank as the original stack, so observability is preserved.

11. Summary

Observable systems allow the initial state to be reconstructed from the measured output history. Unobservable systems contain nonzero initial directions that generate no output signature. A system may still be detectable when all hidden directions decay asymptotically. The worked examples show four common mechanisms: dynamic coupling can reveal unmeasured states; decoupling can hide states; stable hidden modes lead to detectability; and sensor combinations can cancel modal directions.

12. References

  1. Kalman, R.E. (1960). On the general theory of control systems. Proceedings of the First International Congress on Automatic Control, 481–492.
  2. 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.
  3. 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.
  4. Popov, V.M. (1964). Hyperstability of control systems. Automation and Remote Control, 25, 267–274.
  5. Hautus, M.L.J. (1969). Controllability and observability conditions of linear autonomous systems. Nederlands Akademie van Wetenschappen, Proceedings, Series A, 72, 443–448.
  6. Wonham, W.M. (1968). On pole assignment in multi-input controllable linear systems. IEEE Transactions on Automatic Control, 12(6), 660–665.
  7. Luenberger, D.G. (1964). Observing the state of a linear system. IEEE Transactions on Military Electronics, 8(2), 74–80.
  8. Rosenbrock, H.H. (1968). State-space and multivariable theory. Proceedings of the Institution of Electrical Engineers, 115(9), 1330–1338.