| """Nonlinear and safety-critical control tools.""" |
|
|
| from __future__ import annotations |
|
|
| import math |
| from typing import Any |
|
|
| import numpy as np |
|
|
| from controlai_agent.registry import registry |
| from controlai_agent.verifier import verifier |
|
|
|
|
| @registry.register( |
| name="cbf_safety_filter", |
| description="Scalar Control Barrier Function (CBF) quadratic program safety filter for single integrator x_dot=u with constraint x >= x_min.", |
| parameters_schema={ |
| "type": "object", |
| "properties": { |
| "x": {"type": "number", "description": "Current state"}, |
| "u_nom": {"type": "number", "description": "Nominal desired control input"}, |
| "alpha": {"type": "number", "description": "CBF class-K gain parameter"}, |
| "x_min": {"type": "number", "description": "Safety lower bound x >= x_min"}, |
| }, |
| "required": ["x", "u_nom", "alpha", "x_min"], |
| }, |
| ) |
| def cbf_safety_filter(x: float, u_nom: float, alpha: float, x_min: float) -> dict[str, Any]: |
| h = x - x_min |
| lower_bound = -alpha * h |
| u_safe = max(u_nom, lower_bound) |
| active = bool(u_nom < lower_bound) |
| |
| v_report = verifier.verify_cbf(x, u_safe, alpha, x_min, u_nom) |
| return { |
| "h": h, |
| "cbf_lower_bound": lower_bound, |
| "u_safe": u_safe, |
| "is_constraint_active": active, |
| "verification": v_report, |
| } |
|
|
|
|
| @registry.register( |
| name="dynamic_inversion", |
| description="Compute exact Nonlinear Dynamic Inversion (NDI) input u for scalar affine system x_dot = a*x + b*u to track target error dynamics x_dot = -gain*(x - r).", |
| parameters_schema={ |
| "type": "object", |
| "properties": { |
| "a": {"type": "number", "description": "Open-loop plant coefficient a"}, |
| "b": {"type": "number", "description": "Control effectiveness coefficient b (b != 0)"}, |
| "x": {"type": "number", "description": "Current state x"}, |
| "reference": {"type": "number", "description": "Setpoint reference r"}, |
| "gain": {"type": "number", "description": "Tracking convergence gain k > 0"}, |
| }, |
| "required": ["a", "b", "x", "reference", "gain"], |
| }, |
| ) |
| def dynamic_inversion(a: float, b: float, x: float, reference: float, gain: float) -> dict[str, Any]: |
| virtual_control = -gain * (x - reference) |
| u = (virtual_control - a * x) / b |
| x_dot = a * x + b * u |
| return { |
| "virtual_control_v": virtual_control, |
| "control_input_u": u, |
| "achieved_x_dot": x_dot, |
| "residual": abs(x_dot - virtual_control), |
| } |
|
|
|
|
| @registry.register( |
| name="feedback_linearization_second_order", |
| description="Compute feedback-linearizing control input for x1_dot = x2, x2_dot = f(x1, x2) + b*u to achieve linear dynamics y_ddot + k2*y_dot + k1*y = 0.", |
| parameters_schema={ |
| "type": "object", |
| "properties": { |
| "f_drift": {"type": "number", "description": "Nonlinear drift term value f(x1, x2) at current state"}, |
| "b": {"type": "number", "description": "Control input gain b != 0"}, |
| "k1": {"type": "number", "description": "Position gain k1 > 0"}, |
| "k2": {"type": "number", "description": "Velocity damping gain k2 > 0"}, |
| "x1": {"type": "number", "description": "Current state x1 (y)"}, |
| "x2": {"type": "number", "description": "Current state x2 (y_dot)"}, |
| }, |
| "required": ["f_drift", "b", "k1", "k2", "x1", "x2"], |
| }, |
| ) |
| def feedback_linearization_second_order( |
| f_drift: float, b: float, k1: float, k2: float, x1: float, x2: float |
| ) -> dict[str, Any]: |
| v_des = -k1 * x1 - k2 * x2 |
| u = (v_des - f_drift) / b |
| closed_poles = np.roots([1.0, k2, k1]) |
| return { |
| "virtual_acceleration_v": v_des, |
| "control_input_u": u, |
| "closed_loop_poles": [[float(p.real), float(p.imag)] for p in closed_poles], |
| "is_stable": bool(np.all(np.real(closed_poles) < 0)), |
| } |
|
|