Use when you must design and simulate an l1-adaptive-control law for a first-order plant with an unknown coefficient: run the state-predictor from the design model, drive the projection-based-adaptation-law with the prediction error, pass the adaptive signal through the low-pass filter omega_c/(s + omega_c) and form the control as the feedforward minus the filtered estimate. Produces the tracking-error and prediction-error time histories, the sigma_hat and filtered-signal histories, the proje...
Installs into .claude/skills of the current project.
Are you the author of L1 Adaptive Control?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/ashfordeou-l1-adaptive-control)
---
name: l1-adaptive-control
description: "Use when you must design and simulate an l1-adaptive-control law for a first-order plant with an unknown coefficient: run the state-predictor from the design model, drive the projection-based-adaptation-law with the prediction error, pass the adaptive signal through the low-pass filter omega_c/(s + omega_c) and form the control as the feedforward minus the filtered estimate. Produces the tracking-error and prediction-error time histories, the sigma_hat and filtered-signal histories, the projection engagement, the convergence verdict and the certified transient-bound check that gate an L1 adaptive control assessment. Trigger: l1-adaptive-control, state-predictor, low-pass-filtered-adaptation, projection-based-adaptation-law, guaranteed-transient-response."
license: Apache-2.0
compliance: STANDARDS-REF
standards:
- id: arp4754a
reference-only: true
gated: false
domain: gnc-autonomy
pack: control
compatibility: "agentskills.io SKILL.md; any SKILL.md host (Claude Code, Hermes, OpenClaw)"
metadata:
domain: gnc-autonomy
subdomain: control
tags: [l1-adaptive-control, state-predictor, low-pass-filtered-adaptation, projection-based-adaptation-law, guaranteed-transient-response]
version: 0.1.0
author: AeroSkills
---
# L1 Adaptive Control (gnc-autonomy/control/l1-adaptive-control)
Use when the task is designing and simulating an L1 adaptive control
law, after Cao and Hovakimyan (L1 Adaptive Control Theory, SIAM 2010,
scalar chapter-2 sigma-only specialization), for a first-order plant
whose coefficient a_p is unknown to the controller and that is hit by
an unknown constant matched disturbance. The controller closes the loop
with a state predictor run from the design model, a projection-based
adaptation law on the prediction error that estimates the lumped
matched uncertainty inside a bounded interval, a low-pass filter
C(s) = omega_c/(s + omega_c) on the adaptive signal, and the control u
= k_g*r - nu against a reference model that sets the command response.
This leaf implements the closed loop in pure Python, stdlib only,
deterministic, discrete-time Euler integration, and reports the
tracking-error and prediction-error histories, the sigma_hat and
filtered-signal histories, the projection engagement, the convergence
verdict and the certified transient-bound check. It pairs with
gnc-autonomy/control/adaptive-control (the architecture that replaces
the state predictor with a gradient update of two gains on the tracking
error) and with gnc-autonomy/control/control-allocation for spreading
the scalar command across redundant effectors; the fixed-gain design
leaves of the control pack cover plants whose coefficient is known.
Scope: single input, deterministic, no noise, constant matched
disturbance, sign of the control effectiveness known positive, a_p
unknown inside a bounded prior.
## Domain quick reference
- Reference model: xm_dot = a_m*xm + b_m*r with a_m < 0; the model
settles at b_m*r/(-a_m) = 1 for the unit command. Euler step: xm_next
= xm + dt*(a_m*xm + b_m*r).
- Truth plant (simulator only): x_dot = a_p*x + b*u + b*d, a_p = -0.5
unknown to the controller, d = 1.0 the unknown constant matched
disturbance. In the design frame the plant reads x_dot = a_m*x +
b*(u + sigma_true) with the lumped uncertainty sigma_true(x) = (a_p -
a_m)*x/b + d = 0.25*x + 1.0.
- State predictor: xh_dot = a_m*xh + b*(u + sigma_hat), run from the
same initial state as the plant. Prediction error: xt = xh - x.
- Projection-based adaptation law on the prediction error: sigma_hat_dot
= gamma*Proj(sigma_hat, -xt), projection onto [-sigma_b, sigma_b].
Discrete clamped Euler step: raw = sigma_hat - dt*gamma*xt, then
clamp to [-sigma_b, sigma_b], so |sigma_hat| <= sigma_b every step.
- Low-pass filter on the adaptive signal, C(s) = omega_c/(s + omega_c):
nu_dot = omega_c*(sigma_hat - nu); Euler step nu_next = nu +
dt*omega_c*(sigma_hat - nu).
- Control law: u = k_g*r - nu with k_g = -a_m/b = 0.5, the unfiltered
feedforward for unit DC tracking minus the filtered cancellation
estimate. The equilibrium control is negative: u* = -0.75 holds the
plant at x = 1 against the disturbance.
- Histories are seeded at index 0 with the initial values and have
length steps + 1: history[k] is the value at time k*dt (after k
advances). Per step the loop samples the prediction error, updates
sigma_hat, advances the filter on the updated estimate, forms the
control, advances plant, predictor and reference model, then appends.
- L1 admissibility of the scenario: ||C(s)*(s - a_m)^-1*b||_L1 =
b/|a_m| = 2.0 for every first-order C(s) with unit DC gain, so the
scalar L1-norm condition with Lipschitz constant L = (a_p - a_m)/b
reduces to (a_p - a_m)/|a_m| < 1: 0.5 at the worked plant, 0.75 at
the prior corner a_p = -0.25. An open-loop-unstable mismatch
violates the condition and the estimator resonator destabilizes the
loop; the worked scenario keeps a_p < 0.
- Reference-loop stability identity at perfect adaptation: the
linearized (x, nu) pair has trace a_p - omega_c = -10.5 and
determinant -a_m*omega_c = 10.0 (stable for every omega_c > 0 when
a_p < 0). The discrete prediction-error pair contracts at |lambda| =
sqrt(1 + a_m*dt) = 0.9949874 per step for every adaptation rate.
- ARP4754A frames the development assurance context for the airborne
function that hosts the adaptive loop; the relations above are
standard L1 adaptive control methodology, summary-only.
## Workflow
1. Fix the loop data: design pole a_m (negative), control effectiveness
b, reference gain b_m, command r, initial state x0, disturbance d,
step dt, adaptation rate gamma, filter bandwidth omega_c, projection
bound sigma_b, prior bounds a_lo..a_hi on the unknown plant
coefficient, and the run length in steps.
2. Sanity-check the pieces with single steps before the full run:
projection boundary behavior and adaptive_update clamping of the
projection-based-adaptation-law, filter_step (the low-pass filter
C(s) = omega_c/(s + omega_c)), plant_step, predictor_step,
reference_step and control_output (u = k_g*r - nu).
3. Form the design-frame lumped uncertainty with sigma_true(x)
(simulator bookkeeping only, never an input to the controller) to
see what the estimator must cancel, and confirm L1 admissibility:
the product (a_hi - a_m)/|a_m| below 1 and the reference-loop
stability signs trace = a_p - omega_c < 0, determinant
-a_m*omega_c > 0.
4. Run the closed loop with simulate(...): read the x, xh, xm state
histories, the sigma_hat and nu (filtered-signal) histories, the u
history, pred_err = xh - x and track_err = x - xm, proj_active,
max_abs_track, max_abs_pred, max_abs_sigma, tail_abs_pred and
tail_sigma_drift.
5. Check the transient-bound verdict of the tracking error: max_abs_track
must sit below the certified scenario bound E_BOUND = 0.75 (the
guaranteed-transient-response check of the run).
6. Read the convergence verdict with convergence_report(res):
converged True when the tail |xt| stays below 1e-4, the tail
sigma_hat drift below 1e-6, and the final sigma_hat within 0.05 of
sigma_ideal(x_final), the ideal cancellation value at the settled
state (assessment target, never an input to the controller).
7. Cross-check the marches against the closed forms:
reference_closed_form against the xm history, filter_closed_form
against a constant-input filter march, no_adaptation_closed_form
against a gamma-0 run (the plant goes to the wrong equilibrium x =
6 without adaptation).
8. Confirm the deterministic checks: rerun for bitwise-identical
histories, reject every non-physical input with ValueError, and run
the contract test scripts/test_l1_adaptive_control.py.
## Worked example
Scenario: a_m = -1.0, b = 2.0, b_m = 1.0, r = 1.0, x0 = 0.0, dt = 0.01
s. The plant coefficient a_p = -0.5 is unknown to the controller (prior
[-1.0, -0.25]: the plant is less damped than the design model assumes)
and an unknown constant matched disturbance d = 1.0 acts on it, so the
estimator must cancel sigma_true(x) = 0.25*x + 1.0. Adaptation rate
gamma = 10.0, filter omega_c = 10.0 rad/s (C(s) = 10/(s + 10)),
projection bound sigma_b = 2.0, k_g = 0.5, run for 6000 steps (60 s).
The plant and the reference model both settle at x = 1, sigma_ideal =
1.25, u* = -0.75. Real module outputs (stdlib math, exit 0):
- Transient: the plant races ahead of the reference model while
sigma_hat ramps; the tracking error peaks at max |e| = 0.5607716796
at k = 43 (t = 0.43 s, x = 0.9116690512 vs xm = 0.3508973716), the
control swings to its most negative u = -1.4394547881 at k = 82 to
arrest the overshoot, the plant overshoots to x = 1.1374400036 at
k = 200 (xm = 0.8660203251, e = +0.2714196785), and the prediction
error peaks at max |xt| = 0.4359531903 just after t = 0.36 s. The
projection clamps sigma_hat to the sigma_b = 2.0 bound on exactly 7
consecutive steps, k = 67..73, and max |sigma_hat| over the run is
2.0000000000 (the bound value).
- Settling: x stays within 1e-3 of 1 permanently from k = 2211 and the
prediction error stays below 1e-4 permanently from k = 3064. Samples
(k, x, xm, e): k = 100 (0.4904796820, 0.6339676587, -1.435e-01),
k = 300 (0.9913721769, 0.9509591059, +4.041e-02), k = 1000
(0.9818672877, 0.9999568288, -1.809e-02), k = 3000 (0.9998807895,
1.0000000000, -1.192e-04), k = 5000 (0.9999993843, 1.0000000000,
-6.157e-07).
- Final state: x_final = 0.9999999587, xm_final = 1.0000000000 (the
reference settles at 1), e_final = -4.13e-08, xt_final = 4.10e-08,
sigma_hat_final = 1.2499999832 against sigma_true(x_final) =
1.2499999897, nu_final = 1.2500000187, u_final = -0.7500000187 with
the equilibrium residual a_p*x + b*u + b*d = -1.68e-08: the control
holds the plant at x = 1 against the disturbance, exactly like u* =
-0.75.
- Verdict: converged True on all three criteria: tail (last 1000
steps, t = 50 to 60 s) max |xt| = 5.604e-07 below the 1e-4
threshold, tail sigma_hat drift = 5.604e-08 below 1e-6, and sigma_dev
= |sigma_hat_final - sigma_ideal| = 6.47e-09 below 0.05. Transient
verdict: max |e| = 0.5607716796 below the certified bound E_BOUND =
0.75 (margin 1.34x).
- Guard runs: with gamma = 0 the plant ignores the disturbance estimate
and converges to the wrong equilibrium x = 6.0 (e(6000) = 5.0,
march matches 6*(1 - 0.995^k) to 1e-9); with the slow filter
omega_c = 0.5 rad/s the loop still converges but the transient bound
degrades to max |e| = 1.555849 (2.77x the omega_c = 10 run).
- Adaptation-rate study: max |track error| = 0.5607716796 at
gamma = 10, 0.3349860564 at 40, 0.2049952822 at 160, 0.1595401164 at
1000 (monotone decrease: the faster the estimator, the tighter the
transient bound), with tail |xt| = 5.60e-07, 6.33e-09, 3.33e-11 and
1.00e-12 respectively (every rate converges). Determinism: a second
identical run is bitwise identical in x, sigma_hat and u.
## Verification
- Confirm the projection unit behavior: interior directions pass,
inward directions at a bound pass, outward directions at a bound
return 0.0; adaptive_update(1.9, -0.2, 10.0, 0.1) returns exactly
SIGMA_B = 2.0 and adaptive_update(0.0, -0.2, 10.0, 0.1) returns 0.2.
- Confirm the worked scenario: x_final = 0.9999999587, sigma_hat_final
= 1.2499999832, nu_final = 1.2500000187, u_final = -0.7500000187,
final errors below 1e-3, equilibrium residual below 1e-6, proj_active
= 7 with the clamp steps exactly k = 67..73, and max_abs_track =
0.5607716796 below E_BOUND = 0.75.
- Confirm convergence_report returns converged True with the tail
criteria from the verdict above.
- Confirm the closed forms: the xm history matches 1 - 0.99^k within
1e-12, a constant-input filter march matches 1 - 0.9^k within 1e-12,
and a gamma-0 plant march matches 6*(1 - 0.995^k) within 1e-9.
- Confirm the reference-loop identity trace = -10.5, det = 10.0 and the
L1-norm integral of the filtered impulse response equals b/|a_m| =
2.0 within 1e-4, with both admissibility products below 1.
- Confirm every non-physical input raises ValueError: a_m >= 0, b == 0,
dt <= 0, gamma < 0, omega_c <= 0, sigma_b <= 0, steps < 2, non-finite
values across the module.
- Run the contract test offline: python3
scripts/test_l1_adaptive_control.py (34 tests, deterministic).
## Pitfalls
- Reading the transient verdict from the wrong metric: the certified
bound E_BOUND = 0.75 applies to max |e| over the whole run
(0.5607716796), not to the tail; the tail metrics feed the separate
convergence verdict, and the two must not be conflated.
- Running too few steps: x settles within 1e-3 of 1 only from about
k = 2211 and the prediction error clears 1e-4 only from about
k = 3065, so a short run reads a premature verdict.
- Zero adaptation: with gamma = 0 the plant converges to the wrong
equilibrium x = 6.0 with tracking error 5.0; the projection-based
adaptation law is what cancels the lumped disturbance.
- Slow filter bandwidth: omega_c sets the achievable transient bound;
at omega_c = 0.5 rad/s max |e| = 1.555849 is 2.77x the omega_c = 10
value, so a very slow filter defeats the transient guarantee.
- Open-loop-unstable mismatch: an a_p >= 0 plant violates the scalar
L1-norm admissibility condition (a_p - a_m)/|a_m| < 1 and the
estimator resonator destabilizes the loop; keep the worked scenario
inside the stable prior.
- sigma_ideal and sigma_true are assessment/simulator values only:
feeding the true lumped uncertainty back into the controller defeats
the adaptive purpose (the controller sees only the plant state and
the prediction error).
- Updating order: the filter must advance on the freshly updated
sigma_hat before the control is formed; the documented Euler ordering
is what drives the discrete prediction-error pair contraction at
|lambda| = 0.9949874 per step.
## Related leaves
- gnc-autonomy/control/adaptive-control: the architecture that replaces
the state predictor and the projection law with a gradient update of
two adaptive gains driven by the tracking error of a first-order
plant, the closest sibling in the pack.
- gnc-autonomy/control/control-allocation: distributing the scalar
command across redundant effectors once the loop closes.
- gnc-autonomy/control/state-space-analysis: eigenvalue and
controllability analysis of plants with known parameters, the
fixed-gain counterpart to online adaptation.
- gnc-autonomy/control/observer-design: reconstructing unmeasured
states when the adaptive loop cannot measure its state directly.
- gnc-autonomy/control/python-control-design: scripted fixed-gain loop
design for known plants, the alternative when no online estimation
is needed.
- gnc-autonomy/control/adaptive-backstepping: recursive tuning-function
backstepping for a second-order strict-feedback plant carrying a
structural drift-coefficient estimate theta_hat; it runs no state
predictor, no projection and no low-pass filter.
## Behavior contract (gate 3)
Run the deterministic contract test (stdlib unittest, offline):
python3 scripts/test_l1_adaptive_control.py
The test covers the worked example contract (finals x_final,
sigma_hat_final, nu_final, u_final and sigma_ideal within 1e-6
relative, final errors below 1e-3, equilibrium residual below 1e-6,
transient metrics max_abs_track = 0.5607716796 below E_BOUND = 0.75
and max_abs_pred = 0.4359531903, proj_active = 7 with clamp steps
k = 67..73, history seeding and lengths), the convergence verdict with
its tail criteria, the settling milestones from k = 2211 and k = 3065,
the reference-model, filter and no-adaptation closed-form identities,
the slow-filter tradeoff at omega_c = 0.5, the adaptation-rate study
over (10, 40, 160, 1000), the L1-norm admissibility integral and the
reference-loop stability identity, the projection unit behavior with
the exact bound anchors, bitwise determinism, module purity (no RNG,
stdlib only) and ValueError rejection of non-physical and non-finite
inputs across the module. All numeric asserts are tolerance based and
pass under both /usr/bin/python3 and the pyenv 3.13 hook interpreter.
## Compliance
- Standards referenced, not reproduced: ARP4754A frames development
assurance for the aircraft functions that host adaptive loops; the L1
equations above are standard adaptive control methodology after Cao
and Hovakimyan (SIAM 2010), summary-only per standards-map.yaml,
reference-only: true.
- Revision note: ARP4754B (2023) supersedes ARP4754A; this skill keys to
ARP4754A as the certification-baseline revision (FAA AC 20-174 cites A);
see standards-map.yaml arp4754a.revision_decision.
- compliance: STANDARDS-REF, gated: false.