Chapter 19: Adaptive Control with Constraints and Saturation (Conceptual)
Lesson 4: Safety Considerations: Limiting Parameter Changes and Control Effort
This lesson develops a safety-oriented layer for adaptive controllers whose nominal tracking law has already been designed. The central objective is not to replace stability analysis with software checks, but to preserve explicit bounds on parameter estimates, parameter velocities, actuator amplitude, actuator slew rate, and supervisory state envelopes. We derive projection and rate-limiting laws, analyze their effect on a Lyapunov inequality, and implement one consistent constrained adaptive controller in Python, C++, Java, MATLAB, Simulink-oriented form, and Wolfram Mathematica.
1. Learning Objectives and Safety Scope
After completing this lesson, students should be able to:
- distinguish stability, performance, and safety constraints in an adaptive loop;
- construct compact admissible sets for adaptive parameters;
- limit the norm or componentwise rate of parameter updates;
- enforce actuator amplitude and slew-rate bounds using the applied control, not merely the requested control;
- gate or freeze adaptation when control authority is unavailable;
- derive a uniformly ultimately bounded tracking result under bounded saturation mismatch and disturbance;
- define monitorable safety indices and a deterministic fallback mode.
The word safety is used here in a control-engineering sense: the controller is equipped with explicit envelopes that prevent unrestricted learning and unrestricted actuation. These mechanisms are necessary design elements, but they do not by themselves constitute certification. Plant-level hazard analysis, sensor validation, redundancy, timing guarantees, and hardware fault handling remain separate engineering activities.
2. Why Nominal Adaptive Stability Does Not Automatically Imply Safe Operation
Consider the linearly parameterized control request \( u_c(t)=\hat{\boldsymbol{\theta} }^{T}(t)\boldsymbol{\phi}(t) \). A nominal Lyapunov proof may assume that this request is applied exactly. A physical actuator instead produces
\[ u(t)=\mathcal{A}\!\left[u_c\right](t), \qquad \Delta u(t)=u(t)-u_c(t), \]
where \( \mathcal{A} \) includes amplitude saturation, rate limits, dead zones, delays, or other actuator dynamics. The mismatch \( \Delta u \) enters the tracking-error dynamics as an additional input. If adaptation continues as though \( \Delta u=0 \), the update law may increase controller gains in an attempt to correct an error that the actuator cannot remove. This is the adaptive analogue of windup.
Three different quantities therefore require independent bounds:
\[ \hat{\boldsymbol{\theta} }(t)\in\Omega_{\theta}, \qquad \left\|\dot{\hat{\boldsymbol{\theta} } }(t)\right\|_2\le \rho_{\theta}, \qquad |u(t)|\le u_{\max},\quad |\dot u(t)|\le \rho_u. \]
A bound on parameters does not bound their rate of change, and a bound on requested control does not guarantee that actuator rate limits are respected. The safety layer must therefore be multi-dimensional.
flowchart TD
R["Reference r"] --> RM["Reference model"]
R --> PHI["Regressor and adaptive controller"]
Y["Measured state or output"] --> PHI
RM --> E["Tracking error"]
Y --> E
E --> AD["Adaptation law"]
AD --> PR["Parameter projection \nand update-rate limit"]
PR --> PHI
PHI --> UC["Requested control u_c"]
UC --> AL["Amplitude and \nslew-rate limiter"]
AL --> U["Applied control u"]
U --> P["Plant"]
P --> Y
UC --> M["Authority mismatch and safety monitor"]
U --> M
Y --> M
M --> AD
M --> FB["Fallback controller when envelope is violated"]
FB --> AL
3. Admissible Parameter Sets and Projection
Suppose prior engineering knowledge supplies lower and upper bounds for each adaptive coefficient. Define the compact convex box
\[ \Omega_{\theta}=\left\{\boldsymbol{\vartheta}\in\mathbb{R}^{p}: \underline{\theta}_i\le \vartheta_i\le\overline{\theta}_i, \ i=1,\ldots,p\right\}. \]
Let \( \mathbf{v}(t) \) denote the unconstrained update direction. For a box, a tangent-cone projection can be implemented componentwise:
\[ \operatorname{Proj}_{\Omega_{\theta},i}(\hat{\boldsymbol{\theta} },\mathbf{v})= \begin{cases} 0, & \hat\theta_i=\overline\theta_i\ \text{and}\ v_i>0,\\ 0, & \hat\theta_i=\underline\theta_i\ \text{and}\ v_i<0,\\ v_i, & \text{otherwise}. \end{cases} \]
The projection removes only outward-pointing motion. Tangential and inward motion remains available, so adaptation can continue without violating the parameter envelope.
3.1 Positive invariance of the box
Assume \( \hat{\boldsymbol{\theta} }(0)\in\Omega_{\theta} \) and \( \dot{\hat{\boldsymbol{\theta} } }=\operatorname{Proj}_{\Omega_{\theta} }(\hat{\boldsymbol{\theta} },\mathbf{v}) \). At an upper face \( \hat\theta_i=\overline\theta_i \), the projected derivative satisfies \( \dot{\hat\theta}_i\le 0 \). At a lower face \( \hat\theta_i=\underline\theta_i \), it satisfies \( \dot{\hat\theta}_i\ge 0 \). Hence no trajectory can cross outward through any face, and
\[ \hat{\boldsymbol{\theta} }(0)\in\Omega_{\theta} \quad\Longrightarrow\quad \hat{\boldsymbol{\theta} }(t)\in\Omega_{\theta},\quad t\ge 0. \]
3.2 Projection inequality used in Lyapunov analysis
If the ideal parameter vector \( \boldsymbol{\theta}^{*}\in\Omega_{\theta} \), then a standard projection property is
\[ \left(\hat{\boldsymbol{\theta} }-\boldsymbol{\theta}^{*}\right)^{T} \boldsymbol{\Gamma}^{-1} \left[\operatorname{Proj}_{\Omega_{\theta} }(\hat{\boldsymbol{\theta} },\mathbf{v})-\mathbf{v}\right] \le 0. \]
Thus projection does not add a positive term to the usual quadratic parameter-error part of the Lyapunov derivative. This is why projection is more than numerical clipping: it is selected to preserve a sign property required by the proof.
4. Limiting the Rate of Parameter Change
Projection bounds the location of the estimate, but a large tracking error or regressor can still generate a large instantaneous update. Define a Euclidean update-rate limiter
\[ \mathcal{R}_{\rho_{\theta} }(\mathbf{v})= \begin{cases} \mathbf{v}, & \|\mathbf{v}\|_2\le\rho_{\theta},\\ \rho_{\theta}\dfrac{\mathbf{v} }{\|\mathbf{v}\|_2}, & \|\mathbf{v}\|_2>\rho_{\theta}. \end{cases} \]
The safety-aware continuous-time update is then
\[ \dot{\hat{\boldsymbol{\theta} } }= \operatorname{Proj}_{\Omega_{\theta} }\!\left( \hat{\boldsymbol{\theta} }, \mathcal{R}_{\rho_{\theta} }\!\left(\mathbf{v}\right)\right). \]
The limiter is radial: it preserves the update direction and changes only its magnitude. A componentwise alternative is \( |\dot{\hat\theta}_i|\le\rho_i \). The radial form is preferable when one global learning-speed budget is meaningful; the componentwise form is preferable when individual gains have different units or safety significance.
4.1 Discrete-time implementation
At sampling instant \( k \), use
\[ \hat{\boldsymbol{\theta} }_{k+1}= \Pi_{\Omega_{\theta} }\!\left[ \hat{\boldsymbol{\theta} }_k+T_s \mathcal{R}_{\rho_{\theta} }(\mathbf{v}_k)\right], \]
where \( \Pi_{\Omega_{\theta} } \) is the Euclidean projection onto the admissible set. The sample-to-sample parameter movement is explicitly bounded:
\[ \left\|\hat{\boldsymbol{\theta} }_{k+1}- \hat{\boldsymbol{\theta} }_k\right\|_2 \le T_s\rho_{\theta}. \]
This inequality is directly testable in software and should be included in unit tests and runtime telemetry.
5. Limiting Control Effort: Amplitude and Slew Rate
Let the requested command be \( u_{c,k} \). A discrete actuator limiter can be defined in two stages:
\[ \bar u_k=\operatorname{sat}_{u_{\max} }(u_{c,k}), \qquad u_k=u_{k-1}+\operatorname{sat}_{T_s\rho_u}(\bar u_k-u_{k-1}), \]
with the symmetric saturation function
\[ \operatorname{sat}_{a}(z)= \begin{cases} -a, & z<-a,\\ z, & |z|\le a,\\ a, & z>a. \end{cases} \]
The construction guarantees
\[ |u_k|\le u_{\max}, \qquad \left|\frac{u_k-u_{k-1} }{T_s}\right|\le\rho_u. \]
The adaptive controller must receive or reconstruct the applied control \( u_k \). The authority mismatch \( \Delta u_k=u_k-u_{c,k} \) is a safety-relevant signal: it measures how much of the requested adaptive action was unavailable.
5.1 Feasible references
No adaptation law can force arbitrary tracking through a bounded actuator. For a scalar plant \( \dot x=-a x+b u \) with \( a>0 \) and \( b>0 \), maintaining a constant equilibrium \( x=r_0 \) requires \( u_{eq}=a r_0/b \). A necessary feasibility condition is
\[ \left|\frac{a r_0}{b}\right|\le u_{\max}. \]
Safety therefore includes command management: reference magnitude and speed must remain compatible with available control authority.
6. Saturation-Aware Adaptation, Gating, and Supervisory Fallback
Let \( g(t)\in[0,1] \) be an adaptation gate derived from authority mismatch. A smooth example is
\[ g(t)=\frac{1}{1+k_{\Delta}|\Delta u(t)|}, \qquad k_{\Delta}>0. \]
For small mismatch, \( g\approx 1 \). During severe saturation, \( g \) approaches zero and learning slows. A hard gate can instead set \( g=0 \) whenever \( |\Delta u|>\delta_u \). Smooth gating avoids abrupt update discontinuities; hard gating is easier to audit.
A practical robust update is
\[ \mathbf{v}=-g(t)\boldsymbol{\Gamma}\boldsymbol{\phi}(t)e(t) -\sigma\hat{\boldsymbol{\theta} }(t), \qquad \sigma>0, \]
followed by rate limiting and projection. The leakage term prevents stale or weakly excited estimates from remaining at large magnitudes, while projection enforces hard bounds.
6.1 Safety monitor
One normalized monitor is
\[ s(t)=\max\left\{ \frac{|u(t)|}{u_{\max} }, \frac{|\dot u(t)|}{\rho_u}, \max_i\frac{|\hat\theta_i(t)|}{\theta_{i,\max} }, \frac{|x(t)|}{x_{\mathrm{safe} } } \right\}. \]
Values near one indicate exhausted margin. When a state envelope, sensor validity check, or timing deadline is violated, the supervisor should execute a deterministic transition: freeze adaptation, select a verified fallback controller, retain actuator limiting, and log the trigger and relevant state.
flowchart TD
A["Sample sensors and applied actuator signal"] --> B["Validate range, freshness, and timing"]
B -->|"invalid"| F["Freeze adaptation \nand select fallback"]
B -->|"valid"| C["Compute tracking error, \nregressor, and requested control"]
C --> D["Apply amplitude and slew-rate limits"]
D --> E["Compute authority mismatch and safety index"]
E --> G{"Envelope or \nauthority violation?"}
G -->|"yes"| F
G -->|"no"| H["Gate, rate-limit, and \nproject parameter update"]
H --> I["Integrate bounded \nparameter estimate"]
F --> J["Apply limited fallback \ncontrol and log event"]
I --> K["Continue next sample"]
J --> K
7. Lyapunov Analysis with Bounded Actuator Mismatch
Consider an error model obtained after nominal matching:
\[ \dot{\mathbf e}=\mathbf A_m\mathbf e+ \mathbf b\,\tilde{\boldsymbol{\theta} }^{T}\boldsymbol{\phi} +\mathbf b\,\Delta u+\mathbf d(t), \]
where \( \mathbf A_m \) is Hurwitz, \( \tilde{\boldsymbol{\theta} }=\hat{\boldsymbol{\theta} }- \boldsymbol{\theta}^{*} \), and \( \|\mathbf d(t)\|_2\le\bar d \). Select \( \mathbf P=\mathbf P^T>0 \) satisfying
\[ \mathbf A_m^T\mathbf P+\mathbf P\mathbf A_m=-\mathbf Q, \qquad \mathbf Q=\mathbf Q^T>0. \]
Use the candidate
\[ V=\mathbf e^T\mathbf P\mathbf e+ \tilde{\boldsymbol{\theta} }^T\boldsymbol{\Gamma}^{-1} \tilde{\boldsymbol{\theta} }. \]
With the unsaturated gradient direction \( -\boldsymbol{\Gamma}\boldsymbol{\phi}\mathbf b^T\mathbf P\mathbf e \), projection preserves the nonpositive parameter cross-term property. Rate limiting or gating may prevent exact cancellation, so collect their residual in \( \mathbf w_{\theta}(t) \). Then
\[ \dot V\le -\lambda_{\min}(\mathbf Q)\|\mathbf e\|_2^2 +2\|\mathbf P\mathbf b\|_2\|\mathbf e\|_2|\Delta u| +2\|\mathbf P\|_2\|\mathbf e\|_2\bar d +2\|\tilde{\boldsymbol{\theta} }\|_2 \|\boldsymbol{\Gamma}^{-1}\mathbf w_{\theta}\|_2. \]
Projection gives a known bound \( \|\tilde{\boldsymbol{\theta} }\|_2\le\bar\theta_e \). Assume \( |\Delta u|\le\bar\Delta_u \) and \( \|\boldsymbol{\Gamma}^{-1}\mathbf w_{\theta}\|_2\le\bar w_{\theta} \). Define
\[ c_1=2\left(\|\mathbf P\mathbf b\|_2\bar\Delta_u+ \|\mathbf P\|_2\bar d\right), \qquad c_0=2\bar\theta_e\bar w_{\theta}. \]
Therefore
\[ \dot V\le -q\|\mathbf e\|_2^2+c_1\|\mathbf e\|_2+c_0, \qquad q=\lambda_{\min}(\mathbf Q). \]
The right side is negative whenever \( \|\mathbf e\|_2>r_e \), where
\[ r_e=\frac{c_1+\sqrt{c_1^2+4qc_0} }{2q}. \]
Hence the tracking error is uniformly ultimately bounded. The radius increases with actuator mismatch, disturbance, and the residual introduced by gated or rate-limited adaptation. If the limits become inactive after a finite time, disturbances vanish, and the nominal excitation conditions hold, the stronger nominal convergence result may be recovered. Under persistent active constraints, claiming asymptotic tracking is generally unjustified.
8. Design Rules and Verification Conditions
8.1 Choosing parameter bounds
Bounds should follow from plant signs, units, known operating envelopes, and a robust margin around plausible ideal parameters. Bounds that are too narrow can exclude \( \boldsymbol{\theta}^{*} \) and create a permanent approximation error. Bounds that are too wide provide little safety value and permit large control requests.
8.2 Choosing the update-rate limit
Select \( \rho_{\theta} \) using the fastest physically credible parameter variation, sample period, sensor noise level, and actuator bandwidth. A useful implementation invariant is
\[ \|\hat{\boldsymbol{\theta} }_{k+1}- \hat{\boldsymbol{\theta} }_k\|_2\le T_s\rho_{\theta}+\varepsilon_{num}, \]
where \( \varepsilon_{num} \) accounts for floating-point roundoff.
8.3 Choosing actuator margins
Continuous operation exactly at the hardware rating leaves no uncertainty margin. The software limits should normally be inside absolute hardware limits. Separate normal, warning, and fallback thresholds allow graceful degradation rather than a single abrupt boundary.
8.4 Required tests
- initial parameters at every face and corner of the admissible set;
- large reference steps that activate amplitude and slew-rate limits;
- sensor noise, bias, dropout, stale timestamps, and out-of-range values;
- incorrect parameter initialization and worst-case modeled plant coefficients;
- sample-time jitter and delayed actuator feedback;
- recovery from saturation and from supervisory fallback;
- assertions for every parameter, parameter-rate, control, control-rate, and state bound.
9. Unified Numerical Example
The implementations use the uncertain scalar plant
\[ \dot x=-a x+b u+d(t), \qquad a=1.25,\quad b=0.80, \]
and reference model
\[ \dot x_m=-a_m x_m+b_m r, \qquad a_m=b_m=1.60. \]
The requested command and error are
\[ u_c=\hat\theta_x x+\hat\theta_r r, \qquad e=x-x_m. \]
The bounded update pipeline is
\[ \mathbf v=-g\boldsymbol{\Gamma}\boldsymbol{\phi}e- \sigma\hat{\boldsymbol{\theta} },\qquad \dot{\hat{\boldsymbol{\theta} } }= \operatorname{Proj}_{\Omega_{\theta} } \left(\hat{\boldsymbol{\theta} }, \mathcal{R}_{\rho_{\theta} }(\mathbf v)\right), \]
with \( \boldsymbol{\phi}=[x\;r]^T \), parameter bounds \( -4\le\hat\theta_i\le4 \), update-rate bound \( \|\dot{\hat{\boldsymbol{\theta} } }\|_2\le1.2 \), amplitude limit \( |u|\le2.2 \), and slew-rate limit \( |\dot u|\le3.5 \). A fallback proportional action is used only when \( |x|\ge3 \).
10. Python Implementation
Chapter19_Lesson4.py
"""Chapter19_Lesson4.py
Safety-aware adaptive control demo with:
1) box projection of adaptive parameters,
2) a norm bound on parameter-update rate,
3) actuator amplitude and slew-rate limits,
4) saturation-aware adaptation gating,
5) a simple supervisory fallback.
"""
from __future__ import annotations
import csv
import math
from dataclasses import dataclass
from pathlib import Path
from typing import Iterable, Sequence
@dataclass(frozen=True)
class Limits:
theta_lower: tuple[float, float] = (-4.0, -4.0)
theta_upper: tuple[float, float] = (4.0, 4.0)
theta_rate_norm: float = 1.2
u_max: float = 2.2
du_max: float = 3.5
x_safe: float = 3.0
saturation_tolerance: float = 0.03
def clamp(value: float, lower: float, upper: float) -> float:
return max(lower, min(upper, value))
def rate_limit(command: float, previous: float, max_rate: float, dt: float) -> float:
max_increment = max_rate * dt
return previous + clamp(command - previous, -max_increment, max_increment)
def norm_limit(vector: Sequence[float], maximum_norm: float) -> list[float]:
magnitude = math.sqrt(sum(item * item for item in vector))
if magnitude <= maximum_norm or magnitude == 0.0:
return list(vector)
scale = maximum_norm / magnitude
return [scale * item for item in vector]
def box_projection(
theta: Sequence[float],
proposed_rate: Sequence[float],
lower: Sequence[float],
upper: Sequence[float],
) -> list[float]:
"""Project the update onto the tangent cone of a box constraint."""
projected: list[float] = []
for value, rate, lo, hi in zip(theta, proposed_rate, lower, upper):
if value <= lo and rate < 0.0:
projected.append(0.0)
elif value >= hi and rate > 0.0:
projected.append(0.0)
else:
projected.append(rate)
return projected
def reference(t: float) -> float:
if t < 2.0:
return 0.0
if t < 8.0:
return 1.0
if t < 14.0:
return -1.2
return 0.7
def simulate(
duration: float = 20.0,
dt: float = 0.002,
output_csv: str | Path = "Chapter19_Lesson4_results.csv",
) -> dict[str, float]:
# Unknown plant used only by the simulator: x_dot = -a*x + b*u + d(t).
a_true = 1.25
b_true = 0.80
# First-order reference model.
a_model = 1.60
b_model = 1.60
# Adaptive-law design constants.
gamma = (5.0, 5.0)
sigma = 0.04
limits = Limits()
x = 0.0
x_model = 0.0
theta = [0.0, 0.0]
u = 0.0
rows: list[tuple[float, ...]] = []
saturation_count = 0
supervisor_count = 0
max_abs_error = 0.0
max_theta_rate = 0.0
steps = int(duration / dt) + 1
for k in range(steps):
t = k * dt
r = reference(t)
error = x - x_model
phi = (x, r)
# Requested adaptive command.
u_command = theta[0] * phi[0] + theta[1] * phi[1]
# Hard amplitude limit followed by hard slew-rate limit.
amplitude_limited = clamp(u_command, -limits.u_max, limits.u_max)
u_limited = rate_limit(amplitude_limited, u, limits.du_max, dt)
saturation_error = u_limited - u_command
limited = abs(saturation_error) > limits.saturation_tolerance
if limited:
saturation_count += 1
# Smoothly reduce learning when requested control cannot be delivered.
gate = 1.0 / (1.0 + 8.0 * abs(saturation_error))
# Supervisory fallback: protect the state envelope and freeze adaptation.
supervisor_active = abs(x) >= limits.x_safe
if supervisor_active:
supervisor_count += 1
gate = 0.0
safe_command = clamp(-1.5 * x, -limits.u_max, limits.u_max)
u_limited = rate_limit(safe_command, u, limits.du_max, dt)
raw_theta_rate = [
gate * (-gamma[i] * phi[i] * error) - sigma * theta[i]
for i in range(2)
]
rate_bounded = norm_limit(raw_theta_rate, limits.theta_rate_norm)
theta_rate = box_projection(
theta,
rate_bounded,
limits.theta_lower,
limits.theta_upper,
)
max_theta_rate = max(
max_theta_rate,
math.sqrt(sum(item * item for item in theta_rate)),
)
# Euler integration. A smaller dt can be selected for verification.
disturbance = 0.10 * math.sin(1.7 * t)
x_dot = -a_true * x + b_true * u_limited + disturbance
x_model_dot = -a_model * x_model + b_model * r
x += dt * x_dot
x_model += dt * x_model_dot
theta = [
clamp(theta[i] + dt * theta_rate[i],
limits.theta_lower[i], limits.theta_upper[i])
for i in range(2)
]
u = u_limited
max_abs_error = max(max_abs_error, abs(error))
safety_index = max(
abs(u) / limits.u_max,
abs(theta[0]) / limits.theta_upper[0],
abs(theta[1]) / limits.theta_upper[1],
)
rows.append((
t, r, x, x_model, error, u_command, u,
theta[0], theta[1], theta_rate[0], theta_rate[1],
gate, safety_index, float(supervisor_active),
))
output_path = Path(output_csv)
with output_path.open("w", newline="", encoding="utf-8") as stream:
writer = csv.writer(stream)
writer.writerow([
"time", "reference", "state", "reference_model", "error",
"u_command", "u_applied", "theta_x", "theta_r",
"theta_x_rate", "theta_r_rate", "adaptation_gate",
"safety_index", "supervisor_active",
])
writer.writerows(rows)
rms_error = math.sqrt(sum(row[4] ** 2 for row in rows) / len(rows))
summary = {
"rms_error": rms_error,
"max_abs_error": max_abs_error,
"max_theta_rate_norm": max_theta_rate,
"saturation_fraction": saturation_count / len(rows),
"supervisor_fraction": supervisor_count / len(rows),
"final_theta_x": theta[0],
"final_theta_r": theta[1],
}
for key, value in summary.items():
print(f"{key}: {value:.6f}")
print(f"CSV written to: {output_path.resolve()}")
# Optional plotting; simulation itself uses only the standard library.
try:
import matplotlib.pyplot as plt # type: ignore
time = [row[0] for row in rows]
plt.figure()
plt.plot(time, [row[2] for row in rows], label="x")
plt.plot(time, [row[3] for row in rows], "--", label="x_m")
plt.xlabel("Time [s]")
plt.ylabel("State")
plt.legend()
plt.tight_layout()
plt.figure()
plt.plot(time, [row[5] for row in rows], label="u_command")
plt.plot(time, [row[6] for row in rows], label="u_applied")
plt.xlabel("Time [s]")
plt.ylabel("Control")
plt.legend()
plt.tight_layout()
plt.figure()
plt.plot(time, [row[7] for row in rows], label="theta_x")
plt.plot(time, [row[8] for row in rows], label="theta_r")
plt.xlabel("Time [s]")
plt.ylabel("Adaptive parameters")
plt.legend()
plt.tight_layout()
plt.show()
except ImportError:
print("Install matplotlib to display plots: python -m pip install matplotlib")
return summary
if __name__ == "__main__":
simulate()
The simulation uses the Python standard library for integration and CSV
export. If matplotlib is installed, it additionally plots
the state, requested/applied control, and adaptive parameters.
11. C++ Implementation
Chapter19_Lesson4.cpp
// Chapter19_Lesson4.cpp
// Safety-aware first-order adaptive-control simulation.
#include <algorithm>
#include <array>
#include <cmath>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>
struct Limits {
std::array<double, 2> thetaLower{-4.0, -4.0};
std::array<double, 2> thetaUpper{4.0, 4.0};
double thetaRateNorm{1.2};
double uMax{2.2};
double duMax{3.5};
double xSafe{3.0};
double saturationTolerance{0.03};
};
double clampValue(double value, double lower, double upper) {
return std::max(lower, std::min(upper, value));
}
double rateLimit(double command, double previous, double maxRate, double dt) {
const double maxIncrement = maxRate * dt;
return previous + clampValue(command - previous, -maxIncrement, maxIncrement);
}
std::array<double, 2> normLimit(std::array<double, 2> value, double maxNorm) {
const double norm = std::hypot(value[0], value[1]);
if (norm > maxNorm && norm > 0.0) {
const double scale = maxNorm / norm;
value[0] *= scale;
value[1] *= scale;
}
return value;
}
std::array<double, 2> boxProjection(
const std::array<double, 2>& theta,
std::array<double, 2> proposed,
const Limits& limits) {
for (std::size_t i = 0; i < theta.size(); ++i) {
if ((theta[i] <= limits.thetaLower[i] && proposed[i] < 0.0) ||
(theta[i] >= limits.thetaUpper[i] && proposed[i] > 0.0)) {
proposed[i] = 0.0;
}
}
return proposed;
}
double reference(double t) {
if (t < 2.0) return 0.0;
if (t < 8.0) return 1.0;
if (t < 14.0) return -1.2;
return 0.7;
}
int main() {
try {
constexpr double dt = 0.002;
constexpr double duration = 20.0;
constexpr double aTrue = 1.25;
constexpr double bTrue = 0.80;
constexpr double aModel = 1.60;
constexpr double bModel = 1.60;
const std::array<double, 2> gamma{5.0, 5.0};
constexpr double sigma = 0.04;
const Limits limits;
double x = 0.0;
double xModel = 0.0;
std::array<double, 2> theta{0.0, 0.0};
double u = 0.0;
std::ofstream csv("Chapter19_Lesson4_results_cpp.csv");
if (!csv) throw std::runtime_error("Cannot open output CSV file.");
csv << "time,reference,state,reference_model,error,u_command,u_applied,"
"theta_x,theta_r,theta_x_rate,theta_r_rate,adaptation_gate,"
"safety_index,supervisor_active\n";
csv << std::setprecision(12);
const int steps = static_cast<int>(duration / dt) + 1;
double sumSquaredError = 0.0;
double maxAbsError = 0.0;
double maxThetaRate = 0.0;
int saturationCount = 0;
int supervisorCount = 0;
for (int k = 0; k < steps; ++k) {
const double t = k * dt;
const double r = reference(t);
const double error = x - xModel;
const std::array<double, 2> phi{x, r};
const double uCommand = theta[0] * x + theta[1] * r;
const double amplitudeLimited = clampValue(uCommand, -limits.uMax, limits.uMax);
double uLimited = rateLimit(amplitudeLimited, u, limits.duMax, dt);
const double saturationError = uLimited - uCommand;
if (std::abs(saturationError) > limits.saturationTolerance) ++saturationCount;
double gate = 1.0 / (1.0 + 8.0 * std::abs(saturationError));
const bool supervisorActive = std::abs(x) >= limits.xSafe;
if (supervisorActive) {
++supervisorCount;
gate = 0.0;
const double safeCommand = clampValue(-1.5 * x, -limits.uMax, limits.uMax);
uLimited = rateLimit(safeCommand, u, limits.duMax, dt);
}
std::array<double, 2> thetaRate{
gate * (-gamma[0] * phi[0] * error) - sigma * theta[0],
gate * (-gamma[1] * phi[1] * error) - sigma * theta[1]
};
thetaRate = normLimit(thetaRate, limits.thetaRateNorm);
thetaRate = boxProjection(theta, thetaRate, limits);
maxThetaRate = std::max(maxThetaRate, std::hypot(thetaRate[0], thetaRate[1]));
const double disturbance = 0.10 * std::sin(1.7 * t);
const double xDot = -aTrue * x + bTrue * uLimited + disturbance;
const double xModelDot = -aModel * xModel + bModel * r;
x += dt * xDot;
xModel += dt * xModelDot;
for (std::size_t i = 0; i < theta.size(); ++i) {
theta[i] = clampValue(theta[i] + dt * thetaRate[i],
limits.thetaLower[i], limits.thetaUpper[i]);
}
u = uLimited;
const double safetyIndex = std::max({
std::abs(u) / limits.uMax,
std::abs(theta[0]) / limits.thetaUpper[0],
std::abs(theta[1]) / limits.thetaUpper[1]
});
sumSquaredError += error * error;
maxAbsError = std::max(maxAbsError, std::abs(error));
csv << t << ',' << r << ',' << x << ',' << xModel << ',' << error << ','
<< uCommand << ',' << u << ',' << theta[0] << ',' << theta[1] << ','
<< thetaRate[0] << ',' << thetaRate[1] << ',' << gate << ','
<< safetyIndex << ',' << (supervisorActive ? 1 : 0) << '\n';
}
std::cout << std::fixed << std::setprecision(6)
<< "rms_error: " << std::sqrt(sumSquaredError / steps) << '\n'
<< "max_abs_error: " << maxAbsError << '\n'
<< "max_theta_rate_norm: " << maxThetaRate << '\n'
<< "saturation_fraction: " << static_cast<double>(saturationCount) / steps << '\n'
<< "supervisor_fraction: " << static_cast<double>(supervisorCount) / steps << '\n'
<< "final_theta_x: " << theta[0] << '\n'
<< "final_theta_r: " << theta[1] << '\n';
return 0;
} catch (const std::exception& error) {
std::cerr << "Error: " << error.what() << '\n';
return 1;
}
}
12. Java Implementation
Chapter19_Lesson4.java
// Chapter19_Lesson4.java
// Safety-aware first-order adaptive-control simulation.
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 Chapter19_Lesson4 {
private Chapter19_Lesson4() { }
private static final class Limits {
final double[] thetaLower = {-4.0, -4.0};
final double[] thetaUpper = {4.0, 4.0};
final double thetaRateNorm = 1.2;
final double uMax = 2.2;
final double duMax = 3.5;
final double xSafe = 3.0;
final double saturationTolerance = 0.03;
}
private static double clamp(double value, double lower, double upper) {
return Math.max(lower, Math.min(upper, value));
}
private static double rateLimit(double command, double previous, double maxRate, double dt) {
double maxIncrement = maxRate * dt;
return previous + clamp(command - previous, -maxIncrement, maxIncrement);
}
private static double[] normLimit(double[] value, double maximumNorm) {
double norm = Math.hypot(value[0], value[1]);
if (norm > maximumNorm && norm > 0.0) {
double scale = maximumNorm / norm;
value[0] *= scale;
value[1] *= scale;
}
return value;
}
private static double[] boxProjection(double[] theta, double[] proposed, Limits limits) {
for (int i = 0; i < theta.length; i++) {
if ((theta[i] <= limits.thetaLower[i] && proposed[i] < 0.0)
|| (theta[i] >= limits.thetaUpper[i] && proposed[i] > 0.0)) {
proposed[i] = 0.0;
}
}
return proposed;
}
private static double reference(double time) {
if (time < 2.0) return 0.0;
if (time < 8.0) return 1.0;
if (time < 14.0) return -1.2;
return 0.7;
}
public static void main(String[] args) {
Locale.setDefault(Locale.ROOT);
final double dt = 0.002;
final double duration = 20.0;
final double aTrue = 1.25;
final double bTrue = 0.80;
final double aModel = 1.60;
final double bModel = 1.60;
final double[] gamma = {5.0, 5.0};
final double sigma = 0.04;
final Limits limits = new Limits();
double x = 0.0;
double xModel = 0.0;
double[] theta = {0.0, 0.0};
double u = 0.0;
int steps = (int) (duration / dt) + 1;
double sumSquaredError = 0.0;
double maxAbsError = 0.0;
double maxThetaRate = 0.0;
int saturationCount = 0;
int supervisorCount = 0;
Path output = Path.of("Chapter19_Lesson4_results_java.csv");
try (BufferedWriter csv = Files.newBufferedWriter(output, StandardCharsets.UTF_8)) {
csv.write("time,reference,state,reference_model,error,u_command,u_applied,"
+ "theta_x,theta_r,theta_x_rate,theta_r_rate,adaptation_gate,"
+ "safety_index,supervisor_active\n");
for (int k = 0; k < steps; k++) {
double time = k * dt;
double r = reference(time);
double error = x - xModel;
double[] phi = {x, r};
double uCommand = theta[0] * x + theta[1] * r;
double amplitudeLimited = clamp(uCommand, -limits.uMax, limits.uMax);
double uLimited = rateLimit(amplitudeLimited, u, limits.duMax, dt);
double saturationError = uLimited - uCommand;
if (Math.abs(saturationError) > limits.saturationTolerance) saturationCount++;
double gate = 1.0 / (1.0 + 8.0 * Math.abs(saturationError));
boolean supervisorActive = Math.abs(x) >= limits.xSafe;
if (supervisorActive) {
supervisorCount++;
gate = 0.0;
double safeCommand = clamp(-1.5 * x, -limits.uMax, limits.uMax);
uLimited = rateLimit(safeCommand, u, limits.duMax, dt);
}
double[] thetaRate = {
gate * (-gamma[0] * phi[0] * error) - sigma * theta[0],
gate * (-gamma[1] * phi[1] * error) - sigma * theta[1]
};
thetaRate = normLimit(thetaRate, limits.thetaRateNorm);
thetaRate = boxProjection(theta, thetaRate, limits);
maxThetaRate = Math.max(maxThetaRate, Math.hypot(thetaRate[0], thetaRate[1]));
double disturbance = 0.10 * Math.sin(1.7 * time);
double xDot = -aTrue * x + bTrue * uLimited + disturbance;
double xModelDot = -aModel * xModel + bModel * r;
x += dt * xDot;
xModel += dt * xModelDot;
for (int i = 0; i < theta.length; i++) {
theta[i] = clamp(theta[i] + dt * thetaRate[i],
limits.thetaLower[i], limits.thetaUpper[i]);
}
u = uLimited;
double safetyIndex = Math.max(Math.abs(u) / limits.uMax,
Math.max(Math.abs(theta[0]) / limits.thetaUpper[0],
Math.abs(theta[1]) / limits.thetaUpper[1]));
sumSquaredError += error * error;
maxAbsError = Math.max(maxAbsError, Math.abs(error));
csv.write(String.format(Locale.ROOT,
"%.12f,%.12f,%.12f,%.12f,%.12f,%.12f,%.12f,"
+ "%.12f,%.12f,%.12f,%.12f,%.12f,%.12f,%d%n",
time, r, x, xModel, error, uCommand, u,
theta[0], theta[1], thetaRate[0], thetaRate[1],
gate, safetyIndex, supervisorActive ? 1 : 0));
}
} catch (IOException exception) {
System.err.println("Unable to write CSV: " + exception.getMessage());
System.exit(1);
}
System.out.printf(Locale.ROOT, "rms_error: %.6f%n", Math.sqrt(sumSquaredError / steps));
System.out.printf(Locale.ROOT, "max_abs_error: %.6f%n", maxAbsError);
System.out.printf(Locale.ROOT, "max_theta_rate_norm: %.6f%n", maxThetaRate);
System.out.printf(Locale.ROOT, "saturation_fraction: %.6f%n",
(double) saturationCount / steps);
System.out.printf(Locale.ROOT, "supervisor_fraction: %.6f%n",
(double) supervisorCount / steps);
System.out.printf(Locale.ROOT, "final_theta_x: %.6f%n", theta[0]);
System.out.printf(Locale.ROOT, "final_theta_r: %.6f%n", theta[1]);
}
}
13. MATLAB and Simulink-Oriented Implementation
Chapter19_Lesson4.m
% Chapter19_Lesson4.m
% Safety-aware adaptive-control simulation with parameter projection,
% parameter-rate limiting, actuator amplitude/rate limits, adaptation gating,
% and a supervisory fallback.
clear; clc; close all;
dt = 0.002;
T = 20.0;
time = (0:dt:T)';
N = numel(time);
% Unknown plant (used only by the simulation).
aTrue = 1.25;
bTrue = 0.80;
% Reference model.
aModel = 1.60;
bModel = 1.60;
% Adaptive law and safety limits.
gamma = [5.0; 5.0];
sigma = 0.04;
thetaLower = [-4.0; -4.0];
thetaUpper = [ 4.0; 4.0];
thetaRateNorm = 1.2;
uMax = 2.2;
duMax = 3.5;
xSafe = 3.0;
satTolerance = 0.03;
x = zeros(N,1);
xModel = zeros(N,1);
r = zeros(N,1);
uCommand = zeros(N,1);
uApplied = zeros(N,1);
theta = zeros(N,2);
thetaRate = zeros(N,2);
gate = ones(N,1);
safetyIndex = zeros(N,1);
supervisor = false(N,1);
for k = 1:N-1
t = time(k);
if t < 2.0
r(k) = 0.0;
elseif t < 8.0
r(k) = 1.0;
elseif t < 14.0
r(k) = -1.2;
else
r(k) = 0.7;
end
e = x(k) - xModel(k);
phi = [x(k); r(k)];
uCommand(k) = theta(k,:) * phi;
% Amplitude and slew-rate limits.
uAmp = min(max(uCommand(k), -uMax), uMax);
maxIncrement = duMax * dt;
increment = min(max(uAmp - uApplied(k), -maxIncrement), maxIncrement);
uNext = uApplied(k) + increment;
deltaU = uNext - uCommand(k);
% Saturation-aware learning gate.
gate(k) = 1.0 / (1.0 + 8.0 * abs(deltaU));
% Supervisory fallback and adaptation freeze.
supervisor(k) = abs(x(k)) >= xSafe;
if supervisor(k)
gate(k) = 0.0;
safeCommand = min(max(-1.5*x(k), -uMax), uMax);
increment = min(max(safeCommand-uApplied(k), -maxIncrement), maxIncrement);
uNext = uApplied(k) + increment;
end
rawRate = gate(k) * (-gamma .* phi * e) - sigma * theta(k,:)';
% Euclidean norm limit on parameter velocity.
rawNorm = norm(rawRate, 2);
if rawNorm > thetaRateNorm
rawRate = thetaRateNorm * rawRate / rawNorm;
end
% Tangent-cone projection for box constraints.
projectedRate = rawRate;
for i = 1:2
if (theta(k,i) <= thetaLower(i) && projectedRate(i) < 0) || ...
(theta(k,i) >= thetaUpper(i) && projectedRate(i) > 0)
projectedRate(i) = 0.0;
end
end
thetaRate(k,:) = projectedRate';
disturbance = 0.10 * sin(1.7*t);
xDot = -aTrue*x(k) + bTrue*uNext + disturbance;
xModelDot = -aModel*xModel(k) + bModel*r(k);
x(k+1) = x(k) + dt*xDot;
xModel(k+1) = xModel(k) + dt*xModelDot;
theta(k+1,:) = min(max(theta(k,:) + dt*thetaRate(k,:), thetaLower'), thetaUpper');
uApplied(k+1) = uNext;
safetyIndex(k) = max([abs(uApplied(k))/uMax, ...
abs(theta(k,1))/thetaUpper(1), ...
abs(theta(k,2))/thetaUpper(2)]);
end
r(end) = r(end-1);
uCommand(end) = theta(end,:) * [x(end); r(end)];
gate(end) = gate(end-1);
thetaRate(end,:) = thetaRate(end-1,:);
safetyIndex(end) = safetyIndex(end-1);
supervisor(end) = supervisor(end-1);
error = x - xModel;
results = table(time,r,x,xModel,error,uCommand,uApplied,theta(:,1),theta(:,2), ...
thetaRate(:,1),thetaRate(:,2),gate,safetyIndex,double(supervisor), ...
'VariableNames', {'time','reference','state','reference_model','error', ...
'u_command','u_applied','theta_x','theta_r','theta_x_rate','theta_r_rate', ...
'adaptation_gate','safety_index','supervisor_active'});
writetable(results, 'Chapter19_Lesson4_results_matlab.csv');
fprintf('RMS error: %.6f\n', rms(error));
fprintf('Maximum |error|: %.6f\n', max(abs(error)));
fprintf('Maximum parameter-rate norm: %.6f\n', max(vecnorm(thetaRate,2,2)));
fprintf('Fraction with actuator mismatch above tolerance: %.6f\n', ...
mean(abs(uApplied-uCommand) > satTolerance));
fprintf('Supervisor-active fraction: %.6f\n', mean(supervisor));
figure;
plot(time,x,'LineWidth',1.2); hold on;
plot(time,xModel,'--','LineWidth',1.2);
xlabel('Time [s]'); ylabel('State'); grid on;
legend('x','x_m','Location','best');
figure;
plot(time,uCommand,'LineWidth',1.0); hold on;
plot(time,uApplied,'LineWidth',1.2);
yline(uMax,':'); yline(-uMax,':');
xlabel('Time [s]'); ylabel('Control'); grid on;
legend('u_c','u','limits','Location','best');
figure;
plot(time,theta(:,1),'LineWidth',1.2); hold on;
plot(time,theta(:,2),'LineWidth',1.2);
yline(thetaUpper(1),':'); yline(thetaLower(1),':');
xlabel('Time [s]'); ylabel('Adaptive parameters'); grid on;
legend('\theta_x','\theta_r','bounds','Location','best');
% Simulink realization:
% 1. Use Integrator blocks for x, x_m, and theta.
% 2. Use Saturation followed by Rate Limiter for the actuator channel.
% 3. Implement the box projection and norm limiter in a MATLAB Function block.
% 4. Feed the actuator mismatch u-u_c to the adaptation-gate calculation.
% 5. Use a Switch block to freeze adaptation when the supervisor is active.
In Simulink, the same equations can be realized with Integrator blocks for plant, model, and adaptive states; a Saturation block followed by a Rate Limiter block; and a MATLAB Function block implementing the norm limiter and tangent-cone projection. The measured output of the limiter must be fed back to compute \( \Delta u=u-u_c \).
14. Wolfram Mathematica Implementation
Chapter19_Lesson4.nb
Notebook[{
Cell["Chapter 19, Lesson 4: Safety-Aware Adaptive Control", "Title"],
Cell["Parameter projection, parameter-rate limits, actuator amplitude/rate limits, adaptation gating, and supervisory fallback.", "Text"],
Cell["ClearAll[\"Global`*\"];
dt = 0.002; endTime = 20.0; steps = Round[endTime/dt];
ClampValue[value_, lower_, upper_] := Min[upper, Max[lower, value]];
ReferenceSignal[t_] := Piecewise[{ {0.0, t < 2.0}, {1.0, t < 8.0}, {-1.2, t < 14.0} }, 0.7];
BoxProject[theta_, proposedRate_, lower_, upper_] := MapThread[
Function[{th, rate, lo, hi}, If[(th <= lo && rate < 0.0) || (th >= hi && rate > 0.0), 0.0, rate]],
{theta, proposedRate, lower, upper}];
aTrue = 1.25; bTrue = 0.80; aModel = 1.60; bModel = 1.60;
gamma = {5.0, 5.0}; sigma = 0.04; thetaLower = {-4.0, -4.0}; thetaUpper = {4.0, 4.0};
thetaRateMaximum = 1.2; uMaximum = 2.2; duMaximum = 3.5; xSafe = 3.0;
state = {0.0, 0.0, {0.0, 0.0}, 0.0};
data = Reap[
Do[
Module[
{t, x, xModel, theta, u, r, error, phi, uCommand, uAmplitude, uNew,
saturationError, gate, supervisor, safeCommand, rawRate, thetaRate,
disturbance, xDot, xModelDot, safetyIndex},
t = k dt; {x, xModel, theta, u} = state; r = ReferenceSignal[t];
error = x - xModel; phi = {x, r}; uCommand = theta . phi;
uAmplitude = ClampValue[uCommand, -uMaximum, uMaximum];
uNew = u + ClampValue[uAmplitude - u, -duMaximum dt, duMaximum dt];
saturationError = uNew - uCommand; gate = 1.0/(1.0 + 8.0 Abs[saturationError]);
supervisor = Abs[x] >= xSafe;
If[supervisor, gate = 0.0; safeCommand = ClampValue[-1.5 x, -uMaximum, uMaximum];
uNew = u + ClampValue[safeCommand - u, -duMaximum dt, duMaximum dt];];
rawRate = gate (-gamma phi error) - sigma theta;
thetaRate = If[Norm[rawRate] > thetaRateMaximum,
thetaRateMaximum rawRate/Norm[rawRate], rawRate];
thetaRate = BoxProject[theta, thetaRate, thetaLower, thetaUpper];
disturbance = 0.10 Sin[1.7 t]; xDot = -aTrue x + bTrue uNew + disturbance;
xModelDot = -aModel xModel + bModel r; x = x + dt xDot; xModel = xModel + dt xModelDot;
theta = MapThread[ClampValue, {theta + dt thetaRate, thetaLower, thetaUpper}];
state = {x, xModel, theta, uNew};
safetyIndex = Max[Abs[uNew]/uMaximum, Abs[theta[[1]]]/thetaUpper[[1]],
Abs[theta[[2]]]/thetaUpper[[2]]];
Sow[{t, r, x, xModel, error, uCommand, uNew, theta[[1]], theta[[2]],
thetaRate[[1]], thetaRate[[2]], gate, safetyIndex, Boole[supervisor]}];
], {k, 0, steps}]
][[2, 1]];
headers = {\"time\", \"reference\", \"state\", \"reference_model\", \"error\", \"u_command\",
\"u_applied\", \"theta_x\", \"theta_r\", \"theta_x_rate\", \"theta_r_rate\",
\"adaptation_gate\", \"safety_index\", \"supervisor_active\"};
Export[\"Chapter19_Lesson4_results_mathematica.csv\", Prepend[data, headers]];
Print[\"RMS error: \", Sqrt[Mean[data[[All, 5]]^2]]];
Print[\"Maximum absolute error: \", Max[Abs[data[[All, 5]]]]];
Print[\"Maximum parameter-rate norm: \", Max[Norm /@ data[[All, {10, 11}]]]];
statePlot = ListLinePlot[{data[[All, {1, 3}]], data[[All, {1, 4}]]},
PlotLegends -> {\"x\", \"x_m\"}, Frame -> True, FrameLabel -> {\"Time [s]\", \"State\"}, PlotRange -> All];
controlPlot = ListLinePlot[{data[[All, {1, 6}]], data[[All, {1, 7}]]},
PlotLegends -> {\"u_command\", \"u_applied\"}, Frame -> True, FrameLabel -> {\"Time [s]\", \"Control\"}, PlotRange -> All];
parameterPlot = ListLinePlot[{data[[All, {1, 8}]], data[[All, {1, 9}]]},
PlotLegends -> {\"theta_x\", \"theta_r\"}, Frame -> True,
FrameLabel -> {\"Time [s]\", \"Adaptive parameters\"}, PlotRange -> All];
Column[{statePlot, controlPlot, parameterPlot}]
", "Input"]
}, WindowTitle -> "Chapter19_Lesson4"]
15. Problems and Solutions
Problem 1 — Positive invariance under box projection.
Let \( \Omega=[\underline\theta,\overline\theta] \) and \( \dot{\hat\theta}=\operatorname{Proj}_{\Omega}(\hat\theta,v) \). Prove that \( \hat\theta(0)\in\Omega \) implies \( \hat\theta(t)\in\Omega \) for all \( t\ge0 \).
Solution.
At \( \hat\theta=\overline\theta \), every outward update has \( v>0 \) and is replaced by zero; therefore \( \dot{\hat\theta}\le0 \). At \( \hat\theta=\underline\theta \), every outward update has \( v<0 \) and is replaced by zero; therefore \( \dot{\hat\theta}\ge0 \). The vector field points inward or tangent at both boundaries, so the interval is positively invariant.
Problem 2 — Simultaneous amplitude and slew-rate guarantees.
Suppose \( |u_{k-1}|\le u_{\max} \) and define \( \bar u_k=\operatorname{sat}_{u_{\max} }(u_{c,k}) \) and \( u_k=u_{k-1}+\operatorname{sat}_{T_s\rho_u}(\bar u_k-u_{k-1}) \). Prove the slew-rate bound and explain why the amplitude bound is preserved.
Solution.
By construction,
\[ |u_k-u_{k-1}|\le T_s\rho_u, \]
which gives the required discrete slew bound. The increment always moves from \( u_{k-1} \) toward \( \bar u_k\in[-u_{\max},u_{\max}] \) without overshoot. Since the interval is convex, every intermediate point also lies in the interval; hence \( |u_k|\le u_{\max} \).
Problem 3 — Ultimate tracking radius.
Given \( \dot V\le-qz^2+c_1z+c_0 \), with \( q>0 \), \( c_1\ge0 \), and \( c_0\ge0 \), find the smallest nonnegative radius outside which \( \dot V<0 \).
Solution.
Solve \( qz^2-c_1z-c_0>0 \). Its positive root is
\[ r=\frac{c_1+\sqrt{c_1^2+4qc_0} }{2q}. \]
The quadratic is positive for \( z>r \); therefore \( \dot V<0 \) outside that ball. Reducing actuator mismatch and update-limiter residual decreases the ultimate radius.
Problem 4 — Reference feasibility.
For \( \dot x=-1.25x+0.80u \) and \( |u|\le2.2 \), determine the largest magnitude of a constant equilibrium reference that can be maintained in the disturbance-free plant.
Solution.
At equilibrium, \( 0=-1.25r_0+0.80u_{eq} \), so
\[ |r_0|\le\frac{0.80}{1.25}(2.2)=1.408. \]
A command beyond this value is not statically feasible. Adaptation cannot remove the physical authority deficit.
Problem 5 — Selecting a parameter-rate limit.
A controller runs at \( T_s=2\text{ ms} \). Software requirements permit the parameter vector to move by at most \( 2.4\times10^{-3} \) per sample in Euclidean norm. Find \( \rho_{\theta} \).
Solution.
\[ \rho_{\theta}=\frac{2.4\times10^{-3} }{2\times10^{-3} }=1.2. \]
This is the value used in the implementations. A runtime assertion should allow only a small numerical tolerance above this bound.
Problem 6 — Failure of command-only monitoring.
Explain why checking only \( |u_c|\le u_{\max} \) is insufficient when the actuator has a slew-rate limit.
Solution.
A requested command may lie inside the amplitude interval but change too rapidly to be realized. Then \( u\ne u_c \) and the error dynamics contain the mismatch input \( \Delta u \). Monitoring the applied actuator signal and its rate is therefore necessary; command amplitude alone does not measure available control authority.
16. Summary
Safe adaptive implementation requires separate envelopes for parameter magnitude, parameter-update rate, actuator amplitude, actuator slew rate, and critical plant states. Projection makes the parameter set positively invariant and preserves a useful Lyapunov inequality. Rate limiting caps learning speed. Actuator limiting exposes an authority mismatch that should gate adaptation and enter the stability analysis. Under persistent bounded mismatch and disturbances, uniformly ultimately bounded tracking is the appropriate conclusion. A deterministic supervisor must freeze adaptation and select a verified fallback whenever the monitored envelope is violated.
17. References
- Ioannou, P.A., & Kokotović, P.V. (1984). Instability analysis and improvement of robustness of adaptive control. Automatica, 20(5), 583–594. https://doi.org/10.1016/0005-1098(84)90009-8.
- 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.
- Chaoui, F.Z., Giri, F., & M'Saad, M. (2001). Adaptive control of input-constrained type-1 plants: Stabilization and tracking. Automatica, 37(2), 197–203.
- Lavretsky, E., Gibson, T.E., & Annaswamy, A.M. (2012). Projection operator in adaptive systems. arXiv preprint, arXiv:1112.4232v6.
- Turner, M.C., Sofrony, J., & Prempain, E. (2020). Anti-windup for model-reference adaptive control schemes with rate-limits. Systems & Control Letters, 137, 104630.
- Tohidi, S.S., Yildiz, Y., & Kolmanovsky, I. (2020). Adaptive control allocation for constrained systems. Automatica, 121, 109161.
- Teel, A.R. (1992). Global stabilization and restricted tracking for multiple integrators with bounded controls. Systems & Control Letters, 18(3), 165–171.
- Sussmann, H.J., Sontag, E.D., & Yang, Y. (1994). A general result on the stabilization of linear systems using bounded controls. IEEE Transactions on Automatic Control, 39(12), 2411–2425.
- Nguyen, N. (2012). Optimal control modification for robust adaptive control with large adaptive gain. Systems & Control Letters, 61(4), 485–494.
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.