From 633bb021e28ddeaaf23940c53a06539fa92637c2 Mon Sep 17 00:00:00 2001 From: wassname Date: Tue, 2 Jun 2026 00:25:41 +0000 Subject: [PATCH] plot(dyn): dot route2's deploy-eval points so sparsity is visible route2's plotted hack/solve is the DEPLOY eval (hk_dep/slv_dep): greedy, n=64, logged every eval_ablate_every=5 steps and EMA-held flat between. The held line reads as per-step-dense and oversells route2's smoothness vs the per-step temperature-sampled (n=28) training curves the other arms plot -- an apples-to- oranges smoothness the reader shouldn't be misled by. _mark_if_sparse dots the real measured points when a series is >50% NaN; dense series (training hack_s, cos sep/leak) stay unmarked. Now the route2 curve visibly rests on ~13 eval points, not 60. Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com> --- scripts/plot_dynamics.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/scripts/plot_dynamics.py b/scripts/plot_dynamics.py index 3b1cfca..3860fc6 100644 --- a/scripts/plot_dynamics.py +++ b/scripts/plot_dynamics.py @@ -213,6 +213,19 @@ def _onset(steps: np.ndarray, hack: np.ndarray) -> int | None: return int(steps[nz[0]]) if len(nz) else None +def _mark_if_sparse(ax, x: np.ndarray, y_raw: np.ndarray, color, alpha=1.0) -> None: + """Dot the REAL measured points when a series is mostly-NaN. route2's plotted + hack/solve is the DEPLOY eval (hk_dep), sampled every eval_ablate_every steps and + EMA-held flat between -- without the dots the held line looks per-step-dense and + oversells route2's smoothness vs the per-step-sampled training curves (Tufte: + the rendering must not imply more data than was measured). Dense series (every + step finite, e.g. training hack_s, cos sep/leak) stay unmarked to avoid clutter.""" + finite = np.isfinite(y_raw) + if finite.sum() and finite.mean() < 0.5: + ax.plot(x[finite], y_raw[finite], ls="", marker="o", ms=2.5, + color=color, alpha=alpha, zorder=4) + + def _ema(y: np.ndarray, span: int = 5) -> np.ndarray: """Causal EMA, span=5. Less lag than a trailing SMA(5) since it weights recent steps more. NaNs hold the previous smoothed value (don't reset it).""" @@ -240,6 +253,7 @@ def _series_panel(ax, runs, cols, colors, ylim, label_series=False): for r in present: ys = _ema(r[col]) ax.plot(r["steps"], ys, color=color, lw=0.7, alpha=0.35, solid_capstyle="round") + _mark_if_sparse(ax, r["steps"], r[col], color, alpha=0.5) stacked.append(ys) # mean over seeds of the smoothed series (runs share the step grid within an arm) L = min(len(y) for y in stacked) @@ -332,11 +346,16 @@ def _overlay_panel(ax, by_arm, arms, key, *, label, with_onset): for r in rs: ys = _ema(r[key]) ax.plot(r["steps"], ys, color=color, lw=0.6, alpha=0.25, solid_capstyle="round") + _mark_if_sparse(ax, r["steps"], r[key], color, alpha=0.4) stacked.append(ys) L = min(len(y) for y in stacked) ym = np.nanmean(np.stack([y[:L] for y in stacked]), axis=0) xm = rs[0]["steps"][:L] ax.plot(xm, ym, color=color, lw=2.0, solid_capstyle="round") + # dot the bold mean at its REAL (pre-EMA) measured steps -- ym is EMA-held so + # it would otherwise read as per-step-dense (see _mark_if_sparse) + raw_mean = np.nanmean(np.stack([r[key][:L] for r in rs]), axis=0) + _mark_if_sparse(ax, xm, raw_mean, color) if with_onset: onsets = [s for r in rs if (s := _onset(r["steps"], r["hack_s"])) is not None] if onsets: