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:
- Known plant order: the dominant plant model is second order.
- Controllability: \(b\neq0\).
- Known control direction: the sign of \(b\) is known.
- Full-state availability: both \(y\) and \(\dot y\) are available.
- Constant parameters: \(a_0,a_1,b\) do not vary with time in the ideal theory.
- Stable reference model: \(\mathbf{A}_m\) is Hurwitz.
- Bounded command: \(r(t)\) is bounded and piecewise continuous.
- 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
- 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.
- 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.
- Monopoli, R.V. (1974). Model reference adaptive control with an augmented error signal. IEEE Transactions on Automatic Control, 19(5), 474–484.
- 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.
- 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.
- 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.
- 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.
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.