Chapter 25: Limitations of State-Feedback Design

Lesson 3: Trade-Offs: Speed of Response vs Control Effort vs Sensitivity

This lesson explains why state-feedback pole placement is not a free operation. Moving closed-loop poles farther left usually improves the nominal speed of response, but it also increases feedback gains, actuator demand, transient amplification, and sensitivity to modeling errors. The lesson connects pole placement, Lyapunov energy integrals, eigenvalue perturbation theory, and sensitivity-function limitations in a form that prepares students for later integral-action, disturbance-rejection, and quadratic-performance chapters.

1. The Performance Triangle in State Feedback

In earlier lessons, state feedback was introduced as \( u=-Kx+r \), producing the closed-loop matrix \( A_c=A-BK \). If the pair \( (A,B) \) is controllable, pole placement can assign the eigenvalues of \( A_c \). However, assignability is not the same as engineering feasibility.

\[ \text{nominal speed} \uparrow \quad \Longrightarrow \quad \|K\| \uparrow,\; \|u\| \uparrow,\; \text{sensitivity risk} \uparrow. \]

A standard scalar measure of closed-loop speed is the spectral abscissa margin \( \alpha_c \):

\[ \alpha_c = -\max_i \operatorname{Re}\lambda_i(A-BK),\quad \alpha_c > 0. \]

Larger \( \alpha_c \) usually means faster exponential decay. But the control input is not abstract; it is the physical signal sent to actuators:

\[ u(t)=-Kx(t),\quad \|u(t)\|_2 \le \|K\|_2\|x(t)\|_2. \]

Thus a high-gain controller can request actuator forces, torques, currents, valve openings, or voltages that are physically unavailable. Even before saturation occurs, large gains can amplify measurement noise, excite neglected high-frequency dynamics, and make the closed-loop eigenstructure more sensitive to perturbations in the model.

flowchart TD
  A["Choose faster desired poles"] --> B["Larger feedback gain K"]
  B --> C["Larger actuator effort"]
  B --> D["Higher noise and uncertainty amplification"]
  C --> E["Possible saturation or overheating"]
  D --> F["Eigenvalue drift and transient amplification"]
  E --> G["Reduce speed or redesign"]
  F --> G
  G --> H["Balanced pole locations and acceptable margins"]
        

2. Algebraic Origin of High Gain in Pole Placement

The gain-growth phenomenon can be seen directly in controllable canonical form. For the SISO companion realization

\[ \dot{x} = \begin{bmatrix} 0 & 1 & 0 & \cdots & 0\\ 0 & 0 & 1 & \cdots & 0\\ \vdots & \vdots & \vdots & \ddots & \vdots\\ 0 & 0 & 0 & \cdots & 1\\ -a_0 & -a_1 & -a_2 & \cdots & -a_{n-1} \end{bmatrix}x+ \begin{bmatrix}0\\0\\ \vdots\\0\\1\end{bmatrix}u,\quad u=-Kx, \]

let the desired closed-loop polynomial be \( \phi_d(s)=s^n+\alpha_{n-1}s^{n-1}+\cdots+\alpha_1s+\alpha_0 \). Then the feedback vector is

\[ K= \begin{bmatrix} \alpha_0-a_0 & \alpha_1-a_1 & \cdots & \alpha_{n-1}-a_{n-1} \end{bmatrix}. \]

Therefore, the numerical size of \( K \) is governed by the coefficients of the desired polynomial. If all desired poles are scaled leftward by a factor \( \gamma \), so that \( \lambda_i^{(d)}=\gamma p_i \) with \( \operatorname{Re}p_i < 0 \), then

\[ \phi_\gamma(s)=\prod_{i=1}^{n}(s-\gamma p_i) =s^n+\gamma c_{n-1}s^{n-1}+\gamma^2 c_{n-2}s^{n-2} +\cdots+\gamma^n c_0. \]

The lower-order polynomial coefficients grow as higher powers of \( \gamma \). Hence aggressive pole placement tends to produce large feedback gains, especially for high-order systems.

Proof sketch: Expanding the product gives elementary symmetric functions of the desired poles. The coefficient multiplying \( s^{n-k} \) is an elementary symmetric polynomial of degree \( k \) in the poles. Replacing every pole by \( \gamma p_i \) multiplies that coefficient by \( \gamma^k \). Therefore, at least one component of \( K \) normally grows polynomially with the requested closed-loop speed.

3. Control-Effort Energy and Lyapunov Equations

A rigorous way to measure actuator demand is the integrated quadratic control effort for the zero-input closed-loop response:

\[ J_u(x_0)=\int_0^\infty u(t)^T R u(t)\,dt,\quad R=R^T\succ 0. \]

For \( u=-Kx \) and \( x(t)=e^{(A-BK)t}x_0 \), this becomes

\[ J_u(x_0)=x_0^T P_u x_0, \]

where \( P_u \) solves the continuous-time Lyapunov equation

\[ (A-BK)^T P_u+P_u(A-BK)+K^T R K=0. \]

This formula is important because it separates two effects. Faster poles make \( e^{A_ct} \) decay sooner, but the controller may inject much larger instantaneous input while the state is nonzero. The net integral can increase rather than decrease.

For a scalar plant \( \dot{x}=ax+bu \), \( b\ne 0 \), choose feedback \( u=-kx \) so that the closed-loop pole is \( -\alpha \). Then \( a-bk=-\alpha \), hence \( k=(a+\alpha)/b \). With \( R=1 \),

\[ J_u(x_0)=\int_0^\infty k^2 e^{-2\alpha t}x_0^2\,dt =\frac{(a+\alpha)^2}{2b^2\alpha}x_0^2. \]

As \( \alpha \to \infty \), \( J_u(x_0)\sim \frac{\alpha}{2b^2}x_0^2 \). The response is faster, but the energy demanded from the actuator grows without bound.

4. Sensitivity of Assigned Poles and Eigenvectors

Let \( A_c=A-BK \) have a simple eigenvalue \( \lambda_i \) with right eigenvector \( v_i \) and left eigenvector \( w_i \):

\[ A_cv_i=\lambda_i v_i,\quad w_i^T A_c=\lambda_i w_i^T. \]

Under a small perturbation \( \Delta A_c \), first-order perturbation theory gives

\[ \delta\lambda_i \approx \frac{w_i^T\Delta A_c v_i}{w_i^T v_i}. \]

Therefore, the eigenvalue sensitivity is large when left and right eigenvectors are nearly orthogonal. A common condition measure is

\[ \kappa_i= \frac{\|w_i\|_2\|v_i\|_2}{|w_i^T v_i|}. \]

In MIMO pole placement, many feedback matrices can assign the same eigenvalues. A numerically robust choice attempts to keep eigenvectors well conditioned, reduce the departure from normality, and avoid unnecessarily large \( \|K\| \). This is why robust pole-assignment algorithms optimize more than the characteristic polynomial.

\[ \|e^{A_ct}\|_2 \le \kappa(V)e^{\alpha(A_c)t},\quad A_c=V\Lambda V^{-1}. \]

Even when every eigenvalue is in the left half-plane, a poorly conditioned eigenvector matrix \( V \) can cause large transient amplification before decay dominates.

5. Frequency-Domain Sensitivity View

Although this chapter is primarily state-space based, sensitivity is easiest to interpret through the loop transfer matrix. In a unity feedback representation, define

\[ S(s)=(I+L(s))^{-1},\quad T(s)=L(s)(I+L(s))^{-1}. \]

Small \( \|S(j\omega)\| \) means good tracking and disturbance rejection at frequency \( \omega \). Small \( \|T(j\omega)\| \) is desirable for measurement-noise attenuation and robustness against neglected high-frequency dynamics. Since \( S+T=I \) in SISO unity feedback, both cannot be made small at the same frequency.

For stable, proper, minimum-phase SISO loops satisfying the usual Bode integral assumptions, the sensitivity waterbed effect is summarized by

\[ \int_0^\infty \ln |S(j\omega)|\,d\omega=0. \]

Reducing sensitivity below one over one frequency band forces sensitivity above one elsewhere. State-feedback designs with very fast poles can improve low-frequency behavior but may enlarge high-frequency complementary sensitivity and increase fragility to unmodeled dynamics.

\[ \|S(j\omega)\| \downarrow \quad \text{over one band} \quad \Longrightarrow \quad \|S(j\omega)\| \uparrow \quad \text{over another band}. \]

6. A Design Procedure for Managing the Trade-Off

A useful engineering procedure is to treat pole placement as the beginning of a design, not the end. After selecting candidate poles, the designer should compute at least:

\[ \alpha_c,\quad \|K\|_2,\quad J_u(x_0)=x_0^TP_ux_0,\quad \kappa(V),\quad \max_\omega \|S(j\omega)\|. \]

A design is questionable if it is fast only because the controller is unrealistically strong. If a moderate decrease in speed greatly reduces \( \|K\| \), \( J_u \), or eigenvalue sensitivity, the slower design is often the better physical controller.

flowchart TD
  A["Start with time-domain specs"] --> B["Choose candidate closed-loop poles"]
  B --> C["Compute feedback gain K"]
  C --> D["Check actuator effort and saturation"]
  D --> E["Check eigenvalue sensitivity and transients"]
  E --> F["Check disturbance/noise sensitivity"]
  F --> G{"All limits acceptable?"}
  G -->|"yes"| H["Accept design and document margins"]
  G -->|"no"| I["Relax speed or change design structure"]
  I --> B
        

7. Python Implementation

The Python example compares slow, medium, and fast pole-placement designs for the same second-order plant. It computes the feedback gain, closed-loop speed, Lyapunov control-effort integral, and Monte Carlo eigenvalue sensitivity under small plant perturbations.

Chapter25_Lesson3.py


"""
Chapter25_Lesson3.py

Trade-offs in state-feedback design:
speed of response vs. control effort vs. sensitivity.

Dependencies:
    pip install numpy scipy matplotlib

Optional extension:
    pip install control
"""

import numpy as np
from scipy.signal import place_poles
from scipy.linalg import solve_continuous_lyapunov
import matplotlib.pyplot as plt


def closed_loop_metrics(A, B, poles, x0):
    """Return K, closed-loop eigenvalues, effort Gramian, and J_u for u=-Kx."""
    result = place_poles(A, B, poles, method="YT")
    K = result.gain_matrix
    Ac = A - B @ K

    # P_u solves Ac.T P + P Ac + K.T K = 0.
    P_u = solve_continuous_lyapunov(Ac.T, -(K.T @ K))
    effort = (x0.T @ P_u @ x0).item()

    eigvals = np.linalg.eigvals(Ac)
    speed = -np.max(np.real(eigvals))
    k_norm = float(np.linalg.norm(K, 2))

    return K, Ac, eigvals, speed, k_norm, effort, P_u


def rk4_simulate(Ac, K, x0, tf=10.0, dt=0.002):
    """Simulate x_dot = Ac x and u = -Kx."""
    n_steps = int(tf / dt) + 1
    t = np.linspace(0.0, tf, n_steps)
    x = np.zeros((n_steps, len(x0)))
    u = np.zeros(n_steps)
    x[0, :] = x0.ravel()

    def f(xv):
        return Ac @ xv

    for k in range(n_steps - 1):
        xv = x[k, :]
        k1 = f(xv)
        k2 = f(xv + 0.5 * dt * k1)
        k3 = f(xv + 0.5 * dt * k2)
        k4 = f(xv + dt * k3)
        x[k + 1, :] = xv + (dt / 6.0) * (k1 + 2*k2 + 2*k3 + k4)
        u[k] = float(-(K @ x[k, :].reshape(-1, 1))[0, 0])
    u[-1] = float(-(K @ x[-1, :].reshape(-1, 1))[0, 0])
    return t, x, u


def monte_carlo_eigenvalue_sensitivity(A, B, K, samples=200, eps=0.02, seed=7):
    """Perturb A and B and measure the spread of closed-loop eigenvalues."""
    rng = np.random.default_rng(seed)
    eigs = []
    for _ in range(samples):
        dA = eps * rng.standard_normal(A.shape)
        dB = eps * rng.standard_normal(B.shape)
        Ac_perturbed = (A + dA) - (B + dB) @ K
        eigs.append(np.linalg.eigvals(Ac_perturbed))
    return np.array(eigs)


def main():
    # Plant: mass-spring-damper type state-space model.
    A = np.array([[0.0, 1.0],
                  [-2.0, -0.4]])
    B = np.array([[0.0],
                  [1.0]])
    x0 = np.array([[1.0],
                   [0.0]])

    pole_sets = {
        "slow": np.array([-1.0 + 1.0j, -1.0 - 1.0j]),
        "medium": np.array([-3.0 + 3.0j, -3.0 - 3.0j]),
        "fast": np.array([-6.0 + 6.0j, -6.0 - 6.0j]),
    }

    print("State-feedback trade-off table")
    print("case       speed       ||K||2       J_u(x0)        poles")
    print("-" * 74)

    trajectories = {}
    for name, poles in pole_sets.items():
        K, Ac, eigvals, speed, k_norm, effort, _ = closed_loop_metrics(A, B, poles, x0)
        print(f"{name:7s} {speed:10.4f} {k_norm:12.4f} {effort:12.4f}   {eigvals}")
        t, x, u = rk4_simulate(Ac, K, x0)
        trajectories[name] = (t, x, u, K, Ac)

    plt.figure()
    for name, (t, x, u, _, _) in trajectories.items():
        plt.plot(t, x[:, 0], label=f"{name}: x1")
    plt.xlabel("time (s)")
    plt.ylabel("state x1")
    plt.title("Faster poles reduce settling time")
    plt.legend()
    plt.grid(True)

    plt.figure()
    for name, (t, x, u, _, _) in trajectories.items():
        plt.plot(t, u, label=f"{name}: u")
    plt.xlabel("time (s)")
    plt.ylabel("control input u")
    plt.title("Faster poles require larger control action")
    plt.legend()
    plt.grid(True)

    for name in ["medium", "fast"]:
        _, _, _, K, _ = trajectories[name]
        eig_cloud = monte_carlo_eigenvalue_sensitivity(A, B, K)
        spread = np.std(eig_cloud.reshape(-1))
        print(f"eigenvalue cloud std for {name:6s}: {spread:.5f}")

    plt.show()


if __name__ == "__main__":
    main()
      

8. C++ Implementation

The C++ version implements the same idea from scratch for a second-order SISO plant. It uses the explicit relationship between desired second-order poles and the feedback vector, then numerically integrates \( \int u^2dt \) by simulation.

Chapter25_Lesson3.cpp


/*
Chapter25_Lesson3.cpp

Trade-offs in SISO state-feedback for a second-order plant.

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

No external libraries are required.
*/

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

struct Vec2 {
    double x1;
    double x2;
};

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

Vec2 add(Vec2 a, Vec2 b) {
    return {a.x1 + b.x1, a.x2 + b.x2};
}

Vec2 scale(Vec2 a, double c) {
    return {c * a.x1, c * a.x2};
}

Vec2 mat_vec(Mat2 A, Vec2 x) {
    return {A.a11 * x.x1 + A.a12 * x.x2, A.a21 * x.x1 + A.a22 * x.x2};
}

double control(double k1, double k2, Vec2 x) {
    return -(k1 * x.x1 + k2 * x.x2);
}

Vec2 rk4_step(Mat2 Ac, Vec2 x, double dt) {
    Vec2 k1 = mat_vec(Ac, x);
    Vec2 k2 = mat_vec(Ac, add(x, scale(k1, 0.5 * dt)));
    Vec2 k3 = mat_vec(Ac, add(x, scale(k2, 0.5 * dt)));
    Vec2 k4 = mat_vec(Ac, add(x, scale(k3, dt)));

    Vec2 sum = add(add(k1, scale(k2, 2.0)), add(scale(k3, 2.0), k4));
    return add(x, scale(sum, dt / 6.0));
}

struct Metrics {
    double k1;
    double k2;
    double speed;
    double gain_norm;
    double effort_integral;
    double max_abs_u;
};

Metrics simulate_design(double desired_real_part, double desired_imag_part) {
    // Plant:
    // x_dot = [0 1; -2 -0.4] x + [0;1] u
    // u = -[k1 k2] x
    //
    // Desired poles: -a +- j b
    // desired characteristic polynomial:
    // s^2 + 2 a s + (a^2 + b^2)
    const double plant_const = 2.0;
    const double plant_s_coeff = 0.4;

    double a = desired_real_part;
    double b = desired_imag_part;
    double desired_s_coeff = 2.0 * a;
    double desired_const = a * a + b * b;

    double k1 = desired_const - plant_const;
    double k2 = desired_s_coeff - plant_s_coeff;

    Mat2 Ac{0.0, 1.0, -plant_const - k1, -plant_s_coeff - k2};

    Vec2 x{1.0, 0.0};
    const double dt = 0.0005;
    const double tf = 10.0;
    int steps = static_cast<int>(tf / dt);

    double effort = 0.0;
    double max_abs_u = 0.0;

    for (int i = 0; i < steps; ++i) {
        double u = control(k1, k2, x);
        effort += u * u * dt;
        max_abs_u = std::max(max_abs_u, std::abs(u));
        x = rk4_step(Ac, x, dt);
    }

    return {k1, k2, a, std::sqrt(k1 * k1 + k2 * k2), effort, max_abs_u};
}

int main() {
    struct CaseData {
        std::string name;
        double a;
        double b;
    };

    std::vector<CaseData> cases = {
        {"slow", 1.0, 1.0},
        {"medium", 3.0, 3.0},
        {"fast", 6.0, 6.0}
    };

    std::cout << "State-feedback speed/effort trade-off\n";
    std::cout << std::setw(10) << "case"
              << std::setw(12) << "speed"
              << std::setw(12) << "k1"
              << std::setw(12) << "k2"
              << std::setw(14) << "||K||"
              << std::setw(16) << "int u^2 dt"
              << std::setw(12) << "max |u|" << "\n";

    for (const auto& c : cases) {
        Metrics m = simulate_design(c.a, c.b);
        std::cout << std::setw(10) << c.name
                  << std::setw(12) << m.speed
                  << std::setw(12) << m.k1
                  << std::setw(12) << m.k2
                  << std::setw(14) << m.gain_norm
                  << std::setw(16) << m.effort_integral
                  << std::setw(12) << m.max_abs_u << "\n";
    }

    return 0;
}
      

9. Java Implementation

The Java implementation mirrors the C++ implementation and is suitable for students who want a no-library version of pole-placement trade-off simulation.

Chapter25_Lesson3.java


/*
Chapter25_Lesson3.java

Trade-offs in SISO state-feedback for a second-order plant.

Compile:
    javac Chapter25_Lesson3.java

Run:
    java Chapter25_Lesson3
*/

public class Chapter25_Lesson3 {
    static class Vec2 {
        double x1;
        double x2;

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

    static class Mat2 {
        double a11;
        double a12;
        double a21;
        double a22;

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

    static class Metrics {
        double k1;
        double k2;
        double speed;
        double gainNorm;
        double effort;
        double maxAbsU;
    }

    static Vec2 add(Vec2 a, Vec2 b) {
        return new Vec2(a.x1 + b.x1, a.x2 + b.x2);
    }

    static Vec2 scale(Vec2 a, double c) {
        return new Vec2(c * a.x1, c * a.x2);
    }

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

    static double control(double k1, double k2, Vec2 x) {
        return -(k1 * x.x1 + k2 * x.x2);
    }

    static Vec2 rk4Step(Mat2 Ac, Vec2 x, double dt) {
        Vec2 k1 = matVec(Ac, x);
        Vec2 k2 = matVec(Ac, add(x, scale(k1, 0.5 * dt)));
        Vec2 k3 = matVec(Ac, add(x, scale(k2, 0.5 * dt)));
        Vec2 k4 = matVec(Ac, add(x, scale(k3, dt)));

        Vec2 sum = add(add(k1, scale(k2, 2.0)), add(scale(k3, 2.0), k4));
        return add(x, scale(sum, dt / 6.0));
    }

    static Metrics simulateDesign(double desiredRealPart, double desiredImagPart) {
        double plantConst = 2.0;
        double plantSCoeff = 0.4;

        double a = desiredRealPart;
        double b = desiredImagPart;
        double desiredSCoeff = 2.0 * a;
        double desiredConst = a * a + b * b;

        double k1 = desiredConst - plantConst;
        double k2 = desiredSCoeff - plantSCoeff;

        Mat2 Ac = new Mat2(0.0, 1.0, -plantConst - k1, -plantSCoeff - k2);

        Vec2 x = new Vec2(1.0, 0.0);
        double dt = 0.0005;
        double tf = 10.0;
        int steps = (int)(tf / dt);

        double effort = 0.0;
        double maxAbsU = 0.0;

        for (int i = 0; i < steps; i++) {
            double u = control(k1, k2, x);
            effort += u * u * dt;
            maxAbsU = Math.max(maxAbsU, Math.abs(u));
            x = rk4Step(Ac, x, dt);
        }

        Metrics m = new Metrics();
        m.k1 = k1;
        m.k2 = k2;
        m.speed = a;
        m.gainNorm = Math.sqrt(k1 * k1 + k2 * k2);
        m.effort = effort;
        m.maxAbsU = maxAbsU;
        return m;
    }

    public static void main(String[] args) {
        String[] names = {"slow", "medium", "fast"};
        double[] realParts = {1.0, 3.0, 6.0};
        double[] imagParts = {1.0, 3.0, 6.0};

        System.out.println("State-feedback speed/effort trade-off");
        System.out.printf("%10s%12s%12s%12s%14s%16s%12s%n",
            "case", "speed", "k1", "k2", "||K||", "int u^2 dt", "max |u|");

        for (int i = 0; i < names.length; i++) {
            Metrics m = simulateDesign(realParts[i], imagParts[i]);
            System.out.printf("%10s%12.4f%12.4f%12.4f%14.4f%16.4f%12.4f%n",
                names[i], m.speed, m.k1, m.k2, m.gainNorm, m.effort, m.maxAbsU);
        }
    }
}
      

10. MATLAB and Simulink Implementation

MATLAB is especially convenient for this lesson because place, lyap, ss, and initial directly correspond to the mathematical operations used above. The script also creates a minimal Simulink closed-loop model when Simulink is available.

Chapter25_Lesson3.m


% Chapter25_Lesson3.m
%
% Trade-offs in state-feedback design:
% speed of response vs. control effort vs. sensitivity.
%
% MATLAB requirements:
%   Control System Toolbox for place, ss, initial, lyap.
% Optional:
%   Simulink for the final programmatic model section.

clear; clc; close all;

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

poleSets = {
    'slow',   [-1+1i, -1-1i];
    'medium', [-3+3i, -3-3i];
    'fast',   [-6+6i, -6-6i]
};

fprintf('State-feedback trade-off table\n');
fprintf('%10s%12s%14s%14s%16s\n', 'case', 'speed', 'norm(K)', 'J_u(x0)', 'eig(A-BK)');

figure; hold on; grid on;
title('Faster poles reduce settling time');
xlabel('time (s)'); ylabel('state x_1');

figure; hold on; grid on;
title('Faster poles require larger input');
xlabel('time (s)'); ylabel('control input u');

for i = 1:size(poleSets, 1)
    name = poleSets{i, 1};
    poles = poleSets{i, 2};

    K = place(A, B, poles);
    Ac = A - B*K;

    % P_u solves Ac' P + P Ac + K'K = 0.
    P_u = lyap(Ac', K'*K);
    J_u = x0' * P_u * x0;

    eigVals = eig(Ac);
    speed = -max(real(eigVals));

    fprintf('%10s%12.4f%14.4f%14.4f    [%8.3f%+8.3fi, %8.3f%+8.3fi]\n', ...
        name, speed, norm(K, 2), J_u, ...
        real(eigVals(1)), imag(eigVals(1)), real(eigVals(2)), imag(eigVals(2)));

    sysCL = ss(Ac, B, C, D);
    t = linspace(0, 10, 2000);
    [y, tOut, x] = initial(sysCL, x0, t);
    u = -(K * x')';

    figure(1); plot(tOut, x(:, 1), 'DisplayName', name);
    figure(2); plot(tOut, u, 'DisplayName', name);
end

figure(1); legend('Location', 'best');
figure(2); legend('Location', 'best');

% Sensitivity experiment: perturb A and B and inspect closed-loop poles.
rng(7);
epsLevel = 0.02;
samples = 200;

for i = 2:3
    name = poleSets{i, 1};
    poles = poleSets{i, 2};
    K = place(A, B, poles);
    eigCloud = zeros(samples, 2);

    for s = 1:samples
        dA = epsLevel * randn(size(A));
        dB = epsLevel * randn(size(B));
        eigCloud(s, :) = eig((A + dA) - (B + dB)*K).';
    end

    fprintf('eigenvalue cloud std for %s design: %.5f\n', name, std(eigCloud(:)));
end

% Optional Simulink sketch:
% This creates a minimal closed-loop state-space model if Simulink is installed.
if exist('simulink', 'file') == 4
    model = 'Chapter25_Lesson3_Simulink';
    if bdIsLoaded(model)
        close_system(model, 0);
    end
    new_system(model);
    open_system(model);

    % Use the medium design for the Simulink block.
    K = place(A, B, [-3+3i, -3-3i]);
    Ac = A - B*K;

    add_block('simulink/Continuous/State-Space', [model '/ClosedLoopPlant'], ...
        'A', mat2str(Ac), 'B', mat2str(B), 'C', mat2str(C), 'D', mat2str(D), ...
        'X0', mat2str(x0), 'Position', [140 100 280 160]);

    add_block('simulink/Sources/Constant', [model '/ZeroInput'], ...
        'Value', '0', 'Position', [40 115 90 145]);

    add_block('simulink/Sinks/Scope', [model '/Scope'], ...
        'Position', [340 105 390 155]);

    add_line(model, 'ZeroInput/1', 'ClosedLoopPlant/1');
    add_line(model, 'ClosedLoopPlant/1', 'Scope/1');

    set_param(model, 'StopTime', '10');
    save_system(model);
    fprintf('Created optional Simulink model: %s.slx\n', model);
end
      

11. Wolfram Mathematica Implementation

Mathematica is useful for symbolic and semi-symbolic verification of gain formulas, matrix exponentials, and Lyapunov-equation solutions. The following notebook-style code uses a direct second-order formula and a vectorized Lyapunov solve.

Chapter25_Lesson3.nb


(* Chapter25_Lesson3.nb

Trade-offs in state-feedback design:
speed of response vs. control effort vs. sensitivity.

This file is plain Wolfram Language content saved with .nb extension.
It can be pasted into a Mathematica notebook or opened as text.
*)

ClearAll["Global`*"];

A = { {0, 1}, {-2, -0.4} };
B = { {0}, {1} };
x0 = {1, 0};

(* For this second-order SISO plant,
   u = -{k1,k2}.x gives:
   Ac = { {0,1},{-2-k1,-0.4-k2} }.
   Desired poles -a +- I b imply:
   s^2 + 2 a s + (a^2 + b^2).
*)
feedbackGain[a_, b_] := Module[
  {desiredS, desiredConst, k1, k2},
  desiredS = 2 a;
  desiredConst = a^2 + b^2;
  k1 = desiredConst - 2;
  k2 = desiredS - 0.4;
  {k1, k2}
];

closedLoopMatrix[k_] := A - B . {k};

(* Solve Ac^T P + P Ac + K^T K == 0 by vectorization. *)
lyapunovEffortMatrix[Ac_, k_] := Module[
  {n, Q, M, vecP, P},
  n = Length[Ac];
  Q = Transpose[{k}] . {k};
  M = KroneckerProduct[IdentityMatrix[n], Transpose[Ac]] +
      KroneckerProduct[Transpose[Ac], IdentityMatrix[n]];
  vecP = LinearSolve[M, -Flatten[Q]];
  P = ArrayReshape[vecP, {n, n}];
  P
];

cases = {
  {"slow", 1, 1},
  {"medium", 3, 3},
  {"fast", 6, 6}
};

table = Table[
  Module[{name, a, b, k, Ac, P, Ju, eigs},
    {name, a, b} = c;
    k = feedbackGain[a, b];
    Ac = closedLoopMatrix[k];
    P = lyapunovEffortMatrix[Ac, k];
    Ju = x0 . P . x0;
    eigs = Eigenvalues[Ac];
    {name, a, k, Norm[k], N[Ju], N[eigs]}
  ],
  {c, cases}
];

Grid[
  Prepend[table, {"case", "speed", "K", "Norm[K]", "J_u(x0)", "Eigenvalues"}],
  Frame -> All
]

stateResponse[name_, a_, b_] := Module[
  {k, Ac, x},
  k = feedbackGain[a, b];
  Ac = closedLoopMatrix[k];
  x[t_] := MatrixExp[Ac t].x0;
  {name, x}
];

responses = stateResponse @@@ cases;

Plot[
  Evaluate[Table[responses[[i, 2]][t][[1]], {i, Length[responses]}]],
  {t, 0, 10},
  PlotLegends -> responses[[All, 1]],
  AxesLabel -> {"t", "x1(t)"},
  PlotLabel -> "Faster closed-loop poles reduce settling time"
]

Plot[
  Evaluate[
    Table[
      With[{k = feedbackGain[cases[[i, 2]], cases[[i, 3]]],
            xfun = responses[[i, 2]]},
        -k . xfun[t]
      ],
      {i, Length[cases]}
    ]
  ],
  {t, 0, 10},
  PlotLegends -> cases[[All, 1]],
  AxesLabel -> {"t", "u(t)"},
  PlotLabel -> "Faster closed-loop poles require larger input"
]
      

12. Problems and Solutions

Problem 1 (Scalar speed-effort law): Consider \( \dot{x}=ax+bu \), \( b\ne 0 \), and \( u=-kx \). Choose \( k \) so that the closed-loop pole is \( -\alpha \), with \( \alpha>0 \). Derive \( J_u(x_0)=\int_0^\infty u(t)^2dt \).

Solution: The closed-loop equation is \( \dot{x}=(a-bk)x \). Requiring \( a-bk=-\alpha \) gives \( k=(a+\alpha)/b \). Since \( x(t)=e^{-\alpha t}x_0 \),

\[ J_u(x_0)=\int_0^\infty k^2 e^{-2\alpha t}x_0^2dt =\frac{(a+\alpha)^2}{2b^2\alpha}x_0^2. \]

For large \( \alpha \), the effort grows approximately linearly with the requested closed-loop speed.

Problem 2 (Gain growth in companion form): For a second-order companion system with plant polynomial \( s^2+a_1s+a_0 \), show how the feedback gain changes when the desired poles are \( -\gamma \) and \( -2\gamma \).

Solution: The desired polynomial is

\[ (s+\gamma)(s+2\gamma)=s^2+3\gamma s+2\gamma^2. \]

In companion form, \( K=[2\gamma^2-a_0,\;3\gamma-a_1] \). The first component grows quadratically in \( \gamma \), while the second grows linearly.

Problem 3 (Lyapunov effort matrix): Let \( A_c \) be Hurwitz and \( u=-Kx \). Prove that \( J_u(x_0)=x_0^TP_ux_0 \), where \( P_u \) solves \( A_c^TP_u+P_uA_c+K^TRK=0 \).

Solution: Since \( A_c \) is Hurwitz, the integral

\[ P_u=\int_0^\infty e^{A_c^Tt}K^TRKe^{A_ct}dt \]

converges. Substituting \( x(t)=e^{A_ct}x_0 \) into the effort integral gives \( J_u=x_0^TP_ux_0 \). Differentiating \( e^{A_c^Tt}K^TRKe^{A_ct} \) and integrating from \( 0 \) to \( \infty \) yields the Lyapunov equation.

Problem 4 (Eigenvalue perturbation): Suppose \( A_cv=\lambda v \) and \( w^TA_c=\lambda w^T \), with \( w^Tv\ne 0 \). Derive the first-order eigenvalue shift caused by \( \Delta A_c \).

Solution: Write \( (A_c+\Delta A_c)(v+\Delta v)=(\lambda+\Delta\lambda)(v+\Delta v) \). Keeping only first-order terms gives \( A_c\Delta v+\Delta A_c v=\lambda\Delta v+\Delta\lambda v \). Premultiplying by \( w^T \) cancels the \( \Delta v \) terms, so

\[ \Delta\lambda\approx \frac{w^T\Delta A_c v}{w^Tv}. \]

Therefore, nearly orthogonal left and right eigenvectors cause high pole sensitivity.

Problem 5 (Interpreting the waterbed effect): For a stable minimum-phase SISO loop satisfying the Bode integral assumptions, explain why making \( |S(j\omega)| \) very small over a low-frequency tracking band cannot make the loop uniformly insensitive at all frequencies.

Solution: The integral relation

\[ \int_0^\infty \ln|S(j\omega)|d\omega=0 \]

implies area conservation for \( \ln|S| \). Negative area created by \( |S|<1 \) must be balanced by positive area where \( |S|>1 \). Hence better tracking in one band is paid for by worse sensitivity in another band.

13. Summary

State feedback can relocate controllable poles, but aggressive pole movement creates unavoidable trade-offs. Faster closed-loop poles usually require larger feedback gains and larger actuator effort. Large gains can increase sensitivity to uncertainty, worsen transient amplification in nonnormal systems, and interact poorly with actuator limits and neglected dynamics. A mature state-feedback design therefore reports not only assigned poles, but also gain size, control-effort energy, eigenvalue conditioning, transient amplification, and disturbance/noise sensitivity.

14. References

  1. Bode, H.W. (1945). Network Analysis and Feedback Amplifier Design. Van Nostrand.
  2. Kalman, R.E. (1960). Contributions to the theory of optimal control. Boletín de la Sociedad Matemática Mexicana, 5, 102–119.
  3. Zames, G. (1981). Feedback and optimal sensitivity: Model reference transformations, multiplicative seminorms, and approximate inverses. IEEE Transactions on Automatic Control, 26(2), 301–320.
  4. Doyle, J.C., & Stein, G. (1981). Multivariable feedback design: Concepts for a classical/modern synthesis. IEEE Transactions on Automatic Control, 26(1), 4–16.
  5. Freudenberg, J.S., & Looze, D.P. (1985). Right half plane poles and zeros and design tradeoffs in feedback systems. IEEE Transactions on Automatic Control, 30(6), 555–565.
  6. Kautsky, J., Nichols, N.K., & Van Dooren, P. (1985). Robust pole assignment in linear state feedback. International Journal of Control, 41(5), 1129–1155.
  7. Francis, B.A., & Zames, G. (1984). On H-infinity-optimal sensitivity theory for SISO feedback systems. IEEE Transactions on Automatic Control, 29(1), 9–16.
  8. Trefethen, L.N., Trefethen, A.E., Reddy, S.C., & Driscoll, T.A. (1993). Hydrodynamic stability without eigenvalues. Science, 261(5121), 578–584.