Chapter 26: State-Feedback with Integral Action

Lesson 5: Examples of Servo Design Using State Augmentation

This lesson consolidates state-feedback servo design with integral action through worked examples. We design augmented controllers, verify controllability of the augmented plant, place the augmented closed-loop poles, prove zero steady-state tracking error for constant references, and implement the method in Python, C++, Java, MATLAB/Simulink, and Wolfram Mathematica.

1. Servo Design Objective

Consider the continuous-time LTI plant \( \dot{x}=Ax+Bu \), \( y=Cx+Du \). A servo controller is not only required to stabilize the plant; it must also force the output \( y(t) \) to track a reference \( r(t) \). For constant references, integral action is introduced by defining the integral error state

\[ \eta(t)=\int_0^t \left(r(\tau)-y(\tau)\right)d\tau,\qquad \dot{\eta}=r-y. \]

In this lesson we assume \( D=0 \) unless stated otherwise. Then the augmented state is \( z=\begin{bmatrix}x^T & \eta^T\end{bmatrix}^T \), and the augmented open-loop dynamics become

\[ \begin{bmatrix}\dot{x}\\\dot{\eta}\end{bmatrix} = \underbrace{\begin{bmatrix}A&0\\-C&0\end{bmatrix} }_{A_a} \begin{bmatrix}x\\\eta\end{bmatrix} + \underbrace{\begin{bmatrix}B\\0\end{bmatrix} }_{B_a}u + \underbrace{\begin{bmatrix}0\\I\end{bmatrix} }_{E_a}r. \]

The state-augmented servo feedback is written as

\[ u=-K_xx+K_i\eta = -\begin{bmatrix}K_x&-K_i\end{bmatrix} \begin{bmatrix}x\\\eta\end{bmatrix} = -K_a z. \]

Hence the augmented closed-loop matrix is

\[ A_{cl}=A_a-B_aK_a = \begin{bmatrix} A-BK_x&BK_i\\ -C&0 \end{bmatrix}. \]

2. Design Workflow

The practical servo design sequence is: choose the tracked output, augment the plant with integral states, verify augmented controllability, assign the augmented poles, and simulate the closed-loop response.

flowchart TD
  A["Start with plant A, B, C, D"] --> B["Choose outputs to track"]
  B --> C["Create integral state eta_dot = r minus y"]
  C --> D["Build augmented matrices Aa, Ba, Ea"]
  D --> E["Check rank of augmented controllability matrix"]
  E --> F{"Full rank?"}
  F -->|yes| G["Choose augmented desired poles"]
  F -->|no| H["Redesign actuator/output selection"]
  G --> I["Compute Ka = [Kx, minus Ki]"]
  I --> J["Simulate closed-loop tracking"]
  J --> K["Check error, input effort, pole locations"]
        

3. Augmented Controllability Condition

The integrator adds new states, so controllability of \( (A,B) \) alone is not sufficient. For direct pole placement of all augmented poles, the augmented pair \( (A_a,B_a) \) must be controllable:

\[ \operatorname{rank}\mathcal{C}_a = \operatorname{rank}\begin{bmatrix} B_a&A_aB_a&A_a^2B_a&\cdots&A_a^{n+p-1}B_a \end{bmatrix} = n+p. \]

For a multivariable plant with \( p \) tracked outputs, a necessary structural condition is that the plant must not have a blocking zero at the origin in the tracked input-output channel:

\[ \operatorname{rank} \begin{bmatrix} A&B\\ C&D \end{bmatrix} = n+p \quad\text{at } s=0. \]

Intuitively, if the plant cannot change the steady-state value of the selected output through the available input channels, then integrating the error only creates an uncontrollable integrator mode.

4. Proof of Zero Steady-State Error for Constant Reference

Suppose \( A_{cl} \) is Hurwitz and the reference \( r(t)=r_0 \) is constant. The closed-loop augmented dynamics are

\[ \dot{z}=A_{cl}z+E_ar_0. \]

Since \( A_{cl} \) is Hurwitz, the solution converges to a finite equilibrium \( z_\infty \) satisfying

\[ 0=A_{cl}z_\infty+E_ar_0. \]

The integrator row of the equilibrium equation is

\[ 0=r_0-Cx_\infty. \]

Therefore,

\[ \lim_{t\to\infty}e(t) = \lim_{t\to\infty}(r_0-y(t)) = r_0-Cx_\infty = 0. \]

This is the central servo property: after the augmented poles are placed in the open left half-plane, the integral state enforces zero steady-state error for constant references.

5. Example 1: Mass-Spring-Damper Position Servo

Consider a normalized mass-spring-damper system:

\[ \ddot{q}+0.8\dot{q}+2q=u,\qquad y=q. \]

With \( x_1=q \) and \( x_2=\dot{q} \),

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

Add the integral state \( \dot{\eta}=r-y=r-x_1 \). Then

\[ A_a= \begin{bmatrix} 0&1&0\\ -2&-0.8&0\\ -1&0&0 \end{bmatrix}, \qquad B_a= \begin{bmatrix} 0\\1\\0 \end{bmatrix}. \]

Choose the desired augmented poles \( \{-2,-2.5,-3\} \). The controller is \( u=-K_xx+K_i\eta \), where \( K_a=\begin{bmatrix}K_x&-K_i\end{bmatrix} \) is chosen so that \( A_a-B_aK_a \) has the selected eigenvalues.

flowchart TD
  R["reference r"] --> S["error sum"]
  Y["output y"] --> S
  S --> INT["integrator eta"]
  INT --> KI["gain Ki"]
  X["state x"] --> KX["gain Kx"]
  KI --> U["control u"]
  KX --> U
  U --> P["plant x_dot = A x + B u"]
  P --> Y
        

6. Example 2: Multi-Output Servo Design

For \( p \) tracked outputs, define \( \eta\in\mathbb{R}^p \) by \( \dot{\eta}=r-y \). If the plant has \( m \) inputs, then the augmented input matrix has size \( (n+p)\times m \). Pole placement may be possible when the augmented controllability matrix has rank \( n+p \). In a square servo problem with \( m=p \), a common design form is

\[ u=-K_xx+K_i\eta,\qquad K_x\in\mathbb{R}^{m\times n},\quad K_i\in\mathbb{R}^{m\times p}. \]

For example, with two outputs and two inputs, \( \eta_1 \) integrates the tracking error of \( y_1 \), while \( \eta_2 \) integrates the tracking error of \( y_2 \). The augmented system is

\[ \begin{bmatrix} \dot{x}\\ \dot{\eta}_1\\ \dot{\eta}_2 \end{bmatrix} = \begin{bmatrix} A&0\\ -C&0 \end{bmatrix} \begin{bmatrix} x\\ \eta_1\\ \eta_2 \end{bmatrix} + \begin{bmatrix} B\\ 0 \end{bmatrix}u + \begin{bmatrix} 0\\ I_2 \end{bmatrix}r. \]

The main design risk is not mathematical notation but feasibility: every integrated output must be controllable at steady state. If one selected output cannot be independently moved by the input channels, the added integrator will introduce an unassignable pole at the origin.

7. Implementation Files and Libraries

The following implementations use the same mass-spring-damper servo example. Python uses NumPy, SciPy, and Matplotlib. C++ uses Eigen. Java implements the required small matrix routines from scratch. MATLAB uses the Control System Toolbox and also creates a minimal Simulink-style model skeleton. Mathematica uses symbolic/numeric matrix operations and StateSpaceModel.

Chapter26_Lesson5.py

# Chapter26_Lesson5.py
# Servo design by state augmentation for a mass-spring-damper plant.
# Libraries: NumPy, SciPy, Matplotlib. Optional python-control can be used similarly.

import numpy as np
from scipy.signal import place_poles
from scipy.integrate import solve_ivp
import matplotlib.pyplot as plt


def controllability_matrix(A: np.ndarray, B: np.ndarray) -> np.ndarray:
    """Return [B, AB, ..., A^(n-1)B]."""
    n = A.shape[0]
    blocks = [B]
    Ak = np.eye(n)
    for _ in range(1, n):
        Ak = Ak @ A
        blocks.append(Ak @ B)
    return np.hstack(blocks)


A = np.array([[0.0, 1.0],
              [-2.0, -0.8]])
B = np.array([[0.0],
              [1.0]])
C = np.array([[1.0, 0.0]])
D = np.array([[0.0]])

n = A.shape[0]
p = C.shape[0]
m = B.shape[1]

Aa = np.block([
    [A, np.zeros((n, p))],
    [-C, np.zeros((p, p))]
])
Ba = np.vstack([B, np.zeros((p, m))])
Ea = np.vstack([np.zeros((n, p)), np.eye(p)])

Wc = controllability_matrix(Aa, Ba)
rank_Wc = np.linalg.matrix_rank(Wc)
print("Augmented controllability rank:", rank_Wc, "of", n + p)
if rank_Wc < n + p:
    raise RuntimeError("The augmented plant is not controllable.")

desired_poles = np.array([-2.0, -2.5, -3.0])
Kaug = place_poles(Aa, Ba, desired_poles).gain_matrix
Kx = Kaug[:, :n]
Ki = -Kaug[:, n:]

print("Kx =", Kx)
print("Ki =", Ki)
print("Closed-loop poles =", np.linalg.eigvals(Aa - Ba @ Kaug))

Acl = Aa - Ba @ Kaug
Cr = np.hstack([C, np.zeros((p, p))])

r_value = 1.0

def closed_loop_rhs(t, z):
    return (Acl @ z.reshape(-1, 1) + Ea * r_value).ravel()

sol = solve_ivp(closed_loop_rhs, (0.0, 8.0), np.zeros(n + p), max_step=0.01, dense_output=True)
t = np.linspace(0.0, 8.0, 801)
z = sol.sol(t)
y = (Cr @ z).ravel()
u = np.array([(-Kx @ z[:n, [i]] + Ki @ z[n:, [i]])[0, 0] for i in range(z.shape[1])])
error = r_value - y

print("Final output y(T) =", y[-1])
print("Final tracking error =", error[-1])
print("Final control input =", u[-1])

plt.figure()
plt.plot(t, y, label="y(t)")
plt.plot(t, r_value * np.ones_like(t), "--", label="r")
plt.xlabel("Time [s]")
plt.ylabel("Output")
plt.title("State-Augmented Servo Response")
plt.grid(True)
plt.legend()

plt.figure()
plt.plot(t, u, label="u(t)")
plt.xlabel("Time [s]")
plt.ylabel("Control input")
plt.title("Servo Control Effort")
plt.grid(True)
plt.legend()
plt.show()

Chapter26_Lesson5.cpp

// Chapter26_Lesson5.cpp
// Servo design by state augmentation using Eigen and Ackermann's formula.
// Build example: g++ -std=c++17 Chapter26_Lesson5.cpp -I /path/to/eigen -O2 -o servo_aug

#include <Eigen/Dense>
#include <iostream>
#include <vector>

using Eigen::MatrixXd;
using Eigen::VectorXd;

MatrixXd controllabilityMatrix(const MatrixXd& A, const MatrixXd& B) {
    const int n = A.rows();
    const int m = B.cols();
    MatrixXd W(n, n * m);
    MatrixXd Ak = MatrixXd::Identity(n, n);
    for (int k = 0; k < n; ++k) {
        W.block(0, k * m, n, m) = Ak * B;
        Ak = Ak * A;
    }
    return W;
}

std::vector<double> polynomialFromRoots(const std::vector<double>& roots) {
    std::vector<double> c{1.0};
    for (double r : roots) {
        std::vector<double> next(c.size() + 1, 0.0);
        for (std::size_t i = 0; i < c.size(); ++i) {
            next[i] += c[i];
            next[i + 1] += -r * c[i];
        }
        c = next;
    }
    return c;
}

MatrixXd matrixPolynomial(const MatrixXd& A, const std::vector<double>& coeff) {
    const int n = A.rows();
    MatrixXd phi = MatrixXd::Zero(n, n);
    for (int i = 0; i <= n; ++i) {
        int power = n - i;
        MatrixXd Apow = MatrixXd::Identity(n, n);
        for (int k = 0; k < power; ++k) Apow = Apow * A;
        phi += coeff[i] * Apow;
    }
    return phi;
}

int main() {
    MatrixXd A(2, 2), B(2, 1), C(1, 2);
    A << 0.0, 1.0,
        -2.0, -0.8;
    B << 0.0,
         1.0;
    C << 1.0, 0.0;

    const int n = 2;
    const int p = 1;
    MatrixXd Aa = MatrixXd::Zero(n + p, n + p);
    MatrixXd Ba = MatrixXd::Zero(n + p, 1);
    MatrixXd Ea = MatrixXd::Zero(n + p, 1);

    Aa.block(0, 0, n, n) = A;
    Aa.block(n, 0, p, n) = -C;
    Ba.block(0, 0, n, 1) = B;
    Ea(n, 0) = 1.0;

    MatrixXd Wc = controllabilityMatrix(Aa, Ba);
    std::cout << "det(Wc) = " << Wc.determinant() << "\\n";

    std::vector<double> desiredPoles{-2.0, -2.5, -3.0};
    std::vector<double> coeff = polynomialFromRoots(desiredPoles);
    MatrixXd phiA = matrixPolynomial(Aa, coeff);

    Eigen::RowVectorXd enT(3);
    enT << 0.0, 0.0, 1.0;
    Eigen::RowVectorXd Kaug = enT * Wc.inverse() * phiA;

    Eigen::RowVector2d Kx = Kaug.segment<2>(0);
    double Ki = -Kaug(2);

    std::cout << "Kaug = " << Kaug << "\\n";
    std::cout << "Kx = " << Kx << "\\n";
    std::cout << "Ki = " << Ki << "\\n";

    MatrixXd Acl = Aa - Ba * Kaug;
    VectorXd z = VectorXd::Zero(3);
    const double r = 1.0;
    const double dt = 0.001;
    const double tf = 8.0;

    for (int k = 0; k < static_cast<int>(tf / dt); ++k) {
        VectorXd zdot = Acl * z + Ea.col(0) * r;
        z += dt * zdot;
    }

    double y = (C * z.head(2))(0, 0);
    double u = -(Kx * z.head(2))(0, 0) + Ki * z(2);
    std::cout << "Final y = " << y << "\\n";
    std::cout << "Final tracking error = " << r - y << "\\n";
    std::cout << "Final u = " << u << "\\n";
    return 0;
}

Chapter26_Lesson5.java

// Chapter26_Lesson5.java
// Servo design by state augmentation using only small from-scratch matrix routines.
// Compile/run: javac Chapter26_Lesson5.java && java Chapter26_Lesson5

import java.util.Arrays;

public class Chapter26_Lesson5 {
    static double[][] multiply(double[][] A, double[][] B) {
        int n = A.length, m = B[0].length, q = B.length;
        double[][] C = new double[n][m];
        for (int i = 0; i < n; i++)
            for (int j = 0; j < m; j++)
                for (int k = 0; k < q; k++)
                    C[i][j] += A[i][k] * B[k][j];
        return C;
    }

    static double[][] add(double[][] A, double[][] B) {
        double[][] C = new double[A.length][A[0].length];
        for (int i = 0; i < A.length; i++)
            for (int j = 0; j < A[0].length; j++)
                C[i][j] = A[i][j] + B[i][j];
        return C;
    }

    static double[][] scale(double a, double[][] A) {
        double[][] C = new double[A.length][A[0].length];
        for (int i = 0; i < A.length; i++)
            for (int j = 0; j < A[0].length; j++)
                C[i][j] = a * A[i][j];
        return C;
    }

    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[][] power(double[][] A, int k) {
        double[][] R = identity(A.length);
        for (int i = 0; i < k; i++) R = multiply(R, A);
        return R;
    }

    static double[][] inverse(double[][] A) {
        int n = A.length;
        double[][] aug = new double[n][2 * n];
        for (int i = 0; i < n; i++) {
            System.arraycopy(A[i], 0, aug[i], 0, n);
            aug[i][n + i] = 1.0;
        }
        for (int col = 0; col < n; col++) {
            int pivot = col;
            for (int r = col + 1; r < n; r++)
                if (Math.abs(aug[r][col]) > Math.abs(aug[pivot][col])) pivot = r;
            double[] tmp = aug[col]; aug[col] = aug[pivot]; aug[pivot] = tmp;
            double div = aug[col][col];
            if (Math.abs(div) < 1e-12) throw new RuntimeException("Singular matrix");
            for (int j = 0; j < 2 * n; j++) aug[col][j] /= div;
            for (int r = 0; r < n; r++) {
                if (r == col) continue;
                double factor = aug[r][col];
                for (int j = 0; j < 2 * n; j++) aug[r][j] -= factor * aug[col][j];
            }
        }
        double[][] inv = new double[n][n];
        for (int i = 0; i < n; i++)
            System.arraycopy(aug[i], n, inv[i], 0, n);
        return inv;
    }

    static double[] polynomialFromRoots(double[] roots) {
        double[] c = {1.0};
        for (double r : roots) {
            double[] next = new double[c.length + 1];
            for (int i = 0; i < c.length; i++) {
                next[i] += c[i];
                next[i + 1] += -r * c[i];
            }
            c = next;
        }
        return c;
    }

    static double[][] controllabilityMatrix(double[][] A, double[][] B) {
        int n = A.length;
        double[][] W = new double[n][n];
        for (int k = 0; k < n; k++) {
            double[][] block = multiply(power(A, k), B);
            for (int i = 0; i < n; i++) W[i][k] = block[i][0];
        }
        return W;
    }

    static double[][] matrixPolynomial(double[][] A, double[] coeff) {
        int n = A.length;
        double[][] phi = new double[n][n];
        for (int i = 0; i <= n; i++) {
            int pow = n - i;
            phi = add(phi, scale(coeff[i], power(A, pow)));
        }
        return phi;
    }

    static double[] rowTimesMatrix(double[] row, double[][] A) {
        double[] y = new double[A[0].length];
        for (int j = 0; j < A[0].length; j++)
            for (int k = 0; k < row.length; k++) y[j] += row[k] * A[k][j];
        return y;
    }

    static double[] matVec(double[][] A, double[] x) {
        double[] y = new double[A.length];
        for (int i = 0; i < A.length; i++)
            for (int j = 0; j < x.length; j++) y[i] += A[i][j] * x[j];
        return y;
    }

    public static void main(String[] args) {
        double[][] Aa = {
            {0.0, 1.0, 0.0},
            {-2.0, -0.8, 0.0},
            {-1.0, 0.0, 0.0}
        };
        double[][] Ba = { {0.0}, {1.0}, {0.0} };
        double[][] Ea = { {0.0}, {0.0}, {1.0} };
        double[][] C = { {1.0, 0.0} };

        double[] roots = {-2.0, -2.5, -3.0};
        double[] coeff = polynomialFromRoots(roots);
        double[][] Wc = controllabilityMatrix(Aa, Ba);
        double[][] phiA = matrixPolynomial(Aa, coeff);

        double[] enT = {0.0, 0.0, 1.0};
        double[] Kaug = rowTimesMatrix(rowTimesMatrix(enT, inverse(Wc)), phiA);
        double[] Kx = {Kaug[0], Kaug[1]};
        double Ki = -Kaug[2];

        System.out.println("Kaug = " + Arrays.toString(Kaug));
        System.out.println("Kx = " + Arrays.toString(Kx));
        System.out.println("Ki = " + Ki);

        double[][] Acl = new double[3][3];
        for (int i = 0; i < 3; i++)
            for (int j = 0; j < 3; j++)
                Acl[i][j] = Aa[i][j] - Ba[i][0] * Kaug[j];

        double[] z = {0.0, 0.0, 0.0};
        double r = 1.0, dt = 0.001, tf = 8.0;
        for (int k = 0; k < (int)(tf / dt); k++) {
            double[] zdot = matVec(Acl, z);
            for (int i = 0; i < 3; i++) zdot[i] += Ea[i][0] * r;
            for (int i = 0; i < 3; i++) z[i] += dt * zdot[i];
        }
        double y = C[0][0] * z[0] + C[0][1] * z[1];
        double u = -(Kx[0] * z[0] + Kx[1] * z[1]) + Ki * z[2];
        System.out.println("Final y = " + y);
        System.out.println("Final tracking error = " + (r - y));
        System.out.println("Final u = " + u);
    }
}

Chapter26_Lesson5.m

% Chapter26_Lesson5.m
% Servo design by state augmentation using MATLAB Control System Toolbox.
% The final section creates a small Simulink-style closed-loop model skeleton.

clear; clc; close all;

A = [0 1; -2 -0.8];
B = [0; 1];
C = [1 0];
D = 0;

n = size(A,1);
p = size(C,1);
m = size(B,2);

Aa = [A zeros(n,p); -C zeros(p,p)];
Ba = [B; zeros(p,m)];
Ea = [zeros(n,p); eye(p)];

Wc = ctrb(Aa, Ba);
fprintf('Augmented controllability rank = %d of %d\n', rank(Wc), n+p);
if rank(Wc) < n+p
    error('The augmented system is not controllable.');
end

poles = [-2 -2.5 -3];
Kaug = place(Aa, Ba, poles);
Kx = Kaug(:,1:n);
Ki = -Kaug(:,n+1:end);

Acl = Aa - Ba*Kaug;
Cr = [C zeros(p,p)];
Dr = zeros(p,p);

fprintf('Kx = '); disp(Kx);
fprintf('Ki = '); disp(Ki);
fprintf('Closed-loop poles = '); disp(eig(Acl).');

sys_cl = ss(Acl, Ea, Cr, Dr);
t = 0:0.01:8;
r = ones(size(t));
y = lsim(sys_cl, r, t);

z = lsim(ss(Acl, Ea, eye(n+p), zeros(n+p,p)), r, t);
u = zeros(length(t),1);
for k = 1:length(t)
    xk = z(k,1:n).';
    etak = z(k,n+1:end).';
    u(k) = -Kx*xk + Ki*etak;
end

figure; plot(t, y, 'LineWidth', 1.5); hold on; plot(t, r, '--'); grid on;
xlabel('Time [s]'); ylabel('Output'); title('State-Augmented Servo Response');
legend('y(t)', 'r');

figure; plot(t, u, 'LineWidth', 1.5); grid on;
xlabel('Time [s]'); ylabel('Control input'); title('Servo Control Effort');

try
    model = 'Chapter26_Lesson5_SimulinkServo';
    if bdIsLoaded(model), close_system(model,0); end
    new_system(model); open_system(model);

    add_block('simulink/Sources/Step', [model '/Reference Step'], ...
        'Position', [40 80 90 110], 'Time', '0', 'Before', '0', 'After', '1');
    add_block('simulink/Continuous/State-Space', [model '/Closed Loop Augmented Servo'], ...
        'Position', [150 65 320 125], ...
        'A', 'Acl', 'B', 'Ea', 'C', 'Cr', 'D', 'Dr');
    add_block('simulink/Sinks/Scope', [model '/Output Scope'], ...
        'Position', [380 75 430 115]);
    add_line(model, 'Reference Step/1', 'Closed Loop Augmented Servo/1');
    add_line(model, 'Closed Loop Augmented Servo/1', 'Output Scope/1');
    set_param(model, 'StopTime', '8');
    save_system(model);
    fprintf('Created Simulink model skeleton: %s.slx\n', model);
catch ME
    warning('Simulink model skeleton was not created: %s', ME.message);
end

Chapter26_Lesson5.nb

Notebook[{
Cell["Chapter26_Lesson5.nb", "Title"],
Cell["Servo design by state augmentation for a mass-spring-damper plant.", "Text"],
Cell[BoxData["
ClearAll[\"Global`*\"]
A = { {0, 1}, {-2, -0.8} };
B = { {0}, {1} };
Cmat = { {1, 0} };
n = Length[A]; p = Length[Cmat];
Aa = ArrayFlatten[{ {A, ConstantArray[0, {n, p}]}, {-Cmat, ConstantArray[0, {p, p}]} }];
Ba = Join[B, ConstantArray[0, {p, 1}]];
Ea = Join[ConstantArray[0, {n, p}], IdentityMatrix[p]];
controllabilityMatrix[aa_, bb_] := ArrayFlatten[{Table[MatrixPower[aa, k].bb, {k, 0, Length[aa] - 1}]}];
Wc = controllabilityMatrix[Aa, Ba];
MatrixRank[Wc]
"], "Input"],
Cell[BoxData["
poles = {-2, -2.5, -3};
poly = CoefficientList[Expand[Times @@ (s - # & /@ poles)], s];
phiA = Sum[poly[[k + 1]] MatrixPower[Aa, k], {k, 0, Length[Aa]}];
Kaug = UnitVector[Length[Aa], Length[Aa]].Inverse[Wc].phiA;
Kx = Kaug[[1 ;; n]];
Ki = -Kaug[[n + 1 ;;]];
{Kaug, Kx, Ki, Eigenvalues[Aa - Ba.{Kaug}]}
"], "Input"],
Cell[BoxData["
Acl = Aa - Ba.{Kaug};
Cr = ArrayFlatten[{ {Cmat, ConstantArray[0, {p, p}]} }];
sys = StateSpaceModel[{Acl, Ea, Cr, ConstantArray[0, {p, p}]}];
y = OutputResponse[sys, UnitStep[t], {t, 0, 8}];
Plot[Evaluate[y], {t, 0, 8}, PlotRange -> All, AxesLabel -> {\"t\", \"y(t)\"}]
"], "Input"]
}]

8. Problems and Solutions

Problem 1: For the plant \( \dot{x}=Ax+Bu,\; y=Cx \), derive the augmented matrices for integral tracking of \( y \).

Solution: Define \( \dot{\eta}=r-Cx \). Then

\[ A_a=\begin{bmatrix}A&0\\-C&0\end{bmatrix},\qquad B_a=\begin{bmatrix}B\\0\end{bmatrix},\qquad E_a=\begin{bmatrix}0\\I\end{bmatrix}. \]

Problem 2: Explain why controllability of \( (A,B) \) does not automatically imply controllability of \( (A_a,B_a) \).

Solution: The augmented system contains new integrator states. These states can be controlled only through the output error channel \( r-Cx \). If the plant input cannot move the tracked output at steady state, the integrator state is not assignable. Therefore, the augmented rank condition must be checked separately.

Problem 3: For the mass-spring-damper example, write the augmented matrices.

Solution:

\[ A_a= \begin{bmatrix} 0&1&0\\ -2&-0.8&0\\ -1&0&0 \end{bmatrix}, \qquad B_a= \begin{bmatrix} 0\\1\\0 \end{bmatrix}, \qquad E_a= \begin{bmatrix} 0\\0\\1 \end{bmatrix}. \]

Problem 4: Suppose the augmented controller stabilizes \( A_{cl} \). Prove that a constant reference is tracked with zero steady-state error.

Solution: Stability implies convergence to an equilibrium. At equilibrium, the integrator equation gives \( 0=r_0-y_\infty \). Therefore \( y_\infty=r_0 \), and the tracking error converges to zero.

Problem 5: Why should the integral pole not usually be placed extremely far to the left?

Solution: A very fast integral mode increases control effort and can amplify actuator saturation, measurement noise, and model mismatch. Although the pole-placement equations may allow very fast poles, practical servo design must balance convergence speed against input magnitude and robustness.

9. Summary

State augmentation converts the tracking problem into a stabilization problem for an enlarged system. For constant references, one integrator per tracked output is sufficient when the augmented pair is controllable and the closed-loop augmented matrix is Hurwitz. The gain partition \( K_a=\begin{bmatrix}K_x&-K_i\end{bmatrix} \) produces the practical law \( u=-K_xx+K_i\eta \). The examples in this lesson show how to move from equations to numerical implementation in several programming environments.

10. References

  1. Francis, B.A., & Wonham, W.M. (1976). The internal model principle of control theory. Automatica, 12(5), 457–465.
  2. Davison, E.J. (1976). The robust control of a servomechanism problem for linear time-invariant multivariable systems. IEEE Transactions on Automatic Control, 21(1), 25–34.
  3. Wonham, W.M. (1967). On pole assignment in multi-input controllable linear systems. IEEE Transactions on Automatic Control, 12(6), 660–665.
  4. Davison, E.J., & Goldenberg, A. (1975). Robust control of a general servomechanism problem: The servo compensator. Automatica, 11(5), 461–471.
  5. Hautus, M.L.J. (1969). Controllability and observability conditions of linear autonomous systems. Proceedings of the Koninklijke Nederlandse Akademie van Wetenschappen, Series A, 72, 443–448.
  6. Morse, A.S., & Wonham, W.M. (1970). Status of noninteracting control. IEEE Transactions on Automatic Control, 16(6), 568–581.