Chapter 6: MRAC for Second-Order Systems

Lesson 1: Tracking Problem for a Second-Order Plant

This lesson formulates the model-reference tracking problem for a second-order single-input single-output plant. We derive the exact model-matching equations, obtain the tracking-error dynamics, establish exponential tracking when the ideal controller parameters are known, and show why fixed gains designed from uncertain plant parameters generally leave a forced tracking error. Adaptive update laws are intentionally deferred to later lessons in this chapter.

1. Learning Objectives and Scope

After completing this lesson, students should be able to:

  • state the second-order model-reference tracking problem precisely;
  • write the plant and reference model in differential-equation, transfer-function, and state-space forms;
  • derive the ideal state-feedback and command-feedforward gains;
  • derive the error equation produced by parameter mismatch;
  • prove asymptotic and exponential tracking under exact matching; and
  • simulate ideal and mismatched fixed-gain cases in several programming environments.

The lesson assumes knowledge of linear systems, state-space models, pole locations, Hurwitz polynomials, and Lyapunov stability. The plant parameters are treated as unknown constants, but no parameter update mechanism is introduced yet.

2. Second-Order Plant and Tracking Objective

Consider the controllable second-order plant

\[ \ddot{y}(t)+a_1\dot{y}(t)+a_0y(t)=b\,u(t), \qquad b\neq 0. \]

The output \(y(t)\) and its derivative \(\dot y(t)\) are assumed measurable. The constants \(a_0\), \(a_1\), and \(b\) are fixed but not necessarily known to the controller designer. For this chapter, the sign of \(b\) is assumed known.

With the state vector

\[ \mathbf{x}(t)=\begin{bmatrix}x_1(t)\\x_2(t)\end{bmatrix} =\begin{bmatrix}y(t)\\\dot y(t)\end{bmatrix}, \]

the plant becomes

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

\[ \mathbf{A}_p=\begin{bmatrix}0&1\\-a_0&-a_1\end{bmatrix}, \qquad \mathbf{B}_p=\begin{bmatrix}0\\b\end{bmatrix}, \qquad \mathbf{C}_p=\begin{bmatrix}1&0\end{bmatrix}. \]

The command \(r(t)\) is bounded and piecewise continuous. The control objective is to make the plant output follow the output of a stable reference model:

\[ e(t)=y(t)-y_m(t), \qquad \lim_{t\,→\,\infty} e(t)=0. \]

A stronger state-tracking objective uses \(\mathbf{e}(t)=\mathbf{x}(t)-\mathbf{x}_m(t)\) and requires \(\mathbf{e}(t)\) to converge to zero.

3. Reference Model and Closed-Loop Architecture

The desired second-order response is represented by

\[ \ddot{y}_m(t)+a_{m1}\dot{y}_m(t)+a_{m0}y_m(t)=b_m r(t), \]

where \(a_{m0}>0\) and \(a_{m1}>0\). Its transfer function is

\[ M(s)=\frac{Y_m(s)}{R(s)} =\frac{b_m}{s^2+a_{m1}s+a_{m0}}. \]

In state space,

\[ \dot{\mathbf{x}}_m=\mathbf{A}_m\mathbf{x}_m+\mathbf{B}_m r, \qquad y_m=\mathbf{C}_m\mathbf{x}_m, \]

\[ \mathbf{A}_m=\begin{bmatrix}0&1\\-a_{m0}&-a_{m1}\end{bmatrix}, \qquad \mathbf{B}_m=\begin{bmatrix}0\\b_m\end{bmatrix}, \qquad \mathbf{C}_m=\begin{bmatrix}1&0\end{bmatrix}. \]

Detailed selection of \(a_{m0}\), \(a_{m1}\), and \(b_m\) is the topic of Lesson 2. Here they are treated as specified constants that make \(\mathbf{A}_m\) Hurwitz.

flowchart TD
  R["Command r(t)"] --> RM["Stable second-order reference model"]
  RM --> YM["Desired output ym(t)"]
  R --> C["Controller: u = -k0*y - k1*ydot + kr*r"]
  Y["Measured y(t), ydot(t)"] --> C
  C --> P["Uncertain second-order plant"]
  P --> Y
  Y --> S["Tracking error e = y - ym"]
  YM --> S
        

4. Fixed Controller Structure and Exact Model Matching

Begin with the full-state controller

\[ u(t)=-k_0y(t)-k_1\dot y(t)+k_r r(t). \]

Substitution into the plant gives

\[ \ddot y+(a_1+b k_1)\dot y+(a_0+b k_0)y=b k_r r. \]

Exact equality between the plant closed-loop differential equation and the reference-model differential equation requires coefficient matching:

\[ a_1+b k_1^*=a_{m1}, \qquad a_0+b k_0^*=a_{m0}, \qquad b k_r^*=b_m. \]

Therefore, the ideal controller parameters are

\[ k_0^*=\frac{a_{m0}-a_0}{b}, \qquad k_1^*=\frac{a_{m1}-a_1}{b}, \qquad k_r^*=\frac{b_m}{b}. \]

These quantities are called ideal because they produce exact model matching if the plant parameters are known. In adaptive control, the controller parameters are time varying estimates of these unknown constants.

4.1 State-space matching equations

Define

\[ \boldsymbol{\omega}(t)= \begin{bmatrix}-x_1(t)\\-x_2(t)\\r(t)\end{bmatrix}, \qquad \boldsymbol{\theta}= \begin{bmatrix}k_0\\k_1\\k_r\end{bmatrix}, \qquad u=\boldsymbol{\theta}^{T}\boldsymbol{\omega}. \]

Let \(\mathbf{K}=[-k_0\;-k_1]\). Exact matching is equivalently expressed as

\[ \mathbf{A}_p+\mathbf{B}_p\mathbf{K}^*=\mathbf{A}_m, \qquad \mathbf{B}_p k_r^*=\mathbf{B}_m. \]

The existence of \(\mathbf{K}^*\) and \(k_r^*\) is the matching condition for this plant-model pair. For the chosen canonical second-order structures and \(b\neq 0\), the equations have the unique solution above.

5. Tracking-Error Dynamics Under Parameter Mismatch

Define parameter errors relative to the ideal values:

\[ \widetilde{k}_0=k_0-k_0^*, \qquad \widetilde{k}_1=k_1-k_1^*, \qquad \widetilde{k}_r=k_r-k_r^*. \]

Subtract the reference-model equation from the actual closed-loop plant equation. After adding and subtracting the desired coefficients, one obtains

\[ \ddot e+a_{m1}\dot e+a_{m0}e =-\left(a_1+b k_1-a_{m1}\right)\dot y -\left(a_0+b k_0-a_{m0}\right)y +\left(b k_r-b_m\right)r. \]

Using the ideal matching equations, this simplifies to

\[ \boxed{ \ddot e+a_{m1}\dot e+a_{m0}e =b\left(-\widetilde{k}_0 y-\widetilde{k}_1\dot y +\widetilde{k}_r r\right). } \]

Thus, parameter mismatch acts as a forcing input applied to an otherwise stable second-order error system. In vector form,

\[ \dot{\mathbf{e}}=\mathbf{A}_m\mathbf{e} +\mathbf{B}_p\widetilde{\boldsymbol{\theta}}^{T} \boldsymbol{\omega}, \qquad \widetilde{\boldsymbol{\theta}} =\boldsymbol{\theta}-\boldsymbol{\theta}^*. \]

This equation is the central starting point for the Lyapunov design and parameter-update laws developed in Lessons 3 and 4.

flowchart TD
  P["Actual plant parameters: a0, a1, b"] --> I["Ideal gains from matching equations"]
  M["Desired model coefficients: am0, am1, bm"] --> I
  I --> E0["If k = k_star: homogeneous stable error dynamics"]
  
flowchart TD
  N["Controller gains differ from ideal values"] --> PE["Parameter errors: k_tilde"]
  PE --> F["Forcing term: b*(-k0_tilde*y - k1_tilde*ydot + kr_tilde*r)"]
  F --> EF["Forced tracking-error dynamics"]
  EF --> A["Need online parameter adjustment in later lessons"]
  

6. Proof of Tracking Under Exact Matching

6.1 Differential-equation proof

When \(\boldsymbol{\theta}=\boldsymbol{\theta}^*\), all parameter errors vanish, and the error equation becomes

\[ \ddot e+a_{m1}\dot e+a_{m0}e=0. \]

Its characteristic polynomial is \(p_m(s)=s^2+a_{m1}s+a_{m0}\). For a real second-order polynomial, \(a_{m1}>0\) and \(a_{m0}>0\) are necessary and sufficient Hurwitz conditions. Hence both roots satisfy \(\operatorname{Re}(\lambda_i)<0\), and

\[ e(t)=c_1e^{\lambda_1 t}+c_2e^{\lambda_2 t} \]

for distinct roots, with the standard repeated-root form when \(\lambda_1=\lambda_2\). Therefore \(e(t)→0\) and \(\dot e(t)→0\) exponentially.

6.2 State-space and Lyapunov proof

Under exact matching, \(\dot{\mathbf{e}}=\mathbf{A}_m\mathbf{e}\). Since \(\mathbf{A}_m\) is Hurwitz, for every symmetric \(\mathbf{Q}=\mathbf{Q}^{T}>0\), there exists a unique symmetric \(\mathbf{P}=\mathbf{P}^{T}>0\) satisfying

\[ \mathbf{A}_m^{T}\mathbf{P}+\mathbf{P}\mathbf{A}_m=-\mathbf{Q}. \]

Choose

\[ V(\mathbf{e})=\mathbf{e}^{T}\mathbf{P}\mathbf{e}. \]

Then

\[ \dot V =\mathbf{e}^{T}\left(\mathbf{A}_m^{T}\mathbf{P} +\mathbf{P}\mathbf{A}_m\right)\mathbf{e} =-\mathbf{e}^{T}\mathbf{Q}\mathbf{e}. \]

Using eigenvalue bounds,

\[ \lambda_{\min}(\mathbf{P})\|\mathbf{e}\|^2 \leq V \leq \lambda_{\max}(\mathbf{P})\|\mathbf{e}\|^2, \]

\[ \dot V\leq -\frac{\lambda_{\min}(\mathbf{Q})} {\lambda_{\max}(\mathbf{P})}V. \]

By the comparison lemma,

\[ V(t)\leq V(0) \exp\!\left(-\frac{\lambda_{\min}(\mathbf{Q})} {\lambda_{\max}(\mathbf{P})}t\right), \]

and consequently

\[ \|\mathbf{e}(t)\|\leq \sqrt{\frac{\lambda_{\max}(\mathbf{P})} {\lambda_{\min}(\mathbf{P})}} \exp\!\left(-\frac{\lambda_{\min}(\mathbf{Q})} {2\lambda_{\max}(\mathbf{P})}t\right) \|\mathbf{e}(0)\|. \]

This proves global exponential convergence of the state-tracking error for the exactly matched, known-parameter closed loop.

7. Transfer-Function Interpretation and Steady-State Tracking

For fixed gains and zero initial conditions, the closed-loop transfer function from command to plant output is

\[ \frac{Y(s)}{R(s)}= \frac{b k_r}{s^2+(a_1+b k_1)s+(a_0+b k_0)}. \]

Exact matching requires equality of numerator and denominator with \(M(s)\). If only the denominator is matched but \(b k_r\neq b_m\), the transient poles are correct but the command gain is wrong. For a unit step and a stable closed loop,

\[ y(\infty)=\frac{b k_r}{a_0+b k_0}, \qquad y_m(\infty)=\frac{b_m}{a_{m0}}. \]

Therefore, denominator matching alone does not guarantee zero steady-state model-following error. Numerator matching is also required.

7.1 Sensitivity to small gain errors

Let the controller gains be close to ideal. The Laplace-domain error equation, ignoring initial conditions, is

\[ E(s)=\frac{b}{s^2+a_{m1}s+a_{m0}} \left[-\widetilde{k}_0Y(s)-\widetilde{k}_1sY(s) +\widetilde{k}_rR(s)\right]. \]

The stable model denominator filters the mismatch signal, but does not force it to disappear. Unless the regressor combination on the right tends to zero, persistent commands can produce persistent tracking error.

8. Assumptions and Boundaries of the Basic Problem

The basic formulation uses the following assumptions:

  1. Known plant order: the dominant plant model is second order.
  2. Controllability: \(b\neq0\).
  3. Known control direction: the sign of \(b\) is known.
  4. Full-state availability: both \(y\) and \(\dot y\) are available.
  5. Constant parameters: \(a_0,a_1,b\) do not vary with time in the ideal theory.
  6. Stable reference model: \(\mathbf{A}_m\) is Hurwitz.
  7. Bounded command: \(r(t)\) is bounded and piecewise continuous.
  8. No neglected dynamics: delays, high-frequency modes, noise, and saturation are excluded at this stage.

Later chapters relax or modify several of these assumptions. In this lesson they isolate the essential model-matching difficulty caused by unknown coefficients.

9. Numerical Experiment

The implementations use the actual plant

\[ \ddot y+0.8\dot y+2y=1.4u \]

and the reference model

\[ \ddot y_m+3\dot y_m+4y_m=4r. \]

The ideal gains are

\[ k_0^*=\frac{4-2}{1.4}=1.428571, \qquad k_1^*=\frac{3-0.8}{1.4}=1.571429, \qquad k_r^*=\frac{4}{1.4}=2.857143. \]

A second fixed controller is designed from the incorrect nominal values \(\hat a_0=1.5\), \(\hat a_1=1.4\), and \(\hat b=1\). It therefore uses

\[ k_0=2.5, \qquad k_1=1.6, \qquad k_r=4. \]

Every implementation integrates the same three systems: the reference model, the plant with ideal gains, and the plant with mismatched fixed gains. The comparison demonstrates the difference between exact model matching and ordinary fixed-gain design under parameter uncertainty.

10. Python Implementation

File: Chapter6_Lesson1.py

Required libraries are NumPy and Matplotlib. The differential equations are integrated from scratch with the classical fourth-order Runge-Kutta method.

"""Chapter6_Lesson1.py

Second-order model-reference tracking experiment.

The script compares:
1. an ideal fixed controller that exactly satisfies the model-matching equations;
2. a fixed controller designed from incorrect nominal plant parameters.

No adaptive update law is used in this lesson. The mismatch case motivates the
adaptive laws developed later in Chapter 6.
"""

from __future__ import annotations

from dataclasses import dataclass
from typing import Callable

import matplotlib.pyplot as plt
import numpy as np


@dataclass(frozen=True)
class PlantParameters:
    a0: float
    a1: float
    b: float


@dataclass(frozen=True)
class ModelParameters:
    am0: float
    am1: float
    bm: float


@dataclass(frozen=True)
class ControllerGains:
    k0: float
    k1: float
    kr: float


def matching_gains(plant: PlantParameters, model: ModelParameters) -> ControllerGains:
    """Return gains for u = -k0*y - k1*ydot + kr*r."""
    if abs(plant.b) < 1.0e-12:
        raise ValueError("The plant input gain b must be nonzero.")
    return ControllerGains(
        k0=(model.am0 - plant.a0) / plant.b,
        k1=(model.am1 - plant.a1) / plant.b,
        kr=model.bm / plant.b,
    )


def command(t: float) -> float:
    """Bounded reference command used by every implementation."""
    if t < 6.0:
        return 1.0
    return 0.5 + 0.5 * np.sin(0.8 * (t - 6.0))


def derivative(
    t: float,
    state: np.ndarray,
    plant: PlantParameters,
    model: ModelParameters,
    ideal: ControllerGains,
    nominal: ControllerGains,
) -> np.ndarray:
    """Dynamics of ideal plant, mismatched plant, and reference model."""
    yi, vi, yn, vn, ym, vm = state
    r = command(t)

    ui = -ideal.k0 * yi - ideal.k1 * vi + ideal.kr * r
    un = -nominal.k0 * yn - nominal.k1 * vn + nominal.kr * r

    dyi = vi
    dvi = -plant.a0 * yi - plant.a1 * vi + plant.b * ui

    dyn = vn
    dvn = -plant.a0 * yn - plant.a1 * vn + plant.b * un

    dym = vm
    dvm = -model.am0 * ym - model.am1 * vm + model.bm * r

    return np.array([dyi, dvi, dyn, dvn, dym, dvm], dtype=float)


def rk4_step(
    f: Callable[..., np.ndarray],
    t: float,
    x: np.ndarray,
    h: float,
    *args: object,
) -> np.ndarray:
    """One classical fourth-order Runge-Kutta step."""
    k1 = f(t, x, *args)
    k2 = f(t + 0.5 * h, x + 0.5 * h * k1, *args)
    k3 = f(t + 0.5 * h, x + 0.5 * h * k2, *args)
    k4 = f(t + h, x + h * k3, *args)
    return x + (h / 6.0) * (k1 + 2.0 * k2 + 2.0 * k3 + k4)


def main() -> None:
    actual = PlantParameters(a0=2.0, a1=0.8, b=1.4)
    reference_model = ModelParameters(am0=4.0, am1=3.0, bm=4.0)

    ideal = matching_gains(actual, reference_model)

    # The nominal design uses incorrect plant parameters and is intentionally
    # not model matching for the actual plant.
    nominal_model = PlantParameters(a0=1.5, a1=1.4, b=1.0)
    nominal = matching_gains(nominal_model, reference_model)

    dt = 0.002
    final_time = 18.0
    time = np.arange(0.0, final_time + dt, dt)
    state = np.zeros((time.size, 6), dtype=float)

    # Nonzero plant-model initial mismatch makes the homogeneous error
    # transient visible even for the ideal matching controller.
    state[0] = np.array([0.25, -0.10, 0.25, -0.10, 0.0, 0.0])

    for index in range(time.size - 1):
        state[index + 1] = rk4_step(
            derivative,
            time[index],
            state[index],
            dt,
            actual,
            reference_model,
            ideal,
            nominal,
        )

    yi, vi, yn, vn, ym, vm = state.T
    reference = np.array([command(t) for t in time])
    error_ideal = yi - ym
    error_nominal = yn - ym

    control_ideal = -ideal.k0 * yi - ideal.k1 * vi + ideal.kr * reference
    control_nominal = -nominal.k0 * yn - nominal.k1 * vn + nominal.kr * reference

    rms_ideal = float(np.sqrt(np.mean(error_ideal**2)))
    rms_nominal = float(np.sqrt(np.mean(error_nominal**2)))

    print("Ideal gains:", ideal)
    print("Mismatched nominal gains:", nominal)
    print(f"RMS tracking error, ideal matching: {rms_ideal:.6f}")
    print(f"RMS tracking error, nominal mismatch: {rms_nominal:.6f}")

    data = np.column_stack(
        (
            time,
            reference,
            ym,
            yi,
            yn,
            error_ideal,
            error_nominal,
            control_ideal,
            control_nominal,
        )
    )
    np.savetxt(
        "Chapter6_Lesson1_results.csv",
        data,
        delimiter=",",
        header=(
            "time,reference,model_output,ideal_output,nominal_output,"
            "ideal_error,nominal_error,ideal_control,nominal_control"
        ),
        comments="",
    )

    figure, axes = plt.subplots(3, 1, figsize=(10, 10), sharex=True)
    axes[0].plot(time, ym, label="reference model")
    axes[0].plot(time, yi, "--", label="ideal matched plant")
    axes[0].plot(time, yn, ":", label="mismatched fixed controller")
    axes[0].set_ylabel("output")
    axes[0].grid(True)
    axes[0].legend()

    axes[1].plot(time, error_ideal, label="ideal error")
    axes[1].plot(time, error_nominal, label="mismatch error")
    axes[1].set_ylabel("tracking error")
    axes[1].grid(True)
    axes[1].legend()

    axes[2].plot(time, control_ideal, label="ideal control")
    axes[2].plot(time, control_nominal, label="nominal control")
    axes[2].set_xlabel("time [s]")
    axes[2].set_ylabel("control input")
    axes[2].grid(True)
    axes[2].legend()

    figure.suptitle("Chapter 6, Lesson 1: Second-Order Tracking Problem")
    figure.tight_layout()
    plt.show()


if __name__ == "__main__":
    main()

11. C++ Implementation

File: Chapter6_Lesson1.cpp

This version uses only the C++17 standard library, implements RK4 from scratch, and writes a CSV file for plotting in another tool.

// Chapter6_Lesson1.cpp
// Second-order model-reference tracking with ideal and mismatched fixed gains.
// Build example: g++ -std=c++17 -O2 Chapter6_Lesson1.cpp -o Chapter6_Lesson1

#include <array>
#include <cmath>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <stdexcept>
#include <string>

struct PlantParameters {
    double a0;
    double a1;
    double b;
};

struct ModelParameters {
    double am0;
    double am1;
    double bm;
};

struct ControllerGains {
    double k0;
    double k1;
    double kr;
};

using State = std::array<double, 6>;

ControllerGains matchingGains(const PlantParameters& plant,
                              const ModelParameters& model) {
    if (std::abs(plant.b) < 1.0e-12) {
        throw std::invalid_argument("The plant input gain b must be nonzero.");
    }
    return {
        (model.am0 - plant.a0) / plant.b,
        (model.am1 - plant.a1) / plant.b,
        model.bm / plant.b,
    };
}

double command(double t) {
    if (t < 6.0) {
        return 1.0;
    }
    return 0.5 + 0.5 * std::sin(0.8 * (t - 6.0));
}

State addScaled(const State& x, const State& dx, double scale) {
    State result{};
    for (std::size_t i = 0; i < x.size(); ++i) {
        result[i] = x[i] + scale * dx[i];
    }
    return result;
}

State derivative(double t,
                 const State& x,
                 const PlantParameters& plant,
                 const ModelParameters& model,
                 const ControllerGains& ideal,
                 const ControllerGains& nominal) {
    const double yi = x[0];
    const double vi = x[1];
    const double yn = x[2];
    const double vn = x[3];
    const double ym = x[4];
    const double vm = x[5];
    const double r = command(t);

    const double ui = -ideal.k0 * yi - ideal.k1 * vi + ideal.kr * r;
    const double un = -nominal.k0 * yn - nominal.k1 * vn + nominal.kr * r;

    return {
        vi,
        -plant.a0 * yi - plant.a1 * vi + plant.b * ui,
        vn,
        -plant.a0 * yn - plant.a1 * vn + plant.b * un,
        vm,
        -model.am0 * ym - model.am1 * vm + model.bm * r,
    };
}

State rk4Step(double t,
              const State& x,
              double h,
              const PlantParameters& plant,
              const ModelParameters& model,
              const ControllerGains& ideal,
              const ControllerGains& nominal) {
    const State k1 = derivative(t, x, plant, model, ideal, nominal);
    const State k2 = derivative(
        t + 0.5 * h, addScaled(x, k1, 0.5 * h), plant, model, ideal, nominal);
    const State k3 = derivative(
        t + 0.5 * h, addScaled(x, k2, 0.5 * h), plant, model, ideal, nominal);
    const State k4 = derivative(
        t + h, addScaled(x, k3, h), plant, model, ideal, nominal);

    State next{};
    for (std::size_t i = 0; i < x.size(); ++i) {
        next[i] = x[i] + (h / 6.0) * (k1[i] + 2.0 * k2[i] + 2.0 * k3[i] + k4[i]);
    }
    return next;
}

int main() {
    try {
        const PlantParameters actual{2.0, 0.8, 1.4};
        const ModelParameters model{4.0, 3.0, 4.0};
        const ControllerGains ideal = matchingGains(actual, model);

        const PlantParameters incorrectNominalPlant{1.5, 1.4, 1.0};
        const ControllerGains nominal = matchingGains(incorrectNominalPlant, model);

        const double dt = 0.002;
        const double finalTime = 18.0;
        const int steps = static_cast<int>(std::llround(finalTime / dt));

        State x{0.25, -0.10, 0.25, -0.10, 0.0, 0.0};
        double squaredIdealError = 0.0;
        double squaredNominalError = 0.0;

        std::ofstream csv("Chapter6_Lesson1_results_cpp.csv");
        if (!csv) {
            throw std::runtime_error("Could not open the CSV output file.");
        }
        csv << "time,reference,model_output,ideal_output,nominal_output,"
               "ideal_error,nominal_error,ideal_control,nominal_control\n";
        csv << std::setprecision(12);

        for (int step = 0; step <= steps; ++step) {
            const double t = step * dt;
            const double r = command(t);
            const double idealError = x[0] - x[4];
            const double nominalError = x[2] - x[4];
            const double idealControl = -ideal.k0 * x[0] - ideal.k1 * x[1] + ideal.kr * r;
            const double nominalControl =
                -nominal.k0 * x[2] - nominal.k1 * x[3] + nominal.kr * r;

            squaredIdealError += idealError * idealError;
            squaredNominalError += nominalError * nominalError;

            csv << t << ',' << r << ',' << x[4] << ',' << x[0] << ',' << x[2] << ','
                << idealError << ',' << nominalError << ',' << idealControl << ','
                << nominalControl << '\n';

            if (step < steps) {
                x = rk4Step(t, x, dt, actual, model, ideal, nominal);
            }
        }

        const double sampleCount = static_cast<double>(steps + 1);
        std::cout << std::fixed << std::setprecision(6);
        std::cout << "Ideal gains: k0=" << ideal.k0 << ", k1=" << ideal.k1
                  << ", kr=" << ideal.kr << '\n';
        std::cout << "Mismatched nominal gains: k0=" << nominal.k0
                  << ", k1=" << nominal.k1 << ", kr=" << nominal.kr << '\n';
        std::cout << "RMS tracking error, ideal matching: "
                  << std::sqrt(squaredIdealError / sampleCount) << '\n';
        std::cout << "RMS tracking error, nominal mismatch: "
                  << std::sqrt(squaredNominalError / sampleCount) << '\n';
        std::cout << "Results written to Chapter6_Lesson1_results_cpp.csv\n";
    } catch (const std::exception& error) {
        std::cerr << "Error: " << error.what() << '\n';
        return 1;
    }
    return 0;
}

12. Java Implementation

File: Chapter6_Lesson1.java

The Java implementation uses the standard library, Java records for parameter groupings, and a handwritten RK4 integrator.

// Chapter6_Lesson1.java
// Second-order model-reference tracking with ideal and mismatched fixed gains.
// Build and run: javac Chapter6_Lesson1.java && java Chapter6_Lesson1

import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Locale;

public final class Chapter6_Lesson1 {
    private record PlantParameters(double a0, double a1, double b) {}
    private record ModelParameters(double am0, double am1, double bm) {}
    private record ControllerGains(double k0, double k1, double kr) {}

    private Chapter6_Lesson1() {}

    private static ControllerGains matchingGains(
            PlantParameters plant, ModelParameters model) {
        if (Math.abs(plant.b()) < 1.0e-12) {
            throw new IllegalArgumentException("The plant input gain b must be nonzero.");
        }
        return new ControllerGains(
                (model.am0() - plant.a0()) / plant.b(),
                (model.am1() - plant.a1()) / plant.b(),
                model.bm() / plant.b());
    }

    private static double command(double time) {
        if (time < 6.0) {
            return 1.0;
        }
        return 0.5 + 0.5 * Math.sin(0.8 * (time - 6.0));
    }

    private static double[] derivative(
            double time,
            double[] state,
            PlantParameters plant,
            ModelParameters model,
            ControllerGains ideal,
            ControllerGains nominal) {
        double yi = state[0];
        double vi = state[1];
        double yn = state[2];
        double vn = state[3];
        double ym = state[4];
        double vm = state[5];
        double reference = command(time);

        double idealControl =
                -ideal.k0() * yi - ideal.k1() * vi + ideal.kr() * reference;
        double nominalControl =
                -nominal.k0() * yn - nominal.k1() * vn + nominal.kr() * reference;

        return new double[] {
            vi,
            -plant.a0() * yi - plant.a1() * vi + plant.b() * idealControl,
            vn,
            -plant.a0() * yn - plant.a1() * vn + plant.b() * nominalControl,
            vm,
            -model.am0() * ym - model.am1() * vm + model.bm() * reference
        };
    }

    private static double[] addScaled(double[] state, double[] direction, double scale) {
        double[] result = new double[state.length];
        for (int i = 0; i < state.length; i++) {
            result[i] = state[i] + scale * direction[i];
        }
        return result;
    }

    private static double[] rk4Step(
            double time,
            double[] state,
            double step,
            PlantParameters plant,
            ModelParameters model,
            ControllerGains ideal,
            ControllerGains nominal) {
        double[] k1 = derivative(time, state, plant, model, ideal, nominal);
        double[] k2 = derivative(
                time + 0.5 * step,
                addScaled(state, k1, 0.5 * step),
                plant,
                model,
                ideal,
                nominal);
        double[] k3 = derivative(
                time + 0.5 * step,
                addScaled(state, k2, 0.5 * step),
                plant,
                model,
                ideal,
                nominal);
        double[] k4 = derivative(
                time + step,
                addScaled(state, k3, step),
                plant,
                model,
                ideal,
                nominal);

        double[] next = new double[state.length];
        for (int i = 0; i < state.length; i++) {
            next[i] = state[i]
                    + (step / 6.0) * (k1[i] + 2.0 * k2[i] + 2.0 * k3[i] + k4[i]);
        }
        return next;
    }

    public static void main(String[] args) {
        Locale.setDefault(Locale.US);

        PlantParameters actual = new PlantParameters(2.0, 0.8, 1.4);
        ModelParameters model = new ModelParameters(4.0, 3.0, 4.0);
        ControllerGains ideal = matchingGains(actual, model);

        PlantParameters incorrectNominalPlant = new PlantParameters(1.5, 1.4, 1.0);
        ControllerGains nominal = matchingGains(incorrectNominalPlant, model);

        double dt = 0.002;
        double finalTime = 18.0;
        int steps = (int) Math.round(finalTime / dt);
        double[] state = {0.25, -0.10, 0.25, -0.10, 0.0, 0.0};

        double squaredIdealError = 0.0;
        double squaredNominalError = 0.0;
        Path output = Path.of("Chapter6_Lesson1_results_java.csv");

        try (BufferedWriter writer = Files.newBufferedWriter(
                output, StandardCharsets.UTF_8)) {
            writer.write("time,reference,model_output,ideal_output,nominal_output,"
                    + "ideal_error,nominal_error,ideal_control,nominal_control\n");

            for (int index = 0; index <= steps; index++) {
                double time = index * dt;
                double reference = command(time);
                double idealError = state[0] - state[4];
                double nominalError = state[2] - state[4];
                double idealControl =
                        -ideal.k0() * state[0] - ideal.k1() * state[1]
                                + ideal.kr() * reference;
                double nominalControl =
                        -nominal.k0() * state[2] - nominal.k1() * state[3]
                                + nominal.kr() * reference;

                squaredIdealError += idealError * idealError;
                squaredNominalError += nominalError * nominalError;

                writer.write(String.format(
                        Locale.US,
                        "%.12f,%.12f,%.12f,%.12f,%.12f,%.12f,%.12f,%.12f,%.12f%n",
                        time,
                        reference,
                        state[4],
                        state[0],
                        state[2],
                        idealError,
                        nominalError,
                        idealControl,
                        nominalControl));

                if (index < steps) {
                    state = rk4Step(time, state, dt, actual, model, ideal, nominal);
                }
            }
        } catch (IOException error) {
            System.err.println("Could not write results: " + error.getMessage());
            System.exit(1);
        }

        double sampleCount = steps + 1.0;
        System.out.printf(
                "Ideal gains: k0=%.6f, k1=%.6f, kr=%.6f%n",
                ideal.k0(), ideal.k1(), ideal.kr());
        System.out.printf(
                "Mismatched nominal gains: k0=%.6f, k1=%.6f, kr=%.6f%n",
                nominal.k0(), nominal.k1(), nominal.kr());
        System.out.printf(
                "RMS tracking error, ideal matching: %.6f%n",
                Math.sqrt(squaredIdealError / sampleCount));
        System.out.printf(
                "RMS tracking error, nominal mismatch: %.6f%n",
                Math.sqrt(squaredNominalError / sampleCount));
        System.out.println("Results written to " + output.toAbsolutePath());
    }
}

13. MATLAB and Simulink Implementation

File: Chapter6_Lesson1.m

The script performs an RK4 simulation in MATLAB and, when Simulink is licensed, programmatically creates and simulates Chapter6_Lesson1_Simulink.slx.

% Chapter6_Lesson1.m
% Second-order model-reference tracking with ideal and mismatched fixed gains.
% The script also builds an equivalent Simulink model when Simulink is licensed.

clear; close all; clc;

% Actual plant: y_ddot + a1*y_dot + a0*y = b*u
plant.a0 = 2.0;
plant.a1 = 0.8;
plant.b  = 1.4;

% Reference model: ym_ddot + am1*ym_dot + am0*ym = bm*r
model.am0 = 4.0;
model.am1 = 3.0;
model.bm  = 4.0;

% Ideal model-matching gains for u = -k0*y - k1*y_dot + kr*r
ideal.k0 = (model.am0 - plant.a0) / plant.b;
ideal.k1 = (model.am1 - plant.a1) / plant.b;
ideal.kr = model.bm / plant.b;

% Intentionally incorrect nominal plant used for fixed-gain design
nominalPlant.a0 = 1.5;
nominalPlant.a1 = 1.4;
nominalPlant.b  = 1.0;
nominal.k0 = (model.am0 - nominalPlant.a0) / nominalPlant.b;
nominal.k1 = (model.am1 - nominalPlant.a1) / nominalPlant.b;
nominal.kr = model.bm / nominalPlant.b;

stepSize = 0.002;
finalTime = 18.0;
time = (0:stepSize:finalTime).';
state = zeros(numel(time), 6);
state(1, :) = [0.25, -0.10, 0.25, -0.10, 0.0, 0.0];

for index = 1:(numel(time) - 1)
    currentTime = time(index);
    currentState = state(index, :).';

    k1 = dynamics(currentTime, currentState, plant, model, ideal, nominal);
    k2 = dynamics(currentTime + stepSize/2, currentState + stepSize*k1/2, ...
        plant, model, ideal, nominal);
    k3 = dynamics(currentTime + stepSize/2, currentState + stepSize*k2/2, ...
        plant, model, ideal, nominal);
    k4 = dynamics(currentTime + stepSize, currentState + stepSize*k3, ...
        plant, model, ideal, nominal);

    state(index + 1, :) = (currentState + ...
        stepSize*(k1 + 2*k2 + 2*k3 + k4)/6).';
end

yi = state(:, 1);
vi = state(:, 2);
yn = state(:, 3);
vn = state(:, 4);
ym = state(:, 5);
reference = arrayfun(@commandSignal, time);

errorIdeal = yi - ym;
errorNominal = yn - ym;
controlIdeal = -ideal.k0*yi - ideal.k1*vi + ideal.kr*reference;
controlNominal = -nominal.k0*yn - nominal.k1*vn + nominal.kr*reference;

rmsIdeal = sqrt(mean(errorIdeal.^2));
rmsNominal = sqrt(mean(errorNominal.^2));

fprintf('Ideal gains: k0=%.6f, k1=%.6f, kr=%.6f\n', ...
    ideal.k0, ideal.k1, ideal.kr);
fprintf('Mismatched nominal gains: k0=%.6f, k1=%.6f, kr=%.6f\n', ...
    nominal.k0, nominal.k1, nominal.kr);
fprintf('RMS tracking error, ideal matching: %.6f\n', rmsIdeal);
fprintf('RMS tracking error, nominal mismatch: %.6f\n', rmsNominal);

results = table(time, reference, ym, yi, yn, errorIdeal, errorNominal, ...
    controlIdeal, controlNominal);
writetable(results, 'Chapter6_Lesson1_results_matlab.csv');

figure('Name', 'Chapter 6 Lesson 1');
tiledlayout(3, 1);
nexttile;
plot(time, ym, 'LineWidth', 1.4); hold on;
plot(time, yi, '--', 'LineWidth', 1.2);
plot(time, yn, ':', 'LineWidth', 1.4);
grid on; ylabel('Output');
legend('Reference model', 'Ideal matched plant', 'Mismatched fixed controller');
title('Second-Order Model-Reference Tracking');

nexttile;
plot(time, errorIdeal, 'LineWidth', 1.2); hold on;
plot(time, errorNominal, 'LineWidth', 1.2);
grid on; ylabel('Tracking error');
legend('Ideal error', 'Mismatch error');

nexttile;
plot(time, controlIdeal, 'LineWidth', 1.2); hold on;
plot(time, controlNominal, 'LineWidth', 1.2);
grid on; xlabel('Time [s]'); ylabel('Control input');
legend('Ideal control', 'Nominal control');

% Optional programmatic Simulink construction. It uses the ideal controller
% and writes y, ym, and e to the MATLAB workspace.
if license('test', 'Simulink')
    buildSimulinkModel(plant, model, ideal, time, reference);
else
    fprintf('Simulink is not licensed; skipped model generation.\n');
end

function derivative = dynamics(t, state, plant, model, ideal, nominal)
    yi = state(1); vi = state(2);
    yn = state(3); vn = state(4);
    ym = state(5); vm = state(6);
    reference = commandSignal(t);

    idealControl = -ideal.k0*yi - ideal.k1*vi + ideal.kr*reference;
    nominalControl = -nominal.k0*yn - nominal.k1*vn + nominal.kr*reference;

    derivative = [
        vi;
        -plant.a0*yi - plant.a1*vi + plant.b*idealControl;
        vn;
        -plant.a0*yn - plant.a1*vn + plant.b*nominalControl;
        vm;
        -model.am0*ym - model.am1*vm + model.bm*reference
    ];
end

function value = commandSignal(t)
    if t < 6.0
        value = 1.0;
    else
        value = 0.5 + 0.5*sin(0.8*(t - 6.0));
    end
end

function buildSimulinkModel(plant, model, ideal, time, reference)
    modelName = 'Chapter6_Lesson1_Simulink';
    if bdIsLoaded(modelName)
        close_system(modelName, 0);
    end
    if isfile([modelName '.slx'])
        delete([modelName '.slx']);
    end

    rSim = [time, reference]; %#ok<NASGU>
    assignin('base', 'rSim', rSim);

    new_system(modelName);
    open_system(modelName);
    set_param(modelName, 'StopTime', num2str(time(end)), 'Solver', 'ode45');

    add_block('simulink/Sources/From Workspace', [modelName '/Reference'], ...
        'VariableName', 'rSim', 'Position', [35 145 125 175]);
    add_block('simulink/Continuous/State-Space', [modelName '/Reference Model'], ...
        'A', mat2str([0 1; -model.am0 -model.am1]), ...
        'B', mat2str([0; model.bm]), 'C', mat2str([1 0]), 'D', '0', ...
        'Position', [190 35 330 95]);
    add_block('simulink/Continuous/State-Space', [modelName '/Plant'], ...
        'A', mat2str([0 1; -plant.a0 -plant.a1]), ...
        'B', mat2str([0; plant.b]), 'C', mat2str(eye(2)), 'D', mat2str([0; 0]), ...
        'InitialCondition', mat2str([0.25; -0.10]), ...
        'Position', [555 195 695 265]);
    add_block('simulink/Signal Routing/Demux', [modelName '/Plant State Demux'], ...
        'Outputs', '2', 'Position', [745 195 750 265]);

    add_block('simulink/Math Operations/Gain', [modelName '/Minus k0'], ...
        'Gain', num2str(-ideal.k0), 'Position', [820 180 900 210]);
    add_block('simulink/Math Operations/Gain', [modelName '/Minus k1'], ...
        'Gain', num2str(-ideal.k1), 'Position', [820 240 900 270]);
    add_block('simulink/Math Operations/Gain', [modelName '/kr'], ...
        'Gain', num2str(ideal.kr), 'Position', [190 135 270 165]);
    add_block('simulink/Math Operations/Sum', [modelName '/Controller Sum'], ...
        'Inputs', '+++', 'Position', [430 190 455 260]);
    add_block('simulink/Math Operations/Sum', [modelName '/Tracking Error'], ...
        'Inputs', '+-', 'Position', [825 65 850 105]);

    add_block('simulink/Sinks/To Workspace', [modelName '/y'], ...
        'VariableName', 'y_sim', 'SaveFormat', 'Structure With Time', ...
        'Position', [930 155 1030 185]);
    add_block('simulink/Sinks/To Workspace', [modelName '/ym'], ...
        'VariableName', 'ym_sim', 'SaveFormat', 'Structure With Time', ...
        'Position', [390 35 490 65]);
    add_block('simulink/Sinks/To Workspace', [modelName '/e'], ...
        'VariableName', 'e_sim', 'SaveFormat', 'Structure With Time', ...
        'Position', [930 75 1030 105]);

    add_line(modelName, 'Reference/1', 'Reference Model/1');
    add_line(modelName, 'Reference/1', 'kr/1');
    add_line(modelName, 'kr/1', 'Controller Sum/1');
    add_line(modelName, 'Controller Sum/1', 'Plant/1');
    add_line(modelName, 'Plant/1', 'Plant State Demux/1');
    add_line(modelName, 'Plant State Demux/1', 'Minus k0/1');
    add_line(modelName, 'Plant State Demux/2', 'Minus k1/1');
    add_line(modelName, 'Minus k0/1', 'Controller Sum/2');
    add_line(modelName, 'Minus k1/1', 'Controller Sum/3');
    add_line(modelName, 'Reference Model/1', 'ym/1');
    add_line(modelName, 'Reference Model/1', 'Tracking Error/2');
    add_line(modelName, 'Plant State Demux/1', 'Tracking Error/1');
    add_line(modelName, 'Plant State Demux/1', 'y/1');
    add_line(modelName, 'Tracking Error/1', 'e/1');

    save_system(modelName);
    sim(modelName);
    fprintf('Created and simulated %s.slx\n', modelName);
end

14. Wolfram Mathematica Implementation

File: Chapter6_Lesson1.nb

The notebook uses NDSolveValue for integration and produces output, error, and control-input plots.

Notebook[{
 Cell["Chapter 6, Lesson 1: Tracking Problem for a Second-Order Plant", "Title"],
 Cell["Ideal model matching versus a fixed controller designed from incorrect nominal parameters. No adaptive update law is used in this lesson.", "Text"],
 Cell[BoxData[
  ToBoxes[
   Defer[
    ClearAll["Global`*"];

    actual = <|"a0" -> 2.0, "a1" -> 0.8, "b" -> 1.4|>;
    model = <|"am0" -> 4.0, "am1" -> 3.0, "bm" -> 4.0|>;
    nominalPlant = <|"a0" -> 1.5, "a1" -> 1.4, "b" -> 1.0|>;

    matchingGains[plant_, referenceModel_] := <|
      "k0" -> (referenceModel["am0"] - plant["a0"])/plant["b"],
      "k1" -> (referenceModel["am1"] - plant["a1"])/plant["b"],
      "kr" -> referenceModel["bm"]/plant["b"]
    |>;

    ideal = matchingGains[actual, model];
    nominal = matchingGains[nominalPlant, model];

    command[t_?NumericQ] := Piecewise[{
      {1.0, t < 6.0}
    }, 0.5 + 0.5 Sin[0.8 (t - 6.0)]];

    initialConditions = {
      yi[0] == 0.25, vi[0] == -0.10,
      yn[0] == 0.25, vn[0] == -0.10,
      ym[0] == 0.0, vm[0] == 0.0
    };

    equations = {
      yi'[t] == vi[t],
      vi'[t] == -actual["a0"] yi[t] - actual["a1"] vi[t]
        + actual["b"] (-ideal["k0"] yi[t] - ideal["k1"] vi[t]
        + ideal["kr"] command[t]),
      yn'[t] == vn[t],
      vn'[t] == -actual["a0"] yn[t] - actual["a1"] vn[t]
        + actual["b"] (-nominal["k0"] yn[t] - nominal["k1"] vn[t]
        + nominal["kr"] command[t]),
      ym'[t] == vm[t],
      vm'[t] == -model["am0"] ym[t] - model["am1"] vm[t]
        + model["bm"] command[t]
    };

    solution = NDSolveValue[
      Join[equations, initialConditions],
      {yi, vi, yn, vn, ym, vm},
      {t, 0, 18},
      MaxStepFraction -> 1/5000
    ];

    {yiSol, viSol, ynSol, vnSol, ymSol, vmSol} = solution;
    idealError[t_?NumericQ] := yiSol[t] - ymSol[t];
    nominalError[t_?NumericQ] := ynSol[t] - ymSol[t];
    idealControl[t_?NumericQ] := -ideal["k0"] yiSol[t]
      - ideal["k1"] viSol[t] + ideal["kr"] command[t];
    nominalControl[t_?NumericQ] := -nominal["k0"] ynSol[t]
      - nominal["k1"] vnSol[t] + nominal["kr"] command[t];

    rms[f_] := Sqrt[NIntegrate[f[t]^2, {t, 0, 18}]/18];
    Print["Ideal gains: ", ideal];
    Print["Mismatched nominal gains: ", nominal];
    Print["RMS tracking error, ideal matching: ", N[rms[idealError], 8]];
    Print["RMS tracking error, nominal mismatch: ", N[rms[nominalError], 8]];

    outputPlot = Plot[
      Evaluate[{ymSol[t], yiSol[t], ynSol[t]}],
      {t, 0, 18},
      PlotLegends -> {"Reference model", "Ideal matched plant", "Mismatched fixed controller"},
      AxesLabel -> {"time [s]", "output"},
      PlotRange -> All,
      ImageSize -> Large
    ];

    errorPlot = Plot[
      Evaluate[{idealError[t], nominalError[t]}],
      {t, 0, 18},
      PlotLegends -> {"Ideal error", "Mismatch error"},
      AxesLabel -> {"time [s]", "tracking error"},
      PlotRange -> All,
      ImageSize -> Large
    ];

    controlPlot = Plot[
      Evaluate[{idealControl[t], nominalControl[t]}],
      {t, 0, 18},
      PlotLegends -> {"Ideal control", "Nominal control"},
      AxesLabel -> {"time [s]", "control input"},
      PlotRange -> All,
      ImageSize -> Large
    ];

    Column[{outputPlot, errorPlot, controlPlot}]
   ]
  ]], "Input"]
},
WindowSize -> {1200, 900},
StyleDefinitions -> "Default.nb"
]

15. Problems and Solutions

Problem 1: Derive the ideal matching gains

Consider \(\ddot y+1.2\dot y+3y=2u\) and the reference model \(\ddot y_m+4\dot y_m+5y_m=5r\). For the controller \(u=-k_0y-k_1\dot y+k_r r\), compute the ideal gains.

Solution:

Matching coefficients gives

\[ 1.2+2k_1^*=4, \qquad 3+2k_0^*=5, \qquad 2k_r^*=5. \]

Therefore,

\[ k_0^*=1, \qquad k_1^*=1.4, \qquad k_r^*=2.5. \]

Problem 2: Derive the mismatch-forced error equation

Starting from the general plant, model, and controller in this lesson, derive the scalar tracking-error equation in terms of \(\widetilde{k}_0\), \(\widetilde{k}_1\), and \(\widetilde{k}_r\).

Solution:

The closed-loop plant is

\[ \ddot y+(a_1+b k_1)\dot y+(a_0+b k_0)y=b k_r r. \]

Subtract the reference model, use \(e=y-y_m\), and add the desired error terms to both sides:

\[ \ddot e+a_{m1}\dot e+a_{m0}e =-(a_1+b k_1-a_{m1})\dot y -(a_0+b k_0-a_{m0})y+(b k_r-b_m)r. \]

Since \(a_1+b k_1^*=a_{m1}\), \(a_0+b k_0^*=a_{m0}\), and \(b k_r^*=b_m\), the result is

\[ \ddot e+a_{m1}\dot e+a_{m0}e =b(-\widetilde{k}_0y-\widetilde{k}_1\dot y +\widetilde{k}_r r). \]

Problem 3: Prove the second-order Hurwitz condition

Show that \(s^2+a_{m1}s+a_{m0}\) is Hurwitz exactly when \(a_{m1}>0\) and \(a_{m0}>0\).

Solution:

The roots satisfy

\[ \lambda_{1,2}=\frac{-a_{m1}\pm \sqrt{a_{m1}^2-4a_{m0}}}{2}. \]

If the discriminant is negative, the real part of both roots is \(-a_{m1}/2<0\) precisely when \(a_{m1}>0\). If the roots are real, their sum is \(-a_{m1}<0\) and product is \(a_{m0}>0\), so both are negative. Conversely, if both roots have negative real parts, their sum and product imply positive \(a_{m1}\) and \(a_{m0}\).

Problem 4: Numerator mismatch and step error

Suppose the denominator is exactly matched, but \(b k_r=0.8b_m\). For a unit-step command, determine the steady-state ratio \(y(\infty)/y_m(\infty)\).

Solution:

With equal denominators, the DC gains differ only by their numerators:

\[ \frac{y(\infty)}{y_m(\infty)} =\frac{b k_r/a_{m0}}{b_m/a_{m0}} =\frac{b k_r}{b_m}=0.8. \]

Thus pole matching alone leaves a 20 percent steady-state model-following error for this command.

Problem 5: State-space matching verification

Verify directly that the ideal gains satisfy \(\mathbf{A}_p+\mathbf{B}_p\mathbf{K}^*=\mathbf{A}_m\) with \(\mathbf{K}^*=[-k_0^*\;-k_1^*]\).

Solution:

\[ \mathbf{A}_p+\mathbf{B}_p\mathbf{K}^* =\begin{bmatrix}0&1\\-a_0&-a_1\end{bmatrix} +\begin{bmatrix}0\\b\end{bmatrix} \begin{bmatrix}-k_0^*&-k_1^*\end{bmatrix}. \]

\[ =\begin{bmatrix}0&1\\ -(a_0+b k_0^*)&-(a_1+b k_1^*)\end{bmatrix} =\begin{bmatrix}0&1\\-a_{m0}&-a_{m1}\end{bmatrix} =\mathbf{A}_m. \]

Problem 6: Interpret a constant mismatch forcing

Assume the command and plant states settle to constants, so the right side of the mismatch error equation approaches a nonzero constant \(d_0\). Determine the limiting error.

Solution:

At steady state, \(\dot e=\ddot e=0\), so

\[ a_{m0}e_{ss}=d_0, \qquad e_{ss}=\frac{d_0}{a_{m0}}. \]

Stability of the error filter limits the response, but a nonzero persistent forcing generally produces nonzero tracking error. This is the motivation for changing the controller parameters online.

16. Summary

A second-order MRAC problem begins with an uncertain plant, a stable second-order reference model, and a parameterized controller. Exact coefficient matching produces unique ideal gains and the homogeneous stable error dynamics \(\dot{\mathbf{e}}=\mathbf{A}_m\mathbf{e}\). When the controller gains differ from their ideal values, the error system is driven by a regressor-dependent forcing term. The next lesson examines how the reference-model coefficients determine speed, damping, control effort, and achievable transient behavior.

17. References

  1. Parks, P.C. (1966). Liapunov redesign of model reference adaptive control systems. IEEE Transactions on Automatic Control, 11(3), 362–367. doi:10.1109/TAC.1966.1098361.
  2. Landau, I.D. (1969). A hyperstability criterion for model reference adaptive control systems. IEEE Transactions on Automatic Control, 14(5), 552–555. doi:10.1109/TAC.1969.1099237.
  3. Monopoli, R.V. (1974). Model reference adaptive control with an augmented error signal. IEEE Transactions on Automatic Control, 19(5), 474–484.
  4. Feuer, A., & Morse, A.S. (1978). Adaptive control of single-input, single-output linear systems. IEEE Transactions on Automatic Control, 23(4), 557–569. doi:10.1109/TAC.1978.1101822.
  5. Narendra, K.S., & Valavani, L.S. (1978). Stable adaptive controller design—direct control. IEEE Transactions on Automatic Control, 23(4), 570–583. doi:10.1109/TAC.1978.1101823.
  6. Morse, A.S. (1980). Global stability of parameter-adaptive control systems. IEEE Transactions on Automatic Control, 25(3), 433–439. doi:10.1109/TAC.1980.1102364.
  7. Narendra, K.S., Lin, Y.-H., & Valavani, L.S. (1980). Stable adaptive controller design, Part II: Proof of stability. IEEE Transactions on Automatic Control, 25(3), 440–448. doi:10.1109/TAC.1980.1102362.
Support CaaT Academy

Help keep these engineering tutorials free and growing

If these lessons, examples, and project pages help you, a small donation supports the continued creation and improvement of free control, robotics, software, and engineering education resources.

Created and maintained by Abolfazl Mohammadijoo.