plot: longrun A4 fig + visible route2 hack≡0 line + committed CSV data source

plot_dynamics now dumps the plotted series to <out>.csv next to the png so the
figure is reproducible from a tracked artifact (logs/ and out/runs/ are gitignored).
ylim floor -0.035 + 'hack ≡ 0' annotation so route2's pinned-at-zero hack line is
visible rather than hidden under the axis.

Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
This commit is contained in:
wassname
2026-06-02 23:19:06 +00:00
parent 19544b3f06
commit bfa2b82aba
+30 -1
View File
@@ -222,6 +222,24 @@ def _series_panel(ax, runs, cols, colors, ylim, label_series=False):
ax.set_ylim(*ylim)
def dump_data(runs: list[dict], out: Path) -> Path:
"""Write the plotted series to a tidy CSV next to the figure so the figure is
reproducible from a committed artifact -- logs/ and out/runs/ are gitignored,
this CSV is not (it lands in out/figs/, which is tracked)."""
csv = out.with_suffix(".csv")
lines = ["arm,seed,step,hack,solve"]
for r in runs:
arm = classify(r)
hk = r.get("hack_s"); sv = r.get("gt_s")
for i, step in enumerate(r["steps"]):
h = hk[i] if hk is not None and i < len(hk) else float("nan")
s = sv[i] if sv is not None and i < len(sv) else float("nan")
lines.append(f"{arm},{r['seed']},{int(step)},{h},{s}")
csv.write_text("\n".join(lines) + "\n")
logger.info(f"wrote {csv} ({len(runs)} runs, reproducibility source)")
return csv
def plot(runs: list[dict], out: Path) -> None:
by_arm: dict[str, list[dict]] = defaultdict(list)
for r in runs:
@@ -229,6 +247,7 @@ def plot(runs: list[dict], out: Path) -> None:
arms = [a for a in ARM_ORDER if a in by_arm]
if not arms:
raise SystemExit("no runs classified into arms")
dump_data(runs, out)
fig, axes = plt.subplots(1, len(arms), figsize=(3.0 * len(arms), 2.6),
sharex=True, sharey=True, squeeze=False)
@@ -237,7 +256,17 @@ def plot(runs: list[dict], out: Path) -> None:
rs = by_arm[arm]
n_seed = len({r["seed"] for r in rs})
ax.set_title(f"{arm}\n(n={n_seed} seed{'s' if n_seed > 1 else ''})", fontsize=9)
_series_panel(ax, rs, RATE_COLS, RATE_COLORS, ylim=(0, 1), label_series=(col == 0))
# ylim floor slightly below 0 so a pinned-at-zero series (route2 hack) draws
# ABOVE the axis line instead of hiding under it -- the whole result is that
# red sits on zero, so it must be visible, not absent.
_series_panel(ax, rs, RATE_COLS, RATE_COLORS, ylim=(-0.035, 1.0), label_series=(col == 0))
# If hack is pinned at zero all panel, say so -- else "no red line" reads as
# a plotting bug rather than the finding.
hk = [r["hack_s"] for r in rs if "hack_s" in r]
if hk and np.nanmax([np.nanmax(h) for h in hk]) < 0.02:
ax.annotate("hack ≡ 0", (0.04, 0.0), xycoords=("axes fraction", "data"),
color=RATE_COLORS["hack_s"], fontsize=8, va="bottom",
xytext=(0, 3), textcoords="offset points")
ax.set_xlabel("optimizer step")
onsets = [s for r in rs if (s := _onset(r["steps"], r["hack_s"])) is not None]
if onsets: