summaryrefslogtreecommitdiff
path: root/research/flossing/analyze_halt_bucket.py
diff options
context:
space:
mode:
Diffstat (limited to 'research/flossing/analyze_halt_bucket.py')
-rw-r--r--research/flossing/analyze_halt_bucket.py122
1 files changed, 122 insertions, 0 deletions
diff --git a/research/flossing/analyze_halt_bucket.py b/research/flossing/analyze_halt_bucket.py
new file mode 100644
index 0000000..2ad80fc
--- /dev/null
+++ b/research/flossing/analyze_halt_bucket.py
@@ -0,0 +1,122 @@
+"""Validate codex's λ*(h, β) = (1/h) log β framework using halted_at buckets.
+
+If the framework is right, then across halt buckets:
+- success samples should all satisfy λ_1 × h_micro × halted_at ≈ log(β_target) (a constant ≪ 0)
+- equivalently: success λ_1 should scale as ~1/halted_at
+
+We use halted_at as a proxy for "effective computation budget" (h in codex's notation).
+For each ACT step we have cycles_per_act micro-Lyapunov-steps:
+ HRM (H=2, L=2): 2*(2+1) = 6
+ TRM (H=3, L=6): 3*(6+1) = 21
+"""
+import numpy as np
+import matplotlib
+matplotlib.use("Agg")
+import matplotlib.pyplot as plt
+
+ROOT = "/home/yurenh2/rrm/research/flossing"
+
+cases = {
+ "HRM_step26040": dict(npz=f"{ROOT}/diag_joint_1k.npz", cycles_per_act=6),
+ "TRM_step13020": dict(npz=f"{ROOT}/diag_trm_step13020_512.npz", cycles_per_act=21),
+}
+
+fig, axes = plt.subplots(2, 3, figsize=(15, 9))
+
+for row, (name, meta) in enumerate(cases.items()):
+ d = np.load(meta["npz"])
+ cpa = meta["cycles_per_act"]
+ lam = d["lyap_spec"][:, 0] # top-1 Lyap per sample (per micro step)
+ succ = d["exact_correct"] > 0.5
+ halted = d["halted_at"].copy()
+ halted[halted == 0] = 16 # never-halt → used full 16 ACT steps
+
+ h_micro = halted * cpa # effective micro-step horizon
+ logbeta = lam * h_micro # log of cumulative decay over h_micro
+ beta = np.exp(np.clip(logbeta, -20, 20)) # implied β
+
+ print(f"\n=== {name} ===")
+ print(f" N={len(lam)} acc={succ.mean():.3f} cycles/ACT={cpa}")
+ print(f" λ_1 mean: succ={lam[succ].mean():+.4f} fail={lam[~succ].mean():+.4f}")
+ print(f" halted_at: succ={halted[succ].mean():.2f} fail={halted[~succ].mean():.2f}")
+ print(f" log β implied succ={logbeta[succ].mean():+.3f} fail={logbeta[~succ].mean():+.3f}")
+ print(f" β implied: succ={beta[succ].mean():.3f} fail={beta[~succ].mean():.3f}")
+
+ # By halt bucket
+ print(f" bucket n_succ n_fail λ_succ λ_fail logβ_succ logβ_fail")
+ buckets = sorted(set(halted.tolist()))
+ for b in buckets:
+ ms = (halted == b) & succ
+ mf = (halted == b) & ~succ
+ if ms.sum() + mf.sum() < 5: continue
+ lam_s = lam[ms].mean() if ms.sum() > 0 else np.nan
+ lam_f = lam[mf].mean() if mf.sum() > 0 else np.nan
+ lb_s = lam_s * b * cpa
+ lb_f = lam_f * b * cpa
+ print(f" h={b:>3} {ms.sum():>4} {mf.sum():>4} "
+ f"{lam_s:+7.4f} {lam_f:+7.4f} {lb_s:+8.3f} {lb_f:+8.3f}")
+
+ # ----- Panel A: λ vs halted_at, succ vs fail -----
+ ax = axes[row, 0]
+ ax.scatter(halted[succ] + np.random.uniform(-0.15, 0.15, succ.sum()), lam[succ],
+ s=5, alpha=0.35, c="C2", label=f"succ (n={succ.sum()})")
+ ax.scatter(halted[~succ] + np.random.uniform(-0.15, 0.15, (~succ).sum()), lam[~succ],
+ s=5, alpha=0.35, c="C3", label=f"fail (n={(~succ).sum()})")
+ # Per-bucket mean
+ means_s, means_f, bs = [], [], []
+ for b in buckets:
+ ms = (halted == b) & succ
+ mf = (halted == b) & ~succ
+ if ms.sum() >= 3: means_s.append((b, lam[ms].mean()))
+ if mf.sum() >= 3: means_f.append((b, lam[mf].mean()))
+ if means_s:
+ xs, ys = zip(*means_s); ax.plot(xs, ys, "C2o-", lw=2, ms=8, label="succ mean")
+ if means_f:
+ xf, yf = zip(*means_f); ax.plot(xf, yf, "C3o-", lw=2, ms=8, label="fail mean")
+ # 1/h reference: codex prediction λ ∝ 1/h
+ if means_s:
+ h_ref = np.array([b for b, _ in means_s])
+ # fit constant = mean of λ*h*cpa over succ
+ c = (lam[succ] * halted[succ] * cpa).mean()
+ ref = c / (h_ref * cpa)
+ ax.plot(h_ref, ref, "k--", lw=1, alpha=0.5, label=f"λ=1/h·log β (log β={c:.2f})")
+ ax.axhline(0, color="k", lw=0.5, ls=":")
+ ax.set_xlabel("halted_at (ACT steps)"); ax.set_ylabel("λ_1 (per micro-step)")
+ ax.set_title(f"{name}: λ_1 vs halt step")
+ ax.legend(fontsize=7); ax.grid(alpha=0.3)
+
+ # ----- Panel B: log β = λ × h_micro by halt bucket -----
+ ax = axes[row, 1]
+ ax.scatter(halted[succ] + np.random.uniform(-0.15, 0.15, succ.sum()), logbeta[succ],
+ s=5, alpha=0.35, c="C2", label="succ")
+ ax.scatter(halted[~succ] + np.random.uniform(-0.15, 0.15, (~succ).sum()), logbeta[~succ],
+ s=5, alpha=0.35, c="C3", label="fail")
+ if means_s:
+ xs = [b for b, _ in means_s]
+ ys = [lam[(halted==b)&succ].mean() * b * cpa for b in xs]
+ ax.plot(xs, ys, "C2o-", lw=2, ms=8, label="succ mean")
+ if means_f:
+ xf = [b for b, _ in means_f]
+ yf = [lam[(halted==b)&~succ].mean() * b * cpa for b in xf]
+ ax.plot(xf, yf, "C3o-", lw=2, ms=8, label="fail mean")
+ ax.axhline(0, color="k", lw=0.5, ls=":")
+ ax.set_xlabel("halted_at"); ax.set_ylabel("log β = λ_1 × halt × cycles/ACT")
+ ax.set_title(f"{name}: implied log β (constant ⇒ 1/h scaling holds)")
+ ax.legend(fontsize=8); ax.grid(alpha=0.3)
+
+ # ----- Panel C: histogram of implied β by succ/fail -----
+ ax = axes[row, 2]
+ bins = np.linspace(-6, 4, 50)
+ ax.hist(logbeta[succ], bins=bins, alpha=0.6, color="C2", label=f"succ μ={logbeta[succ].mean():+.2f}")
+ ax.hist(logbeta[~succ], bins=bins, alpha=0.6, color="C3", label=f"fail μ={logbeta[~succ].mean():+.2f}")
+ ax.axvline(0, color="k", lw=0.5, ls=":")
+ ax.set_xlabel("log β implied"); ax.set_ylabel("count")
+ ax.set_title(f"{name}: log β distribution")
+ ax.legend(fontsize=8); ax.grid(alpha=0.3)
+
+fig.suptitle("Codex's λ*(h, β) = (1/h) log β prediction: across halt buckets, "
+ "λ should scale as 1/h with constant log β", fontsize=11)
+fig.tight_layout()
+out = f"{ROOT}/plots_halt_bucket.png"
+fig.savefig(out, dpi=130)
+print(f"\n→ {out}")