atakan
feat: Initial open-source release of ControlAI
9936912
Raw
History Blame Contribute Delete
3.89 kB
"""Robust control and uncertain system tools."""
from __future__ import annotations
from typing import Any
import numpy as np
from controlai_agent.registry import registry
@registry.register(
name="kharitonov_stability_test",
description="Evaluate robust Hurwitz stability of a real interval polynomial using Kharitonov's Theorem (tests the 4 extreme polynomials).",
parameters_schema={
"type": "object",
"properties": {
"lower_bounds": {
"type": "array",
"items": {"type": "number"},
"description": "Lower coefficient bounds [a0^-, a1^-, a2^-, a3^-] in ascending powers",
},
"upper_bounds": {
"type": "array",
"items": {"type": "number"},
"description": "Upper coefficient bounds [a0^+, a1^+, a2^+, a3^+] in ascending powers",
},
},
"required": ["lower_bounds", "upper_bounds"],
},
)
def kharitonov_stability_test(lower_bounds: list[float], upper_bounds: list[float]) -> dict[str, Any]:
lows = np.array(lower_bounds, dtype=float)
highs = np.array(upper_bounds, dtype=float)
polys = [
[lows[0], lows[1], highs[2], highs[3]],
[highs[0], highs[1], lows[2], lows[3]],
[highs[0], lows[1], lows[2], highs[3]],
[lows[0], highs[1], highs[2], lows[3]],
]
pole_sets = []
stable_flags = []
for poly in polys:
desc_poly = poly[::-1] # descending for np.roots
roots = np.roots(desc_poly)
pole_sets.append([[float(r.real), float(r.imag)] for r in roots])
stable_flags.append(bool(np.all(np.real(roots) < 0)))
robustly_stable = all(stable_flags)
return {
"kharitonov_polynomials_ascending": polys,
"stable_flags": stable_flags,
"is_robustly_hurwitz": robustly_stable,
"poles": pole_sets,
}
@registry.register(
name="small_gain_test",
description="Apply the Small Gain Theorem: certifies closed-loop input-output stability if induced norm product ||G1|| * ||G2|| < 1.",
parameters_schema={
"type": "object",
"properties": {
"gamma1": {"type": "number", "minimum": 0.0, "description": "H-infinity or induced norm bound of operator G1"},
"gamma2": {"type": "number", "minimum": 0.0, "description": "H-infinity or induced norm bound of operator G2"},
},
"required": ["gamma1", "gamma2"],
},
)
def small_gain_test(gamma1: float, gamma2: float) -> dict[str, Any]:
product = gamma1 * gamma2
certified = bool(product < 1.0)
return {
"norm_product": product,
"is_stability_certified": certified,
"stability_margin": 1.0 - product if certified else 0.0,
}
@registry.register(
name="multiplicative_uncertainty_test",
description="Check robust stability condition max |W(jw) * T(jw)| < 1 for output multiplicative plant uncertainty over frequency samples.",
parameters_schema={
"type": "object",
"properties": {
"weights": {"type": "array", "items": {"type": "number"}, "description": "Multiplicative weight |W(jw)| samples"},
"complementary_sensitivity": {"type": "array", "items": {"type": "number"}, "description": "Nominal |T(jw)| samples"},
},
"required": ["weights", "complementary_sensitivity"],
},
)
def multiplicative_uncertainty_test(
weights: list[float], complementary_sensitivity: list[float]
) -> dict[str, Any]:
w_arr = np.array(weights, dtype=float)
t_arr = np.array(complementary_sensitivity, dtype=float)
products = w_arr * t_arr
peak = float(np.max(products))
is_satisfied = bool(peak < 1.0)
return {
"pointwise_products": products.tolist(),
"peak_weighted_product": peak,
"is_robustly_stable_on_grid": is_satisfied,
}