Chapter 22: Robust Adaptive Control – Interaction with Robust Methods
Lesson 4: When to Prefer Pure Robust Control vs Adaptive Control
This lesson develops a rigorous decision framework for choosing between a fixed robust controller and an adaptive controller. The central issue is not whether adaptation is more advanced, but whether online parameter adjustment supplies information and performance that a fixed worst-case design cannot provide without introducing unacceptable sensitivity, complexity, or certification burden.
1. Learning Objectives and the Design Question
After completing this lesson, students should be able to:
- separate parametric uncertainty from unmodeled dynamics and disturbances;
- derive a worst-case stability guarantee for a fixed robust controller;
- derive a Lyapunov inequality for a robustified adaptive controller;
- identify situations in which adaptation improves achievable performance;
- recognize situations in which adaptation creates unnecessary risk; and
- justify a robust, adaptive, or combined architecture using explicit criteria.
Let a plant depend on an uncertain constant or slowly varying parameter \( \theta(t) \in \Theta \) and on an unmodeled dynamic operator \( \Delta \in \mathcal{D} \). The design choice is between a fixed controller \( K_R \) and an online-adjusted controller \( K_A(\hat{\theta}(t)) \). A useful abstract objective is
\[ \text{choose } \mathcal{C} \in \{K_R,K_A\} \text{ so that }\quad \mathcal{S}(\mathcal{C})=1,\qquad J(\mathcal{C}) \le J_{\mathrm{req} },\qquad C_{\mathrm{impl} }(\mathcal{C}) \le C_{\max}, \]
where \( \mathcal{S}=1 \) denotes the required stability property, \( J \) is a performance measure, and \( C_{\mathrm{impl} } \) represents implementation, validation, and certification cost. No controller class dominates all three terms for every uncertainty structure.
2. Classify the Uncertainty Before Selecting the Controller
Consider the control-oriented decomposition
\[ \dot{x}=f(x,u,\theta)+B_d d+\delta_f(x,u,t),\qquad y=h(x)+n, \]
where \( \theta \) denotes structured parameters, \( d \) an exogenous disturbance, \( \delta_f \) unmodeled dynamics, and \( n \) measurement noise. These terms have different implications:
- Bounded structured parametric uncertainty: often suitable for either robust design or adaptation. Adaptation is valuable when the operating range is too wide for one fixed high-performance controller.
- Unknown but slowly varying parameters: favorable to adaptation when the variation is observable through available regressors.
- High-frequency unmodeled dynamics and delay: generally favor bandwidth limitation, filtering, and robust margins rather than aggressive adaptation.
- Broadband disturbances and measurement noise: do not constitute useful parameter information and can drive parameter drift unless leakage, projection, dead zones, or normalization are used.
- Abrupt topology or sign changes: may violate the matching conditions of a standard adaptive law; supervisory switching or fault accommodation may be required.
The key distinction is whether uncertainty is learnable in the chosen controller parameterization. A disturbance can be large yet not learnable; a parameter change can be small yet highly valuable to estimate.
3. Pure Robust Control: One Controller for an Uncertainty Set
In pure robust control, the controller parameters are fixed after design. The objective is to prove stability and performance uniformly over an admissible set. For the scalar uncertain plant
\[ \dot{x}=a x+b u+d,\qquad a\in[a_{\min},a_{\max}],\quad b\in[b_{\min},b_{\max}],\quad b_{\min}>0, \]
choose the fixed state feedback \( u=-k x \). Then
\[ \dot{x}=-(bk-a)x+d. \]
If a desired decay rate \( \lambda>0 \) is prescribed, the worst-case design condition is
\[ b_{\min}k-a_{\max}\ge\lambda. \]
For \( V=\tfrac12x^2 \) and \( |d(t)|\le\bar d \),
\[ \dot V=x\dot x\le-\lambda x^2+|x|\bar d. \]
The comparison inequality yields the input-to-state bound
\[ |x(t)|\le e^{-\lambda t}|x(0)|+ \frac{\bar d}{\lambda}\left(1-e^{-\lambda t}\right),\qquad \limsup_{t\rightarrow\infty}|x(t)|\le\frac{\bar d}{\lambda}. \]
The guarantee is explicit and does not depend on excitation. Its cost is conservatism: the gain is selected for the worst combination \( (a_{\max},b_{\min}) \), even when the actual plant is much easier to control.
3.1 Common Quadratic Robustness for a Polytopic LTI Family
Let the closed-loop matrix belong to a convex polytope
\[ A_{\mathrm{cl} }(\alpha)=\sum_{i=1}^{N}\alpha_iA_i,\qquad \alpha_i\ge0,\qquad\sum_{i=1}^{N}\alpha_i=1. \]
If there exist \( P=P^T>0 \) and \( Q=Q^T>0 \) satisfying
\[ A_i^TP+PA_i\le-Q,\qquad i=1,\ldots,N, \]
then every convex combination is quadratically stable because
\[ A_{\mathrm{cl} }^TP+PA_{\mathrm{cl} } =\sum_{i=1}^{N}\alpha_i(A_i^TP+PA_i) \le-\sum_{i=1}^{N}\alpha_iQ=-Q. \]
This is a representative pure robust certificate: it is checked offline, remains valid without parameter identification, and provides a uniform Lyapunov function over the entire modeled set.
4. Adaptive Control: Adjust the Controller Using Online Error
Consider the scalar plant and reference model
\[ \dot{x}=a x+b u+d,\qquad \dot{x}_m=-a_mx_m+b_mr,\qquad a_m>0, \]
with known control direction \( b>0 \). Select the direct adaptive controller
\[ u=\hat\theta_xx+\hat\theta_rr =\hat\theta^T\phi,\qquad \phi=\begin{bmatrix}x&r\end{bmatrix}^T. \]
The ideal matching parameters satisfy
\[ a+b\theta_x^*=-a_m,\qquad b\theta_r^*=b_m. \]
With \( e=x-x_m \) and \( \tilde\theta=\hat\theta-\theta^* \), the error equation is
\[ \dot e=-a_me+b\tilde\theta^T\phi+d. \]
For the ideal gradient update \( \dot{\hat\theta}=-\Gamma b\phi e \), use
\[ V=\frac12e^2+\frac12\tilde\theta^T\Gamma^{-1}\tilde\theta. \]
When \( d=0 \) and the parameters are constant,
\[ \dot V=-a_me^2. \]
Thus the tracking error is regulated without selecting a fixed gain for the worst plant. However, the proof does not by itself guarantee convergence of \( \tilde\theta \); persistent excitation is required for parameter convergence.
4.1 Robustified Adaptive Law and Ultimate Boundedness
In the presence of disturbances and imperfect modeling, use leakage toward a nominal parameter vector and projection onto a known set:
\[ \dot{\hat\theta}=\operatorname{Proj}_{\Theta} \left[-\Gamma b\phi e-\sigma\Gamma (\hat\theta-\theta_0)\right],\qquad\sigma>0. \]
Ignoring only the nonpositive boundary contribution of the projection operator, the derivative satisfies
\[ \dot V\le-a_me^2+ed- \sigma\tilde\theta^T(\tilde\theta+\theta^*-\theta_0). \]
Using Young's inequality twice gives
\[ ed\le\frac{a_m}{2}e^2+\frac{d^2}{2a_m}, \]
\[ -\sigma\tilde\theta^T(\tilde\theta+\theta^*-\theta_0) \le-\frac{\sigma}{2}\|\tilde\theta\|^2+ \frac{\sigma}{2}\|\theta^*-\theta_0\|^2. \]
Therefore
\[ \dot V\le-\frac{a_m}{2}e^2- \frac{\sigma}{2}\|\tilde\theta\|^2+ \frac{d^2}{2a_m}+ \frac{\sigma}{2}\|\theta^*-\theta_0\|^2. \]
This establishes uniform ultimate boundedness rather than exact asymptotic convergence. The residual set shrinks with smaller disturbance magnitude and better nominal parameters, but decreasing leakage too far can weaken protection against drift.
5. Guarantee Versus Achievable Performance
The robust controller solves an offline minimax problem of the form
\[ J_R^*=\inf_{K\in\mathcal{K} } \sup_{\theta\in\Theta,\,\Delta\in\mathcal{D} } \|T_{zw}(K,\theta,\Delta)\|, \]
while an adaptive controller attempts to approach the performance of a parameter-dependent ideal controller:
\[ K_A(t)=K(\hat\theta(t)),\qquad J_A(t)\approx J(K(\theta(t)),\theta(t)) \text{ when } \hat\theta(t)\approx\theta(t). \]
Adaptation is attractive when \( J_R^*>J_{\mathrm{req} } \) because the fixed design is too conservative, but a family of local controllers can meet the requirement. This advantage exists only if the parameter-dependent performance improvement exceeds the transient cost of learning and the uncertainty is sufficiently observable.
A useful decomposition of adaptive performance is
\[ J_A=J_{\mathrm{ideal} }+J_{\mathrm{est} }+J_{\mathrm{transient} }+ J_{\mathrm{robustification} }, \]
where the last three terms represent estimation error, learning transients, and bias introduced by leakage, dead zones, filters, or projection. Pure robust control eliminates these learning terms but may have a larger worst-case conservatism term.
6. Decision Criteria
6.1 Prefer Pure Robust Control When
- the uncertainty set is credible, compact, and not excessively large;
- a single fixed controller meets all worst-case stability and performance requirements;
- unmodeled dynamics, delay, saturation, or noise dominate parametric uncertainty;
- available commands do not provide persistent or interval excitation;
- repeatability, deterministic transients, and simple verification are primary;
- controller authority is limited and learning transients cannot be tolerated;
- the plant operates near one regime, so online adjustment offers little benefit; or
- the parameterization or matching conditions are doubtful.
6.2 Prefer Adaptive Control When
- structured parameters vary over a range that makes one fixed controller overly conservative;
- the control direction and essential plant structure are known;
- the uncertain parameters enter through a usable regressor;
- signals contain enough excitation for the required tracking or identification objective;
- parameter changes are slow relative to the closed-loop and estimator dynamics;
- performance after adaptation is materially better than the robust worst-case design;
- projection bounds and safe fallback behavior can be specified; and
- online computation, monitoring, and validation are acceptable.
6.3 Prefer a Combined Robust-Adaptive Architecture When
- a robust baseline can maintain safety while adaptation improves nominal performance;
- parametric uncertainty and unmodeled dynamics are both important;
- the adaptive component can be limited, filtered, projected, or disabled by supervision; or
- certification requires a verified invariant envelope independent of estimator convergence.
flowchart TD
A["Start: characterize uncertainty and requirements"] --> B["Can one fixed controller satisfy worst-case stability and performance?"]
B -->|"yes"| C["Are repeatability and \nsimple verification dominant?"]
C -->|"yes"| R["Prefer pure robust control"]
C -->|"no"| D["Would online adjustment provide \na material performance gain?"]
B -->|"no"| D
D -->|"no"| R
D -->|"yes"| E["Is uncertainty structured,\n matched, and observable \nthrough regressors?"]
E -->|"no"| H["Use robust redesign,\n gain scheduling, switching,\n or model revision"]
E -->|"yes"| F["Are excitation, rate of variation,\n computation, and safety limits adequate?"]
F -->|"yes"| A1["Prefer adaptive control \nwith projection and \nrobust modification"]
F -->|"no"| H
A1 --> G["Add verified robust baseline \nwhen safety must not \ndepend on learning"]
7. Why Adaptation Can Be the Wrong Choice
Adaptation changes the closed loop from a fixed dynamical system into a nonlinear time-varying interconnection containing state and parameter dynamics. Several failure mechanisms follow.
7.1 Lack of Excitation
Tracking can be satisfactory while the parameter error remains nonzero. If the regressor is not persistently exciting, the information matrix
\[ W(t,T)=\int_t^{t+T}\phi(\tau)\phi^T(\tau)\,d\tau \]
may be singular or poorly conditioned. Then gain adaptation can respond to noise or disturbances rather than genuine parameter information.
7.2 Fast Adaptation Is Not Equivalent to Better Adaptation
Increasing \( \Gamma \) accelerates the ideal gradient law, but it also raises sensitivity to measurement noise, sampling error, delay, and neglected high-frequency dynamics. A practical design therefore limits estimator bandwidth below frequencies at which the plant model is no longer trustworthy. There is no universal numerical separation ratio; it must be justified from identified dynamics and robustness analysis.
7.3 Parameter Drift Under Disturbances
When \( e \) is caused by an unmodeled disturbance rather than a parameter mismatch, an integral update law can continue moving the gains. Projection prevents escape from a known safe set, while leakage and dead zones stop indefinite accumulation.
7.4 Matching Conditions May Fail
The ideal parameter vector exists only if the chosen controller structure can reproduce the desired model. If no \( \theta^* \) satisfies the matching equations, adaptation can minimize an error without eliminating the structural mismatch. A robust redesign or richer model is then more appropriate.
8. A Quantitative Selection Test
A disciplined comparison can be organized around four margins:
\[ M_S=J_{\mathrm{req} }-J_R^*,\qquad M_P=J_R^*-J_{A,\mathrm{pred} }, \]
\[ M_I=\lambda_{\min}\!\left(\int_t^{t+T} \phi\phi^T d\tau\right),\qquad M_R=\omega_{\mathrm{model} }-\omega_{\mathrm{adapt} }. \]
Here \( M_S \) is the fixed robust feasibility margin, \( M_P \) the predicted adaptive performance benefit, \( M_I \) an information or excitation margin, and \( M_R \) a model-validity bandwidth margin. Interpret them as follows:
- If \( M_S\ge0 \) and the fixed design is not excessively costly, pure robust control is normally the default.
- If \( M_S<0 \) but \( M_P \) is large, adaptation or scheduling may be necessary.
- If \( M_I \) is small, do not expect parameter convergence; use a tracking-oriented robust modification or retain the robust design.
- If \( M_R\le0 \), adaptation acts in a frequency range in which the model is unreliable; reduce bandwidth or abandon adaptation.
These are engineering margins rather than universal constants. Their purpose is to force the design team to document the assumptions that make adaptation useful.
9. Comparative Benchmark
All five implementations use the plant
\[ \dot x=a(t)x+u+d(t), \]
with the piecewise parameter
\[ a(t)=\begin{cases} 0.8,&0\le t<20,\\ 2.2,&20\le t<35,\\ 1.2,&t\ge35, \end{cases} \]
a sinusoidal disturbance plus a temporary pulse, and a piecewise-constant command. The reference model is
\[ \dot x_m=-2x_m+2r. \]
The fixed robust controller is \( u_R=-4.5x+3r \). Since \( a(t)\le2.2 \), the closed-loop pole \( a(t)-4.5\le-2.3 \) remains uniformly negative. Its feedforward gain gives unity steady-state tracking at the nominal value \( a_0=1.5 \), not at every possible parameter.
The adaptive controller is
\[ u_A=\hat\theta_xx+\hat\theta_rr, \]
\[ \dot{\hat\theta}_x=-\gamma_xxe- \sigma\gamma_x(\hat\theta_x-\theta_{x0}),\qquad \dot{\hat\theta}_r=-\gamma_rre- \sigma\gamma_r(\hat\theta_r-\theta_{r0}), \]
with projection bounds \( \hat\theta_x\in[-6,-2] \) and \( \hat\theta_r\in[0,4] \). The benchmark compares RMS tracking error relative to the reference model, peak control effort, and adaptive gain trajectories. It is illustrative rather than a universal proof that one method is superior.
flowchart TD
R["Command r"] --> M["Reference model"]
R --> CR["Fixed robust controller"]
R --> CA["Adaptive controller"]
P1["Uncertain plant copy"] --> YR["Robust response"]
P2["Uncertain plant copy"] --> YA["Adaptive response"]
CR --> P1
CA --> P2
M --> E["Tracking error e = x_adaptive - x_model"]
YA --> E
E --> U["Projection and leakage update"]
U --> CA
YR --> Q["Compare RMS error and control effort"]
YA --> Q
10. Python Implementation
File: Chapter22_Lesson4.py
The implementation uses NumPy for arrays, Matplotlib for plots, explicit Euler integration, and an outward-derivative projection rule.
# Chapter22_Lesson4.py
# Pure robust control versus adaptive control on the same uncertain scalar plant.
# Dependencies: numpy, matplotlib
from __future__ import annotations
import csv
from dataclasses import dataclass
from math import sin, sqrt
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
@dataclass(frozen=True)
class Config:
dt: float = 0.001
final_time: float = 50.0
model_a: float = 2.0
model_b: float = 2.0
robust_gain: float = 4.5
robust_feedforward: float = 3.0
gamma_x: float = 6.0
gamma_r: float = 4.0
sigma: float = 0.03
theta_x_min: float = -6.0
theta_x_max: float = -2.0
theta_r_min: float = 0.0
theta_r_max: float = 4.0
def reference(t: float) -> float:
if t < 15.0:
return 1.0
if t < 30.0:
return -0.5
return 0.8
def plant_parameter(t: float) -> float:
if t < 20.0:
return 0.8
if t < 35.0:
return 2.2
return 1.2
def disturbance(t: float) -> float:
pulse = 0.2 if 27.0 <= t <= 29.0 else 0.0
return 0.15 * sin(8.0 * t) + pulse
def projected_derivative(theta: float, derivative: float, lower: float, upper: float) -> float:
if theta <= lower and derivative < 0.0:
return 0.0
if theta >= upper and derivative > 0.0:
return 0.0
return derivative
def rms(values: np.ndarray) -> float:
return float(sqrt(float(np.mean(values * values))))
def main() -> None:
cfg = Config()
sample_count = int(round(cfg.final_time / cfg.dt)) + 1
time = np.linspace(0.0, cfg.final_time, sample_count)
x_robust = np.zeros(sample_count)
x_adaptive = np.zeros(sample_count)
x_model = np.zeros(sample_count)
u_robust = np.zeros(sample_count)
u_adaptive = np.zeros(sample_count)
theta_x_history = np.zeros(sample_count)
theta_r_history = np.zeros(sample_count)
theta_x_nominal = -3.5
theta_r_nominal = 2.0
theta_x = theta_x_nominal
theta_r = theta_r_nominal
theta_x_history[0] = theta_x
theta_r_history[0] = theta_r
for k in range(sample_count - 1):
tk = float(time[k])
r = reference(tk)
a = plant_parameter(tk)
d = disturbance(tk)
# Fixed robust controller. k > a_max guarantees a negative closed-loop pole.
u_robust[k] = -cfg.robust_gain * x_robust[k] + cfg.robust_feedforward * r
x_robust_dot = a * x_robust[k] + u_robust[k] + d
x_robust[k + 1] = x_robust[k] + cfg.dt * x_robust_dot
# Reference model used by the adaptive controller.
x_model_dot = -cfg.model_a * x_model[k] + cfg.model_b * r
x_model[k + 1] = x_model[k] + cfg.dt * x_model_dot
# Direct adaptive controller with projection and leakage toward nominal gains.
error = x_adaptive[k] - x_model[k]
u_adaptive[k] = theta_x * x_adaptive[k] + theta_r * r
x_adaptive_dot = a * x_adaptive[k] + u_adaptive[k] + d
x_adaptive[k + 1] = x_adaptive[k] + cfg.dt * x_adaptive_dot
theta_x_dot = (
-cfg.gamma_x * x_adaptive[k] * error
- cfg.sigma * cfg.gamma_x * (theta_x - theta_x_nominal)
)
theta_r_dot = (
-cfg.gamma_r * r * error
- cfg.sigma * cfg.gamma_r * (theta_r - theta_r_nominal)
)
theta_x_dot = projected_derivative(
theta_x, theta_x_dot, cfg.theta_x_min, cfg.theta_x_max
)
theta_r_dot = projected_derivative(
theta_r, theta_r_dot, cfg.theta_r_min, cfg.theta_r_max
)
theta_x = float(
np.clip(theta_x + cfg.dt * theta_x_dot, cfg.theta_x_min, cfg.theta_x_max)
)
theta_r = float(
np.clip(theta_r + cfg.dt * theta_r_dot, cfg.theta_r_min, cfg.theta_r_max)
)
theta_x_history[k + 1] = theta_x
theta_r_history[k + 1] = theta_r
u_robust[-1] = u_robust[-2]
u_adaptive[-1] = u_adaptive[-2]
reference_history = np.array([reference(float(t)) for t in time])
parameter_history = np.array([plant_parameter(float(t)) for t in time])
robust_error = x_robust - x_model
adaptive_error = x_adaptive - x_model
print("Tracking relative to the reference model")
print(f"Pure robust RMS error: {rms(robust_error):.6f}")
print(f"Adaptive RMS error: {rms(adaptive_error):.6f}")
print(f"Pure robust peak |u|: {np.max(np.abs(u_robust)):.6f}")
print(f"Adaptive peak |u|: {np.max(np.abs(u_adaptive)):.6f}")
print(f"Final adaptive gains: theta_x={theta_x:.6f}, theta_r={theta_r:.6f}")
csv_path = Path("Chapter22_Lesson4_python.csv")
with csv_path.open("w", newline="", encoding="utf-8") as stream:
writer = csv.writer(stream)
writer.writerow(
[
"time",
"reference",
"plant_parameter_a",
"reference_model",
"x_robust",
"x_adaptive",
"u_robust",
"u_adaptive",
"theta_x",
"theta_r",
]
)
writer.writerows(
zip(
time,
reference_history,
parameter_history,
x_model,
x_robust,
x_adaptive,
u_robust,
u_adaptive,
theta_x_history,
theta_r_history,
)
)
fig, axes = plt.subplots(3, 1, figsize=(10, 9), sharex=True)
axes[0].plot(time, reference_history, "--", label="command r")
axes[0].plot(time, x_model, label="reference model")
axes[0].plot(time, x_robust, label="pure robust")
axes[0].plot(time, x_adaptive, label="adaptive")
axes[0].set_ylabel("state")
axes[0].grid(True)
axes[0].legend()
axes[1].plot(time, u_robust, label="u robust")
axes[1].plot(time, u_adaptive, label="u adaptive")
axes[1].set_ylabel("control")
axes[1].grid(True)
axes[1].legend()
axes[2].plot(time, theta_x_history, label="theta_x")
axes[2].plot(time, theta_r_history, label="theta_r")
axes[2].plot(time, parameter_history, "--", label="plant a(t)")
axes[2].set_xlabel("time [s]")
axes[2].set_ylabel("parameters")
axes[2].grid(True)
axes[2].legend()
fig.tight_layout()
fig.savefig("Chapter22_Lesson4_python.png", dpi=180)
plt.show()
if __name__ == "__main__":
main()
11. C++ Implementation
File: Chapter22_Lesson4.cpp
This standard C++17 version requires no external numerical library and exports all signals to CSV for plotting in another tool.
// Chapter22_Lesson4.cpp
// Pure robust control versus adaptive control on an uncertain scalar plant.
// Build: g++ -std=c++17 -O2 Chapter22_Lesson4.cpp -o Chapter22_Lesson4
#include <algorithm>
#include <cmath>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>
struct Config {
double dt = 0.001;
double final_time = 50.0;
double model_a = 2.0;
double model_b = 2.0;
double robust_gain = 4.5;
double robust_feedforward = 3.0;
double gamma_x = 6.0;
double gamma_r = 4.0;
double sigma = 0.03;
double theta_x_min = -6.0;
double theta_x_max = -2.0;
double theta_r_min = 0.0;
double theta_r_max = 4.0;
};
double reference(double t) {
if (t < 15.0) return 1.0;
if (t < 30.0) return -0.5;
return 0.8;
}
double plantParameter(double t) {
if (t < 20.0) return 0.8;
if (t < 35.0) return 2.2;
return 1.2;
}
double disturbance(double t) {
const double pulse = (t >= 27.0 && t <= 29.0) ? 0.2 : 0.0;
return 0.15 * std::sin(8.0 * t) + pulse;
}
double projectDerivative(double theta, double derivative, double lower, double upper) {
if (theta <= lower && derivative < 0.0) return 0.0;
if (theta >= upper && derivative > 0.0) return 0.0;
return derivative;
}
double clamp(double value, double lower, double upper) {
return std::max(lower, std::min(upper, value));
}
double rms(const std::vector<double>& values) {
double sum = 0.0;
for (double value : values) sum += value * value;
return std::sqrt(sum / static_cast<double>(values.size()));
}
int main() {
try {
const Config cfg;
const std::size_t count =
static_cast<std::size_t>(std::llround(cfg.final_time / cfg.dt)) + 1U;
std::vector<double> time(count), command(count), a_history(count);
std::vector<double> x_robust(count, 0.0), x_adaptive(count, 0.0), x_model(count, 0.0);
std::vector<double> u_robust(count, 0.0), u_adaptive(count, 0.0);
std::vector<double> theta_x_history(count, 0.0), theta_r_history(count, 0.0);
const double theta_x_nominal = -3.5;
const double theta_r_nominal = 2.0;
double theta_x = theta_x_nominal;
double theta_r = theta_r_nominal;
theta_x_history[0] = theta_x;
theta_r_history[0] = theta_r;
for (std::size_t k = 0; k + 1U < count; ++k) {
const double t = static_cast<double>(k) * cfg.dt;
const double r = reference(t);
const double a = plantParameter(t);
const double d = disturbance(t);
time[k] = t;
command[k] = r;
a_history[k] = a;
u_robust[k] = -cfg.robust_gain * x_robust[k] + cfg.robust_feedforward * r;
const double robust_dot = a * x_robust[k] + u_robust[k] + d;
x_robust[k + 1U] = x_robust[k] + cfg.dt * robust_dot;
const double model_dot = -cfg.model_a * x_model[k] + cfg.model_b * r;
x_model[k + 1U] = x_model[k] + cfg.dt * model_dot;
const double error = x_adaptive[k] - x_model[k];
u_adaptive[k] = theta_x * x_adaptive[k] + theta_r * r;
const double adaptive_dot = a * x_adaptive[k] + u_adaptive[k] + d;
x_adaptive[k + 1U] = x_adaptive[k] + cfg.dt * adaptive_dot;
double theta_x_dot = -cfg.gamma_x * x_adaptive[k] * error
- cfg.sigma * cfg.gamma_x * (theta_x - theta_x_nominal);
double theta_r_dot = -cfg.gamma_r * r * error
- cfg.sigma * cfg.gamma_r * (theta_r - theta_r_nominal);
theta_x_dot = projectDerivative(
theta_x, theta_x_dot, cfg.theta_x_min, cfg.theta_x_max);
theta_r_dot = projectDerivative(
theta_r, theta_r_dot, cfg.theta_r_min, cfg.theta_r_max);
theta_x = clamp(theta_x + cfg.dt * theta_x_dot,
cfg.theta_x_min, cfg.theta_x_max);
theta_r = clamp(theta_r + cfg.dt * theta_r_dot,
cfg.theta_r_min, cfg.theta_r_max);
theta_x_history[k + 1U] = theta_x;
theta_r_history[k + 1U] = theta_r;
}
time.back() = cfg.final_time;
command.back() = reference(cfg.final_time);
a_history.back() = plantParameter(cfg.final_time);
u_robust.back() = u_robust[count - 2U];
u_adaptive.back() = u_adaptive[count - 2U];
std::vector<double> robust_error(count), adaptive_error(count);
double robust_peak_u = 0.0;
double adaptive_peak_u = 0.0;
for (std::size_t k = 0; k < count; ++k) {
robust_error[k] = x_robust[k] - x_model[k];
adaptive_error[k] = x_adaptive[k] - x_model[k];
robust_peak_u = std::max(robust_peak_u, std::abs(u_robust[k]));
adaptive_peak_u = std::max(adaptive_peak_u, std::abs(u_adaptive[k]));
}
std::cout << std::fixed << std::setprecision(6)
<< "Tracking relative to the reference model\n"
<< "Pure robust RMS error: " << rms(robust_error) << '\n'
<< "Adaptive RMS error: " << rms(adaptive_error) << '\n'
<< "Pure robust peak |u|: " << robust_peak_u << '\n'
<< "Adaptive peak |u|: " << adaptive_peak_u << '\n'
<< "Final adaptive gains: theta_x=" << theta_x
<< ", theta_r=" << theta_r << '\n';
std::ofstream csv("Chapter22_Lesson4_cpp.csv");
if (!csv) throw std::runtime_error("Could not open CSV output file.");
csv << "time,reference,plant_parameter_a,reference_model,x_robust,x_adaptive,"
"u_robust,u_adaptive,theta_x,theta_r\n";
csv << std::setprecision(12);
for (std::size_t k = 0; k < count; ++k) {
csv << time[k] << ',' << command[k] << ',' << a_history[k] << ','
<< x_model[k] << ',' << x_robust[k] << ',' << x_adaptive[k] << ','
<< u_robust[k] << ',' << u_adaptive[k] << ','
<< theta_x_history[k] << ',' << theta_r_history[k] << '\n';
}
return 0;
} catch (const std::exception& ex) {
std::cerr << "Error: " << ex.what() << '\n';
return 1;
}
}
12. Java Implementation
File: Chapter22_Lesson4.java
The Java version uses primitive arrays and the standard file API. The public class name matches the source filename.
// Chapter22_Lesson4.java
// Pure robust control versus adaptive control on an uncertain scalar plant.
// Build and run: javac Chapter22_Lesson4.java && java Chapter22_Lesson4
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 Chapter22_Lesson4 {
private static final double DT = 0.001;
private static final double FINAL_TIME = 50.0;
private static final double MODEL_A = 2.0;
private static final double MODEL_B = 2.0;
private static final double ROBUST_GAIN = 4.5;
private static final double ROBUST_FEEDFORWARD = 3.0;
private static final double GAMMA_X = 6.0;
private static final double GAMMA_R = 4.0;
private static final double SIGMA = 0.03;
private static final double THETA_X_MIN = -6.0;
private static final double THETA_X_MAX = -2.0;
private static final double THETA_R_MIN = 0.0;
private static final double THETA_R_MAX = 4.0;
private Chapter22_Lesson4() {
}
private static double reference(double t) {
if (t < 15.0) return 1.0;
if (t < 30.0) return -0.5;
return 0.8;
}
private static double plantParameter(double t) {
if (t < 20.0) return 0.8;
if (t < 35.0) return 2.2;
return 1.2;
}
private static double disturbance(double t) {
double pulse = (t >= 27.0 && t <= 29.0) ? 0.2 : 0.0;
return 0.15 * Math.sin(8.0 * t) + pulse;
}
private static double projectDerivative(
double theta, double derivative, double lower, double upper) {
if (theta <= lower && derivative < 0.0) return 0.0;
if (theta >= upper && derivative > 0.0) return 0.0;
return derivative;
}
private static double clamp(double value, double lower, double upper) {
return Math.max(lower, Math.min(upper, value));
}
private static double rms(double[] values) {
double sum = 0.0;
for (double value : values) sum += value * value;
return Math.sqrt(sum / values.length);
}
public static void main(String[] args) {
Locale.setDefault(Locale.US);
int count = (int) Math.round(FINAL_TIME / DT) + 1;
double[] time = new double[count];
double[] command = new double[count];
double[] aHistory = new double[count];
double[] xRobust = new double[count];
double[] xAdaptive = new double[count];
double[] xModel = new double[count];
double[] uRobust = new double[count];
double[] uAdaptive = new double[count];
double[] thetaXHistory = new double[count];
double[] thetaRHistory = new double[count];
double thetaXNominal = -3.5;
double thetaRNominal = 2.0;
double thetaX = thetaXNominal;
double thetaR = thetaRNominal;
thetaXHistory[0] = thetaX;
thetaRHistory[0] = thetaR;
for (int k = 0; k < count - 1; k++) {
double t = k * DT;
double r = reference(t);
double a = plantParameter(t);
double d = disturbance(t);
time[k] = t;
command[k] = r;
aHistory[k] = a;
uRobust[k] = -ROBUST_GAIN * xRobust[k] + ROBUST_FEEDFORWARD * r;
double robustDot = a * xRobust[k] + uRobust[k] + d;
xRobust[k + 1] = xRobust[k] + DT * robustDot;
double modelDot = -MODEL_A * xModel[k] + MODEL_B * r;
xModel[k + 1] = xModel[k] + DT * modelDot;
double error = xAdaptive[k] - xModel[k];
uAdaptive[k] = thetaX * xAdaptive[k] + thetaR * r;
double adaptiveDot = a * xAdaptive[k] + uAdaptive[k] + d;
xAdaptive[k + 1] = xAdaptive[k] + DT * adaptiveDot;
double thetaXDot = -GAMMA_X * xAdaptive[k] * error
- SIGMA * GAMMA_X * (thetaX - thetaXNominal);
double thetaRDot = -GAMMA_R * r * error
- SIGMA * GAMMA_R * (thetaR - thetaRNominal);
thetaXDot = projectDerivative(thetaX, thetaXDot, THETA_X_MIN, THETA_X_MAX);
thetaRDot = projectDerivative(thetaR, thetaRDot, THETA_R_MIN, THETA_R_MAX);
thetaX = clamp(thetaX + DT * thetaXDot, THETA_X_MIN, THETA_X_MAX);
thetaR = clamp(thetaR + DT * thetaRDot, THETA_R_MIN, THETA_R_MAX);
thetaXHistory[k + 1] = thetaX;
thetaRHistory[k + 1] = thetaR;
}
time[count - 1] = FINAL_TIME;
command[count - 1] = reference(FINAL_TIME);
aHistory[count - 1] = plantParameter(FINAL_TIME);
uRobust[count - 1] = uRobust[count - 2];
uAdaptive[count - 1] = uAdaptive[count - 2];
double[] robustError = new double[count];
double[] adaptiveError = new double[count];
double robustPeakU = 0.0;
double adaptivePeakU = 0.0;
for (int k = 0; k < count; k++) {
robustError[k] = xRobust[k] - xModel[k];
adaptiveError[k] = xAdaptive[k] - xModel[k];
robustPeakU = Math.max(robustPeakU, Math.abs(uRobust[k]));
adaptivePeakU = Math.max(adaptivePeakU, Math.abs(uAdaptive[k]));
}
System.out.printf("Tracking relative to the reference model%n");
System.out.printf("Pure robust RMS error: %.6f%n", rms(robustError));
System.out.printf("Adaptive RMS error: %.6f%n", rms(adaptiveError));
System.out.printf("Pure robust peak |u|: %.6f%n", robustPeakU);
System.out.printf("Adaptive peak |u|: %.6f%n", adaptivePeakU);
System.out.printf(
"Final adaptive gains: theta_x=%.6f, theta_r=%.6f%n", thetaX, thetaR);
Path output = Path.of("Chapter22_Lesson4_java.csv");
try (BufferedWriter writer = Files.newBufferedWriter(
output, StandardCharsets.UTF_8)) {
writer.write("time,reference,plant_parameter_a,reference_model,x_robust,"
+ "x_adaptive,u_robust,u_adaptive,theta_x,theta_r\n");
for (int k = 0; k < count; k++) {
writer.write(String.format(Locale.US,
"%.12f,%.12f,%.12f,%.12f,%.12f,%.12f,%.12f,%.12f,%.12f,%.12f%n",
time[k], command[k], aHistory[k], xModel[k], xRobust[k],
xAdaptive[k], uRobust[k], uAdaptive[k],
thetaXHistory[k], thetaRHistory[k]));
}
} catch (IOException ex) {
System.err.println("Could not write CSV: " + ex.getMessage());
System.exit(1);
}
}
}
13. MATLAB and Simulink Implementation
File: Chapter22_Lesson4.m
The MATLAB script produces the same benchmark, saves a table, and includes a block-level Simulink mapping. In Simulink, projection should be implemented in the parameter-derivative path, with explicit saturation bounds and anti-windup logic rather than silently clipping only the plotted signal.
% Chapter22_Lesson4.m
% Pure robust control versus adaptive control on an uncertain scalar plant.
% The script uses explicit Euler integration so its equations match the
% Python, C++, Java, and Mathematica versions exactly.
clear; clc; close all;
dt = 0.001;
Tf = 50.0;
t = (0:dt:Tf)';
N = numel(t);
modelA = 2.0;
modelB = 2.0;
robustGain = 4.5;
robustFeedforward = 3.0;
gammaX = 6.0;
gammaR = 4.0;
sigma = 0.03;
thetaXBounds = [-6.0, -2.0];
thetaRBounds = [0.0, 4.0];
thetaXNominal = -3.5;
thetaRNominal = 2.0;
thetaX = thetaXNominal;
thetaR = thetaRNominal;
xRobust = zeros(N, 1);
xAdaptive = zeros(N, 1);
xModel = zeros(N, 1);
uRobust = zeros(N, 1);
uAdaptive = zeros(N, 1);
thetaXHistory = zeros(N, 1);
thetaRHistory = zeros(N, 1);
rHistory = zeros(N, 1);
aHistory = zeros(N, 1);
thetaXHistory(1) = thetaX;
thetaRHistory(1) = thetaR;
for k = 1:N-1
tk = t(k);
r = referenceSignal(tk);
a = plantParameter(tk);
d = disturbanceSignal(tk);
rHistory(k) = r;
aHistory(k) = a;
uRobust(k) = -robustGain*xRobust(k) + robustFeedforward*r;
xRobustDot = a*xRobust(k) + uRobust(k) + d;
xRobust(k+1) = xRobust(k) + dt*xRobustDot;
xModelDot = -modelA*xModel(k) + modelB*r;
xModel(k+1) = xModel(k) + dt*xModelDot;
e = xAdaptive(k) - xModel(k);
uAdaptive(k) = thetaX*xAdaptive(k) + thetaR*r;
xAdaptiveDot = a*xAdaptive(k) + uAdaptive(k) + d;
xAdaptive(k+1) = xAdaptive(k) + dt*xAdaptiveDot;
thetaXDot = -gammaX*xAdaptive(k)*e ...
- sigma*gammaX*(thetaX - thetaXNominal);
thetaRDot = -gammaR*r*e ...
- sigma*gammaR*(thetaR - thetaRNominal);
thetaXDot = projectedDerivative(thetaX, thetaXDot, thetaXBounds);
thetaRDot = projectedDerivative(thetaR, thetaRDot, thetaRBounds);
thetaX = min(thetaXBounds(2), max(thetaXBounds(1), thetaX + dt*thetaXDot));
thetaR = min(thetaRBounds(2), max(thetaRBounds(1), thetaR + dt*thetaRDot));
thetaXHistory(k+1) = thetaX;
thetaRHistory(k+1) = thetaR;
end
rHistory(end) = referenceSignal(Tf);
aHistory(end) = plantParameter(Tf);
uRobust(end) = uRobust(end-1);
uAdaptive(end) = uAdaptive(end-1);
robustError = xRobust - xModel;
adaptiveError = xAdaptive - xModel;
rmsRobust = sqrt(mean(robustError.^2));
rmsAdaptive = sqrt(mean(adaptiveError.^2));
fprintf('Tracking relative to the reference model\n');
fprintf('Pure robust RMS error: %.6f\n', rmsRobust);
fprintf('Adaptive RMS error: %.6f\n', rmsAdaptive);
fprintf('Pure robust peak |u|: %.6f\n', max(abs(uRobust)));
fprintf('Adaptive peak |u|: %.6f\n', max(abs(uAdaptive)));
fprintf('Final adaptive gains: theta_x=%.6f, theta_r=%.6f\n', thetaX, thetaR);
results = table(t, rHistory, aHistory, xModel, xRobust, xAdaptive, ...
uRobust, uAdaptive, thetaXHistory, thetaRHistory, ...
'VariableNames', {'time','reference','plant_parameter_a','reference_model', ...
'x_robust','x_adaptive','u_robust','u_adaptive','theta_x','theta_r'});
writetable(results, 'Chapter22_Lesson4_matlab.csv');
figure('Name', 'Chapter 22 Lesson 4');
tiledlayout(3,1);
nexttile;
plot(t, rHistory, '--', t, xModel, t, xRobust, t, xAdaptive, 'LineWidth', 1.0);
grid on; ylabel('state');
legend('command r','reference model','pure robust','adaptive','Location','best');
nexttile;
plot(t, uRobust, t, uAdaptive, 'LineWidth', 1.0);
grid on; ylabel('control'); legend('u robust','u adaptive','Location','best');
nexttile;
plot(t, thetaXHistory, t, thetaRHistory, t, aHistory, '--', 'LineWidth', 1.0);
grid on; xlabel('time [s]'); ylabel('parameters');
legend('theta_x','theta_r','plant a(t)','Location','best');
exportgraphics(gcf, 'Chapter22_Lesson4_matlab.png', 'Resolution', 180);
% Simulink mapping:
% 1. Use one Integrator block for each plant and one for the reference model.
% 2. Use a MATLAB Function block for referenceSignal, plantParameter, disturbanceSignal,
% and the projected adaptive-law equations.
% 3. Feed x_adaptive and x_model into a Sum block to form e.
% 4. Implement theta_x*x + theta_r*r with Product and Sum blocks.
% 5. Add Saturation blocks on theta_x and theta_r to realize projection bounds.
function r = referenceSignal(t)
if t < 15.0
r = 1.0;
elseif t < 30.0
r = -0.5;
else
r = 0.8;
end
end
function a = plantParameter(t)
if t < 20.0
a = 0.8;
elseif t < 35.0
a = 2.2;
else
a = 1.2;
end
end
function d = disturbanceSignal(t)
pulse = double(t >= 27.0 && t <= 29.0)*0.2;
d = 0.15*sin(8.0*t) + pulse;
end
function value = projectedDerivative(theta, derivative, bounds)
if (theta <= bounds(1) && derivative < 0.0) || ...
(theta >= bounds(2) && derivative > 0.0)
value = 0.0;
else
value = derivative;
end
end
14. Wolfram Mathematica Implementation
File: Chapter22_Lesson4.nb
The file contains Wolfram Language source suitable for direct evaluation in a notebook. It exports CSV data and a combined figure.
(* Chapter22_Lesson4.nb
Wolfram Language source that can be pasted into a notebook and evaluated.
Pure robust control versus adaptive control on an uncertain scalar plant. *)
ClearAll["Global`*"];
dt = 0.001;
tf = 50.0;
time = Range[0.0, tf, dt];
n = Length[time];
modelA = 2.0;
modelB = 2.0;
robustGain = 4.5;
robustFeedforward = 3.0;
gammaX = 6.0;
gammaR = 4.0;
sigma = 0.03;
thetaXBounds = {-6.0, -2.0};
thetaRBounds = {0.0, 4.0};
thetaXNominal = -3.5;
thetaRNominal = 2.0;
reference[t_] := Piecewise[{ {1.0, t < 15.0}, {-0.5, t < 30.0} }, 0.8];
plantParameter[t_] := Piecewise[{ {0.8, t < 20.0}, {2.2, t < 35.0} }, 1.2];
disturbance[t_] := 0.15 Sin[8.0 t] + If[27.0 <= t <= 29.0, 0.2, 0.0];
projectDerivative[theta_, derivative_, {lower_, upper_}] := Which[
theta <= lower && derivative < 0.0, 0.0,
theta >= upper && derivative > 0.0, 0.0,
True, derivative
];
xRobust = ConstantArray[0.0, n];
xAdaptive = ConstantArray[0.0, n];
xModel = ConstantArray[0.0, n];
uRobust = ConstantArray[0.0, n];
uAdaptive = ConstantArray[0.0, n];
thetaXHistory = ConstantArray[0.0, n];
thetaRHistory = ConstantArray[0.0, n];
rHistory = reference /@ time;
aHistory = plantParameter /@ time;
thetaX = thetaXNominal;
thetaR = thetaRNominal;
thetaXHistory[[1]] = thetaX;
thetaRHistory[[1]] = thetaR;
Do[
t = time[[k]];
r = rHistory[[k]];
a = aHistory[[k]];
d = disturbance[t];
uRobust[[k]] = -robustGain xRobust[[k]] + robustFeedforward r;
xRobustDot = a xRobust[[k]] + uRobust[[k]] + d;
xRobust[[k + 1]] = xRobust[[k]] + dt xRobustDot;
xModelDot = -modelA xModel[[k]] + modelB r;
xModel[[k + 1]] = xModel[[k]] + dt xModelDot;
error = xAdaptive[[k]] - xModel[[k]];
uAdaptive[[k]] = thetaX xAdaptive[[k]] + thetaR r;
xAdaptiveDot = a xAdaptive[[k]] + uAdaptive[[k]] + d;
xAdaptive[[k + 1]] = xAdaptive[[k]] + dt xAdaptiveDot;
thetaXDot = -gammaX xAdaptive[[k]] error
- sigma gammaX (thetaX - thetaXNominal);
thetaRDot = -gammaR r error
- sigma gammaR (thetaR - thetaRNominal);
thetaXDot = projectDerivative[thetaX, thetaXDot, thetaXBounds];
thetaRDot = projectDerivative[thetaR, thetaRDot, thetaRBounds];
thetaX = Clip[thetaX + dt thetaXDot, thetaXBounds];
thetaR = Clip[thetaR + dt thetaRDot, thetaRBounds];
thetaXHistory[[k + 1]] = thetaX;
thetaRHistory[[k + 1]] = thetaR;
, {k, 1, n - 1}];
uRobust[[-1]] = uRobust[[-2]];
uAdaptive[[-1]] = uAdaptive[[-2]];
robustError = xRobust - xModel;
adaptiveError = xAdaptive - xModel;
rms[v_] := Sqrt[Mean[v^2]];
Print["Tracking relative to the reference model"];
Print["Pure robust RMS error: ", NumberForm[rms[robustError], {8, 6}]];
Print["Adaptive RMS error: ", NumberForm[rms[adaptiveError], {8, 6}]];
Print["Pure robust peak |u|: ", NumberForm[Max[Abs[uRobust]], {8, 6}]];
Print["Adaptive peak |u|: ", NumberForm[Max[Abs[uAdaptive]], {8, 6}]];
Print["Final adaptive gains: theta_x=", thetaX, ", theta_r=", thetaR];
header = {"time", "reference", "plant_parameter_a", "reference_model",
"x_robust", "x_adaptive", "u_robust", "u_adaptive", "theta_x", "theta_r"};
data = Transpose[{time, rHistory, aHistory, xModel, xRobust, xAdaptive,
uRobust, uAdaptive, thetaXHistory, thetaRHistory}];
Export["Chapter22_Lesson4_mathematica.csv", Prepend[data, header]];
statePlot = ListLinePlot[
{Transpose[{time, rHistory}], Transpose[{time, xModel}],
Transpose[{time, xRobust}], Transpose[{time, xAdaptive}]},
PlotLegends -> {"command r", "reference model", "pure robust", "adaptive"},
Frame -> True, FrameLabel -> {"time [s]", "state"}, ImageSize -> Large
];
controlPlot = ListLinePlot[
{Transpose[{time, uRobust}], Transpose[{time, uAdaptive}]},
PlotLegends -> {"u robust", "u adaptive"}, Frame -> True,
FrameLabel -> {"time [s]", "control"}, ImageSize -> Large
];
parameterPlot = ListLinePlot[
{Transpose[{time, thetaXHistory}], Transpose[{time, thetaRHistory}],
Transpose[{time, aHistory}]},
PlotLegends -> {"theta_x", "theta_r", "plant a(t)"}, Frame -> True,
FrameLabel -> {"time [s]", "parameters"}, ImageSize -> Large
];
figure = GraphicsColumn[{statePlot, controlPlot, parameterPlot}];
Export["Chapter22_Lesson4_mathematica.png", figure, ImageResolution -> 180];
figure
15. Interpreting the Benchmark Correctly
The fixed controller has an immediate worst-case pole guarantee and does not need informative motion. Its tracking equilibrium varies with \( a(t) \), so it accepts performance variation in exchange for simplicity and certainty.
The adaptive controller attempts to recover the reference-model dynamics by changing its gains. During informative command changes it can reduce the model-following error, but the estimates are biased toward nominal values by leakage and are bounded by projection. Therefore the plotted gains should not be interpreted as exact physical estimates of the plant parameter.
A valid comparison must report at least:
- model-following RMS and peak errors;
- control RMS and peak magnitude;
- gain trajectories and time spent on projection boundaries;
- sensitivity to sampling period and measurement noise;
- performance under a non-exciting constant command;
- performance after increasing unmodeled high-frequency dynamics; and
- behavior when the actuator saturates.
If the adaptive advantage disappears under these tests, the fixed robust controller is the better engineering choice even when the adaptive proof is mathematically valid for the nominal model.
16. Problems and Solutions
Problem 1 (Worst-Case Scalar Gain): For \( \dot x=ax+bu+d \), suppose \( a\in[-1,3] \), \( b\in[0.5,1.5] \), and \( u=-kx \). Find the smallest gain that guarantees a decay rate of at least \( \lambda=2 \) for the disturbance-free system.
Solution: The worst closed-loop coefficient is obtained at \( a=a_{\max}=3 \) and \( b=b_{\min}=0.5 \). Require
\[ b_{\min}k-a_{\max}\ge\lambda. \]
\[ 0.5k-3\ge2\quad\Longrightarrow\quad k\ge10. \]
Thus \( k=10 \) is the smallest admissible gain. Larger gains increase the stability margin but may increase control effort and noise amplification.
Problem 2 (Polytopic Lyapunov Proof): Prove that a common matrix \( P>0 \) satisfying \( A_i^TP+PA_i<0 \) at every vertex guarantees stability for every convex combination of the vertices.
Solution: Let \( A(\alpha)=\sum_i\alpha_iA_i \), where \( \alpha_i\ge0 \) and \( \sum_i\alpha_i=1 \). Then
\[ A(\alpha)^TP+PA(\alpha)= \sum_i\alpha_i(A_i^TP+PA_i). \]
A convex combination of negative-definite matrices is negative definite. Therefore \( V=x^TPx \) decreases for every admissible \( \alpha \), giving uniform quadratic stability.
Problem 3 (Adaptive Cross-Term Cancellation): Starting from \( \dot e=-a_me+b\tilde\theta^T\phi \), derive an update law that cancels the state-parameter cross term in the derivative of \( V=\tfrac12e^2+\tfrac12\tilde\theta^T \Gamma^{-1}\tilde\theta \).
Solution: Differentiate:
\[ \dot V=-a_me^2+b e\tilde\theta^T\phi+ \tilde\theta^T\Gamma^{-1}\dot{\hat\theta}. \]
Choose
\[ \dot{\hat\theta}=-\Gamma b\phi e. \]
Then the last term equals \( -b\tilde\theta^T\phi e \), exactly canceling the cross term and leaving \( \dot V=-a_me^2 \).
Problem 4 (Ultimate Bound with Disturbance): Suppose an adaptive Lyapunov analysis yields
\[ \dot V\le-c_1e^2-c_2\|\tilde\theta\|^2+c_3, \qquad c_i>0. \]
Explain why this proves ultimate boundedness and identify the set outside which \( \dot V<0 \).
Solution: The derivative is negative whenever
\[ c_1e^2+c_2\|\tilde\theta\|^2>c_3. \]
Therefore trajectories are driven toward the ellipsoidal set
\[ \mathcal{B}=\{(e,\tilde\theta): c_1e^2+c_2\|\tilde\theta\|^2\le c_3\}. \]
The inequality does not imply convergence to the origin because the disturbance-related constant \( c_3 \) prevents \( \dot V \) from being negative everywhere outside the origin.
Problem 5 (Controller Selection): A precision positioning stage has a well-bounded payload range, strong sensor noise above 200 Hz, a fixed repeated trajectory, and a certified fixed controller that meets all settling-time requirements. Should adaptation be added solely to estimate payload mass online?
Solution: Pure robust control is preferred. The existing controller already satisfies worst-case performance; the repeated trajectory may not provide independent excitation; high-frequency noise can contaminate the update law; and online mass estimation adds verification burden without a demonstrated performance gap. Adaptation becomes justified only if a new requirement cannot be met by the verified robust design and the estimator can be safely bounded and validated.
Problem 6 (When Adaptation Is Necessary): An aircraft model has a known control direction and a linearly parameterized aerodynamic coefficient that changes slowly with icing. A single fixed controller is stable over the envelope but violates the required tracking error at both extremes. Local controllers designed for each coefficient satisfy the requirement. State which additional facts must be established before choosing adaptive control.
Solution: The designer must establish:
- existence of matching ideal gains across the admissible coefficient range;
- sufficient excitation or a composite prediction signal;
- a bound on the coefficient rate relative to adaptation bandwidth;
- projection limits that retain safe control authority;
- robustness to neglected dynamics, delay, noise, and saturation;
- a verified fallback or robust baseline; and
- validation scenarios covering estimator transients and boundary cases.
17. Summary
Pure robust control is normally preferred when a credible bounded uncertainty set can be handled by one fixed controller with acceptable performance. Its principal strengths are uniform offline guarantees, repeatability, and lower implementation and certification complexity.
Adaptive control is preferred when uncertainty is structured and learnable, the operating range makes a fixed design excessively conservative, and online adjustment produces a material performance benefit. Its stability argument must be supplemented by projection, leakage, normalization, filtering, saturation handling, and explicit treatment of excitation and model validity.
The most defensible architecture in safety-critical systems is often a robust baseline that guarantees a safe envelope, with a bounded adaptive augmentation that improves performance but is not solely responsible for safety.
18. References
- Rohrs, C.E., Valavani, L., Athans, M., & Stein, G. (1985). Robustness of continuous-time adaptive control algorithms in the presence of unmodeled dynamics. IEEE Transactions on Automatic Control, 30(9), 881–889.
- Ioannou, P.A., & Kokotovic, P.V. (1984). Robust redesign of adaptive control. IEEE Transactions on Automatic Control, 29(3), 202–211.
- Ioannou, P.A., & Kokotovic, P.V. (1984). Instability analysis and improvement of robustness of adaptive control. Automatica, 20(5), 583–594.
- Zames, G. (1981). Feedback and optimal sensitivity: Model reference transformations, multiplicative seminorms, and approximate inverses. IEEE Transactions on Automatic Control, 26(2), 301–320.
- Doyle, J.C. (1978). Guaranteed margins for LQG regulators. IEEE Transactions on Automatic Control, 23(4), 756–757.
- Kharitonov, V.L. (1978). The asymptotic stability of the equilibrium state of a family of systems of linear differential equations. Differentsial'nye Uravneniya, 14(11), 2086–2088.
- Megretski, A., & Rantzer, A. (1997). System analysis via integral quadratic constraints. IEEE Transactions on Automatic Control, 42(6), 819–830.
- Narendra, K.S., & Annaswamy, A.M. (1987). A new adaptive law for robust adaptation without persistent excitation. IEEE Transactions on Automatic Control, 32(2), 134–145.
- Narendra, K.S., & Annaswamy, A.M. (1986). Robust adaptive control in the presence of bounded disturbances. IEEE Transactions on Automatic Control, 31(4), 306–315.
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.