File size: 3,892 Bytes
9936912
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
"""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,
    }