Chapter 2: Uncertainty and Parametric Modeling for Adaptation

Lesson 5: Where Parameter Adaptation Actually Helps (and Where It Doesn’t)

This lesson explains the boundary between useful parameter adaptation and misleading adaptation. We study when online parameter adjustment improves tracking, stabilization, or disturbance rejection, and when the adaptive law is trying to estimate the wrong object. The emphasis is on matched parametric uncertainty, regressor correctness, excitation, and simple Lyapunov reasoning.

1. Motivation: Adaptation Is Not a Universal Cure

In the previous lessons, we introduced parametric uncertainty, linearly parameterized models, regressor vectors, parameter vectors, and matching conditions. The natural question is now: when does estimating parameters online actually improve the closed-loop system?

Parameter adaptation is most useful when the unknown part of the plant can be represented by a finite-dimensional parameter vector \( \theta^\ast \) multiplying a known regressor \( \phi(t,x,u) \). A typical scalar uncertainty model is

\[ \Delta(t,x,u)=\theta^{\ast T}\phi(t,x,u) \]

Adaptation is much less useful when the problem is dominated by unmodeled dynamics, wrong regressors, actuator saturation, time delay, strong measurement noise, or uncertainty that enters through a channel the input cannot cancel. In those cases, changing \( \hat{\theta} \) may reduce a numerical error signal temporarily, but the parameter estimate may lose physical meaning.

flowchart TD
  A["Unknown behavior observed"] --> B["Can it be represented as theta^T phi?"]
  B -->|yes| C["Is the uncertainty matched \nto the input channel?"]
  B -->|no| F["Adaptation is \nstructurally unreliable"]
  C -->|yes| D["Are signals \nsufficiently informative?"]
  C -->|no| G["Input cannot cancel \nthe uncertainty directly"]
  D -->|yes| E["Parameter adaptation \ncan help tracking and \npossibly identification"]
  D -->|no| H["Tracking may improve \nbut parameter convergence \nis weak"]
  F --> I["Prefer modeling, robust design, \nfiltering, or redesign"]
  G --> I
  H --> J["Use adaptation cautiously; \ndo not overinterpret thetaHat"]
        

2. Baseline Case Where Adaptation Helps

Consider the scalar plant

\[ \dot{x}=u+\theta^{\ast T}\phi(t,x) \]

where \( x \in \mathbb{R} \) is the measured state, \( u \in \mathbb{R} \) is the control input, \( \theta^\ast \in \mathbb{R}^p \) is an unknown constant parameter vector, and \( \phi(t,x)\in\mathbb{R}^p \) is a known regressor. Let the adaptive controller be

\[ u=-\lambda x-\hat{\theta}^{T}\phi(t,x), \qquad \lambda > 0 \]

Define the parameter estimation error as

\[ \tilde{\theta}=\theta^\ast-\hat{\theta}. \]

The closed-loop state equation becomes

\[ \dot{x}=-\lambda x+\tilde{\theta}^{T}\phi(t,x). \]

The term \( \tilde{\theta}^{T}\phi(t,x) \) is the remaining uncertainty after adaptive cancellation. If \( \hat{\theta}=\theta^\ast \), this term vanishes and the state satisfies \( \dot{x}=-\lambda x \).

3. Lyapunov Explanation: Why the Matched Case Works

Choose the adaptive law

\[ \dot{\hat{\theta} }=\Gamma \phi(t,x)x, \qquad \Gamma=\Gamma^T > 0. \]

Since \( \tilde{\theta}=\theta^\ast-\hat{\theta} \) and \( \theta^\ast \) is constant,

\[ \dot{\tilde{\theta} }=-\dot{\hat{\theta} } =-\Gamma \phi(t,x)x. \]

Consider the Lyapunov candidate

\[ V(x,\tilde{\theta}) = \frac{1}{2}x^2+ \frac{1}{2}\tilde{\theta}^{T}\Gamma^{-1}\tilde{\theta}. \]

Differentiating along the closed-loop trajectories gives

\[ \begin{aligned} \dot{V} &= x\dot{x} + \tilde{\theta}^{T}\Gamma^{-1}\dot{\tilde{\theta} }\\ &= x\left(-\lambda x+\tilde{\theta}^{T}\phi\right) + \tilde{\theta}^{T}\Gamma^{-1} \left(-\Gamma \phi x\right)\\ &= -\lambda x^2 +x\tilde{\theta}^{T}\phi -x\tilde{\theta}^{T}\phi\\ &= -\lambda x^2. \end{aligned} \]

Therefore, the adaptive law cancels the cross-term \( x\tilde{\theta}^{T}\phi \) in the Lyapunov derivative. This is the central mathematical reason that matched linearly parameterized uncertainty is a good target for adaptation.

The result proves boundedness of \( x(t) \) and \( \tilde{\theta}(t) \), and also shows that \( x(t) \) has finite energy:

\[ \int_{0}^{\infty}x^2(t)\,dt \le \frac{V(0)}{\lambda}. \]

This does not automatically prove parameter convergence. It proves that the tracking/stabilization objective is compatible with the adaptive law. Parameter convergence requires additional signal richness, which will be treated formally in Chapter 10.

4. Cases Where Parameter Adaptation Actually Helps

Parameter adaptation is useful when the uncertainty satisfies the following structural conditions:

  1. Correct parametric structure: the unknown term is close to \( \theta^{\ast T}\phi(t,x,u) \).
  2. Matched uncertainty: the uncertainty enters through the same channel as the input.
  3. Known regressor: \( \phi(t,x,u) \) is computable from measured or generated signals.
  4. Moderate time variation: parameters are constant or slowly varying compared with the adaptation dynamics.
  5. Useful excitation: the signals are informative enough to distinguish parameter directions.
  6. Safe control authority: the actuator can apply the adaptive command without severe saturation.

In control-oriented terms, adaptation is valuable when a controller with fixed conservative gains would work poorly across a family of plants, while online parameter adjustment can reduce conservatism. A simple example is a scalar family

\[ \dot{x}=a^\ast x+bu,\qquad b > 0, \]

where \( a^\ast \) is unknown but appears in the known regressor \( \phi=x \). If \( a^\ast \) varies from plant to plant, adaptation can tune the cancellation term online instead of forcing one fixed high-gain controller to handle all cases.

5. Cases Where Parameter Adaptation Does Not Help Much

Adaptation is not reliable when the unknown behavior cannot be written in the assumed regressor form. Suppose the true plant is

\[ \dot{x}=u+\theta^{\ast T}\phi(t,x)+d(t), \]

but the adaptive law only models \( \theta^{\ast T}\phi(t,x) \). Then the closed-loop dynamics become

\[ \dot{x} = -\lambda x+\tilde{\theta}^{T}\phi(t,x)+d(t). \]

Using the same Lyapunov function,

\[ \dot{V} = -\lambda x^2+xd(t). \]

Completing the square gives

\[ \dot{V} \le -\frac{\lambda}{2}x^2+ \frac{1}{2\lambda}d^2(t). \]

Thus a bounded disturbance generally gives a bounded residual tracking error rather than exact convergence. The adaptive law should not be expected to identify a constant parameter that explains \( d(t) \), because no such constant parameter exists in the assumed model.

flowchart TD
  A["Observed error remains"] --> B["Check parametric model"]
  B --> C["Wrong regressor?"]
  B --> D["Unmodeled dynamics?"]
  B --> E["Unmatched channel?"]
  B --> F["Weak excitation?"]
  B --> G["Noise or saturation?"]
  C --> H["Parameter estimate \nloses physical \nmeaning"]
  D --> I["Adaptive law may \nchase dynamics"]
  E --> J["Input cannot cancel \nuncertainty directly"]
  F --> K["Tracking can improve \nbut thetaHat may not \nconverge"]
  G --> L["Possible drift or \nnoisy parameter \nmotion"]
  H --> M["Do not trust adaptation \nas identification"]
  I --> M
  J --> M
  K --> M
  L --> M
        

6. Matched vs Unmatched Uncertainty

For a linear state-space model,

\[ \dot{x}=Ax+bu+E\theta^\ast \phi(t,x), \]

the uncertainty is matched if the uncertainty direction lies in the input direction. In a single-input system, this means the columns of \( E \) must lie in the span of \( b \). Equivalently, there must exist a vector \( \rho \) such that

\[ E\theta^\ast \phi(t,x)=b\rho^T\phi(t,x). \]

If this condition holds, the input can cancel the uncertainty using an adaptive estimate of \( \rho \). If it does not hold, the component orthogonal to the input channel remains:

\[ \left(I-b(b^Tb)^{-1}b^T\right)E\theta^\ast\phi(t,x) \neq 0. \]

The adaptive law may still change parameters, but it cannot directly remove this unmatched component through \( u \). This is why matching conditions are not merely technical assumptions; they determine whether the control input has the authority to cancel the modeled uncertainty.

7. Parameter Convergence vs Tracking Improvement

A critical distinction is:

  • Tracking improvement means \( x(t) \) or the tracking error becomes small.
  • Parameter convergence means \( \hat{\theta}(t)\to\theta^\ast \).

In the matched scalar example, the Lyapunov proof gives \( \dot{V}=-\lambda x^2 \), but it does not prove \( \hat{\theta}(t)\to\theta^\ast \). To identify all components of \( \theta^\ast \), the regressor must sufficiently excite all parameter directions. Informally, for some constants \( \alpha > 0 \), \( T > 0 \), the regressor should satisfy a condition of the form

\[ \int_{t}^{t+T}\phi(\tau)\phi^T(\tau)\,d\tau \ge \alpha I \qquad \text{for all } t. \]

This condition will be developed rigorously in Chapter 10 under the name persistent excitation. At this point, the lesson is practical: small tracking error does not necessarily mean correct parameter estimation.

8. Python Implementation

Chapter2_Lesson5.py simulates four cases: matched parametric uncertainty, poor excitation, unmodeled disturbance, and wrong regressor. The numerical comparison demonstrates that adaptation is most meaningful when the uncertainty model is structurally correct.

Chapter2_Lesson5.py

"""
Chapter2_Lesson5.py
Adaptive Control - Chapter 2, Lesson 5
Where Parameter Adaptation Actually Helps (and Where It Doesn't)

This script compares four scalar uncertainty cases:
1) matched_parametric: the adaptive law has the correct regressor/channel.
2) poor_excitation: the regressor is too small/decaying for good identification.
3) unmodeled_disturbance: the plant contains a disturbance not represented by theta*phi.
4) wrong_regressor: the true uncertainty is not the assumed model regressor.

Run:
    python Chapter2_Lesson5.py
"""

import math
from dataclasses import dataclass
from typing import Dict, List

import numpy as np
import matplotlib.pyplot as plt


@dataclass
class SimResult:
    name: str
    t: np.ndarray
    x: np.ndarray
    theta_hat: np.ndarray
    u: np.ndarray
    rms_x: float
    final_abs_x: float
    final_theta_hat: float


def plant_uncertainty(case: str, t: float, x: float, theta_true: float) -> tuple[float, float]:
    """Return (phi_model, true_uncertainty)."""
    if case == "matched_parametric":
        phi = math.sin(1.1 * t) + 0.5 * math.cos(0.3 * t)
        return phi, theta_true * phi
    if case == "poor_excitation":
        phi = 0.05 * math.exp(-0.3 * t)
        return phi, theta_true * phi
    if case == "unmodeled_disturbance":
        phi = math.sin(1.1 * t)
        disturbance = 0.7 * math.sin(8.0 * t)
        return phi, theta_true * phi + disturbance
    if case == "wrong_regressor":
        phi = math.sin(1.1 * t)
        true_unmodeled_nonlinearity = theta_true * (x * x / (1.0 + x * x))
        return phi, true_unmodeled_nonlinearity
    raise ValueError(f"Unknown case: {case}")


def simulate(case: str, T: float = 25.0, dt: float = 0.002) -> SimResult:
    n = int(T / dt) + 1
    t = np.linspace(0.0, T, n)
    x = np.zeros(n)
    theta_hat = np.zeros(n)
    u = np.zeros(n)

    # Known stabilizing feedback plus adaptive cancellation.
    lambda_cl = 1.5
    gamma = 4.0
    theta_true = 2.0
    x[0] = 1.5
    theta_hat[0] = 0.0

    for k in range(n - 1):
        phi, uncertainty = plant_uncertainty(case, t[k], x[k], theta_true)
        u[k] = -lambda_cl * x[k] - theta_hat[k] * phi
        x_dot = u[k] + uncertainty
        theta_dot = gamma * x[k] * phi

        x[k + 1] = x[k] + dt * x_dot
        theta_hat[k + 1] = theta_hat[k] + dt * theta_dot

        # Safety projection: not a robust adaptive design, only a simulation guard.
        theta_hat[k + 1] = min(max(theta_hat[k + 1], -10.0), 10.0)

    phi, uncertainty = plant_uncertainty(case, t[-1], x[-1], theta_true)
    u[-1] = -lambda_cl * x[-1] - theta_hat[-1] * phi

    rms_x = float(np.sqrt(np.mean(x ** 2)))
    return SimResult(case, t, x, theta_hat, u, rms_x, abs(float(x[-1])), float(theta_hat[-1]))


def main() -> None:
    cases = [
        "matched_parametric",
        "poor_excitation",
        "unmodeled_disturbance",
        "wrong_regressor",
    ]
    results: List[SimResult] = [simulate(case) for case in cases]

    print("Case comparison")
    print("-" * 80)
    print(f"{'case':24s} {'RMS(x)':>12s} {'|x(T)|':>12s} {'theta_hat(T)':>14s}")
    for res in results:
        print(f"{res.name:24s} {res.rms_x:12.5f} {res.final_abs_x:12.5f} {res.final_theta_hat:14.5f}")

    fig, axes = plt.subplots(2, 1, figsize=(10, 7), sharex=True)
    for res in results:
        axes[0].plot(res.t, res.x, label=res.name)
        axes[1].plot(res.t, res.theta_hat, label=res.name)
    axes[0].set_ylabel("state x(t)")
    axes[0].set_title("Parameter adaptation helps only when the uncertainty model is structurally correct")
    axes[0].grid(True)
    axes[1].set_xlabel("time (s)")
    axes[1].set_ylabel("estimated parameter theta_hat(t)")
    axes[1].grid(True)
    axes[1].legend(loc="best")
    plt.tight_layout()
    plt.savefig("Chapter2_Lesson5_python_results.png", dpi=180)
    plt.show()


if __name__ == "__main__":
    main()
      

9. C++ Implementation

The C++ version avoids external libraries and prints numerical comparison metrics. It is suitable for embedded-style demonstrations or console-based simulation.

Chapter2_Lesson5.cpp

/*
Chapter2_Lesson5.cpp
Adaptive Control - Chapter 2, Lesson 5
Console simulation of when parameter adaptation helps and when it does not.

Compile:
    g++ -std=c++17 Chapter2_Lesson5.cpp -o Chapter2_Lesson5
Run:
    ./Chapter2_Lesson5
*/

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

struct UncertaintyPair {
    double phi;
    double uncertainty;
};

struct SimResult {
    std::string name;
    double rmsX;
    double finalAbsX;
    double finalThetaHat;
};

UncertaintyPair plant_uncertainty(const std::string& caseName, double t, double x, double thetaTrue) {
    if (caseName == "matched_parametric") {
        double phi = std::sin(1.1 * t) + 0.5 * std::cos(0.3 * t);
        return {phi, thetaTrue * phi};
    }
    if (caseName == "poor_excitation") {
        double phi = 0.05 * std::exp(-0.3 * t);
        return {phi, thetaTrue * phi};
    }
    if (caseName == "unmodeled_disturbance") {
        double phi = std::sin(1.1 * t);
        double disturbance = 0.7 * std::sin(8.0 * t);
        return {phi, thetaTrue * phi + disturbance};
    }
    if (caseName == "wrong_regressor") {
        double phi = std::sin(1.1 * t);
        double trueNonlinear = thetaTrue * (x * x / (1.0 + x * x));
        return {phi, trueNonlinear};
    }
    throw std::runtime_error("Unknown case: " + caseName);
}

SimResult simulate(const std::string& caseName, double T = 25.0, double dt = 0.002) {
    const int n = static_cast<int>(T / dt) + 1;
    const double lambdaCl = 1.5;
    const double gamma = 4.0;
    const double thetaTrue = 2.0;

    double x = 1.5;
    double thetaHat = 0.0;
    double sumX2 = 0.0;

    for (int k = 0; k < n; ++k) {
        double t = k * dt;
        sumX2 += x * x;
        UncertaintyPair up = plant_uncertainty(caseName, t, x, thetaTrue);
        double u = -lambdaCl * x - thetaHat * up.phi;
        double xDot = u + up.uncertainty;
        double thetaDot = gamma * x * up.phi;

        if (k < n - 1) {
            x += dt * xDot;
            thetaHat += dt * thetaDot;
            if (thetaHat > 10.0) thetaHat = 10.0;
            if (thetaHat < -10.0) thetaHat = -10.0;
        }
    }

    return {caseName, std::sqrt(sumX2 / n), std::abs(x), thetaHat};
}

int main() {
    std::vector<std::string> cases = {
        "matched_parametric",
        "poor_excitation",
        "unmodeled_disturbance",
        "wrong_regressor"
    };

    std::cout << "Case comparison\n";
    std::cout << std::string(76, '-') << "\n";
    std::cout << std::left << std::setw(26) << "case"
              << std::right << std::setw(14) << "RMS(x)"
              << std::setw(14) << "|x(T)|"
              << std::setw(16) << "theta_hat(T)" << "\n";

    for (const auto& caseName : cases) {
        SimResult r = simulate(caseName);
        std::cout << std::left << std::setw(26) << r.name
                  << std::right << std::setw(14) << std::fixed << std::setprecision(5) << r.rmsX
                  << std::setw(14) << r.finalAbsX
                  << std::setw(16) << r.finalThetaHat << "\n";
    }

    return 0;
}
      

10. Java Implementation

The Java implementation mirrors the C++ version and is useful for object-oriented educational software or GUI extensions.

Chapter2_Lesson5.java

/*
Chapter2_Lesson5.java
Adaptive Control - Chapter 2, Lesson 5
Console simulation of useful vs misleading parameter adaptation.

Compile:
    javac Chapter2_Lesson5.java
Run:
    java Chapter2_Lesson5
*/

public class Chapter2_Lesson5 {
    static class UncertaintyPair {
        final double phi;
        final double uncertainty;
        UncertaintyPair(double phi, double uncertainty) {
            this.phi = phi;
            this.uncertainty = uncertainty;
        }
    }

    static class SimResult {
        final String name;
        final double rmsX;
        final double finalAbsX;
        final double finalThetaHat;
        SimResult(String name, double rmsX, double finalAbsX, double finalThetaHat) {
            this.name = name;
            this.rmsX = rmsX;
            this.finalAbsX = finalAbsX;
            this.finalThetaHat = finalThetaHat;
        }
    }

    static UncertaintyPair plantUncertainty(String caseName, double t, double x, double thetaTrue) {
        switch (caseName) {
            case "matched_parametric": {
                double phi = Math.sin(1.1 * t) + 0.5 * Math.cos(0.3 * t);
                return new UncertaintyPair(phi, thetaTrue * phi);
            }
            case "poor_excitation": {
                double phi = 0.05 * Math.exp(-0.3 * t);
                return new UncertaintyPair(phi, thetaTrue * phi);
            }
            case "unmodeled_disturbance": {
                double phi = Math.sin(1.1 * t);
                double disturbance = 0.7 * Math.sin(8.0 * t);
                return new UncertaintyPair(phi, thetaTrue * phi + disturbance);
            }
            case "wrong_regressor": {
                double phi = Math.sin(1.1 * t);
                double trueNonlinear = thetaTrue * (x * x / (1.0 + x * x));
                return new UncertaintyPair(phi, trueNonlinear);
            }
            default:
                throw new IllegalArgumentException("Unknown case: " + caseName);
        }
    }

    static SimResult simulate(String caseName) {
        double T = 25.0;
        double dt = 0.002;
        int n = (int)(T / dt) + 1;
        double lambdaCl = 1.5;
        double gamma = 4.0;
        double thetaTrue = 2.0;

        double x = 1.5;
        double thetaHat = 0.0;
        double sumX2 = 0.0;

        for (int k = 0; k < n; ++k) {
            double t = k * dt;
            sumX2 += x * x;
            UncertaintyPair up = plantUncertainty(caseName, t, x, thetaTrue);
            double u = -lambdaCl * x - thetaHat * up.phi;
            double xDot = u + up.uncertainty;
            double thetaDot = gamma * x * up.phi;

            if (k < n - 1) {
                x += dt * xDot;
                thetaHat += dt * thetaDot;
                thetaHat = Math.max(-10.0, Math.min(10.0, thetaHat));
            }
        }

        return new SimResult(caseName, Math.sqrt(sumX2 / n), Math.abs(x), thetaHat);
    }

    public static void main(String[] args) {
        String[] cases = {
            "matched_parametric",
            "poor_excitation",
            "unmodeled_disturbance",
            "wrong_regressor"
        };

        System.out.println("Case comparison");
        System.out.println("----------------------------------------------------------------------------");
        System.out.printf("%-26s %14s %14s %16s%n", "case", "RMS(x)", "|x(T)|", "theta_hat(T)");
        for (String caseName : cases) {
            SimResult r = simulate(caseName);
            System.out.printf("%-26s %14.5f %14.5f %16.5f%n",
                    r.name, r.rmsX, r.finalAbsX, r.finalThetaHat);
        }
    }
}
      

11. MATLAB / Simulink Implementation

The MATLAB script simulates the same adaptive law. For Simulink, the update equations can be placed in a MATLAB Function block with two integrator states: \( x \) and \( \hat{\theta} \).

Chapter2_Lesson5.m

% Chapter2_Lesson5.m
% Adaptive Control - Chapter 2, Lesson 5
% Where Parameter Adaptation Actually Helps (and Where It Doesn't)
%
% This MATLAB script compares matched parametric uncertainty with cases where
% adaptation is weak or misleading because excitation/model structure is poor.
% The same state/update equations can be placed in a Simulink MATLAB Function
% block with two states: x and thetaHat.

clear; clc; close all;

cases = {"matched_parametric", "poor_excitation", "unmodeled_disturbance", "wrong_regressor"};
results = struct([]);

for i = 1:numel(cases)
    results(i) = simulate_case(cases{i}); %#ok<SAGROW>
end

fprintf('Case comparison\n');
fprintf('%s\n', repmat('-', 1, 78));
fprintf('%-26s %14s %14s %16s\n', 'case', 'RMS(x)', '|x(T)|', 'theta_hat(T)');
for i = 1:numel(results)
    fprintf('%-26s %14.5f %14.5f %16.5f\n', ...
        results(i).name, results(i).rms_x, results(i).final_abs_x, results(i).final_theta_hat);
end

figure('Name', 'Chapter02 Lesson5 Adaptive Help vs Failure');
subplot(2,1,1); hold on; grid on;
for i = 1:numel(results)
    plot(results(i).t, results(i).x, 'DisplayName', results(i).name);
end
ylabel('state x(t)');
title('Parameter adaptation helps only when the uncertainty model is structurally correct');
legend('Location', 'best');

subplot(2,1,2); hold on; grid on;
for i = 1:numel(results)
    plot(results(i).t, results(i).theta_hat, 'DisplayName', results(i).name);
end
xlabel('time (s)');
ylabel('estimated parameter thetaHat(t)');

% Simulink implementation note:
% Create a MATLAB Function block with inputs (t, x, thetaHat, caseId) and
% outputs (xDot, thetaDot, u). Use an Integrator block for x and another
% Integrator block for thetaHat. The function body is the logic used in
% plant_uncertainty() and the adaptive law below.

function result = simulate_case(case_name)
    T = 25.0;
    dt = 0.002;
    t = 0:dt:T;
    n = numel(t);

    lambda_cl = 1.5;
    gamma = 4.0;
    theta_true = 2.0;

    x = zeros(1, n);
    theta_hat = zeros(1, n);
    u = zeros(1, n);
    x(1) = 1.5;
    theta_hat(1) = 0.0;

    for k = 1:n-1
        [phi, uncertainty] = plant_uncertainty(case_name, t(k), x(k), theta_true);
        u(k) = -lambda_cl * x(k) - theta_hat(k) * phi;
        x_dot = u(k) + uncertainty;
        theta_dot = gamma * x(k) * phi;

        x(k+1) = x(k) + dt * x_dot;
        theta_hat(k+1) = theta_hat(k) + dt * theta_dot;
        theta_hat(k+1) = min(max(theta_hat(k+1), -10.0), 10.0);
    end

    [phi, ~] = plant_uncertainty(case_name, t(end), x(end), theta_true);
    u(end) = -lambda_cl * x(end) - theta_hat(end) * phi;

    result.name = char(case_name);
    result.t = t;
    result.x = x;
    result.theta_hat = theta_hat;
    result.u = u;
    result.rms_x = sqrt(mean(x.^2));
    result.final_abs_x = abs(x(end));
    result.final_theta_hat = theta_hat(end);
end

function [phi, uncertainty] = plant_uncertainty(case_name, t, x, theta_true)
    switch case_name
        case "matched_parametric"
            phi = sin(1.1 * t) + 0.5 * cos(0.3 * t);
            uncertainty = theta_true * phi;
        case "poor_excitation"
            phi = 0.05 * exp(-0.3 * t);
            uncertainty = theta_true * phi;
        case "unmodeled_disturbance"
            phi = sin(1.1 * t);
            disturbance = 0.7 * sin(8.0 * t);
            uncertainty = theta_true * phi + disturbance;
        case "wrong_regressor"
            phi = sin(1.1 * t);
            uncertainty = theta_true * (x * x / (1.0 + x * x));
        otherwise
            error('Unknown case: %s', case_name);
    end
end
      

12. Wolfram Mathematica Implementation

The Mathematica implementation computes the same comparison and plots the state and parameter-estimate trajectories.

Chapter2_Lesson5.nb

(*
Chapter2_Lesson5.nb
Adaptive Control - Chapter 2, Lesson 5
Wolfram Mathematica code: useful vs misleading parameter adaptation.

Open this file in Mathematica/Wolfram Desktop or paste the cells into a notebook.
*)

ClearAll[plantUncertainty, simulateCase];

thetaTrue = 2.0;
lambdaCl = 1.5;
gamma = 4.0;

plantUncertainty[case_, t_, x_] := Module[{phi, disturbance},
  Switch[case,
    "matched_parametric",
      phi = Sin[1.1 t] + 0.5 Cos[0.3 t]; {phi, thetaTrue phi},
    "poor_excitation",
      phi = 0.05 Exp[-0.3 t]; {phi, thetaTrue phi},
    "unmodeled_disturbance",
      phi = Sin[1.1 t]; disturbance = 0.7 Sin[8.0 t]; {phi, thetaTrue phi + disturbance},
    "wrong_regressor",
      phi = Sin[1.1 t]; {phi, thetaTrue (x^2/(1 + x^2))},
    _,
      Print["Unknown case: ", case]; Abort[]
  ]
];

simulateCase[case_, T_: 25.0, dt_: 0.002] := Module[
  {n, data, x, thetaHat, t, phi, uncertainty, u, xDot, thetaDot},
  n = Floor[T/dt] + 1;
  x = 1.5;
  thetaHat = 0.0;
  data = Table[
    t = (k - 1) dt;
    {phi, uncertainty} = plantUncertainty[case, t, x];
    u = -lambdaCl x - thetaHat phi;
    xDot = u + uncertainty;
    thetaDot = gamma x phi;
    If[k < n,
      x = x + dt xDot;
      thetaHat = Clip[thetaHat + dt thetaDot, {-10.0, 10.0}];
    ];
    {t, x, thetaHat, u},
    {k, 1, n}
  ];
  <|
    "case" -> case,
    "data" -> data,
    "rmsX" -> Sqrt[Mean[data[[All, 2]]^2]],
    "finalAbsX" -> Abs[Last[data][[2]]],
    "finalThetaHat" -> Last[data][[3]]
  |>
];

cases = {"matched_parametric", "poor_excitation", "unmodeled_disturbance", "wrong_regressor"};
results = simulateCase /@ cases;

Grid[
  Prepend[
    ({ #case, NumberForm[#rmsX, {8, 5}], NumberForm[#finalAbsX, {8, 5}],
       NumberForm[#finalThetaHat, {8, 5}]} & /@ results),
    {"case", "RMS(x)", "|x(T)|", "thetaHat(T)"}
  ],
  Frame -> All
]

statePlots = ListLinePlot[
  Table[results[[i]]["data"][[All, {1, 2}]], {i, Length[results]}],
  PlotLegends -> cases,
  AxesLabel -> {"time (s)", "x(t)"},
  PlotLabel -> "State response: when adaptation helps and when it does not",
  ImageSize -> Large
];

thetaPlots = ListLinePlot[
  Table[results[[i]]["data"][[All, {1, 3}]], {i, Length[results]}],
  PlotLegends -> cases,
  AxesLabel -> {"time (s)", "thetaHat(t)"},
  PlotLabel -> "Parameter estimate evolution",
  ImageSize -> Large
];

Column[{statePlots, thetaPlots}]
      

13. Problems and Solutions

Problem 1 (Matched Scalar Uncertainty): Consider \( \dot{x}=u+\theta^\ast\phi(t) \) with control \( u=-\lambda x-\hat{\theta}\phi(t) \). Choose an adaptive law so that the cross-term in the Lyapunov derivative is canceled.

Solution: Define \( \tilde{\theta}=\theta^\ast-\hat{\theta} \). Then \( \dot{x}=-\lambda x+\tilde{\theta}\phi(t) \). Choose

\[ \dot{\hat{\theta} }=\gamma \phi(t)x, \qquad \gamma > 0. \]

With

\[ V=\frac{1}{2}x^2+\frac{1}{2\gamma}\tilde{\theta}^2, \]

we get

\[ \begin{aligned} \dot{V} &= x(-\lambda x+\tilde{\theta}\phi) + \frac{1}{\gamma}\tilde{\theta}(-\gamma\phi x)\\ &= -\lambda x^2. \end{aligned} \]

Therefore, the adaptive law cancels the cross-term and guarantees boundedness of \( x \) and \( \tilde{\theta} \).

Problem 2 (Disturbance Not Represented by the Regressor): Suppose the true plant is \( \dot{x}=u+\theta^\ast\phi(t)+d(t) \), but the controller assumes only the parametric term. Derive the Lyapunov derivative.

Solution: Using the same controller and adaptive law,

\[ \dot{x}=-\lambda x+\tilde{\theta}\phi(t)+d(t). \]

The Lyapunov derivative becomes

\[ \dot{V} = -\lambda x^2+xd(t). \]

Thus the disturbance term remains. If \( |d(t)| \le d_{\max} \), then

\[ \dot{V} \le -\frac{\lambda}{2}x^2+ \frac{d_{\max}^2}{2\lambda}. \]

The system may have a residual error bound, but exact asymptotic convergence is not guaranteed by this adaptive law.

Problem 3 (Weak Excitation): Let \( \phi(t)=0 \) for all \( t \ge 5 \). Can the parameter estimate converge to \( \theta^\ast \) after \( t=5 \)?

Solution: For \( t \ge 5 \),

\[ \dot{\hat{\theta} }=\gamma \phi(t)x(t)=0. \]

Hence \( \hat{\theta}(t) \) becomes constant after \( t=5 \). Unless it has already reached \( \theta^\ast \), it cannot converge further. This shows that bounded tracking and correct identification are different objectives.

Problem 4 (Unmatched Uncertainty): Consider \( \dot{x}=Ax+bu+E\theta^\ast\phi(t) \). State a condition under which the uncertainty is matched.

Solution: The uncertainty is matched if the uncertainty direction lies in the input subspace. For a single-input system, this means there exists \( \rho \) such that

\[ E\theta^\ast\phi(t)=b\rho^T\phi(t). \]

If no such \( \rho \) exists, then a component of the uncertainty cannot be canceled by the scalar input \( u \).

Problem 5 (Wrong Regressor): The true uncertainty is \( \Delta^\ast(t,x)=\theta^\ast x^2 \), but the adaptive controller uses \( \phi(t,x)=x \). Explain why the parameter estimate should not be interpreted physically.

Solution: The assumed model is \( \hat{\Delta}(t,x)=\hat{\theta}x \), but the true uncertainty is quadratic in \( x \). There is generally no constant \( \hat{\theta} \) such that \( \hat{\theta}x=\theta^\ast x^2 \) for all \( x \). The adaptive law may choose a time-varying estimate that reduces the instantaneous error, but the estimate does not identify the true physical parameter.

14. Summary

Parameter adaptation helps when the uncertainty is matched, linearly parameterized, and represented by a correct regressor. A basic Lyapunov argument shows that the adaptive law can cancel the parameter-error cross-term and guarantee boundedness. However, adaptation does not automatically solve unmodeled dynamics, wrong regressors, unmatched uncertainty, poor excitation, saturation, or strong measurement noise. The most important lesson is that tracking improvement and parameter identification are different objectives.

15. References

  1. Narendra, K.S., & Valavani, L.S. (1978). Stable adaptive controller design—Direct control. IEEE Transactions on Automatic Control, 23(4), 570–583.
  2. Monopoli, R.V. (1974). Model reference adaptive control with an augmented error signal. IEEE Transactions on Automatic Control, 19(5), 474–484.
  3. Kreisselmeier, G., & Narendra, K.S. (1982). Stable model reference adaptive control in the presence of bounded disturbances. IEEE Transactions on Automatic Control, 27(6), 1169–1175.
  4. Ioannou, P.A., & Kokotovic, P.V. (1984). Instability analysis and improvement of robustness of adaptive control. Automatica, 20(5), 583–594.
  5. 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.
  6. Narendra, K.S., & Annaswamy, A.M. (1987). Persistent excitation in adaptive systems. International Journal of Control, 45(1), 127–160.
  7. Sastry, S.S., & Bodson, M. (1989). Adaptive control: Stability, convergence, and robustness. Prentice Hall.
  8. Middleton, R.H., & Goodwin, G.C. (1988). Adaptive control of time-varying linear systems. IEEE Transactions on Automatic Control, 33(2), 150–155.
  9. Ortega, R., & Tang, Y. (1989). Robustness of adaptive controllers—A survey. Automatica, 25(5), 651–677.
  10. Ioannou, P.A., & Sun, J. (1996). Robust Adaptive Control. Prentice Hall.
Support CaaT Academy

Help keep these engineering tutorials free and growing

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

Created and maintained by Abolfazl Mohammadijoo.