Chapter 26: State-Feedback with Integral Action

Lesson 1: Need for Steady-State Accuracy in State-Space Framework

State feedback can relocate closed-loop poles and shape transient behavior, but pole placement alone does not automatically guarantee accurate reference tracking or disturbance rejection. This lesson explains the steady-state accuracy problem in state-space form, derives the equilibrium equations, proves the limitation of static prefilters, and motivates why integral action becomes necessary in later lessons.

1. Conceptual Overview

In previous chapters, state feedback was introduced mainly as a method for assigning the eigenvalues of the closed-loop matrix. For a continuous time LTI system

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

a basic state-feedback law has the form \( \mathbf{u}(t)=-K\mathbf{x}(t)+\mathbf{v}(t) \). The resulting closed-loop state equation is

\[ \dot{\mathbf{x} }(t)=(A-BK)\mathbf{x}(t)+B\mathbf{v}(t). \]

If \( A-BK \) is Hurwitz, then the transient state converges. However, convergence of the state does not mean that \( \mathbf{y}(t) \) converges to the desired reference \( \mathbf{r} \). The final output depends on the equilibrium map from the command input \( \mathbf{v} \) to the output \( \mathbf{y} \).

\[ \boxed{\text{Pole placement shapes } A-BK; \\ \text{steady-state accuracy depends on the equilibrium map from } \mathbf{r} \text{ to } \mathbf{y}.} \]

flowchart TD
  A["Choose state feedback K"] --> B["Closed-loop matrix A - B K"]
  B --> C["Transient poles assigned"]
  C --> D["Check equilibrium output"]
  D --> E{"Does y_ss equal r?"}
  E -->|"yes"| F["Steady-state tracking is \ncorrect for nominal model"]
  E -->|"no"| G["Static prefilter or \nintegral action is needed"]
  G --> H["Prefilter corrects \nnominal step tracking"]
  G --> I["Integral action rejects \nconstant errors robustly"]
        

2. Equilibrium Equations for State Feedback

Assume that the command input is constant, \( \mathbf{v}(t)=\mathbf{v}_{ss} \), and that \( A-BK \) is nonsingular and Hurwitz. The steady-state value \( \mathbf{x}_{ss} \) satisfies

\[ \mathbf{0}=(A-BK)\mathbf{x}_{ss}+B\mathbf{v}_{ss}. \]

Therefore,

\[ \mathbf{x}_{ss}=-(A-BK)^{-1}B\mathbf{v}_{ss}. \]

The corresponding output equilibrium is

\[ \mathbf{y}_{ss}= \left[-C(A-BK)^{-1}B+D\right]\mathbf{v}_{ss}. \]

The matrix \( G_{cl}(0)=-C(A-BK)^{-1}B+D \) is the closed-loop DC gain from the external command \( \mathbf{v} \) to the output \( \mathbf{y} \). If the desired reference is \( \mathbf{r} \), then exact steady-state tracking requires

\[ \mathbf{r}=G_{cl}(0)\mathbf{v}_{ss}. \]

Thus, the reference must be mapped into a command through an equilibrium correction. In the SISO case, one often chooses \( v=N\bar{}\,r \), where

\[ N\bar{}=\frac{1}{-C(A-BK)^{-1}B+D}. \]

In MIMO systems, the same idea requires solving a matrix equation. If \( G_{cl}(0) \) is square and nonsingular, then

\[ \mathbf{v}_{ss}=G_{cl}(0)^{-1}\mathbf{r}. \]

3. Why Pole Placement Alone Does Not Guarantee Tracking

Suppose the plant is SISO with \( D=0 \) and the controller is \( u=-Kx+r \). Then \( v=r \). For a unit step reference \( r=1 \), the steady-state output is

\[ y_{ss}=-C(A-BK)^{-1}B. \]

In general this number is not equal to one. Therefore, the steady-state tracking error is

\[ e_{ss}=r-y_{ss}=1+C(A-BK)^{-1}B. \]

This expression depends on the closed-loop DC gain, not only on the eigenvalues of \( A-BK \). Two feedback matrices can assign the same characteristic polynomial but produce different DC maps, especially in MIMO systems where eigenstructure and input-output alignment matter.

Key interpretation: pole placement decides how fast stored state energy decays; steady-state accuracy decides where the final equilibrium lies.

4. Reference Prefilter and the Regulator Equations

A static reference prefilter modifies the controller to

\[ \mathbf{u}(t)=-K\mathbf{x}(t)+N\mathbf{r}(t). \]

For constant reference \( \mathbf{r} \), the steady-state equations become

\[ \mathbf{0}=(A-BK)\mathbf{x}_{ss}+BN\mathbf{r}, \qquad \mathbf{y}_{ss}=C\mathbf{x}_{ss}+DN\mathbf{r}. \]

Exact tracking for every constant reference requires \( \mathbf{y}_{ss}=\mathbf{r} \). Hence

\[ \left[-C(A-BK)^{-1}B+D\right]N=I. \]

If the closed-loop DC gain matrix is square and nonsingular, the nominal prefilter is

\[ N=\left[-C(A-BK)^{-1}B+D\right]^{-1}. \]

This is useful but fragile: it is computed from the nominal model. If the plant parameters change or if an unknown constant disturbance enters the system, this static correction generally no longer makes the tracking error zero.

5. Proof of the Final Value Relation

For a stable closed-loop system \( \dot{x}=A_{cl}x+Bv \), where \( A_{cl}=A-BK \), the solution for constant \( v \) is

\[ x(t)=e^{A_{cl}t}x(0)+\int_0^t e^{A_{cl}(t-\sigma)}Bv\,d\sigma. \]

Since \( A_{cl} \) is Hurwitz, \( e^{A_{cl}t}\to 0 \) as \( t\to\infty \). Also,

\[ \int_0^t e^{A_{cl}(t-\sigma)}\,d\sigma =\int_0^t e^{A_{cl}\eta}\,d\eta =A_{cl}^{-1}\left(e^{A_{cl}t}-I\right). \]

Taking the limit gives

\[ \lim_{t\to\infty}x(t)=-A_{cl}^{-1}Bv. \]

Therefore,

\[ \lim_{t\to\infty}y(t)= \left[-C(A-BK)^{-1}B+D\right]v. \]

This proves that the final output is governed by the closed-loop DC map. Therefore, asymptotic stability is necessary for convergence, but it is not sufficient for exact reference tracking.

6. Effect of Constant Disturbance

Consider a matched constant disturbance entering through the same input channel:

\[ \dot{\mathbf{x} }=(A-BK)\mathbf{x}+BN\mathbf{r}+B\mathbf{d}. \]

The steady-state output becomes

\[ \mathbf{y}_{ss}= \left[-C(A-BK)^{-1}B+D\right]N\mathbf{r} -C(A-BK)^{-1}B\mathbf{d}. \]

Even if \( N \) is selected so that the first term gives \( \mathbf{r} \), the disturbance term remains. Therefore, the steady-state error is generally nonzero:

\[ \mathbf{e}_{ss}=\mathbf{r}-\mathbf{y}_{ss} =C(A-BK)^{-1}B\mathbf{d}. \]

This is the central reason that integral action is introduced in this chapter. A static prefilter can correct nominal reference scaling, but it cannot in general force the tracking error to zero under constant disturbances or modeling errors.

flowchart TD
  R["reference r"] --> N["static prefilter N"]
  N --> SUM["input sum"]
  D["constant disturbance d"] --> SUM
  X["state feedback -K x"] --> SUM
  SUM --> P["plant dynamics"]
  P --> Y["output y"]
  Y --> E["tracking error e = r - y"]
  E --> INT["integral action in next lessons"]
        

7. Numerical Example

Consider the second-order plant

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

Choose \( K=\begin{bmatrix}4 & 2\end{bmatrix} \). Then

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

The closed-loop DC gain from \( v \) to \( y \) is

\[ G_{cl}(0)=-C(A-BK)^{-1}B=\frac{1}{6}. \]

Hence, if \( v=r \), a unit step reference produces \( y_{ss}=1/6 \), not \( 1 \). The required prefilter is

\[ N\bar{}=\frac{1}{G_{cl}(0)}=6. \]

With \( v=6r \), the nominal unit step is tracked at steady state. However, if a constant matched disturbance \( d=0.2 \) enters the input channel, then

\[ y_{ss}=\frac{6r+d}{6}=r+\frac{d}{6}. \]

For \( r=1 \), the output approaches \( 1.0333\ldots \), so the tracking error is not zero.

8. Software Libraries and Implementation Notes

The computations in this lesson require matrix inversion, eigenvalue checks, state-space simulation, and plotting. Common libraries include:

  • Python: numpy, scipy, matplotlib, and optionally python-control.
  • C++: scratch matrix routines for small examples, or Eigen for larger systems.
  • Java: scratch routines for small examples, or libraries such as EJML or Apache Commons Math.
  • MATLAB/Simulink: Control System Toolbox functions such as ss, lsim, dcgain, and place.
  • Wolfram Mathematica: StateSpaceModel, OutputResponse, Inverse, and symbolic equilibrium manipulation.

9. Python Implementation

Chapter26_Lesson1.py

"""
Chapter26_Lesson1.py

Need for steady-state accuracy in a state-space framework.
The script compares:
  1) state feedback with no reference prefilter,
  2) state feedback with static prefilter Nbar,
  3) the same prefilter under a constant matched input disturbance.

Dependencies:
  numpy, scipy, matplotlib

Optional control-engineering libraries to explore after this lesson:
  python-control: StateSpace, forced_response, dcgain, place
"""

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


def simulate(A, B, C, K, nbar, reference=1.0, disturbance=0.0, t_final=8.0):
    """Simulate xdot = (A-BK)x + B(nbar*r + d), y = Cx."""
    Acl = A - B @ K

    def rhs(t, x):
        u_total = float(nbar * reference + disturbance)
        return Acl @ x + (B[:, 0] * u_total)

    t_eval = np.linspace(0.0, t_final, 600)
    sol = solve_ivp(rhs, (0.0, t_final), np.zeros(A.shape[0]), t_eval=t_eval)
    y = (C @ sol.y).reshape(-1)
    return sol.t, y


def main():
    A = np.array([[0.0, 1.0],
                  [-2.0, -3.0]])
    B = np.array([[0.0],
                  [1.0]])
    C = np.array([[1.0, 0.0]])
    K = np.array([[4.0, 2.0]])

    Acl = A - B @ K

    dc_closed = -C @ np.linalg.inv(Acl) @ B
    nbar = float(1.0 / dc_closed[0, 0])

    print("A-BK =")
    print(Acl)
    print("Closed-loop DC map from prefilter input v to y:", float(dc_closed[0, 0]))
    print("Required static prefilter Nbar:", nbar)

    cases = [
        ("Nbar = 1, no disturbance", 1.0, 1.0, 0.0),
        ("Nbar = computed, no disturbance", nbar, 1.0, 0.0),
        ("Nbar = computed, disturbance d = 0.2", nbar, 1.0, 0.2),
    ]

    for label, nb, r, d in cases:
        t, y = simulate(A, B, C, K, nb, reference=r, disturbance=d)
        print(f"{label}: final y approximately {y[-1]:.6f}, final error {r - y[-1]:.6f}")

    plt.figure()
    for label, nb, r, d in cases:
        t, y = simulate(A, B, C, K, nb, reference=r, disturbance=d)
        plt.plot(t, y, label=label)
    plt.axhline(1.0, linestyle="--", label="reference")
    plt.xlabel("time (s)")
    plt.ylabel("output y(t)")
    plt.title("Chapter 26 Lesson 1: steady-state accuracy")
    plt.legend()
    plt.grid(True)
    plt.tight_layout()
    plt.show()


if __name__ == "__main__":
    main()
      

10. C++ Implementation

Chapter26_Lesson1.cpp

/*
Chapter26_Lesson1.cpp

Need for steady-state accuracy in a state-space framework.
Scratch 2-state implementation without external dependencies.

Compile:
  g++ -std=c++17 Chapter26_Lesson1.cpp -o Chapter26_Lesson1

Run:
  ./Chapter26_Lesson1
*/

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

struct Mat2 {
    double a11, a12, a21, a22;
};

struct Vec2 {
    double x1, x2;
};

Mat2 subtractBK(const Mat2& A, const Vec2& B, const Vec2& K) {
    return {
        A.a11 - B.x1 * K.x1, A.a12 - B.x1 * K.x2,
        A.a21 - B.x2 * K.x1, A.a22 - B.x2 * K.x2
    };
}

Mat2 inverse2(const Mat2& M) {
    double det = M.a11 * M.a22 - M.a12 * M.a21;
    if (std::abs(det) < 1e-12) {
        throw std::runtime_error("Matrix is singular or nearly singular.");
    }
    return {M.a22 / det, -M.a12 / det, -M.a21 / det, M.a11 / det};
}

Vec2 matVec(const Mat2& M, const Vec2& v) {
    return {M.a11 * v.x1 + M.a12 * v.x2, M.a21 * v.x1 + M.a22 * v.x2};
}

double dot(const Vec2& c, const Vec2& x) {
    return c.x1 * x.x1 + c.x2 * x.x2;
}

Vec2 rhs(const Mat2& Acl, const Vec2& B, const Vec2& x, double inputTotal) {
    Vec2 ax = matVec(Acl, x);
    return {ax.x1 + B.x1 * inputTotal, ax.x2 + B.x2 * inputTotal};
}

Vec2 rk4Step(const Mat2& Acl, const Vec2& B, const Vec2& x, double inputTotal, double h) {
    Vec2 k1 = rhs(Acl, B, x, inputTotal);
    Vec2 x2{x.x1 + 0.5 * h * k1.x1, x.x2 + 0.5 * h * k1.x2};
    Vec2 k2 = rhs(Acl, B, x2, inputTotal);
    Vec2 x3{x.x1 + 0.5 * h * k2.x1, x.x2 + 0.5 * h * k2.x2};
    Vec2 k3 = rhs(Acl, B, x3, inputTotal);
    Vec2 x4{x.x1 + h * k3.x1, x.x2 + h * k3.x2};
    Vec2 k4 = rhs(Acl, B, x4, inputTotal);

    return {
        x.x1 + (h / 6.0) * (k1.x1 + 2.0 * k2.x1 + 2.0 * k3.x1 + k4.x1),
        x.x2 + (h / 6.0) * (k1.x2 + 2.0 * k2.x2 + 2.0 * k3.x2 + k4.x2)
    };
}

double simulateFinalY(const Mat2& Acl, const Vec2& B, const Vec2& C,
                      double nbar, double reference, double disturbance) {
    Vec2 x{0.0, 0.0};
    double h = 0.001;
    double tFinal = 8.0;
    int steps = static_cast<int>(tFinal / h);
    double inputTotal = nbar * reference + disturbance;

    for (int i = 0; i < steps; ++i) {
        x = rk4Step(Acl, B, x, inputTotal, h);
    }
    return dot(C, x);
}

int main() {
    Mat2 A{0.0, 1.0, -2.0, -3.0};
    Vec2 B{0.0, 1.0};
    Vec2 C{1.0, 0.0};
    Vec2 K{4.0, 2.0};

    Mat2 Acl = subtractBK(A, B, K);
    Mat2 invAcl = inverse2(Acl);
    Vec2 invAclB = matVec(invAcl, B);

    double dcClosed = -dot(C, invAclB);
    double nbar = 1.0 / dcClosed;

    std::cout << std::fixed << std::setprecision(6);
    std::cout << "Closed-loop DC map from prefilter input v to y: " << dcClosed << "\n";
    std::cout << "Required static prefilter Nbar: " << nbar << "\n\n";

    struct CaseData {
        std::string label;
        double nbar;
        double reference;
        double disturbance;
    };

    std::vector<CaseData> cases = {
        {"Nbar = 1, no disturbance", 1.0, 1.0, 0.0},
        {"Nbar = computed, no disturbance", nbar, 1.0, 0.0},
        {"Nbar = computed, disturbance d = 0.2", nbar, 1.0, 0.2}
    };

    for (const auto& item : cases) {
        double yFinal = simulateFinalY(Acl, B, C, item.nbar, item.reference, item.disturbance);
        std::cout << item.label
                  << ": final y approximately " << yFinal
                  << ", final error " << (item.reference - yFinal) << "\n";
    }

    return 0;
}
      

11. Java Implementation

Chapter26_Lesson1.java

/*
Chapter26_Lesson1.java

Need for steady-state accuracy in a state-space framework.
Scratch 2-state implementation without external dependencies.

Compile:
  javac Chapter26_Lesson1.java

Run:
  java Chapter26_Lesson1
*/

public class Chapter26_Lesson1 {
    static class Mat2 {
        double a11, a12, a21, a22;

        Mat2(double a11, double a12, double a21, double a22) {
            this.a11 = a11;
            this.a12 = a12;
            this.a21 = a21;
            this.a22 = a22;
        }
    }

    static class Vec2 {
        double x1, x2;

        Vec2(double x1, double x2) {
            this.x1 = x1;
            this.x2 = x2;
        }
    }

    static Mat2 subtractBK(Mat2 A, Vec2 B, Vec2 K) {
        return new Mat2(
            A.a11 - B.x1 * K.x1, A.a12 - B.x1 * K.x2,
            A.a21 - B.x2 * K.x1, A.a22 - B.x2 * K.x2
        );
    }

    static Mat2 inverse2(Mat2 M) {
        double det = M.a11 * M.a22 - M.a12 * M.a21;
        if (Math.abs(det) < 1e-12) {
            throw new IllegalArgumentException("Matrix is singular or nearly singular.");
        }
        return new Mat2(M.a22 / det, -M.a12 / det, -M.a21 / det, M.a11 / det);
    }

    static Vec2 matVec(Mat2 M, Vec2 v) {
        return new Vec2(M.a11 * v.x1 + M.a12 * v.x2,
                        M.a21 * v.x1 + M.a22 * v.x2);
    }

    static double dot(Vec2 c, Vec2 x) {
        return c.x1 * x.x1 + c.x2 * x.x2;
    }

    static Vec2 rhs(Mat2 Acl, Vec2 B, Vec2 x, double inputTotal) {
        Vec2 ax = matVec(Acl, x);
        return new Vec2(ax.x1 + B.x1 * inputTotal,
                        ax.x2 + B.x2 * inputTotal);
    }

    static Vec2 rk4Step(Mat2 Acl, Vec2 B, Vec2 x, double inputTotal, double h) {
        Vec2 k1 = rhs(Acl, B, x, inputTotal);
        Vec2 x2 = new Vec2(x.x1 + 0.5 * h * k1.x1, x.x2 + 0.5 * h * k1.x2);
        Vec2 k2 = rhs(Acl, B, x2, inputTotal);
        Vec2 x3 = new Vec2(x.x1 + 0.5 * h * k2.x1, x.x2 + 0.5 * h * k2.x2);
        Vec2 k3 = rhs(Acl, B, x3, inputTotal);
        Vec2 x4 = new Vec2(x.x1 + h * k3.x1, x.x2 + h * k3.x2);
        Vec2 k4 = rhs(Acl, B, x4, inputTotal);

        return new Vec2(
            x.x1 + (h / 6.0) * (k1.x1 + 2.0 * k2.x1 + 2.0 * k3.x1 + k4.x1),
            x.x2 + (h / 6.0) * (k1.x2 + 2.0 * k2.x2 + 2.0 * k3.x2 + k4.x2)
        );
    }

    static double simulateFinalY(Mat2 Acl, Vec2 B, Vec2 C,
                                 double nbar, double reference, double disturbance) {
        Vec2 x = new Vec2(0.0, 0.0);
        double h = 0.001;
        double tFinal = 8.0;
        int steps = (int) (tFinal / h);
        double inputTotal = nbar * reference + disturbance;

        for (int i = 0; i < steps; i++) {
            x = rk4Step(Acl, B, x, inputTotal, h);
        }
        return dot(C, x);
    }

    public static void main(String[] args) {
        Mat2 A = new Mat2(0.0, 1.0, -2.0, -3.0);
        Vec2 B = new Vec2(0.0, 1.0);
        Vec2 C = new Vec2(1.0, 0.0);
        Vec2 K = new Vec2(4.0, 2.0);

        Mat2 Acl = subtractBK(A, B, K);
        Mat2 invAcl = inverse2(Acl);
        Vec2 invAclB = matVec(invAcl, B);

        double dcClosed = -dot(C, invAclB);
        double nbar = 1.0 / dcClosed;

        System.out.printf("Closed-loop DC map from prefilter input v to y: %.6f%n", dcClosed);
        System.out.printf("Required static prefilter Nbar: %.6f%n%n", nbar);

        String[] labels = {
            "Nbar = 1, no disturbance",
            "Nbar = computed, no disturbance",
            "Nbar = computed, disturbance d = 0.2"
        };
        double[] nbars = {1.0, nbar, nbar};
        double[] references = {1.0, 1.0, 1.0};
        double[] disturbances = {0.0, 0.0, 0.2};

        for (int i = 0; i < labels.length; i++) {
            double yFinal = simulateFinalY(Acl, B, C, nbars[i], references[i], disturbances[i]);
            System.out.printf("%s: final y approximately %.6f, final error %.6f%n",
                              labels[i], yFinal, references[i] - yFinal);
        }
    }
}
      

12. MATLAB/Simulink Implementation

Chapter26_Lesson1.m

% Chapter26_Lesson1.m
%
% Need for steady-state accuracy in a state-space framework.
% Requires basic MATLAB. The optional ss/lsim part uses Control System Toolbox.

clear; clc; close all;

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

K = [4 2];
Acl = A - B*K;

dcClosed = -C * (Acl \ B);
Nbar = 1 / dcClosed;

fprintf('Closed-loop DC map from prefilter input v to y: %.6f\n', dcClosed);
fprintf('Required static prefilter Nbar: %.6f\n\n', Nbar);

t = linspace(0, 8, 600);
x0 = [0; 0];

cases = {
    'Nbar = 1, no disturbance', 1.0, 1.0, 0.0;
    'Nbar = computed, no disturbance', Nbar, 1.0, 0.0;
    'Nbar = computed, disturbance d = 0.2', Nbar, 1.0, 0.2
};

figure; hold on; grid on;
for i = 1:size(cases, 1)
    label = cases{i, 1};
    nb = cases{i, 2};
    r = cases{i, 3};
    d = cases{i, 4};

    inputTotal = nb*r + d;

    % Since the input is constant:
    % x(t) = integral_0^t exp(Acl*(t-s))*B*inputTotal ds.
    % ode45 is used to keep the implementation transparent.
    f = @(time, x) Acl*x + B*inputTotal;
    [tout, xout] = ode45(f, t, x0);
    y = (C*xout')';

    fprintf('%s: final y approximately %.6f, final error %.6f\n', ...
            label, y(end), r - y(end));

    plot(tout, y, 'DisplayName', label);
end

yline(1.0, '--', 'reference', 'DisplayName', 'reference');
xlabel('time (s)');
ylabel('output y(t)');
title('Chapter 26 Lesson 1: steady-state accuracy');
legend('Location', 'best');

% Optional Control System Toolbox version:
% sys1 = ss(Acl, B, C, D);
% y1 = lsim(sys1, ones(size(t))*Nbar, t);
      

For a Simulink implementation, create a State-Space block with matrices \( A,B,C,D \), feed back the full state through a gain block \( K \), insert a prefilter gain \( N\bar{} \) before the summing junction, and compare the output with the reference using a scope. In Lesson 2, an integrator block will be inserted in the error path.

13. Wolfram Mathematica Implementation

Chapter26_Lesson1.nb

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

Acl = A - B.K;
dcClosed = -Cmat.Inverse[Acl].B;
Nbar = 1/dcClosed[[1, 1]];

{Acl, dcClosed, Nbar}

xss[nb_, r_, d_] := -Inverse[Acl].B.(nb r + d)
yss[nb_, r_, d_] := First[Flatten[Cmat.xss[nb, r, d]]]

Table[
  {case, yss @@ case, 1 - yss @@ case},
  {case, { {1, 1, 0}, {Nbar, 1, 0}, {Nbar, 1, 0.2} } }
]

response[nb_, r_, d_] :=
  OutputResponse[
    StateSpaceModel[{Acl, B, Cmat, { {0} } }],
    nb r + d,
    t
  ]

Plot[
  Evaluate[
    {
      response[1, 1, 0],
      response[Nbar, 1, 0],
      response[Nbar, 1, 0.2]
    }
  ],
  {t, 0, 8},
  PlotLegends -> {"Nbar=1", "Nbar computed", "Nbar computed, d=0.2"},
  GridLines -> Automatic
]
      

14. Problems and Solutions

Problem 1: Consider the SISO system \( \dot{x}=Ax+Bu,\; y=Cx \) with state feedback \( u=-Kx+r \). Derive the steady-state tracking error for a unit step reference.

Solution: The closed-loop dynamics are \( \dot{x}=(A-BK)x+Br \). If \( A-BK \) is Hurwitz and nonsingular, then

\[ x_{ss}=-(A-BK)^{-1}B. \]

Hence \( y_{ss}=-C(A-BK)^{-1}B \). Since the reference is one,

\[ e_{ss}=1+C(A-BK)^{-1}B. \]

Problem 2: For the system in Section 7, compute the required static prefilter.

Solution: We have

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

Direct inversion gives

\[ -C(A-BK)^{-1}B=\frac{1}{6}. \]

Therefore,

\[ N\bar{}=6. \]

Problem 3: Explain why a nominal prefilter cannot guarantee zero steady-state error under an unknown constant disturbance.

Solution: With matched disturbance \( d \), the equilibrium output is

\[ y_{ss}=\left[-C(A-BK)^{-1}B+D\right]Nr -C(A-BK)^{-1}Bd. \]

The first term can be forced to equal \( r \) by choosing \( N \) from the nominal model. However, the second term depends on the unknown disturbance. Unless the disturbance is measured and canceled exactly, the final tracking error remains nonzero.

Problem 4: Suppose \( G_{cl}(0)=-C(A-BK)^{-1}B+D \) is a square matrix. What condition is required for a static prefilter to track every constant reference?

Solution: The equation \( G_{cl}(0)N=I \) must be solvable. For a square system this requires \( \det(G_{cl}(0))\neq 0 \). Then \( N=G_{cl}(0)^{-1} \). If the matrix is singular, some reference directions cannot be produced at steady state by the available input-output map.

Problem 5: A student says: “If all closed-loop poles are placed far to the left, the step tracking error must be zero.” Is this correct?

Solution: No. Moving poles far to the left can make transients faster, but the final output still equals \( G_{cl}(0)v \). The value of \( G_{cl}(0) \) is a DC-gain property of the closed-loop input-output map, not merely an eigenvalue property. Fast convergence to the wrong equilibrium is still inaccurate tracking.

15. Summary

This lesson showed that state feedback solves a transient stabilization and pole-placement problem, but steady-state tracking requires separate equilibrium analysis. The closed-loop DC map determines whether a reference is tracked accurately. A static prefilter can correct nominal step tracking when the DC map is invertible, but it does not generally reject constant disturbances or modeling errors. These limitations motivate the state augmentation with integral of tracking error developed in Lesson 2.

16. 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. Wonham, W.M. (1967). On pole assignment in multi-input controllable linear systems. IEEE Transactions on Automatic Control, 12(6), 660–665.
  3. 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.
  4. Francis, B.A., & Wonham, W.M. (1976). The internal model principle of control theory. Automatica, 12(5), 457–465.
  5. Francis, B.A. (1977). The linear multivariable regulator problem. SIAM Journal on Control and Optimization, 15(3), 486–505.
  6. Davison, E.J., & Goldenberg, A. (1975). Robust control of a general servomechanism problem: the servo compensator. Automatica, 11(5), 461–471.
  7. Wonham, W.M., & Pearson, J.B. (1974). Regulation and internal stabilization in linear multivariable systems. SIAM Journal on Control, 12(1), 5–18.
  8. Hautus, M.L.J. (1983). Linear matrix equations with applications to the regulator problem. Outils et Modèles Mathématiques pour l'Automatique, l'Analyse de Systèmes et le Traitement du Signal, 399–412.