Chapter 26: Applications in Aerospace and Automotive Systems
Lesson 5: Practical Certification/Validation Concerns (High-Level Discussion)
This lesson explains why a stability proof, a successful simulation, or a large Monte Carlo campaign is necessary but not sufficient evidence for deploying an adaptive controller in a safety-critical aircraft or road vehicle. We develop a certification-oriented assurance framework based on bounded adaptation, explicit safety requirements, traceability, deterministic implementation, scenario-based verification, statistical validation, independent monitoring, and controlled fallback behavior.
1. From Control Performance to Assurance Evidence
In an academic adaptive-control design, the principal claim is often that the tracking error is stable or converges under stated assumptions. In certification-oriented development, the claim is broader: the implemented function must satisfy allocated safety and performance requirements throughout a declared operating domain, under credible faults, disturbances, numerical effects, timing constraints, sensor errors, actuator limits, mode transitions, maintenance actions, and software changes.
Let the closed-loop implementation be represented by \( \mathcal{S}=(P,C_A,C_B,M,E) \), where \(P\) is the plant, \(C_A\) the adaptive controller, \(C_B\) a verified baseline or fallback controller, \(M\) a runtime monitor, and \(E\) the execution platform. Certification-oriented validation asks whether a structured body of evidence supports a claim of the form
\[ \forall \omega \in \Omega_{\mathrm{declared}},\qquad \mathcal{S}(\omega) \models \left(\mathcal{R}_{\mathrm{safety}}\land \mathcal{R}_{\mathrm{performance}}\land \mathcal{R}_{\mathrm{timing}}\right), \]
where \(\omega\) collects uncertain parameters, initial conditions, disturbances, sensor characteristics, failures, operating modes, and software/hardware configurations. The declared domain must be explicit. Evidence outside an undefined or changing domain cannot support a precise assurance claim.
flowchart TD
A["Hazards and operational intent"] --> B["System safety requirements"]
B --> C["Adaptive function requirements and bounded authority"]
C --> D["Architecture: adaptive controller + monitor + fallback"]
D --> E["Model and algorithm verification"]
E --> F["Software implementation verification"]
F --> G["SIL / PIL / HIL / vehicle tests"]
G --> H["Coverage, statistical evidence, and assurance case"]
H --> I["Configuration-controlled release decision"]
I --> J["Operational monitoring and controlled change"]
The central lesson is that certification is not a single test. It is a lifecycle argument connecting requirements, architecture, analysis, implementation, verification results, configuration records, and operational limitations.
2. Why Adaptive Controllers Create Additional Assurance Questions
A fixed controller has a static parameter set after release. An adaptive controller has an internal state \(\hat{\theta}(t)\) or gain matrix that changes during operation. Therefore, the effective closed-loop dynamics are time varying even when the software binary is unchanged. The assurance problem must address at least four sources of behavioral variability:
- Dependence on the trajectory of measured data and excitation.
- Dependence on initialization, resets, mode transitions, and retained parameters.
- Interaction with saturation, delays, quantization, scheduling, and numerical integration.
- Possible drift or inappropriate learning under noise, unmodeled dynamics, or failed sensors.
Suppose the implemented controller is \(u(t)=u_b(x,t)+\hat{\theta}^{T}(t)\phi(x,t)\). A conventional test at a single parameter value verifies only one member of a family of controllers. A more useful assurance statement quantifies over a certified parameter set \(\Theta_c\):
\[ \hat{\theta}(0)\in\Theta_c, \quad \dot{\hat{\theta}}=f_a(\hat{\theta},x,r), \quad \hat{\theta}(t)\in\Theta_c\ \forall t\geq 0. \]
The last condition is not merely a mathematical convenience. It converts an unbounded set of possible online controllers into a bounded, reviewable authority envelope.
3. Requirement Decomposition and Traceability
A useful adaptive-control requirement must be observable, testable, and linked to a system hazard or operational objective. Vague statements such as “the controller shall adapt safely” are not verifiable. A requirement set should separate functional, performance, envelope, adaptation, transition, timing, and evidence requirements.
Example requirement classes:
- Tracking: after a specified settling interval, \(|e(t)|\leq e_{\max}\).
- Hard envelope: \(h_i(x(t),u(t))\leq 0\) for all safety-critical constraints.
- Adaptive authority: \(\hat{\theta}(t)\in\Theta_c\) and \(\|\dot{\hat{\theta}}(t)\|\leq \rho_{\max}\).
- Engagement: adaptation is enabled only when validity predicates are true.
- Transition: switching to fallback does not create prohibited transients.
- Timing: worst-case execution time and data age remain below allocated limits.
- Detectability: monitor faults and invalid adaptation conditions are annunciated and recorded.
Let \(R_i\) denote a requirement, \(D_j\) a design element, \(T_k\) a test or analysis, and \(A_l\) an artifact. A traceability relation is complete when every high-level requirement has downstream evidence and every implemented function has an upstream justification:
\[ \forall R_i\ \exists D_j,T_k,A_l: R_i\mapsto D_j\mapsto T_k\mapsto A_l, \qquad \forall D_j\ \exists R_i: R_i\mapsto D_j. \]
Bidirectional traceability prevents two common failures: unverified requirements and unintended functionality.
4. Bounded Adaptation and a Lyapunov Assurance Argument
Consider an error model
\[ \dot e=-\lambda e+\tilde{\theta}^{T}\phi+d, \qquad \tilde{\theta}=\theta-\hat{\theta}, \qquad \lambda>0, \]
with bounded disturbance \(|d(t)|\leq d_{\max}\). Use a projected, leakage-modified adaptive law
\[ \dot{\hat{\theta}}= \operatorname{Proj}_{\Theta_c} \left(\Gamma\phi e-\sigma\Gamma(\hat{\theta}-\theta_0)\right), \qquad \Gamma=\Gamma^{T}>0, \quad \sigma>0. \]
For the Lyapunov candidate
\[ V=\frac{1}{2}e^2+\frac{1}{2}\tilde{\theta}^{T}\Gamma^{-1}\tilde{\theta}, \]
the projection inequality ensures that replacing the unconstrained update by its projection does not increase the parameter-error contribution to \(\dot V\). The cross term \(e\tilde{\theta}^{T}\phi\) cancels, giving the bound
\[ \dot V\leq -\lambda e^2+ed -\sigma\tilde{\theta}^{T}(\hat{\theta}-\theta_0). \]
Write \(\hat{\theta}-\theta_0=(\theta-\theta_0)-\tilde{\theta}\) and apply Young's inequality to the disturbance and leakage cross terms. For positive constants \(c_1,c_2,c_3\), one obtains
\[ \dot V\leq -c_1 e^2-c_2\|\tilde{\theta}\|^2+c_3, \]
which establishes uniform ultimate boundedness. This proof supports only the assumptions explicitly modeled: valid regressor signals, bounded disturbance, correct implementation, no unacceptable timing violation, and a correctly enforced projection set. Certification evidence must therefore verify those assumptions and the implementation of the law; it cannot cite the proof while leaving the assumptions untested.
5. Safety Envelopes, Runtime Monitoring, and Fallback
Define a safe set
\[ \mathcal{X}_{\mathrm{safe}}= \left\{z:\ h_i(z)\leq 0,\ i=1,\ldots,m\right\}, \qquad z=[x^{T},u^{T},\hat{\theta}^{T}]^{T}. \]
A runtime assurance architecture limits the adaptive controller's authority. The monitor evaluates state margins, actuator margins, parameter bounds, signal validity, excitation, timing, and numerical health. When a warning predicate becomes true, control transfers to a baseline controller whose behavior is more completely verified over the recovery domain.
\[ u(t)= \begin{cases} u_A(t), & q_M(t)=0,\\ u_B(t), & q_M(t)=1, \end{cases} \qquad q_M(t)=\bigvee_{i=1}^{m} \left(h_i(z(t))\geq-\delta_i\right). \]
The margins \(\delta_i>0\) must account for monitor latency, actuator dynamics, computation delay, and worst-case state growth during transition. If \(L_M\) is the total detection-and-switch latency and \(\|\dot z\|\leq \nu_{\max}\), a basic margin condition is
\[ \delta_i\geq \sup_{z\in\mathcal{X}_{\mathrm{warn}}} \|\nabla h_i(z)\|\,\nu_{\max}L_M+\delta_{i,\mathrm{model}}. \]
Switching logic itself requires verification: no chatter, bounded transient, deterministic priority, valid state initialization in the fallback controller, and an explicit policy for re-engagement or permanent inhibition.
flowchart TD
S["Sensors and \nvalidity checks"] --> M["Safety monitor"]
S --> A["Adaptive controller"]
M --> G["Authority selector"]
A --> G
B["Verified baseline \ncontroller"] --> G
G --> ACT["Actuator command limits"]
ACT --> P["Vehicle / plant"]
P --> S
M --> L["Event log and \nevidence recorder"]
M -->|"warning or invalid assumption"| G
6. Verification Layers: Model, Code, Platform, and Vehicle
No single fidelity level exposes every failure mechanism. A practical campaign uses complementary layers:
- Analytical verification: stability, boundedness, reachability, invariants, and transition margins.
- Model-in-the-loop: idealized nonlinear dynamics and systematic uncertainty sweeps.
- Software-in-the-loop: production algorithms, exact data types, initialization, and exception paths.
- Processor-in-the-loop: compiler, target arithmetic, scheduling, timing, and memory behavior.
- Hardware-in-the-loop: real interfaces, sensors, actuators, buses, delays, and injected faults.
- Vehicle or flight test: confirmation of integrated assumptions in a controlled envelope.
Let \(\mathcal{F}_j\) be the set of failure modes observable at layer \(j\). The campaign is structurally incomplete when an identified hazard has no verification layer capable of exciting and observing its causal failure mode:
\[ \forall H_i\ \exists j:\quad \mathcal{C}(H_i)\cap\mathcal{F}_j\neq\varnothing, \]
where \(\mathcal{C}(H_i)\) denotes credible causes of hazard \(H_i\). This is why pure desktop simulation cannot close an assurance argument involving target timing, sensor wiring, bus corruption, or actuator dynamics.
7. Scenario Space, Coverage, and Corner Cases
Define a normalized scenario vector \(s=[p^{T},x_0^{T},d^{T},n^{T},f^{T},m^{T}]^{T}\in\mathcal{S}\), containing plant parameters, initial state, disturbances, noise, faults, and modes. A defensible test plan combines requirement-derived cases, boundary cases, combinatorial cases, adversarial searches, random sampling, and known difficult adaptive-control cases such as low excitation and saturation.
A simple geometric coverage indicator for a finite test set \(\mathcal{T}_N=\{s_1,\ldots,s_N\}\) is the fill distance
\[ h(\mathcal{T}_N,\mathcal{S})= \sup_{s\in\mathcal{S}} \min_{1\leq i\leq N}\|s-s_i\|_W, \]
where \(W\) scales heterogeneous variables. Smaller fill distance means better geometric coverage, but it does not guarantee hazard coverage. Rare transitions, correlated faults, delayed sensor recovery, and parameter-reset logic must be targeted explicitly. Coverage should therefore be reported as a vector, not a single percentage:
\[ \mathbf{c}= [c_{\mathrm{requirements}},c_{\mathrm{code}},c_{\mathrm{MC/DC}}, c_{\mathrm{parameter}},c_{\mathrm{mode}},c_{\mathrm{fault}}, c_{\mathrm{boundary}}]^{T}. \]
8. Statistical Validation and the Meaning of “Zero Failures”
Suppose each independently sampled scenario produces a pass/fail outcome with unknown failure probability \(p_f\) under a precisely defined sampling distribution. If \(k\) failures occur in \(N\) trials, the observed rate \(k/N\) is not itself a confidence bound.
For zero observed failures, an exact one-sided upper confidence bound at confidence \(1-\alpha\) follows from \(\Pr(K=0)=(1-p_f)^N\):
\[ (1-p_U)^N=\alpha \quad\Longrightarrow\quad p_U=1-\alpha^{1/N}. \]
For example, at 95% confidence and \(N=1000\), zero failures supports only an upper bound of approximately \(3\times 10^{-3}\). It does not demonstrate probabilities associated with catastrophic aircraft or automotive hazards. Very low target rates require analytical structure, decomposition, independence arguments, formal verification, architecture, field evidence, and conservative safety margins—not brute-force testing alone.
For general \(k\), the implementation lab reports a one-sided Wilson upper bound:
\[ p_U= \frac{ \hat p+\frac{z^2}{2N}+ z\sqrt{\frac{\hat p(1-\hat p)}{N}+\frac{z^2}{4N^2}} }{1+\frac{z^2}{N}}, \qquad \hat p=\frac{k}{N}. \]
The statistical claim is valid only for the declared scenario distribution. A biased or incomplete generator can produce a narrow confidence interval about the wrong population.
9. Aerospace Assurance Context
In civil airborne development, software assurance is commonly organized around a system-level safety process, allocated development-assurance levels, lifecycle plans, requirements, design, source code, verification, configuration management, quality assurance, and certification liaison. DO-178C is accepted by the FAA through AC 20-115D as a means of compliance for airborne software aspects, but it is not a controller-design standard and it does not by itself prove closed-loop safety.
For an adaptive flight-control function, the project must establish how adaptation affects system requirements, software objectives, parameter data, model-based artifacts, tools, timing, and verification independence. The FAA/NASA adaptive-systems research emphasizes system-level characteristics, bounded learning state, real-time monitors, engage/disengage logic, smooth transitions, protection against noise-driven drift, and verification tailored to the specific adaptive architecture.
A practical evidence package may include a plan for software aspects of certification, system and software requirements, model standards, adaptation-bound rationale, safety monitor requirements, robustness analysis, test procedures/results, structural coverage, worst-case timing evidence, tool-qualification rationale, configuration index, problem reports, change-impact analysis, and a final accomplishment summary. The exact compliance basis is agreed with the responsible authority and cannot be inferred from this lesson.
10. Automotive Assurance Context
Automotive validation commonly separates hazards caused by malfunctioning electrical or electronic behavior from hazards caused by limitations of the intended functionality. ISO 26262 provides a functional-safety lifecycle for safety-related E/E systems, while ISO 21448 addresses safety of the intended functionality for functions whose correct behavior may still be unsafe in particular scenarios. Adaptive functions can implicate both: an implementation defect may be a malfunction, while insufficient excitation, perception limitation, or an inadequately bounded adaptation policy may create an unsafe intended behavior without a conventional hardware fault.
Vehicle cybersecurity and software-update governance also matter because adaptive parameters, calibration data, logs, and update mechanisms can alter field behavior. UN Regulations 155 and 156 establish management-system expectations for cybersecurity and software updates in applicable type-approval contexts. For adaptive control, configuration control must answer whether learned parameters persist across key cycles, whether they are treated as calibration or operational data, how resets are authorized, and how an update changes the validated scenario domain.
Automotive evidence often combines hazard analysis and risk assessment, safety goals, technical safety requirements, freedom-from-interference arguments, software unit and integration tests, HIL campaigns, fault injection, vehicle tests, scenario databases, production controls, field monitoring, and controlled software-update processes.
11. Numerical, Timing, and Implementation Determinism
A continuous-time proof does not automatically cover sampled implementation. With Euler integration,
\[ \hat{\theta}_{k+1}= \Pi_{\Theta_c}\left( \hat{\theta}_k+T_s f_a(\hat{\theta}_k,x_k,r_k) \right), \]
the sampling interval \(T_s\), numerical precision, overflow behavior, quantization, task jitter, and projection order affect the result. The actual software requirement should specify these details. For a task with execution time \(C_k\), release jitter \(J_k\), and data age \(A_k\), a timing requirement might be
\[ C_k+J_k\leq D_k, \qquad A_k\leq A_{\max}, \qquad T_{s,\min}\leq T_{s,k}\leq T_{s,\max}. \]
The project should define behavior for NaN/Inf, stale data, impossible sensor values, arithmetic saturation, reset during adaptation, memory corruption detection, and missed deadlines. Reproducible random seeds are useful for regression testing, but deterministic pseudo-random tests must be supplemented by independently generated cases.
12. Certification-Oriented Validation Lab
The following implementations use the same educational scalar plant \(\dot x=ax+b(u+d)\). The adaptive gain is projected into a fixed interval, the actuator command is saturated, and a monitor selects a verified fallback gain near the warning boundary. A randomized campaign varies plant coefficients, initial state, harmonic disturbance, and pulse disturbance. Each run checks hard state limits, actuator limits, parameter bounds, and terminal tracking error. Passing these tests does not certify a product; the programs demonstrate how requirements can be converted into executable evidence records.
Chapter26_Lesson5.py
"""Chapter 26, Lesson 5: certification-oriented validation harness.
This educational program is not a certification tool. It demonstrates bounded
adaptation, a runtime safety monitor, Monte Carlo scenario testing, and a
simple statistical acceptance summary.
"""
from __future__ import annotations
from dataclasses import dataclass
from math import sqrt
import argparse
import csv
import random
from pathlib import Path
from typing import Iterable
@dataclass(frozen=True)
class Limits:
x_warning: float = 1.20
x_hard: float = 2.00
u_max: float = 5.00
k_min: float = 0.00
k_max: float = 4.00
final_error_max: float = 0.20
@dataclass(frozen=True)
class Scenario:
a: float
b: float
x0: float
disturbance_amplitude: float
disturbance_frequency: float
pulse_start: float
pulse_duration: float
pulse_amplitude: float
@dataclass
class RunResult:
passed: bool
reason: str
max_abs_x: float
max_abs_u: float
final_abs_x: float
fallback_activations: int
min_k: float
max_k: float
def clamp(value: float, lower: float, upper: float) -> float:
return max(lower, min(upper, value))
def projection_update(k_hat: float, raw_rate: float, limits: Limits) -> float:
"""Tangent-cone projection for a scalar bounded parameter."""
if k_hat <= limits.k_min and raw_rate < 0.0:
return 0.0
if k_hat >= limits.k_max and raw_rate > 0.0:
return 0.0
return raw_rate
def disturbance(t: float, s: Scenario) -> float:
harmonic = s.disturbance_amplitude * __import__("math").sin(
2.0 * __import__("math").pi * s.disturbance_frequency * t
)
pulse = s.pulse_amplitude if s.pulse_start <= t < s.pulse_start + s.pulse_duration else 0.0
return harmonic + pulse
def simulate(
scenario: Scenario,
limits: Limits,
dt: float = 0.005,
horizon: float = 8.0,
) -> RunResult:
x = scenario.x0
k_hat = 0.8
gamma = 2.4
sigma = 0.18
k_nominal = 0.8
k_baseline = 1.2
k_fallback = 2.8
max_abs_x = abs(x)
max_abs_u = 0.0
min_k = k_hat
max_k = k_hat
fallback_active = False
fallback_activations = 0
reason = "PASS"
steps = int(horizon / dt)
for step in range(steps):
t = step * dt
raw_rate = gamma * x * x - sigma * (k_hat - k_nominal)
k_dot = projection_update(k_hat, raw_rate, limits)
k_hat = clamp(k_hat + dt * k_dot, limits.k_min, limits.k_max)
u_candidate = -(k_baseline + k_hat) * x
warning = abs(x) >= limits.x_warning or abs(u_candidate) >= 0.95 * limits.u_max
if warning and not fallback_active:
fallback_active = True
fallback_activations += 1
elif fallback_active and abs(x) < 0.65 * limits.x_warning:
fallback_active = False
commanded_u = -k_fallback * x if fallback_active else u_candidate
u = clamp(commanded_u, -limits.u_max, limits.u_max)
d = disturbance(t, scenario)
x_dot = scenario.a * x + scenario.b * (u + d)
x += dt * x_dot
max_abs_x = max(max_abs_x, abs(x))
max_abs_u = max(max_abs_u, abs(u))
min_k = min(min_k, k_hat)
max_k = max(max_k, k_hat)
if not (-1.0e100 < x < 1.0e100):
reason = "NON_FINITE_STATE"
break
if abs(x) > limits.x_hard:
reason = "HARD_STATE_ENVELOPE"
break
if abs(u) > limits.u_max + 1.0e-12:
reason = "ACTUATOR_LIMIT"
break
if k_hat < limits.k_min - 1.0e-12 or k_hat > limits.k_max + 1.0e-12:
reason = "PARAMETER_BOUND"
break
final_abs_x = abs(x)
if reason == "PASS" and final_abs_x > limits.final_error_max:
reason = "FINAL_TRACKING_ERROR"
return RunResult(
passed=(reason == "PASS"),
reason=reason,
max_abs_x=max_abs_x,
max_abs_u=max_abs_u,
final_abs_x=final_abs_x,
fallback_activations=fallback_activations,
min_k=min_k,
max_k=max_k,
)
def sample_scenarios(count: int, seed: int) -> Iterable[Scenario]:
rng = random.Random(seed)
for _ in range(count):
yield Scenario(
a=rng.uniform(-0.30, 0.55),
b=rng.uniform(0.70, 1.30),
x0=rng.uniform(-1.45, 1.45),
disturbance_amplitude=rng.uniform(0.00, 0.22),
disturbance_frequency=rng.uniform(0.10, 1.10),
pulse_start=rng.uniform(1.0, 5.0),
pulse_duration=rng.uniform(0.05, 0.40),
pulse_amplitude=rng.uniform(-0.35, 0.35),
)
def wilson_upper_bound(failures: int, total: int, z: float = 1.6448536269514722) -> float:
"""One-sided approximately 95% Wilson upper bound for a binomial rate."""
if total <= 0:
raise ValueError("total must be positive")
phat = failures / total
denominator = 1.0 + z * z / total
center = phat + z * z / (2.0 * total)
radius = z * sqrt(phat * (1.0 - phat) / total + z * z / (4.0 * total * total))
return min(1.0, (center + radius) / denominator)
def run_campaign(count: int, seed: int, csv_path: Path | None) -> int:
limits = Limits()
results: list[RunResult] = []
rows: list[dict[str, object]] = []
for index, scenario in enumerate(sample_scenarios(count, seed), start=1):
result = simulate(scenario, limits)
results.append(result)
rows.append(
{
"scenario": index,
"passed": result.passed,
"reason": result.reason,
"a": scenario.a,
"b": scenario.b,
"x0": scenario.x0,
"max_abs_x": result.max_abs_x,
"max_abs_u": result.max_abs_u,
"final_abs_x": result.final_abs_x,
"fallback_activations": result.fallback_activations,
"min_k": result.min_k,
"max_k": result.max_k,
}
)
failures = sum(not result.passed for result in results)
observed_rate = failures / count
upper = wilson_upper_bound(failures, count)
print("Certification-oriented validation campaign")
print(f"Scenarios: {count}")
print(f"Failures: {failures}")
print(f"Observed failure rate: {observed_rate:.6f}")
print(f"One-sided 95% Wilson upper bound: {upper:.6f}")
print(f"Worst |x|: {max(r.max_abs_x for r in results):.6f}")
print(f"Worst |u|: {max(r.max_abs_u for r in results):.6f}")
print(f"Worst final |x|: {max(r.final_abs_x for r in results):.6f}")
print(f"Total fallback activations: {sum(r.fallback_activations for r in results)}")
if csv_path is not None:
csv_path.parent.mkdir(parents=True, exist_ok=True)
with csv_path.open("w", newline="", encoding="utf-8") as handle:
writer = csv.DictWriter(handle, fieldnames=list(rows[0].keys()))
writer.writeheader()
writer.writerows(rows)
print(f"CSV evidence written to: {csv_path}")
return 0 if failures == 0 else 2
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--scenarios", type=int, default=1000)
parser.add_argument("--seed", type=int, default=2605)
parser.add_argument("--csv", type=Path, default=Path("Chapter26_Lesson5_results.csv"))
args = parser.parse_args()
if args.scenarios <= 0:
parser.error("--scenarios must be positive")
return run_campaign(args.scenarios, args.seed, args.csv)
if __name__ == "__main__":
raise SystemExit(main())
Chapter26_Lesson5.cpp
// Chapter 26, Lesson 5: certification-oriented validation harness.
// Educational demonstration only; this is not a certification tool.
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <iomanip>
#include <iostream>
#include <limits>
#include <random>
#include <stdexcept>
#include <string>
#include <vector>
struct Limits {
double x_warning = 1.20;
double x_hard = 2.00;
double u_max = 5.00;
double k_min = 0.00;
double k_max = 4.00;
double final_error_max = 0.20;
};
struct Scenario {
double a;
double b;
double x0;
double disturbance_amplitude;
double disturbance_frequency;
double pulse_start;
double pulse_duration;
double pulse_amplitude;
};
struct RunResult {
bool passed;
std::string reason;
double max_abs_x;
double max_abs_u;
double final_abs_x;
int fallback_activations;
double min_k;
double max_k;
};
double clamp_value(double value, double lower, double upper) {
return std::max(lower, std::min(upper, value));
}
double projection_update(double k_hat, double raw_rate, const Limits& limits) {
if (k_hat <= limits.k_min && raw_rate < 0.0) return 0.0;
if (k_hat >= limits.k_max && raw_rate > 0.0) return 0.0;
return raw_rate;
}
double disturbance(double t, const Scenario& s) {
constexpr double pi = 3.14159265358979323846;
const double harmonic = s.disturbance_amplitude *
std::sin(2.0 * pi * s.disturbance_frequency * t);
const bool in_pulse = t >= s.pulse_start && t < s.pulse_start + s.pulse_duration;
return harmonic + (in_pulse ? s.pulse_amplitude : 0.0);
}
RunResult simulate(const Scenario& scenario, const Limits& limits,
double dt = 0.005, double horizon = 8.0) {
double x = scenario.x0;
double k_hat = 0.8;
const double gamma = 2.4;
const double sigma = 0.18;
const double k_nominal = 0.8;
const double k_baseline = 1.2;
const double k_fallback = 2.8;
double max_abs_x = std::abs(x);
double max_abs_u = 0.0;
double min_k = k_hat;
double max_k = k_hat;
bool fallback_active = false;
int fallback_activations = 0;
std::string reason = "PASS";
const int steps = static_cast<int>(horizon / dt);
for (int step = 0; step < steps; ++step) {
const double t = step * dt;
const double raw_rate = gamma * x * x - sigma * (k_hat - k_nominal);
const double k_dot = projection_update(k_hat, raw_rate, limits);
k_hat = clamp_value(k_hat + dt * k_dot, limits.k_min, limits.k_max);
const double u_candidate = -(k_baseline + k_hat) * x;
const bool warning = std::abs(x) >= limits.x_warning ||
std::abs(u_candidate) >= 0.95 * limits.u_max;
if (warning && !fallback_active) {
fallback_active = true;
++fallback_activations;
} else if (fallback_active && std::abs(x) < 0.65 * limits.x_warning) {
fallback_active = false;
}
const double commanded_u = fallback_active ? -k_fallback * x : u_candidate;
const double u = clamp_value(commanded_u, -limits.u_max, limits.u_max);
const double x_dot = scenario.a * x + scenario.b * (u + disturbance(t, scenario));
x += dt * x_dot;
max_abs_x = std::max(max_abs_x, std::abs(x));
max_abs_u = std::max(max_abs_u, std::abs(u));
min_k = std::min(min_k, k_hat);
max_k = std::max(max_k, k_hat);
if (!std::isfinite(x)) {
reason = "NON_FINITE_STATE";
break;
}
if (std::abs(x) > limits.x_hard) {
reason = "HARD_STATE_ENVELOPE";
break;
}
if (std::abs(u) > limits.u_max + 1.0e-12) {
reason = "ACTUATOR_LIMIT";
break;
}
if (k_hat < limits.k_min - 1.0e-12 || k_hat > limits.k_max + 1.0e-12) {
reason = "PARAMETER_BOUND";
break;
}
}
const double final_abs_x = std::abs(x);
if (reason == "PASS" && final_abs_x > limits.final_error_max) {
reason = "FINAL_TRACKING_ERROR";
}
return {reason == "PASS", reason, max_abs_x, max_abs_u, final_abs_x,
fallback_activations, min_k, max_k};
}
double wilson_upper_bound(int failures, int total,
double z = 1.6448536269514722) {
if (total <= 0) throw std::invalid_argument("total must be positive");
const double phat = static_cast<double>(failures) / total;
const double denominator = 1.0 + z * z / total;
const double center = phat + z * z / (2.0 * total);
const double radius = z * std::sqrt(
phat * (1.0 - phat) / total + z * z / (4.0 * total * total));
return std::min(1.0, (center + radius) / denominator);
}
int main(int argc, char* argv[]) {
int count = 1000;
std::uint32_t seed = 2605;
if (argc >= 2) count = std::stoi(argv[1]);
if (argc >= 3) seed = static_cast<std::uint32_t>(std::stoul(argv[2]));
if (count <= 0) {
std::cerr << "Scenario count must be positive.\n";
return 1;
}
std::mt19937 rng(seed);
auto uniform = [&rng](double low, double high) {
return std::uniform_real_distribution<double>(low, high)(rng);
};
const Limits limits;
std::vector<RunResult> results;
results.reserve(static_cast<std::size_t>(count));
for (int i = 0; i < count; ++i) {
Scenario s{
uniform(-0.30, 0.55), uniform(0.70, 1.30), uniform(-1.45, 1.45),
uniform(0.00, 0.22), uniform(0.10, 1.10), uniform(1.0, 5.0),
uniform(0.05, 0.40), uniform(-0.35, 0.35)
};
results.push_back(simulate(s, limits));
}
int failures = 0;
int fallback_total = 0;
double worst_x = 0.0;
double worst_u = 0.0;
double worst_final = 0.0;
for (const auto& r : results) {
failures += r.passed ? 0 : 1;
fallback_total += r.fallback_activations;
worst_x = std::max(worst_x, r.max_abs_x);
worst_u = std::max(worst_u, r.max_abs_u);
worst_final = std::max(worst_final, r.final_abs_x);
}
std::cout << std::fixed << std::setprecision(6);
std::cout << "Certification-oriented validation campaign\n";
std::cout << "Scenarios: " << count << "\n";
std::cout << "Failures: " << failures << "\n";
std::cout << "Observed failure rate: "
<< static_cast<double>(failures) / count << "\n";
std::cout << "One-sided 95% Wilson upper bound: "
<< wilson_upper_bound(failures, count) << "\n";
std::cout << "Worst |x|: " << worst_x << "\n";
std::cout << "Worst |u|: " << worst_u << "\n";
std::cout << "Worst final |x|: " << worst_final << "\n";
std::cout << "Total fallback activations: " << fallback_total << "\n";
return failures == 0 ? 0 : 2;
}
Chapter26_Lesson5.java
// Chapter 26, Lesson 5: certification-oriented validation harness.
// Educational demonstration only; this is not a certification tool.
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Random;
public final class Chapter26_Lesson5 {
private Chapter26_Lesson5() {}
private static final class Limits {
final double xWarning = 1.20;
final double xHard = 2.00;
final double uMax = 5.00;
final double kMin = 0.00;
final double kMax = 4.00;
final double finalErrorMax = 0.20;
}
private static final class Scenario {
final double a;
final double b;
final double x0;
final double disturbanceAmplitude;
final double disturbanceFrequency;
final double pulseStart;
final double pulseDuration;
final double pulseAmplitude;
Scenario(double a, double b, double x0, double disturbanceAmplitude,
double disturbanceFrequency, double pulseStart,
double pulseDuration, double pulseAmplitude) {
this.a = a;
this.b = b;
this.x0 = x0;
this.disturbanceAmplitude = disturbanceAmplitude;
this.disturbanceFrequency = disturbanceFrequency;
this.pulseStart = pulseStart;
this.pulseDuration = pulseDuration;
this.pulseAmplitude = pulseAmplitude;
}
}
private static final class RunResult {
final boolean passed;
final String reason;
final double maxAbsX;
final double maxAbsU;
final double finalAbsX;
final int fallbackActivations;
final double minK;
final double maxK;
RunResult(boolean passed, String reason, double maxAbsX, double maxAbsU,
double finalAbsX, int fallbackActivations, double minK, double maxK) {
this.passed = passed;
this.reason = reason;
this.maxAbsX = maxAbsX;
this.maxAbsU = maxAbsU;
this.finalAbsX = finalAbsX;
this.fallbackActivations = fallbackActivations;
this.minK = minK;
this.maxK = maxK;
}
}
private static double clamp(double value, double lower, double upper) {
return Math.max(lower, Math.min(upper, value));
}
private static double projectionUpdate(double kHat, double rawRate, Limits limits) {
if (kHat <= limits.kMin && rawRate < 0.0) {
return 0.0;
}
if (kHat >= limits.kMax && rawRate > 0.0) {
return 0.0;
}
return rawRate;
}
private static double disturbance(double t, Scenario s) {
double harmonic = s.disturbanceAmplitude
* Math.sin(2.0 * Math.PI * s.disturbanceFrequency * t);
boolean inPulse = t >= s.pulseStart && t < s.pulseStart + s.pulseDuration;
return harmonic + (inPulse ? s.pulseAmplitude : 0.0);
}
private static RunResult simulate(Scenario scenario, Limits limits,
double dt, double horizon) {
double x = scenario.x0;
double kHat = 0.8;
final double gamma = 2.4;
final double sigma = 0.18;
final double kNominal = 0.8;
final double kBaseline = 1.2;
final double kFallback = 2.8;
double maxAbsX = Math.abs(x);
double maxAbsU = 0.0;
double minK = kHat;
double maxK = kHat;
boolean fallbackActive = false;
int fallbackActivations = 0;
String reason = "PASS";
int steps = (int) (horizon / dt);
for (int step = 0; step < steps; step++) {
double t = step * dt;
double rawRate = gamma * x * x - sigma * (kHat - kNominal);
double kDot = projectionUpdate(kHat, rawRate, limits);
kHat = clamp(kHat + dt * kDot, limits.kMin, limits.kMax);
double uCandidate = -(kBaseline + kHat) * x;
boolean warning = Math.abs(x) >= limits.xWarning
|| Math.abs(uCandidate) >= 0.95 * limits.uMax;
if (warning && !fallbackActive) {
fallbackActive = true;
fallbackActivations++;
} else if (fallbackActive && Math.abs(x) < 0.65 * limits.xWarning) {
fallbackActive = false;
}
double commandedU = fallbackActive ? -kFallback * x : uCandidate;
double u = clamp(commandedU, -limits.uMax, limits.uMax);
double xDot = scenario.a * x + scenario.b * (u + disturbance(t, scenario));
x += dt * xDot;
maxAbsX = Math.max(maxAbsX, Math.abs(x));
maxAbsU = Math.max(maxAbsU, Math.abs(u));
minK = Math.min(minK, kHat);
maxK = Math.max(maxK, kHat);
if (!Double.isFinite(x)) {
reason = "NON_FINITE_STATE";
break;
}
if (Math.abs(x) > limits.xHard) {
reason = "HARD_STATE_ENVELOPE";
break;
}
if (Math.abs(u) > limits.uMax + 1.0e-12) {
reason = "ACTUATOR_LIMIT";
break;
}
if (kHat < limits.kMin - 1.0e-12 || kHat > limits.kMax + 1.0e-12) {
reason = "PARAMETER_BOUND";
break;
}
}
double finalAbsX = Math.abs(x);
if (reason.equals("PASS") && finalAbsX > limits.finalErrorMax) {
reason = "FINAL_TRACKING_ERROR";
}
return new RunResult(
reason.equals("PASS"), reason, maxAbsX, maxAbsU,
finalAbsX, fallbackActivations, minK, maxK
);
}
private static double uniform(Random rng, double low, double high) {
return low + (high - low) * rng.nextDouble();
}
private static double wilsonUpperBound(int failures, int total, double z) {
if (total <= 0) {
throw new IllegalArgumentException("total must be positive");
}
double phat = (double) failures / total;
double denominator = 1.0 + z * z / total;
double center = phat + z * z / (2.0 * total);
double radius = z * Math.sqrt(
phat * (1.0 - phat) / total + z * z / (4.0 * total * total)
);
return Math.min(1.0, (center + radius) / denominator);
}
public static void main(String[] args) {
Locale.setDefault(Locale.ROOT);
int count = args.length >= 1 ? Integer.parseInt(args[0]) : 1000;
long seed = args.length >= 2 ? Long.parseLong(args[1]) : 2605L;
if (count <= 0) {
throw new IllegalArgumentException("Scenario count must be positive.");
}
Random rng = new Random(seed);
Limits limits = new Limits();
List<RunResult> results = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
Scenario scenario = new Scenario(
uniform(rng, -0.30, 0.55),
uniform(rng, 0.70, 1.30),
uniform(rng, -1.45, 1.45),
uniform(rng, 0.00, 0.22),
uniform(rng, 0.10, 1.10),
uniform(rng, 1.0, 5.0),
uniform(rng, 0.05, 0.40),
uniform(rng, -0.35, 0.35)
);
results.add(simulate(scenario, limits, 0.005, 8.0));
}
int failures = 0;
int fallbackTotal = 0;
double worstX = 0.0;
double worstU = 0.0;
double worstFinal = 0.0;
for (RunResult result : results) {
failures += result.passed ? 0 : 1;
fallbackTotal += result.fallbackActivations;
worstX = Math.max(worstX, result.maxAbsX);
worstU = Math.max(worstU, result.maxAbsU);
worstFinal = Math.max(worstFinal, result.finalAbsX);
}
System.out.println("Certification-oriented validation campaign");
System.out.printf("Scenarios: %d%n", count);
System.out.printf("Failures: %d%n", failures);
System.out.printf("Observed failure rate: %.6f%n", (double) failures / count);
System.out.printf("One-sided 95%% Wilson upper bound: %.6f%n",
wilsonUpperBound(failures, count, 1.6448536269514722));
System.out.printf("Worst |x|: %.6f%n", worstX);
System.out.printf("Worst |u|: %.6f%n", worstU);
System.out.printf("Worst final |x|: %.6f%n", worstFinal);
System.out.printf("Total fallback activations: %d%n", fallbackTotal);
if (failures > 0) {
System.exit(2);
}
}
}
Chapter26_Lesson5.m
% Chapter 26, Lesson 5: certification-oriented validation harness.
% Educational demonstration only; this is not a certification tool.
clear; clc;
rng(2605, 'twister');
scenarioCount = 1000;
limits.xWarning = 1.20;
limits.xHard = 2.00;
limits.uMax = 5.00;
limits.kMin = 0.00;
limits.kMax = 4.00;
limits.finalErrorMax = 0.20;
results = repmat(struct( ...
'passed', false, ...
'reason', "", ...
'maxAbsX', 0.0, ...
'maxAbsU', 0.0, ...
'finalAbsX', 0.0, ...
'fallbackActivations', 0, ...
'minK', 0.0, ...
'maxK', 0.0), scenarioCount, 1);
for i = 1:scenarioCount
scenario.a = uniformSample(-0.30, 0.55);
scenario.b = uniformSample(0.70, 1.30);
scenario.x0 = uniformSample(-1.45, 1.45);
scenario.disturbanceAmplitude = uniformSample(0.00, 0.22);
scenario.disturbanceFrequency = uniformSample(0.10, 1.10);
scenario.pulseStart = uniformSample(1.0, 5.0);
scenario.pulseDuration = uniformSample(0.05, 0.40);
scenario.pulseAmplitude = uniformSample(-0.35, 0.35);
results(i) = simulateScenario(scenario, limits, 0.005, 8.0);
end
failures = sum(~[results.passed]);
observedRate = failures / scenarioCount;
upper = wilsonUpperBound(failures, scenarioCount, 1.6448536269514722);
fprintf('Certification-oriented validation campaign\n');
fprintf('Scenarios: %d\n', scenarioCount);
fprintf('Failures: %d\n', failures);
fprintf('Observed failure rate: %.6f\n', observedRate);
fprintf('One-sided 95%% Wilson upper bound: %.6f\n', upper);
fprintf('Worst |x|: %.6f\n', max([results.maxAbsX]));
fprintf('Worst |u|: %.6f\n', max([results.maxAbsU]));
fprintf('Worst final |x|: %.6f\n', max([results.finalAbsX]));
fprintf('Total fallback activations: %d\n', sum([results.fallbackActivations]));
T = struct2table(results);
writetable(T, 'Chapter26_Lesson5_results.csv');
function result = simulateScenario(scenario, limits, dt, horizon)
x = scenario.x0;
kHat = 0.8;
gamma = 2.4;
sigma = 0.18;
kNominal = 0.8;
kBaseline = 1.2;
kFallback = 2.8;
maxAbsX = abs(x);
maxAbsU = 0.0;
minK = kHat;
maxK = kHat;
fallbackActive = false;
fallbackActivations = 0;
reason = "PASS";
steps = floor(horizon / dt);
for step = 0:(steps - 1)
t = step * dt;
rawRate = gamma * x^2 - sigma * (kHat - kNominal);
kDot = projectionUpdate(kHat, rawRate, limits);
kHat = clampValue(kHat + dt * kDot, limits.kMin, limits.kMax);
uCandidate = -(kBaseline + kHat) * x;
warning = abs(x) >= limits.xWarning || ...
abs(uCandidate) >= 0.95 * limits.uMax;
if warning && ~fallbackActive
fallbackActive = true;
fallbackActivations = fallbackActivations + 1;
elseif fallbackActive && abs(x) < 0.65 * limits.xWarning
fallbackActive = false;
end
if fallbackActive
commandedU = -kFallback * x;
else
commandedU = uCandidate;
end
u = clampValue(commandedU, -limits.uMax, limits.uMax);
harmonic = scenario.disturbanceAmplitude * ...
sin(2.0 * pi * scenario.disturbanceFrequency * t);
inPulse = t >= scenario.pulseStart && ...
t < scenario.pulseStart + scenario.pulseDuration;
pulse = scenario.pulseAmplitude * double(inPulse);
d = harmonic + pulse;
xDot = scenario.a * x + scenario.b * (u + d);
x = x + dt * xDot;
maxAbsX = max(maxAbsX, abs(x));
maxAbsU = max(maxAbsU, abs(u));
minK = min(minK, kHat);
maxK = max(maxK, kHat);
if ~isfinite(x)
reason = "NON_FINITE_STATE";
break;
end
if abs(x) > limits.xHard
reason = "HARD_STATE_ENVELOPE";
break;
end
if abs(u) > limits.uMax + 1.0e-12
reason = "ACTUATOR_LIMIT";
break;
end
if kHat < limits.kMin - 1.0e-12 || kHat > limits.kMax + 1.0e-12
reason = "PARAMETER_BOUND";
break;
end
end
finalAbsX = abs(x);
if reason == "PASS" && finalAbsX > limits.finalErrorMax
reason = "FINAL_TRACKING_ERROR";
end
result.passed = reason == "PASS";
result.reason = reason;
result.maxAbsX = maxAbsX;
result.maxAbsU = maxAbsU;
result.finalAbsX = finalAbsX;
result.fallbackActivations = fallbackActivations;
result.minK = minK;
result.maxK = maxK;
end
function rate = projectionUpdate(kHat, rawRate, limits)
if kHat <= limits.kMin && rawRate < 0.0
rate = 0.0;
elseif kHat >= limits.kMax && rawRate > 0.0
rate = 0.0;
else
rate = rawRate;
end
end
function value = clampValue(value, lower, upper)
value = max(lower, min(upper, value));
end
function value = uniformSample(lower, upper)
value = lower + (upper - lower) * rand();
end
function upper = wilsonUpperBound(failures, total, z)
pHat = failures / total;
denominator = 1.0 + z^2 / total;
center = pHat + z^2 / (2.0 * total);
radius = z * sqrt(pHat * (1.0 - pHat) / total + ...
z^2 / (4.0 * total^2));
upper = min(1.0, (center + radius) / denominator);
end
Chapter26_Lesson5.nb
(* Chapter 26, Lesson 5: certification-oriented validation harness.
Educational demonstration only; this is not a certification tool. *)
ClearAll["Global`*"];
SeedRandom[2605];
limits = <|
"xWarning" -> 1.20,
"xHard" -> 2.00,
"uMax" -> 5.00,
"kMin" -> 0.00,
"kMax" -> 4.00,
"finalErrorMax" -> 0.20
|>;
clamp[value_, lower_, upper_] := Max[lower, Min[upper, value]];
projectionUpdate[kHat_, rawRate_, lim_] := Which[
kHat <= lim["kMin"] && rawRate < 0.0, 0.0,
kHat >= lim["kMax"] && rawRate > 0.0, 0.0,
True, rawRate
];
disturbance[t_, s_] := Module[{harmonic, inPulse},
harmonic = s["disturbanceAmplitude"] *
Sin[2.0 Pi s["disturbanceFrequency"] t];
inPulse = s["pulseStart"] <= t <
s["pulseStart"] + s["pulseDuration"];
harmonic + If[inPulse, s["pulseAmplitude"], 0.0]
];
simulateScenario[s_, lim_, dt_: 0.005, horizon_: 8.0] := Module[
{x = s["x0"], kHat = 0.8, gamma = 2.4, sigma = 0.18,
kNominal = 0.8, kBaseline = 1.2, kFallback = 2.8,
maxAbsX, maxAbsU = 0.0, minK, maxK, fallbackActive = False,
fallbackActivations = 0, reason = "PASS", steps, t, rawRate,
kDot, uCandidate, warning, commandedU, u, xDot, finalAbsX},
maxAbsX = Abs[x];
minK = kHat;
maxK = kHat;
steps = Floor[horizon/dt];
Do[
t = step dt;
rawRate = gamma x^2 - sigma (kHat - kNominal);
kDot = projectionUpdate[kHat, rawRate, lim];
kHat = clamp[kHat + dt kDot, lim["kMin"], lim["kMax"]];
uCandidate = -(kBaseline + kHat) x;
warning = Abs[x] >= lim["xWarning"] ||
Abs[uCandidate] >= 0.95 lim["uMax"];
If[warning && ! fallbackActive,
fallbackActive = True;
fallbackActivations++,
If[fallbackActive && Abs[x] < 0.65 lim["xWarning"],
fallbackActive = False]
];
commandedU = If[fallbackActive, -kFallback x, uCandidate];
u = clamp[commandedU, -lim["uMax"], lim["uMax"]];
xDot = s["a"] x + s["b"] (u + disturbance[t, s]);
x = x + dt xDot;
maxAbsX = Max[maxAbsX, Abs[x]];
maxAbsU = Max[maxAbsU, Abs[u]];
minK = Min[minK, kHat];
maxK = Max[maxK, kHat];
If[! MachineNumberQ[x], reason = "NON_FINITE_STATE"; Break[]];
If[Abs[x] > lim["xHard"], reason = "HARD_STATE_ENVELOPE"; Break[]];
If[Abs[u] > lim["uMax"] + 10^-12, reason = "ACTUATOR_LIMIT"; Break[]];
If[kHat < lim["kMin"] - 10^-12 || kHat > lim["kMax"] + 10^-12,
reason = "PARAMETER_BOUND"; Break[]],
{step, 0, steps - 1}
];
finalAbsX = Abs[x];
If[reason == "PASS" && finalAbsX > lim["finalErrorMax"],
reason = "FINAL_TRACKING_ERROR"];
<|
"passed" -> (reason == "PASS"),
"reason" -> reason,
"maxAbsX" -> maxAbsX,
"maxAbsU" -> maxAbsU,
"finalAbsX" -> finalAbsX,
"fallbackActivations" -> fallbackActivations,
"minK" -> minK,
"maxK" -> maxK
|>
];
sampleScenario[] := <|
"a" -> RandomReal[{-0.30, 0.55}],
"b" -> RandomReal[{0.70, 1.30}],
"x0" -> RandomReal[{-1.45, 1.45}],
"disturbanceAmplitude" -> RandomReal[{0.00, 0.22}],
"disturbanceFrequency" -> RandomReal[{0.10, 1.10}],
"pulseStart" -> RandomReal[{1.0, 5.0}],
"pulseDuration" -> RandomReal[{0.05, 0.40}],
"pulseAmplitude" -> RandomReal[{-0.35, 0.35}]
|>;
wilsonUpperBound[failures_, total_, z_: 1.6448536269514722] := Module[
{pHat, denominator, center, radius},
pHat = N[failures/total];
denominator = 1.0 + z^2/total;
center = pHat + z^2/(2.0 total);
radius = z Sqrt[pHat (1.0 - pHat)/total + z^2/(4.0 total^2)];
Min[1.0, (center + radius)/denominator]
];
scenarioCount = 1000;
results = Table[simulateScenario[sampleScenario[], limits], {scenarioCount}];
failures = Count[results[[All, "passed"]], False];
summary = <|
"Scenarios" -> scenarioCount,
"Failures" -> failures,
"ObservedFailureRate" -> N[failures/scenarioCount],
"OneSided95PercentWilsonUpperBound" ->
wilsonUpperBound[failures, scenarioCount],
"WorstAbsX" -> Max[results[[All, "maxAbsX"]]],
"WorstAbsU" -> Max[results[[All, "maxAbsU"]]],
"WorstFinalAbsX" -> Max[results[[All, "finalAbsX"]]],
"TotalFallbackActivations" -> Total[results[[All, "fallbackActivations"]]]
|>;
Dataset[summary]
Export["Chapter26_Lesson5_results.csv", Normal[Dataset[results]]];
13. Evidence Review Checklist
- Is the intended function and operating domain explicit?
- Are hazards traced to adaptive-control and monitor requirements?
- Are adaptive parameters, rates, reset rules, and persistence bounded?
- Are all assumptions in the stability proof verified or monitored?
- Is the fallback controller valid over the complete recovery domain?
- Are switch latency, hysteresis, and transition transients bounded?
- Are sampling, precision, overflow, timing, and stale-data behaviors specified?
- Does the test campaign cover boundaries, modes, faults, low excitation, noise, and saturation?
- Are random campaigns reproducible and statistically interpreted?
- Are production code, tools, models, parameters, and results configuration controlled?
- Is verification independence commensurate with safety criticality?
- Does every change trigger documented impact analysis and regression evidence?
14. Problems and Solutions
Problem 1 (Zero-Failure Confidence): A campaign executes \(N=3000\) independent scenarios and observes no failures. Compute the exact one-sided 95% upper confidence bound on the scenario-distribution failure probability.
Solution:
\[ p_U=1-0.05^{1/3000}. \]
Using \(\ln(0.05)\approx-2.9957\),
\[ 0.05^{1/3000}= \exp\left(\frac{-2.9957}{3000}\right) \approx 0.999002, \qquad p_U\approx9.98\times10^{-4}. \]
Thus zero failures in 3000 trials supports a failure probability below approximately \(10^{-3}\) for the sampled distribution, not a universal system hazard probability.
Problem 2 (Projection Invariance): For scalar parameter bounds \(\Theta_c=[\theta_{\min},\theta_{\max}]\), define
\[ \operatorname{Proj}(\hat\theta,y)= \begin{cases} 0, & \hat\theta=\theta_{\min}\ \land\ y<0,\\ 0, & \hat\theta=\theta_{\max}\ \land\ y>0,\\ y, & \text{otherwise}. \end{cases} \]
Prove that a continuous trajectory initialized in \(\Theta_c\) cannot leave the interval.
Solution: At the lower boundary, every outward velocity has \(y<0\) and is replaced by zero; all permitted velocities are therefore nonnegative. At the upper boundary, every outward velocity has \(y>0\) and is replaced by zero; all permitted velocities are nonpositive. The vector field lies in the tangent cone of the closed interval at every boundary point. By the positive-invariance condition for closed convex sets, \(\hat\theta(t)\in\Theta_c\) for all future time.
Problem 3 (Monitor Margin): A state constraint is \(h(x)=|x|-2\leq0\). The total monitor and switch latency is \(L_M=40\text{ ms}\), and the worst-case state rate before fallback takes effect is \(|\dot x|\leq3\). A model-error allowance of 0.08 is required. Find a conservative warning threshold.
Solution: The growth allowance is \(3\times0.04=0.12\). Adding model allowance gives \(\delta=0.20\). Therefore the monitor should trigger no later than
\[ |x|\geq 2-0.20=1.80. \]
Additional actuator and computation uncertainties would require a larger margin.
Problem 4 (Discrete Projection Order): Compare \(\hat\theta_{k+1}=\Pi(\hat\theta_k+T_s y_k)\) with updating first and checking the bound only on the next sample. Why is the latter unacceptable for a hard parameter requirement?
Solution: Post-update projection guarantees the stored value at every completed update lies in the certified set. Delayed checking permits one full sample with an out-of-bound gain. During that sample, the controller can command an unverified value, invalidating both the parameter-bound requirement and any proof that assumes the bound. Therefore the saturation/projection operation must be part of the atomic update before the parameter becomes visible to the control calculation.
Problem 5 (Traceability Gap): A test demonstrates that the adaptive parameter remains bounded, but no high-level requirement specifies reset behavior after a sensor fault. Explain the assurance gap and formulate a testable requirement.
Solution: Parameter boundedness does not determine whether a learned value contaminated by faulty data is retained, reset, or re-used. The system can therefore remain inside numerical bounds while starting the next mode with an unsafe effective controller. A testable requirement is: “When sensor-validity predicate \(v_s\) becomes false for more than \(T_f\), adaptation shall be inhibited within \(T_i\), the baseline controller shall be selected within \(T_b\), and \(\hat\theta\) shall be reset to the approved initialization value before any re-engagement.” Verification then measures each latency, output transition, and reset value.
15. Summary
Practical certification and validation of adaptive control require a structured assurance case rather than a claim based solely on stability or simulation. The adaptive authority must be bounded; assumptions must be verified or monitored; fallback behavior and mode transitions must be explicitly designed; implementation details such as sampling, timing, arithmetic, and reset logic must be specified; and evidence must span analytical, software, hardware, and vehicle levels. Statistical testing quantifies performance over a declared scenario distribution, but cannot by itself establish extremely low safety-hazard probabilities. Aerospace and automotive frameworks differ in terminology and regulatory process, yet both demand traceable requirements, configuration-controlled evidence, independent verification, and disciplined change management.
16. References
Theoretical and verification papers
- 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.
- Ioannou, P.A., & Kokotovic, P.V. (1984). Instability analysis and improvement of robustness of adaptive control. Automatica, 20(5), 583–594.
- Narendra, K.S., & Annaswamy, A.M. (1987). Persistent excitation in adaptive systems. International Journal of Control, 45(1), 127–160.
- Pomet, J.-B., & Praly, L. (1992). Adaptive nonlinear regulation: estimation from the Lyapunov equation. IEEE Transactions on Automatic Control, 37(6), 729–740.
- Prajna, S., Jadbabaie, A., & Pappas, G.J. (2007). A framework for worst-case and stochastic safety verification using barrier certificates. IEEE Transactions on Automatic Control, 52(8), 1415–1428.
- Ames, A.D., Xu, X., Grizzle, J.W., & Tabuada, P. (2017). Control barrier function based quadratic programs for safety critical systems. IEEE Transactions on Automatic Control, 62(8), 3861–3876.
Assurance and standards context
- Federal Aviation Administration (2017). AC 20-115D: Airborne Software Development Assurance Using EUROCAE ED-12 and RTCA DO-178.
- Federal Aviation Administration and NASA Langley Research Center (2016). Verification of Adaptive Systems, DOT/FAA/TC-16/4.
- Nguyen, N.T. (2018). Verification and validation challenges for adaptive flight control systems. NASA Technical Reports Server presentation 20180001223.
- ISO (2018). ISO 26262: Road Vehicles—Functional Safety.
- ISO (2022). ISO 21448: Road Vehicles—Safety of the Intended Functionality.
- United Nations Economic Commission for Europe (2021). UN Regulation No. 155: Cyber Security and Cyber Security Management System.
- United Nations Economic Commission for Europe (2021). UN Regulation No. 156: Software Update and Software Updates Management System.
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.