Chapter 8: Adaptive Control

Lesson 5: Lab: Adaptive Control vs Fixed-Model Control

In this lab-style lesson we implement and compare two controllers for a robot joint: a fixed-model computed-torque controller using nominal parameters, and an adaptive computed-torque controller that updates its parameter estimates online. We use a single-joint robot model for clarity, but the structure extends to general manipulator dynamics. You will derive the relevant equations, design simulation experiments, and implement the controllers in Python, C++, Java, MATLAB/Simulink, and Wolfram Mathematica.

1. Lab Objectives and Setup

We consider a single revolute joint of a robot arm described by the standard rigid-body dynamics form

\[ M(q)\ddot q + C(q,\dot q)\dot q + g(q) = \tau \]

For a 1-DOF link, we pick a simple parametric model

\[ \theta_1 \ddot q + \theta_2 \dot q + \theta_3 \sin(q) = \tau, \]

where the (unknown) physical parameters are grouped in \( \boldsymbol{\theta} = [\theta_1,\theta_2,\theta_3]^\top \). This keeps the lab analytically tractable while preserving the essential structure of robot dynamics.

The desired joint trajectory is a smooth reference

\[ q_d(t) = q_0 + A \sin(\omega t), \quad \dot q_d(t) = A\omega\cos(\omega t), \quad \ddot q_d(t) = -A\omega^2\sin(\omega t). \]

Define the tracking error and filtered error as

\[ e = q - q_d, \quad \dot e = \dot q - \dot q_d, \quad s = \dot e + \lambda e, \]

where \( \lambda > 0 \) is a design constant. The lab will:

  • Implement a fixed-model computed-torque controller using nominal parameters.
  • Implement an adaptive controller with online parameter update laws.
  • Run both controllers on the same plant under parameter mismatch.
  • Compare tracking error, control effort, and parameter estimates.
flowchart TD
  S["Start lab"] --> M["Choose 1-DOF model and true parameters"]
  M --> C1["Design fixed-model controller (nominal theta)"]
  M --> C2["Design adaptive controller (update law)"]
  C1 --> SIM1["Simulate fixed-model case"]
  C2 --> SIM2["Simulate adaptive case"]
  SIM1 --> MET["Compute metrics (RMSE, energy)"]
  SIM2 --> MET
  MET --> COMP["Compare performance and interpret"]
        

2. Dynamics in Regressor Form

From earlier lessons you know that manipulator dynamics can be written in regressor form. For the chosen 1-DOF model we can write

\[ \tau = Y(q,\dot q,\dot q_r,\ddot q_r)\,\boldsymbol{\theta}, \]

where the reference velocity and acceleration are

\[ \dot q_r = \dot q_d - \lambda e, \quad \ddot q_r = \ddot q_d - \lambda \dot e, \]

and the regressor is chosen as

\[ Y(q,\dot q,\dot q_r,\ddot q_r) = \begin{bmatrix} \ddot q_r & \dot q_r & \sin(q) \end{bmatrix}. \]

Using the filtered error \( s = \dot q - \dot q_r \), the closed-loop error dynamics (for both controllers) can be put in the form

\[ \theta_1 \dot s + \theta_2 s + k_s s = Y(q,\dot q,\dot q_r,\ddot q_r)\,\tilde{\boldsymbol{\theta}}, \]

where \( k_s > 0 \) is a scalar feedback gain and \( \tilde{\boldsymbol{\theta}} = \boldsymbol{\theta} - \hat{\boldsymbol{\theta}} \) is the parameter estimation error. The main difference between fixed-model and adaptive schemes is whether \( \hat{\boldsymbol{\theta}} \) is constant or updated.

3. Fixed-Model Computed-Torque Controller

The fixed-model computed-torque controller uses constant nominal parameters \( \hat{\boldsymbol{\theta}}_0 \) obtained from design specifications, identification, or datasheets:

\[ \tau_f = Y(q,\dot q,\dot q_r,\ddot q_r)\,\hat{\boldsymbol{\theta}}_0 - k_s s. \]

The ideal case is \( \hat{\boldsymbol{\theta}}_0 = \boldsymbol{\theta} \), where the plant is perfectly known; then the filtered error dynamics simplifies to

\[ \theta_1 \dot s + \theta_2 s + k_s s = 0, \]

giving exponential convergence of \( s \), and by construction of \( s \), exponential tracking of \( e \) as well.

Under parameter mismatch \( \tilde{\boldsymbol{\theta}}_0 = \boldsymbol{\theta} - \hat{\boldsymbol{\theta}}_0 \neq 0 \), the error dynamics becomes

\[ \theta_1 \dot s + \theta_2 s + k_s s = Y(q,\dot q,\dot q_r,\ddot q_r)\,\tilde{\boldsymbol{\theta}}_0, \]

so the right-hand side acts as a disturbance. The tracking error can be made small by choosing large \( k_s \), but not arbitrarily small without amplifying noise and unmodeled dynamics. This motivates adaptation.

4. Adaptive Computed-Torque Controller

The adaptive controller replaces the fixed parameters by online estimates \( \hat{\boldsymbol{\theta}}(t) \):

\[ \tau_a = Y(q,\dot q,\dot q_r,\ddot q_r)\,\hat{\boldsymbol{\theta}} - k_s s. \]

Using the same derivation as in previous lessons, the filtered error dynamics becomes

\[ \theta_1 \dot s + \theta_2 s + k_s s = Y(q,\dot q,\dot q_r,\ddot q_r)\,\tilde{\boldsymbol{\theta}}, \quad \tilde{\boldsymbol{\theta}} = \boldsymbol{\theta} - \hat{\boldsymbol{\theta}}. \]

The Slotine–Li–type gradient update law for the parameters is

\[ \dot{\hat{\boldsymbol{\theta}}} = -\Gamma Y(q,\dot q,\dot q_r,\ddot q_r)^\top s, \quad \Gamma = \Gamma^\top > 0. \]

Consider the Lyapunov function candidate

\[ V = \tfrac12 \theta_1 s^2 + \tfrac12 \tilde{\boldsymbol{\theta}}^\top \Gamma^{-1}\tilde{\boldsymbol{\theta}}. \]

Its time derivative along the closed-loop system is

\[ \dot V = \theta_1 s \dot s + \tilde{\boldsymbol{\theta}}^\top \Gamma^{-1} \dot{\tilde{\boldsymbol{\theta}}}. \]

Using the error dynamics and \( \dot{\tilde{\boldsymbol{\theta}}} = -\dot{\hat{\boldsymbol{\theta}}} \) gives

\[ \theta_1 \dot s = -(\theta_2 + k_s) s + Y\tilde{\boldsymbol{\theta}}, \quad \dot{\tilde{\boldsymbol{\theta}}} = \Gamma Y^\top s. \]

Substituting and simplifying,

\[ \dot V = s\left[-(\theta_2 + k_s) s + Y\tilde{\boldsymbol{\theta}}\right] + \tilde{\boldsymbol{\theta}}^\top \Gamma^{-1}\Gamma Y^\top s = -(\theta_2 + k_s)s^2 \le 0. \]

Thus all signals remain bounded and \( s \in L_2 \cap L_\infty \), and \( s \rightarrow 0 \) as \( t \rightarrow \infty \). If the regressor \( Y(q,\dot q,\dot q_r,\ddot q_r) \) is persistently exciting, then \( \hat{\boldsymbol{\theta}}(t) \) converges to \( \boldsymbol{\theta} \); otherwise, we still get asymptotic tracking but only partial parameter convergence.

flowchart TD
  R["Reference q_d"] --> ERR["Compute e, s"]
  Q["Plant joint"] --> ERR
  ERR --> CT_F["Fixed-model law tau_f"]
  ERR --> CT_A["Adaptive law tau_a"]
  CT_F --> PLANT["Joint dynamics"]
  CT_A --> PLANT
  PLANT --> Q
  CT_A --> UPD["Update theta_hat using Y^T s"]
        

5. Simulation Experiment Design

Choose concrete numerical values for the true parameters; for example:

\[ \theta_1 = 2.0, \quad \theta_2 = 0.4, \quad \theta_3 = 5.0. \]

The nominal model used in the fixed-model controller is intentionally wrong, e.g.

\[ \hat{\boldsymbol{\theta}}_0 = \begin{bmatrix} 1.5 \\ 0.2 \\ 4.0 \end{bmatrix}. \]

For the adaptive controller, initialize \( \hat{\boldsymbol{\theta}}(0) = \hat{\boldsymbol{\theta}}_0 \) and pick design gains

\[ \lambda = 5, \quad k_s = 10, \quad \Gamma = \operatorname{diag}(\gamma_1,\gamma_2,\gamma_3), \quad \gamma_i > 0. \]

We consider the following scenarios:

  • Scenario A: No model error (\( \hat{\boldsymbol{\theta}}_0 = \boldsymbol{\theta} \)).
  • Scenario B: Parameter mismatch only (as above).
  • Scenario C: Parameter mismatch plus a bounded disturbance \( d(t) \) added to \tau.

For each scenario, simulate over \( t \in [0,T] \) with sufficiently small step size and compute:

\[ \text{RMSE}_e = \sqrt{\frac{1}{T} \int_0^T e(t)^2 \,\mathrm{d}t}, \quad J_u = \frac{1}{T} \int_0^T \tau(t)^2 \,\mathrm{d}t, \quad \|\tilde{\boldsymbol{\theta}}(T)\|. \]

Compare these metrics between fixed-model and adaptive controllers and interpret the results in terms of robustness to parameter uncertainty and the effect of adaptation gains.

6. Python Lab Implementation

Below is a minimal Python script using numpy and scipy.integrate.solve_ivp to simulate the plant with both controllers. You can easily extend this to multi-DOF robots by vectorizing the equations and using matrix versions of \( Y \) and \( \Gamma \).


import numpy as np
from math import sin, cos
from scipy.integrate import solve_ivp

# True parameters (plant)
theta_true = np.array([2.0, 0.4, 5.0])  # [theta1, theta2, theta3]

# Nominal model (fixed-model controller)
theta_nom = np.array([1.5, 0.2, 4.0])

# Adaptive initial parameters
theta_hat0 = theta_nom.copy()

# Gains
lam = 5.0
k_s = 10.0
Gamma = np.diag([5.0, 5.0, 5.0])  # adaptation gains

A = 0.5
omega = 1.0

def desired_traj(t):
    qd = A * np.sin(omega * t)
    dqd = A * omega * np.cos(omega * t)
    ddqd = -A * omega**2 * np.sin(omega * t)
    return qd, dqd, ddqd

def regressor(q, dq, qd, dqd, ddqd):
    e = q - qd
    de = dq - dqd
    dq_r = dqd - lam * e
    ddq_r = ddqd - lam * de
    Y = np.array([ddq_r, dq_r, np.sin(q)])  # shape (3,)
    s = dq - dq_r
    return Y, s

def plant_accel(q, dq, tau):
    theta1, theta2, theta3 = theta_true
    # theta1 * ddq + theta2 * dq + theta3 * sin(q) = tau
    return (tau - theta2 * dq - theta3 * np.sin(q)) / theta1

def rhs_fixed(t, x):
    # x = [q, dq]
    q, dq = x
    qd, dqd, ddqd = desired_traj(t)
    Y, s = regressor(q, dq, qd, dqd, ddqd)
    tau = Y.dot(theta_nom) - k_s * s
    ddq = plant_accel(q, dq, tau)
    return [dq, ddq]

def rhs_adaptive(t, x):
    # x = [q, dq, th1_hat, th2_hat, th3_hat]
    q, dq = x[0], x[1]
    theta_hat = x[2:5]
    qd, dqd, ddqd = desired_traj(t)
    Y, s = regressor(q, dq, qd, dqd, ddqd)
    tau = Y.dot(theta_hat) - k_s * s
    ddq = plant_accel(q, dq, tau)
    # adaptation law: dtheta_hat = -Gamma * Y^T * s
    dtheta_hat = -Gamma.dot(Y * s)
    return [dq, ddq, dtheta_hat[0], dtheta_hat[1], dtheta_hat[2]]

# Initial conditions
q0 = 0.0
dq0 = 0.0
x0_fixed = [q0, dq0]
x0_adapt = [q0, dq0, theta_hat0[0], theta_hat0[1], theta_hat0[2]]

t_span = (0.0, 20.0)
t_eval = np.linspace(t_span[0], t_span[1], 4001)

sol_fixed = solve_ivp(rhs_fixed, t_span, x0_fixed, t_eval=t_eval)
sol_adapt = solve_ivp(rhs_adaptive, t_span, x0_adapt, t_eval=t_eval)

# Post-processing: compute tracking error and control signals
q_fixed = sol_fixed.y[0]
q_adapt = sol_adapt.y[0]
t = t_eval

def compute_metrics(q_traj):
    e = np.zeros_like(q_traj)
    for i, ti in enumerate(t):
        qd, _, _ = desired_traj(ti)
        e[i] = q_traj[i] - qd
    rmse = np.sqrt(np.trapz(e**2, t) / (t[-1] - t[0]))
    return rmse, e

rmse_fixed, e_fixed = compute_metrics(q_fixed)
rmse_adapt, e_adapt = compute_metrics(q_adapt)

print("RMSE (fixed-model):", rmse_fixed)
print("RMSE (adaptive):   ", rmse_adapt)

# You can also reconstruct tau(t) by re-running regressor with stored states.
      

Extend this code by storing \( Y(t) \), \( s(t) \), \( \hat{\boldsymbol{\theta}}(t) \), and reconstructing \( \tau(t) \) for energy and saturation analysis.

7. C++ Implementation Sketch

The next snippet illustrates a simple Euler-integrated simulation loop in C++. For real robot control, you would use a fixed sampling period, a real-time thread, and linear algebra libraries such as Eigen.


#include <iostream>
#include <cmath>

struct Params {
    double theta1{2.0};
    double theta2{0.4};
    double theta3{5.0};
};

struct AdaptiveState {
    double q{0.0};
    double dq{0.0};
    double th1_hat{1.5};
    double th2_hat{0.2};
    double th3_hat{4.0};
};

double lam = 5.0;
double k_s = 10.0;
double gamma1 = 5.0, gamma2 = 5.0, gamma3 = 5.0;
double A = 0.5, omega = 1.0;

void desired_traj(double t, double &qd, double &dqd, double &ddqd) {
    qd = A * std::sin(omega * t);
    dqd = A * omega * std::cos(omega * t);
    ddqd = -A * omega * omega * std::sin(omega * t);
}

void regressor(double q, double dq, double qd, double dqd, double ddqd,
               double &Y1, double &Y2, double &Y3, double &s) {
    double e = q - qd;
    double de = dq - dqd;
    double dq_r = dqd - lam * e;
    double ddq_r = ddqd - lam * de;
    Y1 = ddq_r;
    Y2 = dq_r;
    Y3 = std::sin(q);
    s = dq - dq_r;
}

double plant_accel(const Params &p, double q, double dq, double tau) {
    return (tau - p.theta2 * dq - p.theta3 * std::sin(q)) / p.theta1;
}

int main() {
    Params plant;
    AdaptiveState x;
    double dt = 0.001;
    double T = 20.0;

    for (double t = 0.0; t <= T; t += dt) {
        double qd, dqd, ddqd;
        desired_traj(t, qd, dqd, ddqd);

        double Y1, Y2, Y3, s;
        regressor(x.q, x.dq, qd, dqd, ddqd, Y1, Y2, Y3, s);

        // Adaptive control
        double tau = Y1 * x.th1_hat + Y2 * x.th2_hat + Y3 * x.th3_hat - k_s * s;

        // Plant dynamics
        double ddq = plant_accel(plant, x.q, x.dq, tau);

        // Euler integration
        x.q  += dt * x.dq;
        x.dq += dt * ddq;

        // Parameter update
        x.th1_hat += dt * (-gamma1 * Y1 * s);
        x.th2_hat += dt * (-gamma2 * Y2 * s);
        x.th3_hat += dt * (-gamma3 * Y3 * s);

        // Logging (e.g., print every few steps or store in arrays)
        if (static_cast<int>(t / dt) % 1000 == 0) {
            std::cout << t << " " << x.q << std::endl;
        }
    }
    return 0;
}
      

To implement the fixed-model controller, keep a second state that does not update th*_hat and uses the constant nominal parameters in place of the estimates.

8. Java Implementation Sketch

Java is less common in low-level robot control, but the same numerical integration ideas apply. The snippet below implements a simple adaptive controller loop.


public class AdaptiveJointLab {

    static class Params {
        double theta1 = 2.0;
        double theta2 = 0.4;
        double theta3 = 5.0;
    }

    static class State {
        double q = 0.0;
        double dq = 0.0;
        double th1_hat = 1.5;
        double th2_hat = 0.2;
        double th3_hat = 4.0;
    }

    static double lam = 5.0;
    static double k_s = 10.0;
    static double g1 = 5.0, g2 = 5.0, g3 = 5.0;
    static double A = 0.5, omega = 1.0;

    static double[] desired(double t) {
        double qd = A * Math.sin(omega * t);
        double dqd = A * omega * Math.cos(omega * t);
        double ddqd = -A * omega * omega * Math.sin(omega * t);
        return new double[]{qd, dqd, ddqd};
    }

    static double[] regressor(double q, double dq,
                              double qd, double dqd, double ddqd) {
        double e = q - qd;
        double de = dq - dqd;
        double dq_r = dqd - lam * e;
        double ddq_r = ddqd - lam * de;
        double Y1 = ddq_r;
        double Y2 = dq_r;
        double Y3 = Math.sin(q);
        double s = dq - dq_r;
        return new double[]{Y1, Y2, Y3, s};
    }

    static double accel(Params p, double q, double dq, double tau) {
        return (tau - p.theta2 * dq - p.theta3 * Math.sin(q)) / p.theta1;
    }

    public static void main(String[] args) {
        Params plant = new Params();
        State x = new State();

        double dt = 0.001;
        double T = 20.0;

        for (double t = 0.0; t <= T; t += dt) {
            double[] dref = desired(t);
            double qd = dref[0], dqd = dref[1], ddqd = dref[2];

            double[] reg = regressor(x.q, x.dq, qd, dqd, ddqd);
            double Y1 = reg[0], Y2 = reg[1], Y3 = reg[2], s = reg[3];

            double tau = Y1 * x.th1_hat + Y2 * x.th2_hat + Y3 * x.th3_hat - k_s * s;
            double ddq = accel(plant, x.q, x.dq, tau);

            x.q += dt * x.dq;
            x.dq += dt * ddq;

            x.th1_hat += dt * (-g1 * Y1 * s);
            x.th2_hat += dt * (-g2 * Y2 * s);
            x.th3_hat += dt * (-g3 * Y3 * s);

            if (((int) (t / dt)) % 1000 == 0) {
                System.out.println(t + " " + x.q);
            }
        }
    }
}
      

As in the C++ case, a fixed-model controller is obtained by freezing th*_hat to nominal values and not applying the adaptation update.

9. MATLAB / Simulink Implementation

In MATLAB, a compact implementation uses ode45. This can be embedded in Simulink using MATLAB Function blocks to represent the control law and plant.


function lab_adaptive_vs_fixed
  theta_true = [2.0; 0.4; 5.0];
  theta_nom  = [1.5; 0.2; 4.0];
  lam = 5.0; k_s = 10.0;
  Gamma = diag([5.0 5.0 5.0]);

  x0_fixed = [0; 0];                % [q; dq]
  x0_adapt = [0; 0; theta_nom];     % [q; dq; th1_hat; th2_hat; th3_hat]

  tspan = [0 20];

  [tF, xF] = ode45(@(t,x) rhs_fixed(t,x,theta_true,theta_nom,lam,k_s), tspan, x0_fixed);
  [tA, xA] = ode45(@(t,x) rhs_adapt(t,x,theta_true,Gamma,lam,k_s),      tspan, x0_adapt);

  qF = xF(:,1);
  qA = xA(:,1);

  [qdF, ~, ~] = desired_traj(tF);
  [qdA, ~, ~] = desired_traj(tA);

  eF = qF - qdF;
  eA = qA - qdA;

  figure; plot(tF, eF, tA, eA);
  xlabel('t'); ylabel('e');
  legend('fixed-model','adaptive');
end

function [qd, dqd, ddqd] = desired_traj(t)
  A = 0.5; omega = 1.0;
  qd   = A .* sin(omega .* t);
  dqd  = A .* omega .* cos(omega .* t);
  ddqd = -A .* omega.^2 .* sin(omega .* t);
end

function dx = rhs_fixed(t,x,theta_true,theta_nom,lam,k_s)
  q  = x(1); dq = x(2);
  [qd,dqd,ddqd] = desired_traj(t);
  e  = q - qd;
  de = dq - dqd;
  dq_r  = dqd - lam * e;
  ddq_r = ddqd - lam * de;
  s   = dq - dq_r;
  Y   = [ddq_r; dq_r; sin(q)];
  tau = Y' * theta_nom - k_s * s;
  dx  = [dq; plant_accel(theta_true,q,dq,tau)];
end

function dx = rhs_adapt(t,x,theta_true,Gamma,lam,k_s)
  q  = x(1); dq = x(2);
  th_hat = x(3:5);
  [qd,dqd,ddqd] = desired_traj(t);
  e  = q - qd;
  de = dq - dqd;
  dq_r  = dqd - lam * e;
  ddq_r = ddqd - lam * de;
  s   = dq - dq_r;
  Y   = [ddq_r; dq_r; sin(q)];
  tau = Y' * th_hat - k_s * s;
  ddq = plant_accel(theta_true,q,dq,tau);
  dth = -Gamma * (Y * s);
  dx  = [dq; ddq; dth];
end

function ddq = plant_accel(theta,q,dq,tau)
  theta1 = theta(1); theta2 = theta(2); theta3 = theta(3);
  ddq = (tau - theta2 * dq - theta3 * sin(q)) / theta1;
end
      

In Simulink, you can implement:

  • A subsystem for the plant integrating \( \ddot q \).
  • A subsystem for the controller computing \( \tau_f \) or \( \tau_a \) based on the measured \( q \) and \( \dot q \).
  • A MATLAB Function block implementing the adaptation law for \( \hat{\boldsymbol{\theta}} \).

This mirrors the architecture needed for real robot implementation with an inner torque loop and outer adaptive loop.

10. Wolfram Mathematica Implementation

Mathematica makes it easy to couple state and parameter dynamics in one NDSolve call. The following code simulates the adaptive controller (fixed-model requires only dropping the parameter update equations).


thetaTrue = {2.0, 0.4, 5.0};
lam = 5.0; ks = 10.0;
Gamma = DiagonalMatrix[{5.0, 5.0, 5.0}];

A = 0.5; omega = 1.0;
qd[t_]   := A*Sin[omega*t];
dqd[t_]  := A*omega*Cos[omega*t];
ddqd[t_] := -A*omega^2*Sin[omega*t];

Y[q_, dq_, t_, thHat_] := Module[
  {e, de, dqR, ddqR},
  e   = q - qd[t];
  de  = dq - dqd[t];
  dqR = dqd[t] - lam*e;
  ddqR = ddqd[t] - lam*de;
  {ddqR, dqR, Sin[q]}
];

plantAccel[q_, dq_, tau_] := Module[{th1, th2, th3},
  {th1, th2, th3} = thetaTrue;
  (tau - th2*dq - th3*Sin[q])/th1
];

eqns = {
  q'[t] == dq[t],
  dq'[t] == plantAccel[q[t], dq[t],
    Y[q[t], dq[t], t, thHat[t]].thHat[t] - ks*(dq[t] - (dqd[t] - lam*(q[t] - qd[t])))
  ],
  thHat'[t] == -Gamma.(Y[q[t], dq[t], t, thHat[t]]*(dq[t] - (dqd[t] - lam*(q[t] - qd[t])))),
  q[0] == 0.0, dq[0] == 0.0,
  thHat[0] == {1.5, 0.2, 4.0}
};

sol = NDSolve[eqns, {q, dq, thHat}, {t, 0, 20}][[1]];

Plot[{q[t] /. sol, qd[t]}, {t, 0, 20},
  PlotLegends -> {"q(t)", "q_d(t)"}
]
      

Additional plots of \( e(t) \) and \( \hat{\boldsymbol{\theta}}(t) \) provide a clear visualization of adaptation and tracking performance.

11. Problems and Solutions

Problem 1 (Steady-State Error with Fixed-Model Control): Consider the 1-DOF model \( \theta_1 \ddot q + \theta_2 \dot q + \theta_3 \sin(q) = \tau \) with fixed-model controller

\[ \tau_f = Y(q,\dot q,\dot q_r,\ddot q_r)\,\hat{\boldsymbol{\theta}}_0 - k_s s. \]

Assume a constant desired position \( q_d = q^\star \) and small angles so that \( \sin(q) \approx q \). Show that under constant parameter error \( \tilde{\boldsymbol{\theta}}_0 \) the steady-state tracking error \( e_{\text{ss}} \) is generally nonzero.

Solution: For a constant reference, \( \dot q_d = \ddot q_d = 0 \). At steady state, all derivatives vanish: \( \dot q = \ddot q = 0 \), so \( e_{\text{ss}} = q_{\text{ss}} - q^\star \) is constant and \( s = \dot e + \lambda e = \lambda e_{\text{ss}} \). The regressor becomes

\[ Y_{\text{ss}} \approx \begin{bmatrix} 0 & 0 & q_{\text{ss}} \end{bmatrix}. \]

The closed-loop equation at steady state is

\[ 0 + 0 + k_s s = Y_{\text{ss}}\tilde{\boldsymbol{\theta}}_0 \quad \Rightarrow \quad k_s \lambda e_{\text{ss}} \approx q_{\text{ss}} \tilde \theta_{0,3}. \]

Since \( q_{\text{ss}} = q^\star + e_{\text{ss}} \), we get approximately

\[ k_s \lambda e_{\text{ss}} \approx (q^\star + e_{\text{ss}})\tilde \theta_{0,3}. \]

For generic \( q^\star \) and \( \tilde \theta_{0,3} \neq 0 \), the only solution of this algebraic equation is a nonzero \( e_{\text{ss}} \) (unless \( q^\star = 0 \)). Hence the fixed-model controller generally exhibits steady-state error under parameter mismatch.

Problem 2 (Lyapunov Analysis of Adaptive Law): For the adaptive controller with update law

\[ \dot{\hat{\boldsymbol{\theta}}} = -\Gamma Y^\top s, \]

and Lyapunov function

\[ V = \tfrac12 \theta_1 s^2 + \tfrac12 \tilde{\boldsymbol{\theta}}^\top \Gamma^{-1}\tilde{\boldsymbol{\theta}}, \]

derive \( \dot V \) and show that \( \dot V = -(\theta_2 + k_s)s^2 \le 0 \).

Solution: We have

\[ \dot V = \theta_1 s \dot s + \tilde{\boldsymbol{\theta}}^\top \Gamma^{-1}\dot{\tilde{\boldsymbol{\theta}}}. \]

From the closed-loop dynamics, \( \theta_1 \dot s = -(\theta_2 + k_s) s + Y\tilde{\boldsymbol{\theta}} \), and from the update law \( \dot{\tilde{\boldsymbol{\theta}}} = \Gamma Y^\top s \). Substituting:

\[ \dot V = s\left[-(\theta_2 + k_s) s + Y\tilde{\boldsymbol{\theta}}\right] + \tilde{\boldsymbol{\theta}}^\top \Gamma^{-1} \Gamma Y^\top s = -(\theta_2 + k_s)s^2. \]

Thus \( \dot V \le 0 \), guaranteeing boundedness of \( s \) and \( \tilde{\boldsymbol{\theta}} \) and convergence of \( s(t) \) to zero.

Problem 3 (Effect of Excitation on Parameter Convergence): Explain qualitatively why, in Scenario A (no model error) with a trivial constant reference \( q_d(t) = 0 \), the adaptive controller may not recover the true parameters \( \boldsymbol{\theta} \) even though tracking is perfect.

Solution: When \( q_d(t) = 0 \) and the controller achieves \( q(t) = 0 \), the regressor satisfies \( Y(t) \equiv 0 \), because all derivative terms and \( \sin(q) \) vanish. Then the update law reduces to

\[ \dot{\hat{\boldsymbol{\theta}}}(t) = -\Gamma Y(t)^\top s(t) = 0, \]

so \( \hat{\boldsymbol{\theta}}(t) \) remains at its initial value. The condition of persistent excitation requires that \( Y(t) \) does not collapse to zero in the limit; a constant trajectory fails this requirement. Therefore we obtain excellent tracking but no parameter convergence.

Problem 4 (RMS Error Comparison): Suppose that numerical integration shows \( \text{RMSE}_e^{\text{fixed}} = 0.12 \) and \( \text{RMSE}_e^{\text{adapt}} = 0.03 \) under the same Scenario B conditions. If the control energy metrics satisfy \( J_u^{\text{fixed}} \approx J_u^{\text{adapt}} \), interpret the advantage of adaptive control in terms of robustness and performance.

Solution: The factor-of-four reduction in RMS tracking error indicates that the adaptive controller has effectively compensated for parameter mismatch. Because the control energy is similar, the improvement is not simply due to aggressive actuation but to better alignment between the internal model and the true plant. This directly reflects the robustness advantage of adaptation over a fixed-model design in the presence of parametric uncertainty.

Problem 5 (Discretization Effect): In the C++ implementation, the Euler step size \( \Delta t \) was chosen as \( 10^{-3} \). Discuss the qualitative effect of taking \( \Delta t \) ten times larger on: (i) numerical stability of the error dynamics, and (ii) accuracy of the parameter updates.

Solution: A larger step size reduces integration accuracy and may destabilize the discrete-time approximation of an otherwise stable continuous-time system. For the adaptive law, the update \( \hat{\boldsymbol{\theta}}_{k+1} = \hat{\boldsymbol{\theta}}_k - \Delta t\,\Gamma Y_k^\top s_k \) becomes coarser; if \( \Delta t \) is too large, the parameters can oscillate or diverge. Thus there is a trade-off between computational cost and fidelity, motivating careful selection of sampling time in real-time implementations.

12. Summary

This lab contrasted adaptive computed-torque control with a fixed-model computed-torque controller on a simple but representative robot joint model. By explicitly constructing the regressor form, implementing the gradient-based parameter update law, and simulating both controllers, you observed:

  • How parameter mismatch degrades performance of fixed-model controllers.
  • How adaptive control restores tracking by shaping both the error dynamics and the parameter estimates.
  • The role of excitation and discretization in the practical behavior of adaptive laws.

The same structure generalizes to multi-DOF manipulators by replacing scalars with matrices and vectors. In subsequent chapters, these ideas interact with optimal and predictive control, as well as safety constraints, to build high-performance, robust robot controllers.

13. References

  1. Slotine, J.-J. E., & Li, W. (1987). On the adaptive control of robot manipulators. International Journal of Robotics Research, 6(3), 49–59.
  2. Craig, J. J., Hsu, P., & Sastry, S. S. (1986). Adaptive control of mechanical manipulators. International Journal of Robotics Research, 6(2), 16–28.
  3. Ortega, R., Loria, A., Nicklasson, P. J., & Sira-Ramirez, H. (1995). On passivity-based adaptive control of robot manipulators. International Journal of Adaptive Control and Signal Processing, 9(6), 543–564.
  4. Ioannou, P. A., & Kokotovic, P. V. (1984). Instability analysis and improvement of robustness of adaptive control. Automatica, 20(5), 583–594.
  5. Sastry, S. S., & Bodson, M. (1989). Adaptive control: Stability, convergence, and robustness. IEEE Control Systems Magazine, 9(2), 17–25.
  6. Narendra, K. S., & Annaswamy, A. M. (1987). Persistent excitation in adaptive systems. International Journal of Control, 45(1), 127–160.
  7. Kelly, R. (1993). A simple set-point robot controller by using only position measurements. IEEE Transactions on Automatic Control, 38(2), 298–302.
  8. Spong, M. W. (1987). On the robust control of robot manipulators. IEEE Transactions on Automatic Control, 32(2), 108–114.