Chapter 27: Reference Tracking and Disturbance Rejection in State Space

Lesson 2: Feedforward Gain Design for Reference Tracking

This lesson develops the static feedforward, or precompensator, gain used with state feedback to obtain zero steady-state error for constant references. We derive the gain using closed-loop DC-gain inversion and the regulator-equation method, prove the steady-state tracking result, discuss MIMO solvability conditions, and implement the computation in Python, C++, Java, MATLAB/Simulink, and Wolfram Mathematica.

1. Motivation: Why a Feedforward Gain Is Needed

In the previous lesson, reference tracking was formulated in state-space form. A common state-feedback law for a plant \( \dot{x}=Ax+Bu,\; y=Cx+Du \) is

\[ u(t)=-Kx(t)+N r(t). \]

The matrix \(K\) shapes the closed-loop modes through \(A-BK\). However, choosing \(K\) only by pole placement usually does not guarantee that the output equals a commanded constant reference at steady state. The feedforward matrix \(N\), also called the reference precompensator, rescales the reference so that the closed-loop DC gain from \(r\) to \(y\) becomes the identity.

flowchart TD
  R["Reference r"] --> N["Feedforward gain N"]
  N --> SUM["Sum: u = -Kx + N r"]
  X["State x"] --> K["State feedback K"]
  K -->|"negative feedback"| SUM
  SUM --> P["Plant: xdot = A x + B u, y = C x + D u"]
  P --> Y["Output y"]
  P --> X
        

The main idea is simple but mathematically important: \(K\) determines the transient dynamics, while \(N\) adjusts the steady-state mapping from reference to output.

2. Closed-Loop Reference-to-Output Model

Consider the continuous-time LTI plant

\[ \dot{x}=Ax+Bu,\qquad y=Cx+Du, \]

with state feedback plus feedforward reference input:

\[ u=-Kx+Nr. \]

Substitution gives

\[ \dot{x}=(A-BK)x+BNr, \qquad y=(C-DK)x+DNr. \]

Define the closed-loop matrices \(A_K=A-BK\) and \(C_K=C-DK\). Then

\[ \dot{x}=A_Kx+BNr,\qquad y=C_Kx+DNr. \]

The transfer matrix from \(r\) to \(y\) is

\[ T_{yr}(s)=\left[C-DK\right]\left(sI-A+BK\right)^{-1}BN+DN. \]

For constant-reference tracking, the central quantity is the DC gain \(T_{yr}(0)\).

3. DC-Gain Inversion Formula for \(N\)

Assume that \(A_K=A-BK\) is nonsingular and stable. The closed-loop DC gain before the reference precompensator is

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

Therefore,

\[ T_{yr}(0)=G_0N. \]

To obtain exact steady-state tracking for all constant references in the tracked output space, impose

\[ G_0N=I. \]

If \(G_0\) is square and nonsingular, the unique static feedforward gain is

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

In the common strictly proper case \(D=0\), this reduces to

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

For SISO systems, this is just a scalar normalization. If \(D=0\), then

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

4. Proof Using the Final-Value Theorem

Let the reference be constant: \(r(t)=r_0\). Suppose \(A_K\) is Hurwitz:

\[ \operatorname{Re}\lambda_i(A_K)<0,\qquad i=1,\ldots,n. \]

Taking Laplace transforms with zero initial condition gives

\[ Y(s)=T_{yr}(s)R(s),\qquad R(s)=\frac{r_0}{s}. \]

The steady-state output is

\[ y_{\infty}=\lim_{t\to\infty}y(t) =\lim_{s\to 0}sY(s) =\lim_{s\to 0}sT_{yr}(s)\frac{r_0}{s} =T_{yr}(0)r_0. \]

Since \(T_{yr}(0)=G_0N\), choosing \(N=G_0^{-1}\) gives

\[ y_{\infty}=I r_0=r_0. \]

Hence the steady-state tracking error satisfies

\[ e_{\infty}=r_0-y_{\infty}=0. \]

This proof also shows the limitation: the result is a steady-state result for constant references under an exact model. It does not by itself guarantee robust tracking under plant uncertainty or constant disturbances.

5. Regulator-Equation Derivation

A more structural derivation is based on the steady-state equations. For a constant reference \(r\), suppose there exist steady-state maps \(X\) and \(U\) such that

\[ x_{\mathrm{ss} }=Xr,\qquad u_{\mathrm{ss} }=Ur. \]

At steady state, \(\dot{x}=0\) and \(y=r\). Therefore,

\[ AX+BU=0,\qquad CX+DU=I. \]

These are the static regulator equations:

\[ \begin{bmatrix}A&B\\ C&D\end{bmatrix} \begin{bmatrix}X\\ U\end{bmatrix} = \begin{bmatrix}0\\ I\end{bmatrix}. \]

The implemented controller is \(u=-Kx+Nr\). At steady state,

\[ Ur=-KXr+Nr. \]

Since this must hold for every constant reference,

\[ \boxed{N=U+KX.} \]

The regulator-equation method is often numerically preferable because it computes the desired steady-state state and input directly.

6. Solvability Conditions and the Role of Zeros

The regulator equations are solvable for arbitrary constant references only if the plant has enough steady-state authority over the tracked outputs. A fundamental rank condition is

\[ \operatorname{rank} \begin{bmatrix}A&B\\ C&D\end{bmatrix} =n+p, \]

where \(n\) is the number of states and \(p\) is the number of tracked outputs. This condition says that the Rosenbrock system matrix has full row rank at \(s=0\). If it fails, the plant has either insufficient static input authority or an invariant zero at the origin that blocks exact constant-reference tracking.

For square MIMO tracking with the same number of inputs and tracked outputs, \(G_0\) must be nonsingular:

\[ \det(G_0)\ne 0. \]

For over-actuated systems, \(G_0N=I\) may have many solutions. A common minimum-norm choice is

\[ N=G_0^{\dagger}, \]

where \(G_0^{\dagger}\) is the Moore-Penrose pseudoinverse. For under-actuated systems, exact tracking of all output components is generally impossible unless the reference is restricted to a reachable output subspace.

7. Practical Design Algorithm

The feedforward gain should be computed after the stabilizing state-feedback gain has been chosen. The following workflow separates transient design from steady-state normalization.

flowchart TD
  A["Start with plant matrices A, B, C, D"] --> B["Design stabilizing state feedback K"]
  B --> C["Form closed-loop matrix Acl = A - B K"]
  C --> D["Check Acl stability"]
  D --> E{"Use DC gain or \nregulator equations?"}
  E -->|"DC gain"| F["Compute G0 = D - (C - D K) inv(Acl) B"]
  F --> G["Set N = inv(G0) if G0 is nonsingular"]
  E -->|"Regulator equations"| H["Solve [A B; C D] [X; U] = [0; I]"]
  H --> I["Set N = U + K X"]
  G --> J["Simulate step tracking"]
  I --> J
  J --> K["Check steady-state error, control effort, sensitivity"]
        

8. Worked SISO Example: Position Tracking

Consider the double-integrator model

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

Let

\[ K=\begin{bmatrix}4&4\end{bmatrix}. \]

Then

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

The characteristic polynomial is

\[ \det(sI-(A-BK))=s^2+4s+4=(s+2)^2, \]

so the closed loop is stable. Since \(D=0\),

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

Direct calculation gives

\[ (A-BK)^{-1}B= \begin{bmatrix}-\frac{1}{4}\\0\end{bmatrix},\qquad C(A-BK)^{-1}B=-\frac{1}{4}. \]

Therefore,

\[ \boxed{N=4.} \]

The final control law is

\[ u=-\begin{bmatrix}4&4\end{bmatrix}x+4r. \]

9. Sensitivity and Limitations of Static Feedforward

Static feedforward is exact only for the nominal model. If the true closed-loop DC gain is \(\widetilde{G}_0\) but the controller uses \(N=G_0^{-1}\), then

\[ y_{\infty}=\widetilde{G}_0G_0^{-1}r. \]

The steady-state error becomes

\[ e_{\infty}=\left(I-\widetilde{G}_0G_0^{-1}\right)r. \]

Thus, small modeling errors can create nonzero offset. This is why integral action and the internal model principle are needed for robust zero steady-state error under constant disturbances and parameter uncertainty. Static feedforward is still valuable because it improves nominal tracking without increasing the order of the controller.

Numerically, avoid explicitly computing matrix inverses when possible. Solve linear systems such as \((A-BK)Z=B\) and then compute \(G_0=D-(C-DK)Z\). This improves numerical stability.

10. Python Implementation

The Python implementation computes \(N\) using both the DC-gain formula and the regulator-equation method, then simulates reference tracking.

Chapter27_Lesson2.py

# Chapter27_Lesson2.py
# Feedforward gain design for reference tracking in continuous-time state space.
# Requirements: numpy, scipy, matplotlib (optional plotting)

import numpy as np
from numpy.linalg import inv, matrix_rank, solve, eigvals
from scipy.integrate import solve_ivp
import matplotlib.pyplot as plt


def feedforward_from_dc_gain(A, B, C, D, K, tol=1e-9):
    """Return N such that the closed-loop DC gain from r to y is identity.

    Plant:  xdot = A x + B u, y = C x + D u
    Control: u = -K x + N r

    G0 = D - (C - D K) (A - B K)^(-1) B
    Choose N = G0^(-1), when G0 is square and nonsingular.
    """
    A = np.asarray(A, dtype=float)
    B = np.asarray(B, dtype=float)
    C = np.asarray(C, dtype=float)
    D = np.asarray(D, dtype=float)
    K = np.asarray(K, dtype=float)

    Acl = A - B @ K
    Ccl = C - D @ K
    G0 = D - Ccl @ solve(Acl, B)

    if G0.shape[0] != G0.shape[1]:
        raise ValueError("G0 must be square for direct inversion. Use regulator equations instead.")
    if matrix_rank(G0, tol=tol) < G0.shape[0]:
        raise ValueError("Closed-loop DC gain is singular; exact static feedforward tracking is impossible.")
    return inv(G0), G0


def feedforward_from_regulator_equations(A, B, C, D, K, tol=1e-9):
    """Solve the steady-state regulator equations.

    [A B] [X] = [0]
    [C D] [U]   [I]

    Then N = U + K X, because u_ss = -K x_ss + N r.
    """
    A = np.asarray(A, dtype=float)
    B = np.asarray(B, dtype=float)
    C = np.asarray(C, dtype=float)
    D = np.asarray(D, dtype=float)
    K = np.asarray(K, dtype=float)

    n = A.shape[0]
    m = B.shape[1]
    p = C.shape[0]
    R = np.block([[A, B], [C, D]])
    RHS = np.vstack([np.zeros((n, p)), np.eye(p)])

    if matrix_rank(R, tol=tol) < n + p:
        raise ValueError("Rosenbrock matrix at s=0 has deficient rank; origin zero or insufficient authority.")

    sol, *_ = np.linalg.lstsq(R, RHS, rcond=None)
    X = sol[:n, :]
    U = sol[n:n + m, :]
    N = U + K @ X
    return N, X, U


def simulate_tracking(A, B, C, D, K, N, r, t_final=8.0, x0=None):
    A = np.asarray(A, dtype=float)
    B = np.asarray(B, dtype=float)
    C = np.asarray(C, dtype=float)
    D = np.asarray(D, dtype=float)
    K = np.asarray(K, dtype=float)
    N = np.asarray(N, dtype=float)
    r = np.asarray(r, dtype=float).reshape(-1, 1)
    n = A.shape[0]
    if x0 is None:
        x0 = np.zeros(n)

    def rhs(t, x):
        x_col = x.reshape(-1, 1)
        u = -K @ x_col + N @ r
        return (A @ x_col + B @ u).ravel()

    t_eval = np.linspace(0.0, t_final, 800)
    sol = solve_ivp(rhs, [0.0, t_final], x0, t_eval=t_eval, rtol=1e-8, atol=1e-10)
    X = sol.y
    U = np.zeros((B.shape[1], len(sol.t)))
    Y = np.zeros((C.shape[0], len(sol.t)))
    for i in range(len(sol.t)):
        x_col = X[:, [i]]
        u = -K @ x_col + N @ r
        y = C @ x_col + D @ u
        U[:, i] = u.ravel()
        Y[:, i] = y.ravel()
    return sol.t, X, U, Y


if __name__ == "__main__":
    A = np.array([[0.0, 1.0],
                  [0.0, 0.0]])
    B = np.array([[0.0],
                  [1.0]])
    C = np.array([[1.0, 0.0]])
    D = np.array([[0.0]])

    K = np.array([[4.0, 4.0]])

    N_dc, G0 = feedforward_from_dc_gain(A, B, C, D, K)
    N_reg, Xss, Uss = feedforward_from_regulator_equations(A, B, C, D, K)

    print("Closed-loop eigenvalues:", eigvals(A - B @ K))
    print("G0 =", G0)
    print("N from DC gain =", N_dc)
    print("N from regulator equations =", N_reg)
    print("X steady-state map =", Xss)
    print("U steady-state map =", Uss)

    t, X, U, Y = simulate_tracking(A, B, C, D, K, N_dc, r=[1.0], t_final=6.0)
    print("Final output y(t_final) =", Y[:, -1])

    plt.figure()
    plt.plot(t, Y[0, :], label="y: position")
    plt.axhline(1.0, linestyle="--", label="reference")
    plt.xlabel("time [s]")
    plt.ylabel("output")
    plt.title("Reference tracking with static feedforward gain")
    plt.grid(True)
    plt.legend()
    plt.show()
      

11. C++ Implementation

The C++ version uses Eigen for dense linear algebra.

Chapter27_Lesson2.cpp

// Chapter27_Lesson2.cpp
// Feedforward gain design using Eigen.
// Compile example: g++ Chapter27_Lesson2.cpp -I /path/to/eigen -O2 -std=c++17 -o Chapter27_Lesson2

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

using Matrix = Eigen::MatrixXd;

Matrix feedforwardFromDCGain(const Matrix& A, const Matrix& B, const Matrix& C,
                             const Matrix& D, const Matrix& K) {
    Matrix Acl = A - B * K;
    Matrix Ccl = C - D * K;
    Matrix G0 = D - Ccl * Acl.fullPivLu().solve(B);

    if (G0.rows() != G0.cols()) {
        throw std::runtime_error("G0 must be square for inversion.");
    }
    if (G0.fullPivLu().rank() < G0.rows()) {
        throw std::runtime_error("G0 is singular; static feedforward cannot give exact tracking.");
    }
    return G0.inverse();
}

Matrix feedforwardFromRegulatorEquations(const Matrix& A, const Matrix& B,
                                          const Matrix& C, const Matrix& D,
                                          const Matrix& K) {
    const int n = A.rows();
    const int m = B.cols();
    const int p = C.rows();

    Matrix R(n + p, n + m);
    R << A, B,
         C, D;

    Matrix RHS = Matrix::Zero(n + p, p);
    RHS.block(n, 0, p, p) = Matrix::Identity(p, p);

    if (R.fullPivLu().rank() < n + p) {
        throw std::runtime_error("Rosenbrock matrix at s=0 has deficient rank.");
    }

    Matrix Sol = R.completeOrthogonalDecomposition().solve(RHS);
    Matrix X = Sol.block(0, 0, n, p);
    Matrix U = Sol.block(n, 0, m, p);
    return U + K * X;
}

int main() {
    Matrix A(2, 2), B(2, 1), C(1, 2), D(1, 1), K(1, 2);
    A << 0.0, 1.0,
         0.0, 0.0;
    B << 0.0,
         1.0;
    C << 1.0, 0.0;
    D << 0.0;
    K << 4.0, 4.0;

    Matrix N1 = feedforwardFromDCGain(A, B, C, D, K);
    Matrix N2 = feedforwardFromRegulatorEquations(A, B, C, D, K);

    std::cout << "N from DC gain:\n" << N1 << "\n\n";
    std::cout << "N from regulator equations:\n" << N2 << "\n\n";

    double dt = 0.001;
    double tf = 6.0;
    Matrix x = Matrix::Zero(2, 1);
    Matrix r = Matrix::Ones(1, 1);
    for (int kstep = 0; kstep < static_cast<int>(tf / dt); ++kstep) {
        Matrix u = -K * x + N1 * r;
        Matrix dx = A * x + B * u;
        x += dt * dx;
    }
    Matrix y = C * x + D * (-K * x + N1 * r);
    std::cout << "Final output y(tf):\n" << y << "\n";
    return 0;
}
      

12. Java Implementation

The Java version implements small dense-matrix routines from scratch so that students can see the linear algebra behind the computation.

Chapter27_Lesson2.java

// Chapter27_Lesson2.java
// Feedforward gain design with small dense-matrix utilities from scratch.

import java.util.Arrays;

public class Chapter27_Lesson2 {
    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 alpha) {
        int n = A.length, m = A[0].length;
        double[][] C = new double[n][m];
        for (int i = 0; i < n; i++)
            for (int j = 0; j < m; j++) C[i][j] = A[i][j] + alpha * B[i][j];
        return C;
    }

    static double[][] solve(double[][] A, double[][] B) {
        int n = A.length, p = B[0].length;
        double[][] aug = new double[n][n + p];
        for (int i = 0; i < n; i++) {
            System.arraycopy(A[i], 0, aug[i], 0, n);
            System.arraycopy(B[i], 0, aug[i], n, p);
        }
        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;
            if (Math.abs(aug[pivot][col]) < 1e-12) throw new RuntimeException("Singular matrix.");
            double[] tmp = aug[col]; aug[col] = aug[pivot]; aug[pivot] = tmp;
            double scale = aug[col][col];
            for (int j = col; j < n + p; j++) aug[col][j] /= scale;
            for (int r = 0; r < n; r++) {
                if (r == col) continue;
                double factor = aug[r][col];
                for (int j = col; j < n + p; j++) aug[r][j] -= factor * aug[col][j];
            }
        }
        double[][] X = new double[n][p];
        for (int i = 0; i < n; i++) System.arraycopy(aug[i], n, X[i], 0, p);
        return X;
    }

    static double[][] inverse(double[][] A) {
        int n = A.length;
        double[][] I = new double[n][n];
        for (int i = 0; i < n; i++) I[i][i] = 1.0;
        return solve(A, I);
    }

    static double[][] feedforwardFromDCGain(double[][] A, double[][] B, double[][] C,
                                            double[][] D, double[][] K) {
        double[][] Acl = add(A, multiply(B, K), -1.0);
        double[][] Ccl = add(C, multiply(D, K), -1.0);
        double[][] AclInvB = solve(Acl, B);
        double[][] G0 = add(D, multiply(Ccl, AclInvB), -1.0);
        return inverse(G0);
    }

    static void printMatrix(String name, double[][] A) {
        System.out.println(name);
        for (double[] row : A) System.out.println(Arrays.toString(row));
    }

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

        double[][] N = feedforwardFromDCGain(A, B, C, D, K);
        printMatrix("N from DC gain:", N);

        double dt = 0.001, tf = 6.0;
        double[][] x = { {0.0}, {0.0} };
        double[][] r = { {1.0} };
        int steps = (int)(tf / dt);
        for (int k = 0; k < steps; k++) {
            double[][] u = add(multiply(N, r), multiply(K, x), -1.0);
            double[][] dx = add(multiply(A, x), multiply(B, u), 1.0);
            x = add(x, dx, dt);
        }
        double[][] uFinal = add(multiply(N, r), multiply(K, x), -1.0);
        double[][] yFinal = add(multiply(C, x), multiply(D, uFinal), 1.0);
        printMatrix("Final output y(tf):", yFinal);
    }
}
      

13. MATLAB and Simulink Implementation

MATLAB provides direct support for state-space objects, pole placement, and step-response simulation. In Simulink, the same controller is built from a State-Space block, a feedback gain block, a feedforward gain block, and a summing junction.

Chapter27_Lesson2.m

% Chapter27_Lesson2.m
% Feedforward gain design for reference tracking.

clear; clc; close all;

% Plant: xdot = A*x + B*u, y = C*x + D*u
A = [0 1; 0 0];
B = [0; 1];
C = [1 0];
D = 0;

% State feedback with desired poles at -2 and -2.
K = place(A, B, [-2 -2]);
Acl = A - B*K;
Ccl = C - D*K;

% Method 1: closed-loop DC-gain inversion.
G0 = D - Ccl*(Acl\B);
N_dc = inv(G0);

% Method 2: regulator equations.
n = size(A,1);
p = size(C,1);
Rosenbrock0 = [A B; C D];
RHS = [zeros(n,p); eye(p)];
Sol = Rosenbrock0 \ RHS;
X = Sol(1:n,:);
U = Sol(n+1:end,:);
N_reg = U + K*X;

fprintf('K =\n'); disp(K);
fprintf('N from DC gain =\n'); disp(N_dc);
fprintf('N from regulator equations =\n'); disp(N_reg);

% Closed-loop reference-to-output model.
Bcl = B*N_dc;
Dcl = D*N_dc;
sys_cl = ss(Acl, Bcl, Ccl, Dcl);
figure;
step(sys_cl, 6);
grid on;
title('Unit-step reference tracking with static feedforward gain');

% Simulink guidance:
% 1. Add a State-Space block with A, B, C, D.
% 2. Add a Gain block K in the negative feedback path from x to u.
% 3. Add a Gain block N_dc in the reference path.
% 4. Sum u = -K*x + N_dc*r and connect it to the plant input.
% 5. Scope y and verify that y approaches r for constant references.
      

14. Wolfram Mathematica Implementation

Mathematica can symbolically and numerically compute the same feedforward gain and simulate the closed-loop state-space model.

Chapter27_Lesson2.nb

(* Chapter27_Lesson2.nb *)
(* Wolfram Mathematica input cells for feedforward gain design. *)

ClearAll["Global`*"];

A = { {0, 1}, {0, 0} };
B = { {0}, {1} };
Cmat = { {1, 0} };
Dmat = { {0} };
K = { {4, 4} };

Acl = A - B.K;
Ccl = Cmat - Dmat.K;

(* Closed-loop DC-gain method: G0 = D - (C - D K).Inverse[A - B K].B *)
G0 = Dmat - Ccl.Inverse[Acl].B;
Ndc = Inverse[G0];

(* Regulator-equation method. *)
n = Length[A];
p = Length[Cmat];
Rosenbrock0 = ArrayFlatten[{ {A, B}, {Cmat, Dmat} }];
RHS = Join[ConstantArray[0, {n, p}], IdentityMatrix[p]];
Sol = LinearSolve[Rosenbrock0, RHS];
X = Sol[[1 ;; n, All]];
U = Sol[[n + 1 ;; All, All]];
Nreg = U + K.X;

Print["Closed-loop eigenvalues = ", Eigenvalues[Acl]];
Print["G0 = ", MatrixForm[G0]];
Print["N from DC gain = ", MatrixForm[Ndc]];
Print["N from regulator equations = ", MatrixForm[Nreg]];

sys = StateSpaceModel[{Acl, B.Ndc, Ccl, Dmat.Ndc}];
OutputResponse[sys, UnitStep[t], {t, 0, 6}]
Plot[Evaluate[%], {t, 0, 6}, GridLines -> Automatic,
     PlotLabel -> "Reference Tracking with Static Feedforward Gain"]
      

15. Problems and Solutions

Problem 1: Consider \(\dot{x}=Ax+Bu,\; y=Cx\) with \(D=0\) and control \(u=-Kx+Nr\). Derive the feedforward gain that gives zero steady-state error for constant references.

Solution: The closed-loop dynamics are

\[ \dot{x}=(A-BK)x+BNr,\qquad y=Cx. \]

At steady state, \(\dot{x}=0\), so

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

Hence

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

Requiring \(y_{\mathrm{ss} }=r\) for every \(r\) gives

\[ -C(A-BK)^{-1}BN=I. \]

Therefore,

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

Problem 2: For the double integrator \(A=\begin{bmatrix}0&1\\0&0\end{bmatrix}\), \(B=\begin{bmatrix}0\\1\end{bmatrix}\), \(C=\begin{bmatrix}1&0\end{bmatrix}\), and \(K=\begin{bmatrix}k_1&k_2\end{bmatrix}\), compute \(N\).

Solution: We have

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

Solving \((A-BK)z=B\) gives

\[ z=(A-BK)^{-1}B= \begin{bmatrix}-\frac{1}{k_1}\\0\end{bmatrix}. \]

Thus

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

Therefore,

\[ \boxed{N=k_1.} \]

For \(K=\begin{bmatrix}4&4\end{bmatrix}\), \(N=4\).

Problem 3: Explain why a static feedforward gain cannot guarantee robust zero steady-state error when the plant DC gain is uncertain.

Solution: The nominal design sets

\[ N=G_0^{-1}. \]

If the true DC gain is \(\widetilde{G}_0\), then the actual steady-state output is

\[ y_{\infty}=\widetilde{G}_0G_0^{-1}r. \]

Unless \(\widetilde{G}_0=G_0\), the factor \(\widetilde{G}_0G_0^{-1}\) is not exactly the identity. Hence a steady-state offset remains. This motivates integral action for robust constant-reference tracking.

Problem 4: Consider the regulator equations

\[ AX+BU=0,\qquad CX+DU=I. \]

Show that \(N=U+KX\) makes \(x_{\mathrm{ss} }=Xr\) and \(u_{\mathrm{ss} }=Ur\) compatible with the feedback law.

Solution: The feedback law gives

\[ u_{\mathrm{ss} }=-Kx_{\mathrm{ss} }+Nr. \]

Substitute \(x_{\mathrm{ss} }=Xr\) and \(u_{\mathrm{ss} }=Ur\):

\[ Ur=-KXr+Nr. \]

For this identity to hold for every \(r\), we need

\[ U=-KX+N. \]

Therefore,

\[ \boxed{N=U+KX.} \]

Problem 5: State the rank condition for exact constant tracking using regulator equations and interpret its failure.

Solution: The required condition is

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

If this condition fails, then the algebraic equations for steady-state state and input cannot be solved for arbitrary constant references. In system-theoretic terms, the plant has a blocking zero at the origin or insufficient input authority over the selected outputs.

16. Summary

Feedforward gain design is the nominal steady-state normalization step that accompanies state-feedback control. Once \(K\) has placed the closed-loop poles, the feedforward gain \(N\) is selected so that the closed-loop DC gain from reference to output equals the identity. The two principal derivations are the DC-gain formula \(N=\left[D-(C-DK)(A-BK)^{-1}B\right]^{-1}\) and the regulator-equation formula \(N=U+KX\). The method gives exact constant-reference tracking for the nominal model, but robustness to disturbances and uncertainty requires the internal-model ideas developed in the next lesson.

17. References

  1. Wonham, W.M., & Pearson, J.B. (1974). Regulation and internal stabilization in linear multivariable systems. SIAM Journal on Control, 12(1), 5–18.
  2. Francis, B.A., & Wonham, W.M. (1976). The internal model principle of control theory. Automatica, 12(5), 457–465.
  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. (1977). The linear multivariable regulator problem. SIAM Journal on Control and Optimization, 15(3), 486–505.
  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. Silverman, L.M. (1969). Inversion of multivariable linear systems. IEEE Transactions on Automatic Control, 14(3), 270–276.
  7. Davison, E.J., & Goldenberg, A. (1975). Robust control of a general servomechanism problem: The servo compensator. Automatica, 11(5), 461–471.
  8. Morse, A.S. (1973). Structural invariants of linear multivariable systems. SIAM Journal on Control, 11(3), 446–465.