atakan
feat: Initial open-source release of ControlAI
9936912
Raw
History Blame Contribute Delete
3.95 kB
"""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)),
}