Chapter 21: Composite Adaptive Control

Lesson 3: Composite Lyapunov Functions for Composite Adaptive Laws

This lesson develops Lyapunov functions that simultaneously account for tracking error, state-prediction error, and parameter-estimation error. The central result is a composite update law whose tracking and prediction terms cancel every indefinite state-parameter cross term in the Lyapunov derivative. We then distinguish boundedness and tracking convergence from the stronger claim of parameter convergence, which still requires an appropriate information or excitation condition.

1. Learning Objectives and Position in the Course

After completing this lesson, students should be able to:

  • construct a positive-definite Lyapunov function for coupled tracking, prediction, and parameter errors;
  • derive a composite adaptive law by exact cancellation of cross terms;
  • prove boundedness and asymptotic convergence of tracking and prediction errors;
  • state precisely why a negative-semidefinite Lyapunov derivative does not by itself prove parameter convergence;
  • use an information matrix or excitation condition to strengthen convergence conclusions; and
  • implement and numerically verify the Lyapunov identity in several programming languages.

The lesson uses material already introduced in Chapters 2–10 and in Lessons 1–2 of this chapter: linear parameterization, parameter error, Lyapunov equations, Barbalat's lemma, persistent excitation, prediction error, normalization, and projection. No new plant-identification course is assumed.

2. Matched Parametric Plant, Reference Model, and State Predictor

Consider the linearly parameterized matched-uncertainty plant

\[ \dot{\mathbf{x}} = \mathbf{A}_p\mathbf{x} + \mathbf{b} \left[u + \boldsymbol{\theta}^{T}\boldsymbol{\phi}(t,\mathbf{x})\right], \qquad \boldsymbol{\theta}\in\mathbb{R}^{p}. \]

The unknown constant vector is \( \boldsymbol{\theta} \), and the measurable regressor is \( \boldsymbol{\phi}(t,\mathbf{x}) \). Let the desired model be

\[ \dot{\mathbf{x}}_m = \mathbf{A}_m\mathbf{x}_m + \mathbf{b}_m r, \qquad \mathbf{A}_m \text{ Hurwitz}. \]

Suppose known matching gains satisfy \( \mathbf{A}_p+\mathbf{b}\mathbf{k}_x^T=\mathbf{A}_m \) and \( \mathbf{b}k_r=\mathbf{b}_m \). Use

\[ u = \mathbf{k}_x^T\mathbf{x}+k_r r- \hat{\boldsymbol{\theta}}^T\boldsymbol{\phi}. \]

Define tracking and parameter errors by \( \mathbf{e}=\mathbf{x}-\mathbf{x}_m \) and \( \tilde{\boldsymbol{\theta}}= \boldsymbol{\theta}-\hat{\boldsymbol{\theta}} \). Their coupled tracking-error dynamics are

\[ \dot{\mathbf{e}}=\mathbf{A}_m\mathbf{e}+ \mathbf{b}\tilde{\boldsymbol{\theta}}^T\boldsymbol{\phi}. \]

To create an additional error carrying parameter information, introduce the predictor

\[ \dot{\hat{\mathbf{x}}}=\mathbf{A}_p\hat{\mathbf{x}}+ \mathbf{b}\left[u+\hat{\boldsymbol{\theta}}^T\boldsymbol{\phi}\right] +\mathbf{L}(\mathbf{x}-\hat{\mathbf{x}}). \]

With \( \boldsymbol{\varepsilon}=\mathbf{x}-\hat{\mathbf{x}} \) and \( \mathbf{A}_o=\mathbf{A}_p-\mathbf{L} \), choose \( \mathbf{L} \) so that \( \mathbf{A}_o \) is Hurwitz. Then

\[ \dot{\boldsymbol{\varepsilon}}=\mathbf{A}_o\boldsymbol{\varepsilon} +\mathbf{b}\tilde{\boldsymbol{\theta}}^T\boldsymbol{\phi}. \]

flowchart TD
  R["Reference r"] --> RM["Reference model"]
  RM --> XM["Desired state xm"]
  X["Measured plant state x"] --> TE["Tracking error e = x - xm"]
  XM --> TE
  X --> PRED["State predictor"]
  PRED --> XH["Predicted state xhat"]
  XH --> PE["Prediction error epsilon = x - xhat"]
  X --> PE
  TE --> LAW["Composite parameter update"]
  PE --> LAW
  LAW --> TH["Parameter estimate theta_hat"]
  TH --> CTRL["Adaptive controller"]
  R --> CTRL
  CTRL --> PLANT["Uncertain plant"]
  PLANT --> X
        

3. Why the Tracking-Only Lyapunov Function Is Insufficient

Let \( \mathbf{P}=\mathbf{P}^T>0 \) solve

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

A standard tracking-only candidate is

\[ V_t=\frac{1}{2}\mathbf{e}^T\mathbf{P}\mathbf{e} +\frac{1}{2}\tilde{\boldsymbol{\theta}}^T \boldsymbol{\Gamma}^{-1}\tilde{\boldsymbol{\theta}}, \qquad \boldsymbol{\Gamma}=\boldsymbol{\Gamma}^T>0. \]

Its derivative is

\[ \dot V_t=-\frac{1}{2}\mathbf{e}^T\mathbf{Q}\mathbf{e} +\tilde{\boldsymbol{\theta}}^T\boldsymbol{\phi}\mathbf{b}^T \mathbf{P}\mathbf{e} -\tilde{\boldsymbol{\theta}}^T\boldsymbol{\Gamma}^{-1} \dot{\hat{\boldsymbol{\theta}}}. \]

The gradient law \( \dot{\hat{\boldsymbol{\theta}}}=\boldsymbol{\Gamma} \boldsymbol{\phi}\mathbf{b}^T\mathbf{P}\mathbf{e} \) cancels the cross term and gives

\[ \dot V_t=-\frac{1}{2}\mathbf{e}^T\mathbf{Q}\mathbf{e}\le 0. \]

This proves boundedness and, under the usual bounded-derivative conditions, \( \mathbf{e}(t)\to\mathbf{0} \). It does not make \( \dot V_t \) negative definite in \( \tilde{\boldsymbol{\theta}} \). Once tracking is accurate, the tracking innovation can become too small to continue identifying the parameters. Composite adaptation adds a second innovation rather than increasing the tracking-error gain blindly.

4. Construction of the Composite Lyapunov Function

Let \( \mathbf{S}=\mathbf{S}^T>0 \) solve the predictor Lyapunov equation

\[ \mathbf{A}_o^T\mathbf{S}+\mathbf{S}\mathbf{A}_o=-\mathbf{R}, \qquad \mathbf{R}=\mathbf{R}^T>0. \]

For a prediction weight \( k_c>0 \), define

\[ V_c=\frac{1}{2}\mathbf{e}^T\mathbf{P}\mathbf{e} +\frac{k_c}{2}\boldsymbol{\varepsilon}^T\mathbf{S} \boldsymbol{\varepsilon} +\frac{1}{2}\tilde{\boldsymbol{\theta}}^T \boldsymbol{\Gamma}^{-1}\tilde{\boldsymbol{\theta}}. \]

Each term has a distinct role: the first measures tracking energy, the second measures predictor inconsistency, and the third measures parameter error in the adaptation-gain metric. Because \( \mathbf{P},\mathbf{S},\boldsymbol{\Gamma}^{-1} \) are positive definite, there exist constants \( c_1,c_2>0 \) such that

\[ c_1\left(\|\mathbf{e}\|^2+\|\boldsymbol{\varepsilon}\|^2+ \|\tilde{\boldsymbol{\theta}}\|^2\right) \le V_c \le c_2\left(\|\mathbf{e}\|^2+\|\boldsymbol{\varepsilon}\|^2+ \|\tilde{\boldsymbol{\theta}}\|^2\right). \]

Thus \( V_c \) is radially unbounded in the complete error vector. This is the basic reason a composite Lyapunov function is stronger than analyzing tracking and estimation subsystems separately.

5. Exact Derivation of the Composite Adaptive Law

Differentiating the three terms gives

\[ \begin{aligned} \dot V_c &=\frac{1}{2}\mathbf{e}^T (\mathbf{A}_m^T\mathbf{P}+\mathbf{P}\mathbf{A}_m)\mathbf{e} +\mathbf{e}^T\mathbf{P}\mathbf{b} \tilde{\boldsymbol{\theta}}^T\boldsymbol{\phi} \\ &\quad+\frac{k_c}{2}\boldsymbol{\varepsilon}^T (\mathbf{A}_o^T\mathbf{S}+\mathbf{S}\mathbf{A}_o) \boldsymbol{\varepsilon} +k_c\boldsymbol{\varepsilon}^T\mathbf{S}\mathbf{b} \tilde{\boldsymbol{\theta}}^T\boldsymbol{\phi} \\ &\quad-\tilde{\boldsymbol{\theta}}^T \boldsymbol{\Gamma}^{-1}\dot{\hat{\boldsymbol{\theta}}}. \end{aligned} \]

Using the Lyapunov equations and collecting parameter cross terms:

\[ \begin{aligned} \dot V_c &=-\frac{1}{2}\mathbf{e}^T\mathbf{Q}\mathbf{e} -\frac{k_c}{2}\boldsymbol{\varepsilon}^T\mathbf{R} \boldsymbol{\varepsilon} \\ &\quad+\tilde{\boldsymbol{\theta}}^T \left[\boldsymbol{\phi}\mathbf{b}^T\mathbf{P}\mathbf{e} +k_c\boldsymbol{\phi}\mathbf{b}^T\mathbf{S} \boldsymbol{\varepsilon} -\boldsymbol{\Gamma}^{-1}\dot{\hat{\boldsymbol{\theta}}}\right]. \end{aligned} \]

Choose the bracketed quantity to be zero. The resulting composite law is

\[ \boxed{\dot{\hat{\boldsymbol{\theta}}}= \boldsymbol{\Gamma}\boldsymbol{\phi} \left(\mathbf{b}^T\mathbf{P}\mathbf{e} +k_c\mathbf{b}^T\mathbf{S}\boldsymbol{\varepsilon}\right)}. \]

Substitution yields the fundamental identity

\[ \boxed{\dot V_c=-\frac{1}{2}\mathbf{e}^T\mathbf{Q}\mathbf{e} -\frac{k_c}{2}\boldsymbol{\varepsilon}^T\mathbf{R} \boldsymbol{\varepsilon}\le 0}. \]

flowchart TD
  E1["Tracking dynamics produce \na parameter cross term"] --> SUM["Collect all \nindefinite terms"]
  E2["Predictor dynamics \nproduce a second \nparameter cross term"] --> SUM
  P1["Parameter-energy derivative \ncontains minus \ntheta_tilde times Gamma_inv \ntimes theta_hat_dot"] --> SUM
  SUM --> LAW["Choose theta_hat_dot to cancel the complete bracket"]
  LAW --> DV["Vdot contains only \nnegative tracking and \nprediction quadratic forms"]
  DV --> BND["All error signals remain bounded"]
  BND --> CONV["Tracking and prediction \nerrors converge under \nbounded-derivative assumptions"]
        

6. Stability Theorem and Proof

Theorem 1 (Composite boundedness and error convergence).

Assume: (i) the matching conditions hold; (ii) \( \mathbf{A}_m \) and \( \mathbf{A}_o \) are Hurwitz; (iii) \( r(t) \) and the regressor are bounded whenever the closed-loop state is bounded; and (iv) \( \boldsymbol{\Gamma}>0 \) and \( k_c>0 \). Under the composite law of Section 5, all components of \( (\mathbf{e},\boldsymbol{\varepsilon}, \tilde{\boldsymbol{\theta}}) \) are bounded. Moreover, \( \mathbf{e}(t)\to\mathbf{0} \) and \( \boldsymbol{\varepsilon}(t)\to\mathbf{0} \) if \( \dot{\mathbf{e}} \) and \( \dot{\boldsymbol{\varepsilon}} \) are bounded.

Proof.

Positive definiteness gives \( V_c(t)>=0 \). Since \( \dot V_c\le0 \), \( V_c(t)\le V_c(0) \), hence all three error vectors are bounded. Integration of the derivative identity gives

\[ \int_0^\infty \mathbf{e}^T\mathbf{Q}\mathbf{e}\,dt +k_c\int_0^\infty \boldsymbol{\varepsilon}^T\mathbf{R}\boldsymbol{\varepsilon}\,dt \le 2V_c(0). \]

Therefore \( \mathbf{e}\in L_2\cap L_\infty \) and \( \boldsymbol{\varepsilon}\in L_2\cap L_\infty \). Bounded reference, state, regressor, and parameter estimate imply bounded error derivatives. Barbalat's lemma then gives \( \mathbf{e}(t)\to\mathbf{0} \) and \( \boldsymbol{\varepsilon}(t)\to\mathbf{0} \). This completes the proof.

The theorem deliberately does not claim \( \tilde{\boldsymbol{\theta}}(t)\to\mathbf{0} \). The Lyapunov derivative has no explicit negative-definite parameter term. Parameter convergence is an identifiability question, not merely a stability question.

7. Regression Prediction Error and a Stronger Composite Derivative

A filtered plant equation can often be arranged as a measurable linear regression

\[ \mathbf{Y}(t)=\boldsymbol{\Omega}(t)\boldsymbol{\theta}, \]

with prediction residual

\[ \boldsymbol{\zeta}=\mathbf{Y}-\boldsymbol{\Omega} \hat{\boldsymbol{\theta}}=oldsymbol{\Omega} \tilde{\boldsymbol{\theta}}. \]

Add the identifier innovation to the adaptive law:

\[ \dot{\hat{\boldsymbol{\theta}}}= \boldsymbol{\Gamma}\left[ \boldsymbol{\phi}\mathbf{b}^T\mathbf{P}\mathbf{e} +k_c\boldsymbol{\phi}\mathbf{b}^T\mathbf{S} \boldsymbol{\varepsilon} +k_I\boldsymbol{\Omega}^T\boldsymbol{\zeta}\right], \qquad k_I>0. \]

With the same \( V_c \), the derivative becomes

\[ \dot V_c=-\frac{1}{2}\mathbf{e}^T\mathbf{Q}\mathbf{e} -\frac{k_c}{2}\boldsymbol{\varepsilon}^T\mathbf{R} \boldsymbol{\varepsilon} -k_I\boldsymbol{\zeta}^T\boldsymbol{\zeta}. \]

This is stronger because the last term equals \( -k_I\tilde{\boldsymbol{\theta}}^T \boldsymbol{\Omega}^T\boldsymbol{\Omega} \tilde{\boldsymbol{\theta}} \). If, after some finite time \( T \), the stored or filtered information satisfies

\[ \boldsymbol{\Omega}^T(t)\boldsymbol{\Omega}(t) \ge \alpha_\Omega\mathbf{I},\qquad t\ge T, \qquad \alpha_\Omega>0, \]

then the parameter term is negative definite. In particular, for the reduced Lyapunov function \( V_r=\frac{1}{2}\mathbf{e}^T\mathbf{P}\mathbf{e}+ \frac{1}{2}\tilde{\boldsymbol{\theta}}^T \boldsymbol{\Gamma}^{-1}\tilde{\boldsymbol{\theta}} \), one obtains

\[ \dot V_r\le-\lambda V_r,\qquad \lambda=\min\left\{ \frac{\lambda_{\min}(\mathbf{Q})}{\lambda_{\max}(\mathbf{P})}, \frac{2k_I\alpha_\Omega} {\lambda_{\max}(\boldsymbol{\Gamma}^{-1})} \right\}. \]

Consequently, for \( t\ge T \),

\[ V_r(t)\le V_r(T)e^{-\lambda(t-T)}, \]

which gives exponential convergence of tracking and parameter errors. The key logical distinction is that the Lyapunov construction supplies the cancellation and stability mechanism, whereas the information matrix supplies identifiability.

8. Scalar Specialization Used in the Implementations

The executable example uses

\[ \dot x=-a x+u+\boldsymbol{\theta}^T\boldsymbol{\phi}(t,x), \qquad \dot x_m=-a_m x_m+r, \]

\[ u=(a-a_m)x+r-\hat{\boldsymbol{\theta}}^T \boldsymbol{\phi}(t,x), \qquad \dot{\hat x}=-a\hat x+u+ \hat{\boldsymbol{\theta}}^T\boldsymbol{\phi}(t,x). \]

Hence

\[ \dot e=-a_m e+\tilde{\boldsymbol{\theta}}^T\boldsymbol{\phi}, \qquad \dot\varepsilon=-a\varepsilon+ \tilde{\boldsymbol{\theta}}^T\boldsymbol{\phi}. \]

The composite law and Lyapunov function are

\[ \dot{\hat{\boldsymbol{\theta}}}=\boldsymbol{\Gamma} \boldsymbol{\phi}(e+k_c\varepsilon), \]

\[ V=\frac{1}{2}e^2+\frac{k_c}{2}\varepsilon^2+ \frac{1}{2}\tilde{\boldsymbol{\theta}}^T \boldsymbol{\Gamma}^{-1}\tilde{\boldsymbol{\theta}}. \]

Direct differentiation gives the exact continuous-time identity

\[ \boxed{\dot V=-a_m e^2-k_c a\varepsilon^2}. \]

The programs use \( \boldsymbol{\phi}(t,x)=[x,\sin(1.3t)]^T \), \( \boldsymbol{\theta}=[0.8,-0.5]^T \), and a two-frequency bounded reference. The time-varying second regressor is included to make the numerical parameter-convergence demonstration informative; the Lyapunov proof itself does not assume those particular numerical signals.

9. Design Parameters, Robustness, and Numerical Interpretation

Prediction weight \( k_c \). Increasing this weight makes predictor inconsistency contribute more strongly to adaptation. Excessive values can amplify measurement noise and predictor discretization error.

Adaptation gain \( \boldsymbol{\Gamma} \). Larger eigenvalues accelerate parameter motion but increase sensitivity to noise, sampling, saturation, and unmodeled dynamics. The gain changes transient speed, not the algebraic sign of the ideal continuous-time derivative.

Predictor poles. The matrix \( \mathbf{A}_o \) must be Hurwitz. Very fast predictor poles can create a noisy prediction residual; very slow poles reduce useful innovation.

Projection and leakage. Projection from Chapter 9 may be applied to preserve known parameter bounds. Leakage or sigma modification from Chapter 8 can be used when disturbances prevent the ideal parameter-error model, but then the derivative contains bounded residual terms and usually yields uniform ultimate boundedness rather than exact asymptotic parameter convergence.

Numerical monotonicity. The analytical result concerns continuous time. A sampled numerical trajectory can show tiny positive increments of \( V \) because of integration and floating-point errors. Decreasing the step size or using a higher-order integrator should reduce those increments.

10. Python Implementation

Chapter21_Lesson3.py

"""Chapter21_Lesson3.py
Composite adaptive control with a tracking-error and prediction-error update.

Plant:
    x_dot = -a*x + u + theta^T*phi(x)
Reference model:
    xm_dot = -am*xm + r
Controller:
    u = (a-am)*x + r - theta_hat^T*phi(x)
Predictor:
    xhat_dot = -a*xhat + u + theta_hat^T*phi(x)
Composite law:
    theta_hat_dot = Gamma*phi(x)*(e + kc*epsilon)

The composite Lyapunov derivative is
    V_dot = -am*e^2 - kc*a*epsilon^2.
"""

from __future__ import annotations

import csv
import math
from dataclasses import dataclass
from typing import List, Tuple

import matplotlib.pyplot as plt
import numpy as np


@dataclass(frozen=True)
class Config:
    a: float = 1.2
    am: float = 2.0
    kc: float = 1.5
    gamma1: float = 8.0
    gamma2: float = 8.0
    theta1: float = 0.8
    theta2: float = -0.5
    dt: float = 0.002
    tf: float = 30.0


def reference(t: float) -> float:
    """Bounded reference with sufficient spectral content for this demonstration."""
    return 0.9 * math.sin(0.7 * t) + 0.45 * math.sin(1.9 * t)


def regressor(t: float, x: float) -> np.ndarray:
    return np.array([x, math.sin(1.3 * t)], dtype=float)


def dynamics(t: float, state: np.ndarray, cfg: Config) -> np.ndarray:
    x, xm, xhat, th1, th2 = state
    phi = regressor(t, x)
    theta = np.array([cfg.theta1, cfg.theta2])
    theta_hat = np.array([th1, th2])
    r = reference(t)

    e = x - xm
    epsilon = x - xhat
    u = (cfg.a - cfg.am) * x + r - float(theta_hat @ phi)

    x_dot = -cfg.a * x + u + float(theta @ phi)
    xm_dot = -cfg.am * xm + r
    xhat_dot = -cfg.a * xhat + u + float(theta_hat @ phi)

    innovation = e + cfg.kc * epsilon
    theta_hat_dot = np.array(
        [cfg.gamma1 * phi[0] * innovation, cfg.gamma2 * phi[1] * innovation]
    )
    return np.array([x_dot, xm_dot, xhat_dot, *theta_hat_dot], dtype=float)


def rk4_step(t: float, state: np.ndarray, dt: float, cfg: Config) -> np.ndarray:
    k1 = dynamics(t, state, cfg)
    k2 = dynamics(t + 0.5 * dt, state + 0.5 * dt * k1, cfg)
    k3 = dynamics(t + 0.5 * dt, state + 0.5 * dt * k2, cfg)
    k4 = dynamics(t + dt, state + dt * k3, cfg)
    return state + (dt / 6.0) * (k1 + 2.0 * k2 + 2.0 * k3 + k4)


def lyapunov(state: np.ndarray, cfg: Config) -> Tuple[float, float]:
    x, xm, xhat, th1, th2 = state
    e = x - xm
    epsilon = x - xhat
    tilde1 = cfg.theta1 - th1
    tilde2 = cfg.theta2 - th2
    v = (
        0.5 * e * e
        + 0.5 * cfg.kc * epsilon * epsilon
        + 0.5 * tilde1 * tilde1 / cfg.gamma1
        + 0.5 * tilde2 * tilde2 / cfg.gamma2
    )
    v_dot_identity = -cfg.am * e * e - cfg.kc * cfg.a * epsilon * epsilon
    return v, v_dot_identity


def simulate(cfg: Config) -> Tuple[np.ndarray, np.ndarray]:
    steps = int(round(cfg.tf / cfg.dt)) + 1
    times = np.linspace(0.0, cfg.tf, steps)
    history = np.zeros((steps, 8), dtype=float)
    state = np.array([0.0, 0.0, 0.8, -0.2, 0.2], dtype=float)

    for i, t in enumerate(times):
        v, v_dot = lyapunov(state, cfg)
        history[i, :5] = state
        history[i, 5] = v
        history[i, 6] = v_dot
        history[i, 7] = reference(t)
        if i + 1 < steps:
            state = rk4_step(t, state, cfg.dt, cfg)

    return times, history


def save_csv(times: np.ndarray, history: np.ndarray, path: str) -> None:
    header = [
        "t",
        "x",
        "xm",
        "xhat",
        "theta_hat_1",
        "theta_hat_2",
        "V",
        "Vdot_identity",
        "r",
    ]
    with open(path, "w", newline="", encoding="utf-8") as handle:
        writer = csv.writer(handle)
        writer.writerow(header)
        for t, row in zip(times, history):
            writer.writerow([t, *row])


def plot_results(times: np.ndarray, history: np.ndarray, cfg: Config) -> None:
    x, xm, xhat = history[:, 0], history[:, 1], history[:, 2]
    th1, th2 = history[:, 3], history[:, 4]
    v = history[:, 5]

    plt.figure()
    plt.plot(times, x, label="plant x")
    plt.plot(times, xm, "--", label="reference xm")
    plt.plot(times, xhat, ":", label="predictor xhat")
    plt.xlabel("time [s]")
    plt.ylabel("state")
    plt.grid(True)
    plt.legend()
    plt.tight_layout()

    plt.figure()
    plt.plot(times, th1, label="theta_hat_1")
    plt.plot(times, th2, label="theta_hat_2")
    plt.axhline(cfg.theta1, linestyle="--", label="theta_1")
    plt.axhline(cfg.theta2, linestyle="--", label="theta_2")
    plt.xlabel("time [s]")
    plt.ylabel("parameter")
    plt.grid(True)
    plt.legend()
    plt.tight_layout()

    plt.figure()
    plt.semilogy(times, np.maximum(v, 1.0e-14))
    plt.xlabel("time [s]")
    plt.ylabel("composite Lyapunov function V")
    plt.grid(True)
    plt.tight_layout()
    plt.show()


def main() -> None:
    cfg = Config()
    times, history = simulate(cfg)
    save_csv(times, history, "Chapter21_Lesson3_results.csv")

    final = history[-1]
    rms_tracking = math.sqrt(float(np.mean((history[:, 0] - history[:, 1]) ** 2)))
    rms_prediction = math.sqrt(float(np.mean((history[:, 0] - history[:, 2]) ** 2)))
    max_v_increase = float(np.max(np.diff(history[:, 5])))

    print(f"RMS tracking error   : {rms_tracking:.6e}")
    print(f"RMS prediction error : {rms_prediction:.6e}")
    print(f"Final theta_hat       : [{final[3]:.6f}, {final[4]:.6f}]")
    print(f"True theta            : [{cfg.theta1:.6f}, {cfg.theta2:.6f}]")
    print(f"Maximum sampled dV    : {max_v_increase:.6e}")
    print("Saved Chapter21_Lesson3_results.csv")
    plot_results(times, history, cfg)


if __name__ == "__main__":
    main()

Required libraries are numpy and matplotlib. The implementation uses an explicit fourth-order Runge–Kutta method, writes a CSV file, and reports the largest sampled increase in the composite Lyapunov function.

11. C++ Implementation

Chapter21_Lesson3.cpp

// Chapter21_Lesson3.cpp
// Composite adaptive control simulation using only the C++ standard library.

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

struct Config {
    double a = 1.2;
    double am = 2.0;
    double kc = 1.5;
    double gamma1 = 8.0;
    double gamma2 = 8.0;
    double theta1 = 0.8;
    double theta2 = -0.5;
    double dt = 0.002;
    double tf = 30.0;
};

using State = std::array<double, 5>; // x, xm, xhat, theta_hat_1, theta_hat_2

static double reference(double t) {
    return 0.9 * std::sin(0.7 * t) + 0.45 * std::sin(1.9 * t);
}

static std::array<double, 2> regressor(double t, double x) {
    return {x, std::sin(1.3 * t)};
}

static State dynamics(double t, const State& s, const Config& c) {
    const double x = s[0];
    const double xm = s[1];
    const double xhat = s[2];
    const double th1 = s[3];
    const double th2 = s[4];
    const auto phi = regressor(t, x);
    const double r = reference(t);
    const double e = x - xm;
    const double epsilon = x - xhat;
    const double u = (c.a - c.am) * x + r - th1 * phi[0] - th2 * phi[1];
    const double unknown = c.theta1 * phi[0] + c.theta2 * phi[1];
    const double estimated = th1 * phi[0] + th2 * phi[1];
    const double innovation = e + c.kc * epsilon;

    return {
        -c.a * x + u + unknown,
        -c.am * xm + r,
        -c.a * xhat + u + estimated,
        c.gamma1 * phi[0] * innovation,
        c.gamma2 * phi[1] * innovation
    };
}

static State add_scaled(const State& x, const State& k, double scale) {
    State out{};
    for (std::size_t i = 0; i < out.size(); ++i) {
        out[i] = x[i] + scale * k[i];
    }
    return out;
}

static State rk4_step(double t, const State& x, double dt, const Config& c) {
    const State k1 = dynamics(t, x, c);
    const State k2 = dynamics(t + 0.5 * dt, add_scaled(x, k1, 0.5 * dt), c);
    const State k3 = dynamics(t + 0.5 * dt, add_scaled(x, k2, 0.5 * dt), c);
    const State k4 = dynamics(t + dt, add_scaled(x, k3, dt), c);

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

static std::array<double, 2> lyapunov(const State& s, const Config& c) {
    const double e = s[0] - s[1];
    const double epsilon = s[0] - s[2];
    const double tilde1 = c.theta1 - s[3];
    const double tilde2 = c.theta2 - s[4];
    const double v = 0.5 * e * e + 0.5 * c.kc * epsilon * epsilon
        + 0.5 * tilde1 * tilde1 / c.gamma1
        + 0.5 * tilde2 * tilde2 / c.gamma2;
    const double vdot = -c.am * e * e - c.kc * c.a * epsilon * epsilon;
    return {v, vdot};
}

int main() {
    try {
        const Config c{};
        const long steps = static_cast<long>(std::llround(c.tf / c.dt)) + 1;
        State s{0.0, 0.0, 0.8, -0.2, 0.2};
        std::ofstream csv("Chapter21_Lesson3_results.csv");
        if (!csv) {
            throw std::runtime_error("Cannot open Chapter21_Lesson3_results.csv");
        }
        csv << "t,x,xm,xhat,theta_hat_1,theta_hat_2,V,Vdot_identity,r\n";
        csv << std::setprecision(12);

        double sum_e2 = 0.0;
        double sum_ep2 = 0.0;
        double previous_v = 0.0;
        double max_dv = -1.0e300;

        for (long i = 0; i < steps; ++i) {
            const double t = i * c.dt;
            const auto vv = lyapunov(s, c);
            if (i > 0) {
                max_dv = std::max(max_dv, vv[0] - previous_v);
            }
            previous_v = vv[0];
            const double e = s[0] - s[1];
            const double epsilon = s[0] - s[2];
            sum_e2 += e * e;
            sum_ep2 += epsilon * epsilon;
            csv << t << ',' << s[0] << ',' << s[1] << ',' << s[2] << ','
                << s[3] << ',' << s[4] << ',' << vv[0] << ',' << vv[1] << ','
                << reference(t) << '\n';
            if (i + 1 < steps) {
                s = rk4_step(t, s, c.dt, c);
            }
        }

        std::cout << std::scientific << std::setprecision(6)
                  << "RMS tracking error   : " << std::sqrt(sum_e2 / steps) << '\n'
                  << "RMS prediction error : " << std::sqrt(sum_ep2 / steps) << '\n'
                  << "Final theta_hat       : [" << s[3] << ", " << s[4] << "]\n"
                  << "True theta            : [" << c.theta1 << ", " << c.theta2 << "]\n"
                  << "Maximum sampled dV    : " << max_dv << '\n'
                  << "Saved Chapter21_Lesson3_results.csv\n";
        return 0;
    } catch (const std::exception& ex) {
        std::cerr << "Error: " << ex.what() << '\n';
        return 1;
    }
}

This version requires only a C++17 compiler and the standard library. It reproduces the same model, gains, RK4 integration, CSV columns, and diagnostics as the Python implementation.

12. Java Implementation

Chapter21_Lesson3.java

// Chapter21_Lesson3.java
// Composite adaptive control simulation using only the Java standard library.

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 Chapter21_Lesson3 {
    private static final class Config {
        final double a = 1.2;
        final double am = 2.0;
        final double kc = 1.5;
        final double gamma1 = 8.0;
        final double gamma2 = 8.0;
        final double theta1 = 0.8;
        final double theta2 = -0.5;
        final double dt = 0.002;
        final double tf = 30.0;
    }

    private static double reference(double t) {
        return 0.9 * Math.sin(0.7 * t) + 0.45 * Math.sin(1.9 * t);
    }

    private static double[] regressor(double t, double x) {
        return new double[] {x, Math.sin(1.3 * t)};
    }

    private static double[] dynamics(double t, double[] s, Config c) {
        double x = s[0];
        double xm = s[1];
        double xhat = s[2];
        double th1 = s[3];
        double th2 = s[4];
        double[] phi = regressor(t, x);
        double r = reference(t);
        double e = x - xm;
        double epsilon = x - xhat;
        double u = (c.a - c.am) * x + r - th1 * phi[0] - th2 * phi[1];
        double unknown = c.theta1 * phi[0] + c.theta2 * phi[1];
        double estimated = th1 * phi[0] + th2 * phi[1];
        double innovation = e + c.kc * epsilon;

        return new double[] {
            -c.a * x + u + unknown,
            -c.am * xm + r,
            -c.a * xhat + u + estimated,
            c.gamma1 * phi[0] * innovation,
            c.gamma2 * phi[1] * innovation
        };
    }

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

    private static double[] rk4Step(double t, double[] x, double dt, Config c) {
        double[] k1 = dynamics(t, x, c);
        double[] k2 = dynamics(t + 0.5 * dt, addScaled(x, k1, 0.5 * dt), c);
        double[] k3 = dynamics(t + 0.5 * dt, addScaled(x, k2, 0.5 * dt), c);
        double[] k4 = dynamics(t + dt, addScaled(x, k3, dt), c);
        double[] next = new double[x.length];
        for (int i = 0; i < x.length; ++i) {
            next[i] = x[i] + (dt / 6.0) * (k1[i] + 2.0 * k2[i] + 2.0 * k3[i] + k4[i]);
        }
        return next;
    }

    private static double[] lyapunov(double[] s, Config c) {
        double e = s[0] - s[1];
        double epsilon = s[0] - s[2];
        double tilde1 = c.theta1 - s[3];
        double tilde2 = c.theta2 - s[4];
        double v = 0.5 * e * e + 0.5 * c.kc * epsilon * epsilon
            + 0.5 * tilde1 * tilde1 / c.gamma1
            + 0.5 * tilde2 * tilde2 / c.gamma2;
        double vdot = -c.am * e * e - c.kc * c.a * epsilon * epsilon;
        return new double[] {v, vdot};
    }

    public static void main(String[] args) {
        Locale.setDefault(Locale.US);
        Config c = new Config();
        int steps = (int) Math.round(c.tf / c.dt) + 1;
        double[] state = new double[] {0.0, 0.0, 0.8, -0.2, 0.2};
        double sumE2 = 0.0;
        double sumEp2 = 0.0;
        double previousV = 0.0;
        double maxDV = -Double.MAX_VALUE;
        Path output = Path.of("Chapter21_Lesson3_results.csv");

        try (BufferedWriter writer = Files.newBufferedWriter(output, StandardCharsets.UTF_8)) {
            writer.write("t,x,xm,xhat,theta_hat_1,theta_hat_2,V,Vdot_identity,r\n");
            for (int i = 0; i < steps; ++i) {
                double t = i * c.dt;
                double[] vv = lyapunov(state, c);
                if (i > 0) {
                    maxDV = Math.max(maxDV, vv[0] - previousV);
                }
                previousV = vv[0];
                double e = state[0] - state[1];
                double epsilon = state[0] - state[2];
                sumE2 += e * e;
                sumEp2 += epsilon * epsilon;
                writer.write(String.format(Locale.US,
                    "%.12g,%.12g,%.12g,%.12g,%.12g,%.12g,%.12g,%.12g,%.12g%n",
                    t, state[0], state[1], state[2], state[3], state[4],
                    vv[0], vv[1], reference(t)));
                if (i + 1 < steps) {
                    state = rk4Step(t, state, c.dt, c);
                }
            }
        } catch (IOException ex) {
            System.err.println("Cannot write output: " + ex.getMessage());
            System.exit(1);
        }

        System.out.printf(Locale.US, "RMS tracking error   : %.6e%n", Math.sqrt(sumE2 / steps));
        System.out.printf(Locale.US, "RMS prediction error : %.6e%n", Math.sqrt(sumEp2 / steps));
        System.out.printf(Locale.US, "Final theta_hat       : [%.6f, %.6f]%n", state[3], state[4]);
        System.out.printf(Locale.US, "True theta            : [%.6f, %.6f]%n", c.theta1, c.theta2);
        System.out.printf(Locale.US, "Maximum sampled dV    : %.6e%n", maxDV);
        System.out.println("Saved Chapter21_Lesson3_results.csv");
    }
}

The Java program uses no third-party library. It writes the simulation history through java.nio.file and is suitable for plotting later with a spreadsheet, Python, or another visualization tool.

13. MATLAB/Simulink-Oriented Implementation

Chapter21_Lesson3.m

% Chapter21_Lesson3.m
% Composite adaptive control with tracking and prediction errors.
clear; clc; close all;

cfg.a = 1.2;
cfg.am = 2.0;
cfg.kc = 1.5;
cfg.Gamma = diag([8.0, 8.0]);
cfg.theta = [0.8; -0.5];
cfg.dt = 0.002;
cfg.tf = 30.0;

t = (0:cfg.dt:cfg.tf)';
N = numel(t);
z = zeros(N, 5);                 % [x, xm, xhat, theta_hat_1, theta_hat_2]
z(1, :) = [0.0, 0.0, 0.8, -0.2, 0.2];
V = zeros(N, 1);
VdotIdentity = zeros(N, 1);
rHist = zeros(N, 1);

for k = 1:N
    [V(k), VdotIdentity(k)] = lyapunovValue(z(k, :)', cfg);
    rHist(k) = referenceSignal(t(k));
    if k < N
        z(k + 1, :) = rk4Step(t(k), z(k, :)', cfg.dt, cfg)';
    end
end

x = z(:, 1); xm = z(:, 2); xhat = z(:, 3);
e = x - xm; epsilon = x - xhat;
rmsTracking = sqrt(mean(e.^2));
rmsPrediction = sqrt(mean(epsilon.^2));
maxSampledDV = max(diff(V));

fprintf('RMS tracking error   : %.6e\n', rmsTracking);
fprintf('RMS prediction error : %.6e\n', rmsPrediction);
fprintf('Final theta_hat       : [%.6f, %.6f]\n', z(end,4), z(end,5));
fprintf('True theta            : [%.6f, %.6f]\n', cfg.theta(1), cfg.theta(2));
fprintf('Maximum sampled dV    : %.6e\n', maxSampledDV);

results = table(t, x, xm, xhat, z(:,4), z(:,5), V, VdotIdentity, rHist, ...
    'VariableNames', {'t','x','xm','xhat','theta_hat_1','theta_hat_2', ...
    'V','Vdot_identity','r'});
writetable(results, 'Chapter21_Lesson3_results.csv');

figure;
plot(t, x, t, xm, '--', t, xhat, ':', 'LineWidth', 1.2);
xlabel('time [s]'); ylabel('state'); grid on;
legend('plant x', 'reference x_m', 'predictor xhat', 'Location', 'best');

figure;
plot(t, z(:,4), t, z(:,5), 'LineWidth', 1.2); hold on;
yline(cfg.theta(1), '--'); yline(cfg.theta(2), '--');
xlabel('time [s]'); ylabel('parameter'); grid on;
legend('theta hat 1', 'theta hat 2', 'theta 1', 'theta 2', 'Location', 'best');

figure;
semilogy(t, max(V, 1.0e-14), 'LineWidth', 1.2);
xlabel('time [s]'); ylabel('composite Lyapunov function V'); grid on;

function r = referenceSignal(t)
    r = 0.9*sin(0.7*t) + 0.45*sin(1.9*t);
end

function phi = regressor(t, x)
    phi = [x; sin(1.3*t)];
end

function dz = modelDynamics(t, z, cfg)
    x = z(1); xm = z(2); xhat = z(3);
    thetaHat = z(4:5);
    phi = regressor(t, x);
    r = referenceSignal(t);
    e = x - xm;
    epsilon = x - xhat;
    u = (cfg.a - cfg.am)*x + r - thetaHat'*phi;
    xDot = -cfg.a*x + u + cfg.theta'*phi;
    xmDot = -cfg.am*xm + r;
    xhatDot = -cfg.a*xhat + u + thetaHat'*phi;
    thetaHatDot = cfg.Gamma*phi*(e + cfg.kc*epsilon);
    dz = [xDot; xmDot; xhatDot; thetaHatDot];
end

function next = rk4Step(t, z, dt, cfg)
    k1 = modelDynamics(t, z, cfg);
    k2 = modelDynamics(t + 0.5*dt, z + 0.5*dt*k1, cfg);
    k3 = modelDynamics(t + 0.5*dt, z + 0.5*dt*k2, cfg);
    k4 = modelDynamics(t + dt, z + dt*k3, cfg);
    next = z + (dt/6)*(k1 + 2*k2 + 2*k3 + k4);
end

function [V, Vdot] = lyapunovValue(z, cfg)
    e = z(1) - z(2);
    epsilon = z(1) - z(3);
    tilde = cfg.theta - z(4:5);
    V = 0.5*e^2 + 0.5*cfg.kc*epsilon^2 ...
        + 0.5*tilde'*(cfg.Gamma\tilde);
    Vdot = -cfg.am*e^2 - cfg.kc*cfg.a*epsilon^2;
end

The script is directly executable in MATLAB. For a Simulink realization, use Integrator blocks for x, xm, xhat, and the two parameter estimates; place the equations in MATLAB Function blocks; and log V and its theoretical derivative for verification.

14. Wolfram Mathematica Implementation

Chapter21_Lesson3.nb

(* Chapter21_Lesson3.nb
   Wolfram Language implementation of composite adaptive control.
   Paste into a Wolfram Mathematica notebook or evaluate as a Wolfram Language file. *)

ClearAll["Global`*"];

cfg = <|
   "a" -> 1.2, "am" -> 2.0, "kc" -> 1.5,
   "Gamma" -> DiagonalMatrix[{8.0, 8.0}],
   "theta" -> {0.8, -0.5}, "dt" -> 0.002, "tf" -> 30.0
   |>;

reference[t_] := 0.9 Sin[0.7 t] + 0.45 Sin[1.9 t];
regressor[t_, x_] := {x, Sin[1.3 t]};

dynamics[t_, z_] := Module[
  {x, xm, xhat, thetaHat, phi, r, e, epsilon, u, xDot, xmDot,
   xhatDot, thetaHatDot},
  x = z[[1]]; xm = z[[2]]; xhat = z[[3]];
  thetaHat = z[[4 ;; 5]];
  phi = regressor[t, x]; r = reference[t];
  e = x - xm; epsilon = x - xhat;
  u = (cfg["a"] - cfg["am"]) x + r - thetaHat.phi;
  xDot = -cfg["a"] x + u + cfg["theta"].phi;
  xmDot = -cfg["am"] xm + r;
  xhatDot = -cfg["a"] xhat + u + thetaHat.phi;
  thetaHatDot = cfg["Gamma"].phi (e + cfg["kc"] epsilon);
  Join[{xDot, xmDot, xhatDot}, thetaHatDot]
  ];

rk4Step[t_, z_, dt_] := Module[{k1, k2, k3, k4},
  k1 = dynamics[t, z];
  k2 = dynamics[t + dt/2, z + dt k1/2];
  k3 = dynamics[t + dt/2, z + dt k2/2];
  k4 = dynamics[t + dt, z + dt k3];
  z + dt (k1 + 2 k2 + 2 k3 + k4)/6
  ];

lyapunov[z_] := Module[{e, epsilon, tilde, v, vdot},
  e = z[[1]] - z[[2]];
  epsilon = z[[1]] - z[[3]];
  tilde = cfg["theta"] - z[[4 ;; 5]];
  v = e^2/2 + cfg["kc"] epsilon^2/2 +
     (tilde.Inverse[cfg["Gamma"]].tilde)/2;
  vdot = -cfg["am"] e^2 - cfg["kc"] cfg["a"] epsilon^2;
  {v, vdot}
  ];

times = Range[0.0, cfg["tf"], cfg["dt"]];
trajectory = FoldList[
   Function[{z, tk}, rk4Step[tk, z, cfg["dt"]]],
   {0.0, 0.0, 0.8, -0.2, 0.2}, Most[times]];

vData = lyapunov /@ trajectory;
rows = MapThread[Join[{#1}, #2, #3, {reference[#1]}] &,
   {times, trajectory, vData}];
Export["Chapter21_Lesson3_results.csv",
  Prepend[rows, {"t", "x", "xm", "xhat", "theta_hat_1",
    "theta_hat_2", "V", "Vdot_identity", "r"}]];

e = trajectory[[All, 1]] - trajectory[[All, 2]];
epsilon = trajectory[[All, 1]] - trajectory[[All, 3]];
Print["RMS tracking error   : ", Sqrt[Mean[e^2]] // N];
Print["RMS prediction error : ", Sqrt[Mean[epsilon^2]] // N];
Print["Final theta_hat       : ", Last[trajectory][[4 ;; 5]] // N];
Print["True theta            : ", cfg["theta"]];

ListLinePlot[
 Transpose[{times, #}] & /@ Transpose[trajectory[[All, 1 ;; 3]]],
 PlotLegends -> {"plant x", "reference xm", "predictor xhat"},
 Frame -> True, FrameLabel -> {"time [s]", "state"}]

ListLinePlot[
 Transpose[{times, #}] & /@ Transpose[trajectory[[All, 4 ;; 5]]],
 PlotLegends -> {"theta_hat_1", "theta_hat_2"}, Frame -> True,
 FrameLabel -> {"time [s]", "parameter"}]

ListLogPlot[Transpose[{times, Clip[vData[[All, 1]], {10^-14, Infinity}]}],
 Frame -> True, FrameLabel -> {"time [s]", "composite Lyapunov V"}]

The file contains Wolfram Language expressions for the same RK4 simulation, CSV export, diagnostics, and plots. It can be pasted into a notebook or evaluated as a Wolfram Language source file.

15. Problems and Fully Worked Solutions

Problem 1 — Derive the matrix composite law.

Starting from the tracking and predictor dynamics in Section 2, derive the adaptive law that makes the derivative of \( V_c \) negative semidefinite.

Solution.

Differentiate each quadratic term, substitute the two error dynamics, and use the Lyapunov equations. The only sign-indefinite expression is

\[ \tilde{\boldsymbol{\theta}}^T \left[\boldsymbol{\phi}\mathbf{b}^T\mathbf{P}\mathbf{e} +k_c\boldsymbol{\phi}\mathbf{b}^T\mathbf{S} \boldsymbol{\varepsilon} -\boldsymbol{\Gamma}^{-1} \dot{\hat{\boldsymbol{\theta}}}\right]. \]

Setting the bracket to zero yields \( \dot{\hat{\boldsymbol{\theta}}}= \boldsymbol{\Gamma}\boldsymbol{\phi} (\mathbf{b}^T\mathbf{P}\mathbf{e}+k_c\mathbf{b}^T \mathbf{S}\boldsymbol{\varepsilon}) \). Substitution leaves only the two negative quadratic forms.

Problem 2 — Establish explicit norm bounds.

Find valid constants \( c_1 \) and \( c_2 \) satisfying the quadratic bounds in Section 4.

Solution.

One valid choice is

\[ c_1=\frac{1}{2}\min\left\{ \lambda_{\min}(\mathbf{P}), k_c\lambda_{\min}(\mathbf{S}), \lambda_{\min}(\boldsymbol{\Gamma}^{-1})\right\}, \]

\[ c_2=\frac{1}{2}\max\left\{ \lambda_{\max}(\mathbf{P}), k_c\lambda_{\max}(\mathbf{S}), \lambda_{\max}(\boldsymbol{\Gamma}^{-1})\right\}. \]

These follow immediately from the Rayleigh quotient inequalities for symmetric positive-definite matrices.

Problem 3 — Prove exponential convergence with an information matrix.

Suppose \( \boldsymbol{\zeta}=\boldsymbol{\Omega} \tilde{\boldsymbol{\theta}} \) and \( \boldsymbol{\Omega}^T\boldsymbol{\Omega} \ge\alpha_\Omega\mathbf{I} \) after time \( T \). Derive a comparison inequality for the reduced tracking-parameter Lyapunov function.

Solution.

The derivative satisfies

\[ \dot V_r\le-\frac{1}{2}\lambda_{\min}(\mathbf{Q}) \|\mathbf{e}\|^2-k_I\alpha_\Omega \|\tilde{\boldsymbol{\theta}}\|^2. \]

Compare each norm with its corresponding term in \( V_r \):

\[ \dot V_r\le-\min\left\{ \frac{\lambda_{\min}(\mathbf{Q})}{\lambda_{\max}(\mathbf{P})}, \frac{2k_I\alpha_\Omega} {\lambda_{\max}(\boldsymbol{\Gamma}^{-1})} \right\}V_r. \]

The comparison lemma gives \( V_r(t)\le V_r(T)e^{-\lambda(t-T)} \).

Problem 4 — Verify the scalar cancellation manually.

For the scalar model in Section 8, show directly that the cross terms cancel.

Solution.

\[ \begin{aligned} \dot V &=e(-a_m e+\tilde{\boldsymbol{\theta}}^T\boldsymbol{\phi}) +k_c\varepsilon(-a\varepsilon+ \tilde{\boldsymbol{\theta}}^T\boldsymbol{\phi}) -\tilde{\boldsymbol{\theta}}^T \boldsymbol{\Gamma}^{-1} \dot{\hat{\boldsymbol{\theta}}} \\ &=-a_m e^2-k_c a\varepsilon^2 +\tilde{\boldsymbol{\theta}}^T\boldsymbol{\phi} (e+k_c\varepsilon) -\tilde{\boldsymbol{\theta}}^T\boldsymbol{\phi} (e+k_c\varepsilon) \\ &=-a_m e^2-k_c a\varepsilon^2. \end{aligned} \]

Problem 5 — Diagnose a sign error.

An implementation mistakenly uses \( \dot{\hat{\boldsymbol{\theta}}}= \boldsymbol{\Gamma}\boldsymbol{\phi}(e-k_c\varepsilon) \) for the scalar model. Compute the resulting derivative and explain the risk.

Solution.

Substitution gives

\[ \dot V=-a_m e^2-k_c a\varepsilon^2 +2k_c\varepsilon\tilde{\boldsymbol{\theta}}^T \boldsymbol{\phi}. \]

The last term has indefinite sign and can dominate the negative quadratic terms. The Lyapunov proof therefore fails, and instability is possible. This illustrates why the sign convention for \( \tilde{\boldsymbol{\theta}} \) must be fixed before deriving or coding the adaptive law.

16. Summary

A composite Lyapunov function combines tracking energy, prediction energy, and parameter-error energy in one positive-definite scalar. Differentiating this function exposes every state-parameter cross term. The composite adaptive law is selected to cancel the entire collection of cross terms, leaving negative quadratic forms in tracking and prediction errors. This establishes boundedness and asymptotic convergence of those errors under standard regularity assumptions. Parameter convergence requires additional information: persistent excitation, finite excitation with stored data, or a positive-definite regression information matrix. Thus composite adaptive control separates two questions cleanly: Lyapunov design guarantees stable interaction of control and estimation, while excitation determines which unknown parameters can actually be learned.

17. References

  1. Slotine, J.-J.E., & Li, W. (1989). Composite adaptive control of robot manipulators. Automatica, 25(4), 509–519.
  2. Boyd, S., & Sastry, S.S. (1986). Necessary and sufficient conditions for parameter convergence in adaptive control. Automatica, 22(6), 629–639.
  3. Narendra, K.S., & Annaswamy, A.M. (1987). Persistent excitation in adaptive systems. International Journal of Control, 45(1), 127–160.
  4. Krause, J.M., & Khargonekar, P.P. (1987). Parameter information content of measurable signals in direct adaptive control. IEEE Transactions on Automatic Control, 32(9), 802–810.
  5. Krstić, M., & Kokotović, P.V. (1995). Adaptive nonlinear design with controller-identifier separation and swapping. IEEE Transactions on Automatic Control, 40(3), 426–440.
  6. Patre, P.M., MacKunis, W., Johnson, M., & Dixon, W.E. (2010). Composite adaptive control for Euler–Lagrange systems with additive disturbances. Automatica, 46(1), 140–147.
  7. Cho, N., Shin, H.-S., Kim, Y., & Tsourdos, A. (2018). Composite model reference adaptive control with parameter convergence under finite excitation. IEEE Transactions on Automatic Control, 63(3), 811–818.
  8. Pan, Y., & Yu, H. (2018). Composite learning robot control with guaranteed parameter convergence. Automatica, 89, 398–406.
  9. Guo, K., Pan, Y., Zheng, D., & Yu, H. (2020). Composite learning control of robotic systems: A least-squares modulated approach. Automatica, 111, 108612.
  10. Panteley, E., Loria, A., & Teel, A.R. (2001). Relaxed persistency of excitation for uniform asymptotic stability. IEEE Transactions on Automatic Control, 46(12), 1874–1886.
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.