Bifurcation-Calibrated Stale-Gradient Controller / bifurcation_controller.py
Failed on benchmark
1"""Small MVP of bifurcation-calibrated delayed two-mode control.
2
3The estimator fits the leading displacement model
4 D(y) = V*y**(2*n) + kappa*mu/(abs(y)**(2*q)+epsilon)
5from section-return observations. The controller uses the resulting safety
6bound mu_max = r**M*abs(V)/abs(kappa) to cap a requested delay.
7"""
8from dataclasses import dataclass
9import numpy as np
10
11@dataclass
12class Calibration:
13 n: int
14 q: int
15 V: float
16 kappa: float
17 radius: float
18 @property
19 def M(self):
20 return 2 * (self.n + self.q)
21 @property
22 def mu_max(self):
23 if self.V >= 0 or self.kappa <= 0:
24 return 0.0
25 return self.radius ** self.M * abs(self.V) / self.kappa
26 def predicted_amplitude(self, mu):
27 if self.V >= 0 or self.kappa <= 0:
28 return np.nan
29 return (self.kappa * mu / abs(self.V)) ** (1.0 / self.M)
30
31def fit_return_map(y, y_next, mu, n=1, q=1, radius=1.0, epsilon=1e-12):
32 """Least-squares estimate of V and positive kappa from return pairs."""
33 y=np.asarray(y, dtype=float); y_next=np.asarray(y_next, dtype=float)
34 mu=np.broadcast_to(np.asarray(mu, dtype=float), y.shape)
35 D=y_next-y
36 A=np.column_stack((y**(2*n), mu/(np.abs(y)**(2*q)+epsilon)))
37 coef, *_ = np.linalg.lstsq(A, D, rcond=None)
38 return Calibration(n, q, float(coef[0]), float(abs(coef[1])), radius)
39
40class DelayedTwoModeController:
41 """Gate mode R from a delayed scalar section coordinate; otherwise mode L."""
42 def __init__(self, base_lr, requested_delay, calibration=None):
43 self.base_lr=float(base_lr); self.requested_delay=int(requested_delay)
44 self.calibration=calibration
45 self.delay=self.safe_delay()
46 self.buffer=[0.0]*(self.delay+1)
47 def safe_delay(self):
48 if self.calibration is None or self.calibration.mu_max <= 0:
49 return 0
50 # mu is approximated by delay times base learning rate.
51 return max(0, min(self.requested_delay,
52 int(self.calibration.mu_max/self.base_lr)))
53 def mode(self, section_value):
54 self.buffer.append(float(section_value))
55 gate=self.buffer[-1-self.delay]
56 return 'R' if gate > 0 else 'L'