"""Ladder: what the loop adds over the base model and the same implant applied once (feedforward). Same 250-item MBPP eval throughout. Per-arm colors follow fig_phase/fig_kcurves; easy pastel, hard dark, overall neutral. """ from pathlib import Path import matplotlib matplotlib.use("Agg") import matplotlib.pyplot as plt OUT = Path(__file__).resolve().parent.parent / "results-loop" # arm overall easy hard color ROWS = [ ("base model (k=0)", 0.488, 0.984, 0.036, "#8a8f98"), ("+ FF implant\n(same adapter, applied once)", 0.496, 0.934, 0.179, "#8a8f98"), ("+ untrained loop (best k)", 0.476, 0.892, 0.179, "#8a8f98"), ("+ trained loop, random-depth\n(randk, k=8)", 0.512, 0.902, 0.393, "#2b6cb0"), ("+ trained loop, curriculum\n(merge, k=4)", 0.512, 0.885, 0.464, "#2b6cb0"), ("+ plan-distilled implant\n(no recurrence; 8-seed mean)*", 0.555, 0.919, 0.457, "#b7791f"), ] fig, ax = plt.subplots(figsize=(8.6, 4.6)) H = 0.22 for i, (name, ov, easy, hard, c) in enumerate(ROWS): y = len(ROWS) - 1 - i ax.barh(y + H, hard, height=H, color=c, zorder=3) ax.barh(y, easy, height=H, color=c, alpha=0.32, zorder=3) ax.barh(y - H, ov, height=H, color="#c9ccd1", zorder=3) for v, dy in ((hard, H), (easy, 0), (ov, -H)): ax.text(v + 0.012, y + dy, f"{v:.1%}", va="center", fontsize=8, color="#333") ax.set_yticks([len(ROWS) - 1 - i for i in range(len(ROWS))]) ax.set_yticklabels([r[0] for r in ROWS], fontsize=9) ax.set_xlim(0, 1.09) ax.set_xlabel("pass@1", fontsize=9) ax.xaxis.set_major_formatter(lambda x, _: f"{x:.0%}") import matplotlib.patches as mpatches ax.legend(handles=[ mpatches.Patch(color="#555a61", label="hard (plan-dependent, n=28)"), mpatches.Patch(color="#555a61", alpha=0.32, label="easy (n=122)"), mpatches.Patch(color="#c9ccd1", label="overall (n=250)"), ], fontsize=8, frameon=False, loc="lower right") ax.grid(True, axis="x", color="#ececec", lw=0.7) ax.set_axisbelow(True) for s in ("top", "right"): ax.spines[s].set_visible(False) ax.set_title("What recurrence adds — and what distillation matches without it\n" "(MBPP; FF = trained adapter without recurrence)", fontsize=10.5) fig.text(0.13, -0.015, "*distill rows evaluated on the 500-item set (hard " "n=55, base hard 3.6-5.5%); all other rows on the 250-item set. " "Distill trains the same adapter on written plans, deploys with " "zero loops.", fontsize=7.5, color="#555") fig.tight_layout() fig.savefig(OUT / "fig_loop_vs_ff.png", dpi=140, facecolor="white", bbox_inches="tight") print("wrote", OUT / "fig_loop_vs_ff.png")