Chapter 24: Implementation Issues and Practical Pitfalls

Lesson 1: Measurement Noise and Unmodeled Dynamics in Adaptive Controllers

This lesson develops a rigorous perturbation analysis of adaptive controllers when measured signals are noisy and the physical plant contains dynamics that are absent from the controller model. We derive the mechanisms behind parameter bias, stochastic gain wandering, loss of nominal Lyapunov cancellation, and excitation of parasitic high-frequency modes. We then establish an ultimate-boundedness result for a leakage-modified adaptive law and translate the analysis into implementable filtering, dead-zone, normalization, projection, and bandwidth-separation rules.

1. Why Ideal Adaptive Proofs Do Not Directly Describe Hardware

A nominal adaptive-control proof normally assumes that the state or output used by the update law is exact, that the regressor is generated by the modeled dynamics, and that every relevant actuator, sensor, and structural mode is represented. In a real loop, the controller receives measured signals, not mathematical states:

\[ y_m(t)=y(t)+n_y(t), \qquad \boldsymbol{\phi}_m(t)=\boldsymbol{\phi}(t)+ \delta\boldsymbol{\phi}(t). \]

Here \( n_y \) is measurement noise and \( \delta\boldsymbol{\phi} \) is regressor corruption. In addition, the actual plant may contain neglected actuator poles, sensor filters, delays, flexible modes, nonlinear friction, quantization, or cross-coupling. These effects can be collected into a perturbation \( \Delta(t) \), but that compact notation must not hide the fact that some perturbations have internal dynamics and phase lag.

flowchart TD
  R["Reference command"] --> RM["Reference model"]
  R --> C["Adaptive controller"]
  YN["Measured output = true output + sensor noise"] --> C
  C --> A["Actuator with omitted pole or delay"]
  A --> P["Physical plant"]
  P --> S["Sensor and sampling"]
  S --> YN
  RM --> E["Measured tracking error"]
  YN --> E
  E --> U["Parameter update law"]
  U --> C
  P --> D["Unmodeled flexible modes and disturbances"]
  D --> S
        

The central implementation question is therefore not whether the nominal adaptive law is stable in its ideal model. It is whether the complete measured-data loop remains bounded and useful under perturbations that continuously enter both the control channel and the learning channel.

2. Perturbed Error Model and the Lost Cancellation

Consider a linearly parameterized tracking-error model already familiar from earlier MRAC chapters:

\[ \dot{\mathbf e} = A_m\mathbf e + b\tilde{\boldsymbol{\theta}}^T \boldsymbol{\phi} + b\Delta, \qquad \tilde{\boldsymbol{\theta}} =\hat{\boldsymbol{\theta}}- \boldsymbol{\theta}^*, \]

where \( A_m \) is Hurwitz and \( \Delta \) represents the matched component of unmodeled dynamics, disturbance, and signal corruption. Let \( P=P^T>0 \) solve

\[ A_m^TP+PA_m=-Q, \qquad Q=Q^T>0. \]

For the nominal gradient update

\[ \dot{\hat{\boldsymbol{\theta}}} =-\Gamma\boldsymbol{\phi}\mathbf e^TPb, \qquad \Gamma=\Gamma^T>0, \]

use the Lyapunov function

\[ V=\mathbf e^TP\mathbf e+ \tilde{\boldsymbol{\theta}}^T \Gamma^{-1}\tilde{\boldsymbol{\theta}}. \]

The parameter cross-term still cancels, but the perturbation remains:

\[ \dot V=-\mathbf e^TQ\mathbf e+ 2\mathbf e^TPb\Delta. \]

Young's inequality gives, for \( \lambda_Q=\lambda_{\min}(Q) \),

\[ 2\left|\mathbf e^TPb\Delta\right| \leq \frac{\lambda_Q}{2}\|\mathbf e\|^2+ \frac{2\|Pb\|^2}{\lambda_Q}|\Delta|^2. \]

Therefore

\[ \dot V\leq -\frac{\lambda_Q}{2}\|\mathbf e\|^2+ \frac{2\|Pb\|^2}{\lambda_Q}|\Delta|^2. \]

This inequality can bound the tracking state when \( \Delta \) is bounded, but it contains no negative term in \( \tilde{\boldsymbol{\theta}} \). Consequently, the nominal proof does not prevent parameter drift along weakly excited directions. Bounded tracking error alone is not a proof that the adaptive gains remain physically acceptable.

3. Measurement Noise Enters the Update Law Multiplicatively

Suppose the adaptive law uses the measured error and measured regressor:

\[ e_m=e+n_e,\qquad \boldsymbol{\phi}_m= \boldsymbol{\phi}+\delta\boldsymbol{\phi}, \qquad \dot{\hat{\boldsymbol{\theta}}} =-\Gamma\boldsymbol{\phi}_m e_m. \]

Expanding the update exposes four terms:

\[ \dot{\hat{\boldsymbol{\theta}}} =-\Gamma\left( \boldsymbol{\phi}e+ \boldsymbol{\phi}n_e+ \delta\boldsymbol{\phi}e+ \delta\boldsymbol{\phi}n_e \right). \]

Even when the sensor noise is zero-mean, the product \( \delta\boldsymbol{\phi}n_e \) need not be zero-mean. This is the adaptive-control version of the errors-in-variables problem: noise corrupts the independent variable used by the estimator, so unbiased output noise alone is not sufficient for unbiased parameter learning.

3.1 Scalar attenuation-bias derivation

Consider a scalar static regression used locally by an adaptive law:

\[ y=\theta^*\phi,\qquad \phi_m=\phi+v,\qquad y_m=y+w, \]

with update

\[ \dot{\hat\theta} =\gamma\phi_m(y_m-\hat\theta\phi_m). \]

At a mean equilibrium, \( E[\dot{\hat\theta}]=0 \), hence

\[ \hat\theta_{\infty}= \frac{E[\phi_m y_m]}{E[\phi_m^2]}. \]

If \( \phi,v,w \) are zero-mean, \( v \) and \( w \) are independent of \( \phi \), and \( E[vw]=0 \), then

\[ E[\phi_m y_m]=\theta^*E[\phi^2], \qquad E[\phi_m^2]=E[\phi^2]+\sigma_v^2. \]

Therefore

\[ \boxed{ \hat\theta_{\infty}= \theta^* \frac{E[\phi^2]} {E[\phi^2]+\sigma_v^2} } \]

and the magnitude is biased toward zero. Increasing the adaptation gain does not remove this structural bias; it only changes how rapidly and how noisily the estimate approaches its biased stochastic neighborhood.

4. Adaptation Speed Versus Stochastic Parameter Variance

Near a locally identifiable operating point, a scalar parameter-error model can often be approximated by the stochastic differential equation

\[ d\tilde\theta= -\gamma r_\phi\tilde\theta\,dt+ \gamma s_n\,dW_t, \qquad r_\phi>0, \]

where \( r_\phi \) is a local regressor-energy term, \( s_n \) is the effective noise intensity, and \( W_t \) is a Wiener process. The mean satisfies

\[ E[\tilde\theta(t)] =E[\tilde\theta(0)] e^{-\gamma r_\phi t}, \]

so the nominal convergence time constant is approximately \( 1/(\gamma r_\phi) \). The variance \( p(t)=E[\tilde\theta^2] \) satisfies

\[ \dot p=-2\gamma r_\phi p+\gamma^2s_n^2. \]

At stationarity,

\[ \boxed{ p_{\infty}= \frac{\gamma s_n^2}{2r_\phi} } \]

Thus a larger adaptation gain produces faster mean convergence but a larger steady parameter variance. When excitation disappears, \( r_\phi=0 \), the stabilizing drift vanishes and \( \dot p=\gamma^2s_n^2 \); the estimate becomes a random walk unless the update law contains leakage, projection, a dead-zone, resetting, or another bounding mechanism.

4.1 Noise filtering

If a measured signal is passed through \( F(s)=\omega_f/(s+\omega_f) \), its output-noise variance is

\[ \sigma_{n,f}^2= \frac{1}{2\pi} \int_{-\infty}^{\infty} |F(j\omega)|^2S_n(\omega)\,d\omega. \]

For ideal two-sided white noise with \( S_n(\omega)=N_0/2 \),

\[ \sigma_{n,f}^2=\frac{N_0\omega_f}{4}. \]

Reducing \( \omega_f \) lowers noise energy, but the filter also adds phase lag. The filter cutoff must therefore remain above the required tracking bandwidth while staying below the frequency region in which sensor noise and neglected modes dominate.

5. Unmodeled Dynamics and the Fast-Adaptation Failure Mechanism

A simple nominal plant may be written

\[ G_0(s)=\frac{b}{s+a}. \]

Suppose the actual actuator contributes an omitted pole:

\[ G_p(s)=\frac{b}{(s+a)(\tau_a s+1)}. \]

At frequencies well below \( 1/\tau_a \), the nominal model is reasonable. Near and above that frequency, the actuator adds magnitude attenuation and phase lag. A high adaptation gain can move controller gains rapidly enough to inject energy into this frequency range. The update law interprets the resulting tracking error as parameter mismatch and increases adaptation activity, even though the error is caused by missing dynamics rather than an adjustable plant coefficient.

A useful frozen-time robustness screen writes the neglected dynamics as a stable uncertainty \( W(s)\Delta_m(s) \) with \( \|\Delta_m\|_\infty\leq1 \). For a frozen controller, a small-gain condition has the form

\[ \left\|T_{zu}(s;\hat{\boldsymbol{\theta}}) W(s)\right\|_\infty<1. \]

This is not by itself a proof for a time-varying adaptive controller. It is an engineering constraint: every admissible frozen controller generated by adaptation should retain adequate robust stability margin, and the parameter-update bandwidth should remain separated from the onset frequency of neglected dynamics.

\[ \omega_a \ll \omega_u, \]

where \( \omega_a \) is an effective adaptation bandwidth and \( \omega_u \) is the frequency at which the model error becomes significant. The symbol \( \ll \) is intentionally qualitative: no universal numerical ratio applies to every plant.

6. Ultimate Boundedness with Sigma Modification

Add leakage to the nominal update law:

\[ \dot{\hat{\boldsymbol{\theta}}} =-\Gamma\boldsymbol{\phi}\mathbf e^TPb -\sigma\Gamma\hat{\boldsymbol{\theta}}, \qquad \sigma>0. \]

Assume \( \|\boldsymbol{\theta}^*\| \leq\bar\theta \) and \( |\Delta(t)|\leq\bar\Delta \). Using the same Lyapunov function as before,

\[ \begin{aligned} \dot V &= -\mathbf e^TQ\mathbf e+ 2\mathbf e^TPb\Delta -2\sigma \tilde{\boldsymbol{\theta}}^T \hat{\boldsymbol{\theta}} \\ &= -\mathbf e^TQ\mathbf e+ 2\mathbf e^TPb\Delta -2\sigma\|\tilde{\boldsymbol{\theta}}\|^2 -2\sigma \tilde{\boldsymbol{\theta}}^T \boldsymbol{\theta}^*. \end{aligned} \]

Since \( -2\tilde{\boldsymbol{\theta}}^T \boldsymbol{\theta}^* \leq \|\tilde{\boldsymbol{\theta}}\|^2+ \|\boldsymbol{\theta}^*\|^2 \),

\[ -2\sigma \tilde{\boldsymbol{\theta}}^T \hat{\boldsymbol{\theta}} \leq -\sigma\|\tilde{\boldsymbol{\theta}}\|^2+ \sigma\bar\theta^2. \]

Combining this result with the perturbation bound yields

\[ \dot V\leq -\frac{\lambda_Q}{2}\|\mathbf e\|^2 -\sigma\|\tilde{\boldsymbol{\theta}}\|^2 +c_\Delta, \]

\[ c_\Delta= \frac{2\|Pb\|^2}{\lambda_Q}\bar\Delta^2+ \sigma\bar\theta^2. \]

Define

\[ \alpha = \min\left\{ \frac{\lambda_Q}{2\lambda_{\max}(P)}, \sigma\lambda_{\min}(\Gamma) \right\}. \]

Then

\[ \dot V\leq-\alpha V+c_\Delta. \]

By the comparison lemma,

\[ V(t)\leq e^{-\alpha t}V(0)+ \frac{c_\Delta}{\alpha} \left(1-e^{-\alpha t}\right). \]

Hence the tracking and parameter errors are uniformly ultimately bounded, with an ultimate Lyapunov level no larger than \( c_\Delta/\alpha \). Leakage prevents unbounded drift, but it also biases the estimate toward zero. Robustness is obtained by accepting a nonzero residual set rather than claiming exact parameter convergence under persistent disturbances.

7. Practical Robustification Architecture

flowchart TD
  M["Raw sensor samples"] --> F["Anti-alias and low-pass filtering"]
  F --> R["Construct measured error and regressor"]
  R --> N["Normalize by 1 + regressor energy"]
  N --> Z["Dead-zone test against noise and modeling floor"]
  Z --> L["Gradient update plus leakage"]
  L --> P["Projection onto certified parameter set"]
  P --> C["Rate limit and discretize parameter command"]
  C --> V["Validate frozen-loop margins and actuator activity"]
  V --> H["Log residuals, gains, saturation, and update status"]
        

7.1 Normalization

\[ \dot{\hat{\boldsymbol{\theta}}} = -\Gamma \frac{ \boldsymbol{\phi}_m e_m }{ \varepsilon+ \boldsymbol{\phi}_m^T\boldsymbol{\phi}_m }, \qquad \varepsilon>0. \]

Normalization prevents a large regressor amplitude from producing an arbitrarily large update. It does not remove noise bias, so it should not be treated as a substitute for filtering or a dead-zone.

7.2 Dead-zone adaptation

\[ \dot{\hat{\boldsymbol{\theta}}} = \begin{cases} -\Gamma\boldsymbol{\phi}_m e_m, & |e_m|>\delta,\\ 0, & |e_m|\leq\delta. \end{cases} \]

The threshold should exceed the combined error floor caused by sensor noise, quantization, and bounded model mismatch. If the error noise is approximately Gaussian with standard deviation \( \sigma_e \), choosing \( \delta=3\sigma_e \) gives a noise-only activation probability of approximately \( 2Q(3)\approx0.0027 \) per independent sample. Correlated samples reduce the number of independent trials but can create bursts, so empirical residual histograms remain important.

7.3 Projection and certified gain sets

Define a convex admissible set \( \Omega_\theta \) containing gains for which frozen-loop stability margins, actuator limits, and sign constraints have been checked:

\[ \hat{\boldsymbol{\theta}}(t)\in\Omega_\theta \quad \text{for all } t. \]

Projection converts physical and robustness knowledge into an invariant parameter constraint. It is especially important when noise or unmodeled dynamics can drive the gradient consistently in one direction.

8. Discrete-Time Implementation and Numerical Conditions

With sample time \( T_s \), a practical update is

\[ \hat{\boldsymbol{\theta}}_{k+1} = \operatorname{Proj}_{\Omega_\theta} \left[ (I-\sigma T_s\Gamma) \hat{\boldsymbol{\theta}}_k - T_s\Gamma \frac{ g_k\boldsymbol{\phi}_{m,k}e_{m,k} }{ \varepsilon+ \boldsymbol{\phi}_{m,k}^T \boldsymbol{\phi}_{m,k} } \right], \]

where \( g_k\in\{0,1\} \) is the dead-zone gate. For a scalar leakage-only mode, forward Euler gives

\[ \hat\theta_{k+1}=(1-\sigma\gamma T_s)\hat\theta_k. \]

Numerical stability of this isolated mode requires \( 0<\sigma\gamma T_s<2 \). The full adaptive loop generally requires a stricter sample time because the plant, actuator, filters, and gradient term are coupled. A controller that is stable in continuous-time analysis can fail after coarse discretization.

Recommended logged channels are measured and filtered error, raw and normalized regressor norms, adaptive gains, projection activity, dead-zone state, actuator saturation, rate limiting, and a residual statistic comparing measured behavior with the nominal model. These channels make it possible to distinguish genuine parameter change from sensor or modeling faults.

9. Python Implementation

The Python program uses NumPy for simulation and Matplotlib for separate tracking, gain-drift, and control-activity figures. Excitation is removed after 15 seconds. In the plain update, \( x_m e_m\approx n^2 \) during regulation, so the feedback gain acquires a systematic drift. The robust update combines normalization, a three-standard-deviation dead-zone, and leakage.

Chapter24_Lesson1.py

"""
Chapter24_Lesson1.py
Measurement noise and unmodeled dynamics in an adaptive controller.

Benchmark:
- Nominal first-order MRAC design.
- Actual plant includes an omitted actuator pole.
- The measured state is corrupted by Gaussian noise.
- Plain gradient adaptation is compared with a robust law using
  normalization, a dead-zone, and sigma leakage.
"""

from pathlib import Path
import numpy as np
import matplotlib.pyplot as plt


SEED = 7
DT = 1.0e-3
T_FINAL = 60.0
NOISE_STD = 0.03
ACTUATOR_TAU = 0.04
U_MAX = 20.0


def reference_input(t: float) -> float:
    """Persistently excite the loop first, then remove excitation."""
    if t < 15.0:
        return 0.8 * np.sin(0.6 * t) + 0.5 * np.sin(1.7 * t)
    return 0.0


def saturate(value: float, limit: float) -> float:
    return float(np.clip(value, -limit, limit))


def run_simulation() -> dict[str, np.ndarray | float]:
    # Plant and reference-model parameters.
    a = 1.0
    b = 1.0
    a_m = 2.0
    b_m = 2.0

    # Adaptation settings.
    gamma_plain = 40.0
    gamma_robust = 20.0
    sigma = 0.08
    dead_zone = 3.0 * NOISE_STD
    normalization_offset = 1.0

    time = np.arange(0.0, T_FINAL + DT, DT)
    n_steps = time.size

    # Shared reference model.
    x_ref = 0.0

    # Plain adaptive loop.
    x_plain = 0.0
    u_act_plain = 0.0
    kx_plain = 0.0
    kr_plain = 0.0

    # Robust adaptive loop.
    x_robust = 0.0
    u_act_robust = 0.0
    kx_robust = 0.0
    kr_robust = 0.0

    # Logged signals.
    log = {
        "t": time,
        "r": np.zeros(n_steps),
        "x_ref": np.zeros(n_steps),
        "x_plain": np.zeros(n_steps),
        "x_robust": np.zeros(n_steps),
        "u_plain": np.zeros(n_steps),
        "u_robust": np.zeros(n_steps),
        "kx_plain": np.zeros(n_steps),
        "kr_plain": np.zeros(n_steps),
        "kx_robust": np.zeros(n_steps),
        "kr_robust": np.zeros(n_steps),
    }

    rng = np.random.default_rng(SEED)

    squared_error_plain = 0.0
    squared_error_robust = 0.0

    for k, t in enumerate(time):
        r = reference_input(float(t))
        measurement_noise = float(rng.normal(0.0, NOISE_STD))

        # ----- Plain gradient adaptation -----
        x_measured_plain = x_plain + measurement_noise
        e_measured_plain = x_measured_plain - x_ref

        u_command_plain = saturate(
            kx_plain * x_measured_plain + kr_plain * r, U_MAX
        )

        kx_plain += DT * (-gamma_plain * x_measured_plain * e_measured_plain)
        kr_plain += DT * (-gamma_plain * r * e_measured_plain)

        # Actual plant contains an unmodeled actuator pole.
        x_plain += DT * (-a * x_plain + b * u_act_plain)
        u_act_plain += DT * (
            (-u_act_plain + u_command_plain) / ACTUATOR_TAU
        )

        # ----- Robust adaptation -----
        x_measured_robust = x_robust + measurement_noise
        e_measured_robust = x_measured_robust - x_ref

        u_command_robust = saturate(
            kx_robust * x_measured_robust + kr_robust * r, U_MAX
        )

        normalizer = (
            normalization_offset + x_measured_robust**2 + r**2
        )
        gate = 1.0 if abs(e_measured_robust) > dead_zone else 0.0

        kx_robust += DT * (
            -gamma_robust
            * gate
            * x_measured_robust
            * e_measured_robust
            / normalizer
            - sigma * kx_robust
        )
        kr_robust += DT * (
            -gamma_robust
            * gate
            * r
            * e_measured_robust
            / normalizer
            - sigma * kr_robust
        )

        x_robust += DT * (-a * x_robust + b * u_act_robust)
        u_act_robust += DT * (
            (-u_act_robust + u_command_robust) / ACTUATOR_TAU
        )

        # Shared reference model.
        x_ref += DT * (-a_m * x_ref + b_m * r)

        squared_error_plain += (x_plain - x_ref) ** 2
        squared_error_robust += (x_robust - x_ref) ** 2

        log["r"][k] = r
        log["x_ref"][k] = x_ref
        log["x_plain"][k] = x_plain
        log["x_robust"][k] = x_robust
        log["u_plain"][k] = u_command_plain
        log["u_robust"][k] = u_command_robust
        log["kx_plain"][k] = kx_plain
        log["kr_plain"][k] = kr_plain
        log["kx_robust"][k] = kx_robust
        log["kr_robust"][k] = kr_robust

    log["rms_plain"] = float(np.sqrt(squared_error_plain / n_steps))
    log["rms_robust"] = float(np.sqrt(squared_error_robust / n_steps))
    return log


def save_results(log: dict[str, np.ndarray | float]) -> None:
    output_dir = Path(__file__).resolve().parent
    csv_path = output_dir / "Chapter24_Lesson1_results.csv"

    columns = [
        "t",
        "r",
        "x_ref",
        "x_plain",
        "x_robust",
        "u_plain",
        "u_robust",
        "kx_plain",
        "kr_plain",
        "kx_robust",
        "kr_robust",
    ]
    matrix = np.column_stack([log[name] for name in columns])
    np.savetxt(
        csv_path,
        matrix,
        delimiter=",",
        header=",".join(columns),
        comments="",
    )

    print(f"Plain-adaptation RMS tracking error:  {log['rms_plain']:.6f}")
    print(f"Robust-adaptation RMS tracking error: {log['rms_robust']:.6f}")
    print(
        "Final plain gains:  "
        f"kx={log['kx_plain'][-1]:.6f}, kr={log['kr_plain'][-1]:.6f}"
    )
    print(
        "Final robust gains: "
        f"kx={log['kx_robust'][-1]:.6f}, kr={log['kr_robust'][-1]:.6f}"
    )
    print(f"CSV written to: {csv_path}")

    plt.figure()
    plt.plot(log["t"], log["x_ref"], label="reference")
    plt.plot(log["t"], log["x_plain"], label="plain adaptation")
    plt.plot(log["t"], log["x_robust"], label="robust adaptation")
    plt.xlabel("Time (s)")
    plt.ylabel("State")
    plt.title("Tracking under noise and an unmodeled actuator pole")
    plt.grid(True)
    plt.legend()
    plt.tight_layout()
    plt.savefig(output_dir / "Chapter24_Lesson1_tracking.png", dpi=180)
    plt.close()

    plt.figure()
    plt.plot(log["t"], log["kx_plain"], label="plain kx")
    plt.plot(log["t"], log["kx_robust"], label="robust kx")
    plt.axvline(15.0, linestyle="--", label="excitation removed")
    plt.xlabel("Time (s)")
    plt.ylabel("Adaptive feedback gain")
    plt.title("Noise-induced drift after excitation disappears")
    plt.grid(True)
    plt.legend()
    plt.tight_layout()
    plt.savefig(output_dir / "Chapter24_Lesson1_gain_drift.png", dpi=180)
    plt.close()

    plt.figure()
    plt.plot(log["t"], log["u_plain"], label="plain control")
    plt.plot(log["t"], log["u_robust"], label="robust control")
    plt.xlabel("Time (s)")
    plt.ylabel("Commanded control")
    plt.title("Control activity")
    plt.grid(True)
    plt.legend()
    plt.tight_layout()
    plt.savefig(output_dir / "Chapter24_Lesson1_control.png", dpi=180)
    plt.close()


if __name__ == "__main__":
    results = run_simulation()
    save_results(results)

10. C++ Implementation

The C++17 implementation uses only the standard library and writes a CSV file suitable for plotting in Python, MATLAB, GNU Octave, or a spreadsheet.

Chapter24_Lesson1.cpp

/*
Chapter24_Lesson1.cpp
Measurement noise and unmodeled dynamics in an adaptive controller.

Compile:
  g++ -std=c++17 -O2 Chapter24_Lesson1.cpp -o Chapter24_Lesson1

Run:
  ./Chapter24_Lesson1
*/

#include <algorithm>
#include <cmath>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <random>
#include <stdexcept>

namespace {

constexpr double kDt = 1.0e-3;
constexpr double kTFinal = 60.0;
constexpr double kNoiseStd = 0.03;
constexpr double kActuatorTau = 0.04;
constexpr double kUMax = 20.0;

double ReferenceInput(double t) {
    if (t < 15.0) {
        return 0.8 * std::sin(0.6 * t) + 0.5 * std::sin(1.7 * t);
    }
    return 0.0;
}

double Saturate(double value, double limit) {
    return std::clamp(value, -limit, limit);
}

}  // namespace

int main() {
    const double a = 1.0;
    const double b = 1.0;
    const double a_m = 2.0;
    const double b_m = 2.0;

    const double gamma_plain = 40.0;
    const double gamma_robust = 20.0;
    const double sigma = 0.08;
    const double dead_zone = 3.0 * kNoiseStd;
    const double normalization_offset = 1.0;

    double x_ref = 0.0;

    double x_plain = 0.0;
    double u_act_plain = 0.0;
    double kx_plain = 0.0;
    double kr_plain = 0.0;

    double x_robust = 0.0;
    double u_act_robust = 0.0;
    double kx_robust = 0.0;
    double kr_robust = 0.0;

    std::mt19937 generator(7);
    std::normal_distribution<double> normal(0.0, kNoiseStd);

    std::ofstream csv("Chapter24_Lesson1_results_cpp.csv");
    if (!csv) {
        throw std::runtime_error("Could not open CSV output file.");
    }

    csv << "t,r,x_ref,x_plain,x_robust,u_plain,u_robust,"
           "kx_plain,kr_plain,kx_robust,kr_robust\n";
    csv << std::setprecision(12);

    double squared_error_plain = 0.0;
    double squared_error_robust = 0.0;
    const int steps = static_cast<int>(kTFinal / kDt) + 1;

    for (int k = 0; k < steps; ++k) {
        const double t = k * kDt;
        const double r = ReferenceInput(t);
        const double measurement_noise = normal(generator);

        const double x_measured_plain = x_plain + measurement_noise;
        const double e_measured_plain = x_measured_plain - x_ref;
        const double u_command_plain = Saturate(
            kx_plain * x_measured_plain + kr_plain * r, kUMax
        );

        kx_plain += kDt * (
            -gamma_plain * x_measured_plain * e_measured_plain
        );
        kr_plain += kDt * (-gamma_plain * r * e_measured_plain);

        x_plain += kDt * (-a * x_plain + b * u_act_plain);
        u_act_plain += kDt * (
            (-u_act_plain + u_command_plain) / kActuatorTau
        );

        const double x_measured_robust = x_robust + measurement_noise;
        const double e_measured_robust = x_measured_robust - x_ref;
        const double u_command_robust = Saturate(
            kx_robust * x_measured_robust + kr_robust * r, kUMax
        );

        const double normalizer =
            normalization_offset
            + x_measured_robust * x_measured_robust
            + r * r;
        const double gate =
            std::abs(e_measured_robust) > dead_zone ? 1.0 : 0.0;

        kx_robust += kDt * (
            -gamma_robust
            * gate
            * x_measured_robust
            * e_measured_robust
            / normalizer
            - sigma * kx_robust
        );
        kr_robust += kDt * (
            -gamma_robust
            * gate
            * r
            * e_measured_robust
            / normalizer
            - sigma * kr_robust
        );

        x_robust += kDt * (-a * x_robust + b * u_act_robust);
        u_act_robust += kDt * (
            (-u_act_robust + u_command_robust) / kActuatorTau
        );

        x_ref += kDt * (-a_m * x_ref + b_m * r);

        squared_error_plain += std::pow(x_plain - x_ref, 2.0);
        squared_error_robust += std::pow(x_robust - x_ref, 2.0);

        csv << t << ',' << r << ',' << x_ref << ','
            << x_plain << ',' << x_robust << ','
            << u_command_plain << ',' << u_command_robust << ','
            << kx_plain << ',' << kr_plain << ','
            << kx_robust << ',' << kr_robust << '\n';
    }

    const double rms_plain = std::sqrt(squared_error_plain / steps);
    const double rms_robust = std::sqrt(squared_error_robust / steps);

    std::cout << std::fixed << std::setprecision(6);
    std::cout << "Plain-adaptation RMS tracking error:  "
              << rms_plain << '\n';
    std::cout << "Robust-adaptation RMS tracking error: "
              << rms_robust << '\n';
    std::cout << "Final plain gains:  kx=" << kx_plain
              << ", kr=" << kr_plain << '\n';
    std::cout << "Final robust gains: kx=" << kx_robust
              << ", kr=" << kr_robust << '\n';
    std::cout << "CSV written to Chapter24_Lesson1_results_cpp.csv\n";

    return 0;
}

11. Java Implementation

The Java implementation uses java.util.Random for Gaussian noise and standard file I/O for the result log.

Chapter24_Lesson1.java

/*
Chapter24_Lesson1.java
Measurement noise and unmodeled dynamics in an adaptive controller.

Compile:
  javac Chapter24_Lesson1.java

Run:
  java Chapter24_Lesson1
*/

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Locale;
import java.util.Random;

public final class Chapter24_Lesson1 {
    private static final double DT = 1.0e-3;
    private static final double T_FINAL = 60.0;
    private static final double NOISE_STD = 0.03;
    private static final double ACTUATOR_TAU = 0.04;
    private static final double U_MAX = 20.0;

    private Chapter24_Lesson1() {
    }

    private static double referenceInput(double t) {
        if (t < 15.0) {
            return 0.8 * Math.sin(0.6 * t)
                + 0.5 * Math.sin(1.7 * t);
        }
        return 0.0;
    }

    private static double saturate(double value, double limit) {
        return Math.max(-limit, Math.min(limit, value));
    }

    public static void main(String[] args) throws IOException {
        Locale.setDefault(Locale.US);

        final double a = 1.0;
        final double b = 1.0;
        final double aM = 2.0;
        final double bM = 2.0;

        final double gammaPlain = 40.0;
        final double gammaRobust = 20.0;
        final double sigma = 0.08;
        final double deadZone = 3.0 * NOISE_STD;
        final double normalizationOffset = 1.0;

        double xRef = 0.0;

        double xPlain = 0.0;
        double uActPlain = 0.0;
        double kxPlain = 0.0;
        double krPlain = 0.0;

        double xRobust = 0.0;
        double uActRobust = 0.0;
        double kxRobust = 0.0;
        double krRobust = 0.0;

        final Random random = new Random(7L);
        final int steps = (int) (T_FINAL / DT) + 1;

        double squaredErrorPlain = 0.0;
        double squaredErrorRobust = 0.0;

        try (
            PrintWriter csv = new PrintWriter(
                new BufferedWriter(
                    new FileWriter(
                        "Chapter24_Lesson1_results_java.csv"
                    )
                )
            )
        ) {
            csv.println(
                "t,r,x_ref,x_plain,x_robust,u_plain,u_robust,"
                + "kx_plain,kr_plain,kx_robust,kr_robust"
            );

            for (int k = 0; k < steps; k++) {
                final double t = k * DT;
                final double r = referenceInput(t);
                final double measurementNoise =
                    NOISE_STD * random.nextGaussian();

                final double xMeasuredPlain =
                    xPlain + measurementNoise;
                final double eMeasuredPlain =
                    xMeasuredPlain - xRef;
                final double uCommandPlain = saturate(
                    kxPlain * xMeasuredPlain + krPlain * r,
                    U_MAX
                );

                kxPlain += DT * (
                    -gammaPlain
                    * xMeasuredPlain
                    * eMeasuredPlain
                );
                krPlain += DT * (
                    -gammaPlain * r * eMeasuredPlain
                );

                xPlain += DT * (-a * xPlain + b * uActPlain);
                uActPlain += DT * (
                    (-uActPlain + uCommandPlain) / ACTUATOR_TAU
                );

                final double xMeasuredRobust =
                    xRobust + measurementNoise;
                final double eMeasuredRobust =
                    xMeasuredRobust - xRef;
                final double uCommandRobust = saturate(
                    kxRobust * xMeasuredRobust + krRobust * r,
                    U_MAX
                );

                final double normalizer =
                    normalizationOffset
                    + xMeasuredRobust * xMeasuredRobust
                    + r * r;
                final double gate =
                    Math.abs(eMeasuredRobust) > deadZone
                        ? 1.0
                        : 0.0;

                kxRobust += DT * (
                    -gammaRobust
                    * gate
                    * xMeasuredRobust
                    * eMeasuredRobust
                    / normalizer
                    - sigma * kxRobust
                );
                krRobust += DT * (
                    -gammaRobust
                    * gate
                    * r
                    * eMeasuredRobust
                    / normalizer
                    - sigma * krRobust
                );

                xRobust += DT * (
                    -a * xRobust + b * uActRobust
                );
                uActRobust += DT * (
                    (-uActRobust + uCommandRobust)
                    / ACTUATOR_TAU
                );

                xRef += DT * (-aM * xRef + bM * r);

                squaredErrorPlain +=
                    Math.pow(xPlain - xRef, 2.0);
                squaredErrorRobust +=
                    Math.pow(xRobust - xRef, 2.0);

                csv.printf(
                    Locale.US,
                    "%.12f,%.12f,%.12f,%.12f,%.12f,"
                    + "%.12f,%.12f,%.12f,%.12f,%.12f,%.12f%n",
                    t,
                    r,
                    xRef,
                    xPlain,
                    xRobust,
                    uCommandPlain,
                    uCommandRobust,
                    kxPlain,
                    krPlain,
                    kxRobust,
                    krRobust
                );
            }
        }

        final double rmsPlain =
            Math.sqrt(squaredErrorPlain / steps);
        final double rmsRobust =
            Math.sqrt(squaredErrorRobust / steps);

        System.out.printf(
            Locale.US,
            "Plain-adaptation RMS tracking error:  %.6f%n",
            rmsPlain
        );
        System.out.printf(
            Locale.US,
            "Robust-adaptation RMS tracking error: %.6f%n",
            rmsRobust
        );
        System.out.printf(
            Locale.US,
            "Final plain gains:  kx=%.6f, kr=%.6f%n",
            kxPlain,
            krPlain
        );
        System.out.printf(
            Locale.US,
            "Final robust gains: kx=%.6f, kr=%.6f%n",
            kxRobust,
            krRobust
        );
        System.out.println(
            "CSV written to Chapter24_Lesson1_results_java.csv"
        );
    }
}

12. MATLAB and Simulink Implementation

The MATLAB script reproduces the benchmark and generates separate figures. The second file is a discrete adaptive-controller function intended for a MATLAB Function block. In Simulink, store \( k_x \) and \( k_r \) with Unit Delay blocks, feed the next-gain outputs back, place the omitted actuator pole in the physical-plant subsystem, and add noise before the controller measurement input.

Chapter24_Lesson1.m

% Chapter24_Lesson1.m
% Measurement noise and unmodeled dynamics in an adaptive controller.
%
% The actual plant contains a first-order actuator pole that is omitted
% from the nominal MRAC design. The measured state contains Gaussian noise.
% Plain gradient adaptation is compared with normalized, dead-zone,
% sigma-modified adaptation.

clear; clc; close all;
rng(7);

dt = 1.0e-3;
tFinal = 60.0;
t = (0:dt:tFinal)';
nSteps = numel(t);

noiseStd = 0.03;
actuatorTau = 0.04;
uMax = 20.0;

a = 1.0;
b = 1.0;
am = 2.0;
bm = 2.0;

gammaPlain = 40.0;
gammaRobust = 20.0;
sigma = 0.08;
deadZone = 3.0 * noiseStd;
normalizationOffset = 1.0;

xRef = 0.0;

xPlain = 0.0;
uActPlain = 0.0;
kxPlain = 0.0;
krPlain = 0.0;

xRobust = 0.0;
uActRobust = 0.0;
kxRobust = 0.0;
krRobust = 0.0;

rLog = zeros(nSteps, 1);
xRefLog = zeros(nSteps, 1);
xPlainLog = zeros(nSteps, 1);
xRobustLog = zeros(nSteps, 1);
uPlainLog = zeros(nSteps, 1);
uRobustLog = zeros(nSteps, 1);
kxPlainLog = zeros(nSteps, 1);
krPlainLog = zeros(nSteps, 1);
kxRobustLog = zeros(nSteps, 1);
krRobustLog = zeros(nSteps, 1);

squaredErrorPlain = 0.0;
squaredErrorRobust = 0.0;

for k = 1:nSteps
    tk = t(k);
    if tk < 15.0
        r = 0.8 * sin(0.6 * tk) + 0.5 * sin(1.7 * tk);
    else
        r = 0.0;
    end

    measurementNoise = noiseStd * randn();

    % Plain gradient adaptation.
    xMeasuredPlain = xPlain + measurementNoise;
    eMeasuredPlain = xMeasuredPlain - xRef;
    uCommandPlain = min(max( ...
        kxPlain * xMeasuredPlain + krPlain * r, ...
        -uMax), uMax);

    kxPlain = kxPlain + dt * ( ...
        -gammaPlain * xMeasuredPlain * eMeasuredPlain);
    krPlain = krPlain + dt * ( ...
        -gammaPlain * r * eMeasuredPlain);

    xPlain = xPlain + dt * (-a * xPlain + b * uActPlain);
    uActPlain = uActPlain + dt * ( ...
        (-uActPlain + uCommandPlain) / actuatorTau);

    % Robust adaptation.
    xMeasuredRobust = xRobust + measurementNoise;
    eMeasuredRobust = xMeasuredRobust - xRef;
    uCommandRobust = min(max( ...
        kxRobust * xMeasuredRobust + krRobust * r, ...
        -uMax), uMax);

    normalizer = normalizationOffset ...
        + xMeasuredRobust^2 + r^2;
    gate = double(abs(eMeasuredRobust) > deadZone);

    kxRobust = kxRobust + dt * ( ...
        -gammaRobust * gate * xMeasuredRobust ...
        * eMeasuredRobust / normalizer ...
        - sigma * kxRobust);
    krRobust = krRobust + dt * ( ...
        -gammaRobust * gate * r ...
        * eMeasuredRobust / normalizer ...
        - sigma * krRobust);

    xRobust = xRobust + dt * (-a * xRobust + b * uActRobust);
    uActRobust = uActRobust + dt * ( ...
        (-uActRobust + uCommandRobust) / actuatorTau);

    xRef = xRef + dt * (-am * xRef + bm * r);

    squaredErrorPlain = squaredErrorPlain + (xPlain - xRef)^2;
    squaredErrorRobust = squaredErrorRobust + (xRobust - xRef)^2;

    rLog(k) = r;
    xRefLog(k) = xRef;
    xPlainLog(k) = xPlain;
    xRobustLog(k) = xRobust;
    uPlainLog(k) = uCommandPlain;
    uRobustLog(k) = uCommandRobust;
    kxPlainLog(k) = kxPlain;
    krPlainLog(k) = krPlain;
    kxRobustLog(k) = kxRobust;
    krRobustLog(k) = krRobust;
end

rmsPlain = sqrt(squaredErrorPlain / nSteps);
rmsRobust = sqrt(squaredErrorRobust / nSteps);

fprintf('Plain-adaptation RMS tracking error:  %.6f\n', rmsPlain);
fprintf('Robust-adaptation RMS tracking error: %.6f\n', rmsRobust);
fprintf('Final plain gains:  kx=%.6f, kr=%.6f\n', ...
    kxPlain, krPlain);
fprintf('Final robust gains: kx=%.6f, kr=%.6f\n', ...
    kxRobust, krRobust);

results = table( ...
    t, rLog, xRefLog, xPlainLog, xRobustLog, ...
    uPlainLog, uRobustLog, ...
    kxPlainLog, krPlainLog, kxRobustLog, krRobustLog, ...
    'VariableNames', { ...
    't', 'r', 'x_ref', 'x_plain', 'x_robust', ...
    'u_plain', 'u_robust', ...
    'kx_plain', 'kr_plain', 'kx_robust', 'kr_robust'});

writetable(results, 'Chapter24_Lesson1_results_matlab.csv');

figure;
plot(t, xRefLog, 'DisplayName', 'reference'); hold on;
plot(t, xPlainLog, 'DisplayName', 'plain adaptation');
plot(t, xRobustLog, 'DisplayName', 'robust adaptation');
grid on; xlabel('Time (s)'); ylabel('State');
title('Tracking under noise and an unmodeled actuator pole');
legend('Location', 'best');

figure;
plot(t, kxPlainLog, 'DisplayName', 'plain k_x'); hold on;
plot(t, kxRobustLog, 'DisplayName', 'robust k_x');
xline(15.0, '--', 'excitation removed');
grid on; xlabel('Time (s)'); ylabel('Adaptive feedback gain');
title('Noise-induced drift after excitation disappears');
legend('Location', 'best');

figure;
plot(t, uPlainLog, 'DisplayName', 'plain control'); hold on;
plot(t, uRobustLog, 'DisplayName', 'robust control');
grid on; xlabel('Time (s)'); ylabel('Commanded control');
title('Control activity');
legend('Location', 'best');

Chapter24_Lesson1_Simulink.m

function [kxNext, krNext, uCommand, adaptationActive] = ...
    Chapter24_Lesson1_Simulink( ...
    xMeasured, xReference, r, kx, kr, ...
    sampleTime, gamma, sigma, deadZone, uMax)
% Chapter24_Lesson1_Simulink.m
%
% Paste this function into a MATLAB Function block, or call it from a
% discrete Simulink subsystem. Store kx and kr with Unit Delay or Memory
% blocks and feed kxNext and krNext back at the next sample.
%
% Inputs:
%   xMeasured, xReference, r  - measured state, model state, command
%   kx, kr                    - current adaptive gains
%   sampleTime                - controller sample period
%   gamma                     - adaptation gain
%   sigma                     - leakage coefficient
%   deadZone                  - adaptation threshold
%   uMax                      - symmetric control limit
%
% Outputs:
%   kxNext, krNext            - gains for the next sample
%   uCommand                  - saturated command
%   adaptationActive          - 1 outside the dead-zone, otherwise 0

eMeasured = xMeasured - xReference;
normalizer = 1.0 + xMeasured^2 + r^2;
adaptationActive = double(abs(eMeasured) > deadZone);

kxDot = -gamma * adaptationActive ...
    * xMeasured * eMeasured / normalizer ...
    - sigma * kx;
krDot = -gamma * adaptationActive ...
    * r * eMeasured / normalizer ...
    - sigma * kr;

kxNext = kx + sampleTime * kxDot;
krNext = kr + sampleTime * krDot;

uUnsaturated = kx * xMeasured + kr * r;
uCommand = min(max(uUnsaturated, -uMax), uMax);
end

13. Wolfram Mathematica Implementation

The notebook expression contains one executable input cell implementing the same benchmark, exporting a CSV file and two figures.

Chapter24_Lesson1.nb


Notebook[{
 Cell["Chapter 24, Lesson 1: Measurement Noise and Unmodeled Dynamics", "Title"],
 Cell["This notebook compares plain gradient adaptation with normalized, dead-zone, sigma-modified adaptation for a plant containing measurement noise and an omitted actuator pole.", "Text"],
 Cell["ClearAll[\"Global`*\"]; seed = 7; SeedRandom[seed];
dt = 0.001; tFinal = 60.0; noiseStd = 0.03; actuatorTau = 0.04; uMax = 20.0;
a = 1.0; b = 1.0; am = 2.0; bm = 2.0;
gammaPlain = 40.0; gammaRobust = 20.0; sigma = 0.08;
deadZone = 3.0 noiseStd; normalizationOffset = 1.0;
referenceInput[t_] := If[t < 15.0,
  0.8 Sin[0.6 t] + 0.5 Sin[1.7 t], 0.0];
saturate[value_, limit_] := Clip[value, {-limit, limit}];
xRef = 0.0; xPlain = 0.0; uActPlain = 0.0; kxPlain = 0.0; krPlain = 0.0;
xRobust = 0.0; uActRobust = 0.0; kxRobust = 0.0; krRobust = 0.0;
squaredErrorPlain = 0.0; squaredErrorRobust = 0.0;
steps = Round[tFinal/dt] + 1; records = Reap[Do[
  t = (k - 1) dt; r = referenceInput[t];
  measurementNoise = RandomVariate[NormalDistribution[0.0, noiseStd]];
  xMeasuredPlain = xPlain + measurementNoise; eMeasuredPlain = xMeasuredPlain - xRef;
  uCommandPlain = saturate[kxPlain xMeasuredPlain + krPlain r, uMax];
  kxPlain = kxPlain + dt (-gammaPlain xMeasuredPlain eMeasuredPlain);
  krPlain = krPlain + dt (-gammaPlain r eMeasuredPlain);
  xPlain = xPlain + dt (-a xPlain + b uActPlain);
  uActPlain = uActPlain + dt ((-uActPlain + uCommandPlain)/actuatorTau);
  xMeasuredRobust = xRobust + measurementNoise; eMeasuredRobust = xMeasuredRobust - xRef;
  uCommandRobust = saturate[kxRobust xMeasuredRobust + krRobust r, uMax];
  normalizer = normalizationOffset + xMeasuredRobust^2 + r^2; gate = If[Abs[eMeasuredRobust] > deadZone, 1.0, 0.0];
  kxRobust = kxRobust + dt (-gammaRobust gate xMeasuredRobust
    eMeasuredRobust/normalizer - sigma kxRobust);
  krRobust = krRobust + dt (-gammaRobust gate r
    eMeasuredRobust/normalizer - sigma krRobust);
  xRobust = xRobust + dt (-a xRobust + b uActRobust);
  uActRobust = uActRobust + dt ((-uActRobust + uCommandRobust)/actuatorTau);
  xRef = xRef + dt (-am xRef + bm r);
  squaredErrorPlain = squaredErrorPlain + (xPlain - xRef)^2;
  squaredErrorRobust = squaredErrorRobust + (xRobust - xRef)^2;
  Sow[{t, r, xRef, xPlain, xRobust, uCommandPlain, uCommandRobust,
    kxPlain, krPlain, kxRobust, krRobust}], {k, 1, steps}]][[2, 1]];
rmsPlain = Sqrt[squaredErrorPlain/steps]; rmsRobust = Sqrt[squaredErrorRobust/steps];
Print[\"Plain-adaptation RMS tracking error: \", NumberForm[rmsPlain, {8, 6}]]; Print[\"Robust-adaptation RMS tracking error: \", NumberForm[rmsRobust, {8, 6}]];
Print[\"Final plain gains: kx=\", NumberForm[kxPlain, {8, 6}],
  \", kr=\", NumberForm[krPlain, {8, 6}]];
Print[\"Final robust gains: kx=\", NumberForm[kxRobust, {8, 6}],
  \", kr=\", NumberForm[krRobust, {8, 6}]];
Export[\"Chapter24_Lesson1_results_mathematica.csv\", Prepend[records,
  {\"t\", \"r\", \"x_ref\", \"x_plain\", \"x_robust\", \"u_plain\", \"u_robust\",
   \"kx_plain\", \"kr_plain\", \"kx_robust\", \"kr_robust\"}]];
trackingPlot = ListLinePlot[
  {records[[All, {1, 3}]], records[[All, {1, 4}]], records[[All, {1, 5}]]},
  PlotLegends -> {\"reference\", \"plain adaptation\", \"robust adaptation\"},
  Frame -> True, FrameLabel -> {\"Time (s)\", \"State\"},
  PlotLabel -> \"Tracking with noise and unmodeled dynamics\", ImageSize -> Large];
gainPlot = ListLinePlot[
  {records[[All, {1, 8}]], records[[All, {1, 10}]]},
  PlotLegends -> {\"plain kx\", \"robust kx\"}, Frame -> True,
  FrameLabel -> {\"Time (s)\", \"Adaptive feedback gain\"},
  PlotLabel -> \"Noise-induced gain drift\", ImageSize -> Large];
Export[\"Chapter24_Lesson1_tracking_mathematica.png\", trackingPlot]; Export[\"Chapter24_Lesson1_gain_drift_mathematica.png\", gainPlot];
", "Input"]
}, WindowTitle -> "Chapter24_Lesson1", StyleDefinitions -> "Default.nb"
]        

14. Problems and Solutions

Problem 1 — Noise-induced drift in regulation: A scalar adaptive feedback gain obeys \( \dot{\hat k}=-\gamma x_m e_m \). During regulation, assume the true state and true error are zero and the same measurement noise appears in both signals: \( x_m=e_m=n \), with \( E[n^2]=\sigma_n^2 \). Find the expected gain drift.

Solution:

\[ E[\dot{\hat k}] =-\gamma E[n^2] =-\gamma\sigma_n^2. \]

Therefore \( E[\hat k(t)] = E[\hat k(0)]-\gamma\sigma_n^2t \). The estimate drifts linearly even though the noise is zero-mean, because the update contains the nonnegative product \( n^2 \).

Problem 2 — Dead-zone false activation: Let the noise-only measured error be Gaussian, \( e_m\sim\mathcal{N}(0,\sigma_e^2) \). Derive the probability that a symmetric dead-zone with threshold \( \delta \) activates adaptation.

Solution:

\[ P(|e_m|>\delta) = 2Q\left(\frac{\delta}{\sigma_e}\right). \]

For \( \delta=3\sigma_e \), this probability is about \( 0.0027 \) for an independent sample. A required false-activation probability \( p_f \) gives

\[ \delta= \sigma_e Q^{-1}\left(\frac{p_f}{2}\right). \]

Problem 3 — Ultimate bound: Suppose \( \dot V\leq-\alpha V+c \), with \( \alpha>0 \) and \( c>0 \). Derive an explicit bound and the ultimate set.

Solution:

Multiply by \( e^{\alpha t} \) and integrate:

\[ \frac{d}{dt} \left(e^{\alpha t}V(t)\right) \leq ce^{\alpha t}, \]

\[ V(t)\leq e^{-\alpha t}V(0)+ \frac{c}{\alpha} \left(1-e^{-\alpha t}\right). \]

Hence \( \limsup_{t→\infty}V(t) \leq c/\alpha \). The residual set shrinks when the disturbance bound decreases or the dissipative coefficient increases, subject to the bias and bandwidth trade-offs created by robustification.

Problem 4 — Filtered white-noise variance: Verify that the first-order filter \( F(s)=\omega_f/(s+\omega_f) \) driven by ideal two-sided white noise with PSD \( N_0/2 \) has output variance \( N_0\omega_f/4 \).

Solution:

\[ \begin{aligned} \sigma_{n,f}^2 &= \frac{1}{2\pi} \int_{-\infty}^{\infty} \frac{\omega_f^2}{\omega^2+\omega_f^2} \frac{N_0}{2}\,d\omega \\ &= \frac{N_0}{4\pi} \left(\pi\omega_f\right) = \frac{N_0\omega_f}{4}. \end{aligned} \]

Problem 5 — Discrete leakage constraint: For \( \hat\theta_{k+1}= (1-\sigma\gamma T_s)\hat\theta_k \), find the forward-Euler stability interval.

Solution:

The scalar multiplier must lie inside the unit disk:

\[ |1-\sigma\gamma T_s|<1. \]

Therefore

\[ 0<\sigma\gamma T_s<2. \]

This condition concerns only the isolated leakage mode. Coupling with the plant and gradient update normally imposes a smaller allowable \( T_s \).

Problem 6 — Errors-in-variables bias: In the scalar regression of Section 3, find the asymptotic estimate when \( E[vw]=c_{vw} \) but the other independence assumptions remain valid.

Solution:

\[ E[\phi_m y_m] = \theta^*E[\phi^2]+c_{vw}, \]

\[ \boxed{ \hat\theta_{\infty} = \frac{ \theta^*E[\phi^2]+c_{vw} }{ E[\phi^2]+\sigma_v^2 } }. \]

Correlated sensor errors can either increase or decrease the estimate, depending on the sign of \( c_{vw} \). The simple attenuation interpretation applies only when the cross-covariance is zero.

15. Summary

Measurement noise affects adaptive control through additive error, regressor corruption, multiplicative update terms, and stochastic parameter variance. Unmodeled dynamics add phase lag and create tracking errors that the update law can mistakenly interpret as adjustable parameter error. The nominal Lyapunov cancellation then provides no direct damping of parameter drift. Filtering, normalization, dead-zones, leakage, projection, rate limits, and certified gain sets address different failure mechanisms and should be combined deliberately. Sigma modification yields uniform ultimate boundedness under bounded perturbations, but exact parameter convergence is replaced by a disturbance-dependent residual set. Practical design therefore requires simultaneous management of adaptation bandwidth, sensor bandwidth, uncertainty onset frequency, sampling, and actuator limits.

16. References

  1. 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. doi:10.1109/TAC.1985.1104070.
  2. Åström, K.J. (1985). A commentary on the C.E. Rohrs et al. paper “Robustness of continuous-time adaptive control algorithms in the presence of unmodeled dynamics.” IEEE Transactions on Automatic Control, AC-30, 889–891.
  3. Ioannou, P.A., & Kokotović, P.V. (1984). Instability analysis and improvement of robustness of adaptive control. Automatica, 20(5), 583–594. doi:10.1016/0005-1098(84)90009-8.
  4. Ioannou, P.A., & Tsakalis, K.S. (1986). A robust direct adaptive controller. IEEE Transactions on Automatic Control, 31(11), 1033–1043. doi:10.1109/TAC.1986.1104168.
  5. Narendra, K.S., & Annaswamy, A.M. (1986). Robust adaptive control in the presence of bounded disturbances. IEEE Transactions on Automatic Control, 31(4), 306–315.
  6. 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.
  7. Kosut, R.L., & Friedlander, B. (1985). Robust adaptive control: Conditions for global stability. IEEE Transactions on Automatic Control, 30(7), 610–624.
  8. Ortega, R., Praly, L., & Landau, I.D. (1985). Robustness of discrete-time direct adaptive controllers. IEEE Transactions on Automatic Control, 30(12), 1179–1187.
  9. Riedle, B.D., & Kokotović, P.V. (1986). Integral manifolds of slow adaptation. IEEE Transactions on Automatic Control, 31(4), 316–324.
  10. Ioannou, P.A., & Sun, J. (1988). Theory and design of robust direct and indirect adaptive-control schemes. International Journal of Control, 47(3), 775–813.
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.