Download kronos_ml/members/control.py from KronosFE/kronos-ml: direct link, hf CLI and curl.
- Browser
- Download file 4.53 kB
-
https://huggingface.co/KronosFE/kronos-ml/resolve/main/kronos_ml/members/control.py
- Command line
-
hf download hf://KronosFE/kronos-ml/kronos_ml/members/control.py
-
curl -L -o control.py https://huggingface.co/KronosFE/kronos-ml/resolve/main/kronos_ml/members/control.py
4.53 kB
| """KAIROS — real-time closed-loop plasma control (MPC) + the safety clamp. | |
| A finite-horizon receding-horizon controller (analytic per-axis QP — no external | |
| solver) drives a reduced first-order plant to setpoint; every command is projected | |
| onto the envelope by KGATE (the model-free clamp KAIROS supplies to the spine). | |
| Includes the model-failure ("Exp-B") adversarial test: broken controllers emit | |
| deliberately-unsafe commands and the clamp lets none escape. | |
| """ | |
| from __future__ import annotations | |
| import time | |
| import numpy as np | |
| from .. import register, get | |
| from ..base import Surrogate, Prediction | |
| from .. import data, uq | |
| # reduced first-order plant (from track5 plant_model): x_{k+1}=x+alpha*(u-x)+couple+noise | |
| _ALPHA = np.array([0.20, 0.20, 0.10, 0.12, 0.05, 0.06]) | |
| _SIGMA = np.array([0.004, 0.004, 0.01, 0.01, 0.002, 0.02]) # per-step disturbance | |
| _H = 8 # MPC horizon | |
| _RHO = 0.02 # control-effort weight | |
| def _mpc(x0, ref): | |
| """Analytic finite-horizon optimal constant-u command for the diagonal | |
| first-order plant (minimises tracking over H steps + rho*effort).""" | |
| x0 = np.asarray(x0, float); ref = np.asarray(ref, float) | |
| k = np.arange(1, _H + 1) | |
| u = np.zeros(6) | |
| for i in range(6): | |
| b = (1 - _ALPHA[i]) ** k # decay of the initial-condition term | |
| c = 1 - b # coefficient on u | |
| d = x0[i] * b - ref[i] # constant term | |
| u[i] = -np.sum(c * d) / (np.sum(c * c) + _RHO * _H) | |
| return u | |
| def _plant_step(x, u_safe, rng): | |
| x = np.asarray(x, float) | |
| nxt = x + _ALPHA * (u_safe - x) + rng.normal(0, _SIGMA) | |
| nxt[2] -= 0.015 * (u_safe[5] - x[5]) # Ip -> beta_N coupling | |
| nxt[4] += 0.0012 * (u_safe[5] - x[5]) # Ip -> eps coupling | |
| return nxt | |
| class KAIROS(Surrogate): | |
| name = "KAIROS"; function = "CONTROL"; phase = 1; status = "BUILT" | |
| provenance = "TWIN" | |
| retired_by = "validated real-time plant controller (device-in-the-loop)" | |
| real_codes = ("receding-horizon MPC",) | |
| gates = ("AC-24", "AC-25") | |
| note = ("real-time closed-loop MPC control (analytic finite-horizon QP); " | |
| "supplies the model-free L4 clamp that KGATE enforces") | |
| def _predict(self, x): | |
| """x = (state6, ref6) -> next command from the MPC, clamped by KGATE.""" | |
| state, ref = x | |
| u = _mpc(state, ref) | |
| safe, ok = get("KGATE").clamp(u) | |
| in_dom = bool(np.allclose(safe, u, atol=1e-6)) | |
| return Prediction(safe, None, in_dom, | |
| note="MPC step, clamped" if not in_dom else "MPC step, in envelope") | |
| def benchmark(self): | |
| rng = np.random.default_rng(uq.SEED) | |
| clamp = get("KGATE").clamp | |
| # (1) closed-loop tracking: drive from a start state to a setpoint | |
| ref = np.array([2.2, 0.3, 3.0, 1.0, 0.2, 30.0]) | |
| x = np.array([2.0, 0.0, 2.0, 0.5, 0.1, 20.0]) | |
| for _ in range(200): | |
| safe, _ = clamp(_mpc(x, ref)); x = _plant_step(x, safe, rng) | |
| rng_scale = np.array([1, 1, 4.2, 3, 0.4, 47.21]) | |
| track_rms = float(np.sqrt(np.mean(((x - ref) / rng_scale) ** 2))) | |
| # (2) Exp-B: broken controllers emit unsafe commands; clamp must catch all | |
| from .spine import _LO, _HI | |
| adversaries = { | |
| "drift": lambda: _HI * (1 + 0.5 * rng.random(6)), | |
| "edge_seeker": lambda: _HI + 5, | |
| "nan_garbage": lambda: np.where(rng.random(6) < 0.3, np.nan, _HI * 3), | |
| } | |
| escapes = 0; steps = 0 | |
| for _, gen in adversaries.items(): | |
| for _ in range(50_000): | |
| safe, _ = clamp(gen()); steps += 1 | |
| if not (np.all(safe >= _LO - 1e-9) and np.all(safe <= _HI + 1e-9)): | |
| escapes += 1 | |
| # (3) real latency of an MPC step + clamp | |
| t0 = time.perf_counter() | |
| for _ in range(2000): | |
| clamp(_mpc(x, ref)) | |
| us = (time.perf_counter() - t0) / 2000 * 1e6 | |
| return {"member": self.name, | |
| "live_tracking_rms_frac": round(track_rms, 4), | |
| "live_expB_model_failure": {"adversaries": list(adversaries), | |
| "steps": steps, "escapes": escapes, | |
| "catch_rate": 1.0 - escapes / steps}, | |
| "live_mpc_step_us": round(us, 1), | |
| "sourced": {"file": "track5_control/clamp_activation_stats.csv", | |
| "headline": "0 escapes / 150k steps / 3000 injected (AC-25); tracking <5%"}} | |