Barrier-Controlled Basin Switching / experiment.py
Failed on benchmark
1import json
2import math
3import random
4import numpy as np
5
6# Barrier-controlled basin switching MVP.
7# Potential U(x)=(x^2-1)^2/4 has minima +/-1 and saddle 0.
8# Delta V = U(0)-U(+/-1)=1/4; U''(min)=2, U''(saddle)=-1.
9# For dX=-U'(X)dt+sqrt(2 eps)dW, Kramers predicts
10# k(eps) ~= sqrt(2)/(2*pi)*exp(-0.25/eps).
11
12SEED = 1306
13DT = 0.01
14N_PATHS = 220
15T = 1200.0
16STEPS = int(T / DT)
17BARRIER = 0.25
18PREF = math.sqrt(2.0) / (2.0 * math.pi)
19
20
21def potential(x):
22 return 0.25 * (x*x - 1.0)**2
23
24
25def grad(x):
26 return x*x*x - x
27
28
29def simulate_rate(eps, seed, n=N_PATHS, t=T):
30 """Estimate committed basin switching rate using +/-0.5 hysteresis."""
31 rng = np.random.default_rng(seed)
32 x = np.full(n, -1.0)
33 state = np.full(n, -1, dtype=np.int8)
34 switches = 0
35 # Count only transitions after reaching the opposite committed region.
36 for _ in range(int(t / DT)):
37 x += -DT * grad(x) + math.sqrt(2.0 * eps * DT) * rng.standard_normal(n)
38 new = state.copy()
39 new[(state == -1) & (x > 0.5)] = 1
40 new[(state == 1) & (x < -0.5)] = -1
41 switches += int(np.count_nonzero(new != state))
42 state = new
43 # Each path has an opportunity to switch in either direction; this is a
44 # conservative event rate and is comparable across noise settings.
45 return switches / (n * t), switches
46
47
48def feedback_rate(q, seed, n=N_PATHS, t=T):
49 """Use eps=DeltaV/q while in the escape-to-target mode."""
50 eps = np.clip(BARRIER / q, 1e-4, 0.20)
51 return simulate_rate(float(eps), seed, n, t)
52
53
54def linear_fit(x, y):
55 return float(np.polyfit(np.asarray(x), np.asarray(y), 1)[0])
56
57
58def main():
59 random.seed(SEED)
60 np.random.seed(SEED)
61
62 # Prediction 1: log transition rate against 1/eps has slope -DeltaV.
63 eps_values = np.array([0.045, 0.055, 0.070, 0.090, 0.120, 0.160])
64 raw = []
65 for i, eps in enumerate(eps_values):
66 rate, events = simulate_rate(float(eps), SEED + i)
67 raw.append({"eps": float(eps), "rate": rate, "events": events})
68 usable = [r for r in raw if r["events"] >= 3]
69 slope = linear_fit([1/r["eps"] for r in usable], [math.log(r["rate"]) for r in usable])
70 slope_error = abs(slope + BARRIER) / BARRIER
71
72 # Prediction 2: feedback makes the exponential part exp(-q), so
73 # log(rate/PREF) versus q should have slope -1.
74 q_values = np.array([1.6, 2.0, 2.5, 3.0, 3.5, 4.0])
75 fb = []
76 for i, q in enumerate(q_values):
77 rate, events = feedback_rate(float(q), SEED + 100 + i)
78 fb.append({"q": float(q), "eps": BARRIER/float(q), "rate": rate, "events": events})
79 usable_fb = [r for r in fb if r["events"] >= 3]
80 feedback_slope = linear_fit([r["q"] for r in usable_fb],
81 [math.log(r["rate"]/PREF) for r in usable_fb])
82 feedback_error = abs(feedback_slope + 1.0)
83
84 # Prediction 3: changing q by delta q changes rate ratio by exp(-delta q).
85 q_lo, q_hi = 2.0, 3.5
86 lo = next(r for r in fb if r["q"] == q_lo)
87 hi = next(r for r in fb if r["q"] == q_hi)
88 observed_ratio = hi["rate"] / lo["rate"]
89 predicted_ratio = math.exp(-(q_hi-q_lo))
90 ratio_factor = observed_ratio / predicted_ratio if predicted_ratio else float("inf")
91
92 # Small optimizer comparison: equal Euler updates, beginning in the left
93 # basin and targeting the right basin. Fixed-noise SGD is compared with
94 # barrier feedback (escape q=2 while left, safe q=8 after reaching right).
95 def train_controller(mode, seed, steps=120000):
96 rng = np.random.default_rng(seed)
97 x = -1.0
98 reached = None
99 switches = 0
100 for k in range(steps):
101 if mode == "fixed":
102 eps = BARRIER / 3.0
103 else:
104 # Known toy barrier estimate; the current basin is identified
105 # by sign and target is the lower-loss right basin.
106 eps = BARRIER / (2.0 if x < 0 else 8.0)
107 x += -DT * grad(x) + math.sqrt(2*eps*DT) * rng.standard_normal()
108 if reached is None and x > 0.5:
109 reached = k + 1
110 if x > 0.5 and x < 0.0:
111 switches += 1
112 return reached, x, switches
113 baseline = train_controller("fixed", SEED + 500)
114 idea = train_controller("controlled", SEED + 501)
115
116 result = {
117 "known_barrier": BARRIER,
118 "kramers_prefactor": PREF,
119 "prediction_1_rate_slope": {"predicted": -BARRIER, "observed": slope,
120 "relative_error": slope_error, "data": raw},
121 "prediction_2_feedback_slope": {"predicted": -1.0, "observed": feedback_slope,
122 "absolute_error": feedback_error, "data": fb},
123 "prediction_3_ratio": {"q_low": q_lo, "q_high": q_hi,
124 "predicted_rate_ratio": predicted_ratio,
125 "observed_rate_ratio": observed_ratio,
126 "observed_over_predicted": ratio_factor},
127 "mini_optimizer": {"baseline_fixed_noise": baseline,
128 "barrier_controlled": idea,
129 "steps": 120000, "dt": DT},
130 "criterion": {"slope_tolerance_fraction": 0.20,
131 "feedback_slope_tolerance": 0.20,
132 "ratio_factor_tolerance": [0.5, 2.0]},
133 "worked": bool(slope_error <= 0.20 and feedback_error <= 0.20 and
134 0.5 <= ratio_factor <= 2.0)
135 }
136 print(json.dumps(result, indent=2))
137
138if __name__ == "__main__":
139 main()