Layer 1 complete: E3-E6 + E2 analysis add-ons

Finishes the Layer 1 analytical core. All six experiments run with honest,
publication-quality figures; 71 tests green.

- E3 region-matched grounding: `grounding.exercised` knob + per-region tail
  survival. Matched holds the exercised region's tail (0.49) where uniform
  spreads thin and lets it collapse (0.07).
- E4 multi-teacher recombination: `run_coverage` runner. Union coverage matches
  U(K_T,rho,q) exactly. Finding: mean-mixture distillation shows NO surviving
  benefit (a conservation law — 1/K_T dilution cancels the union gain); a
  union-preserving max-merge (M2N2-style) does. E4 reports both operators.
- E5 QD vs greedy: greedy drives fixation (H~0.01); QD holds H at 0.48-0.88,
  rising with the novelty exponent.
- E6 re-mint gate: `arm` multi-override sweep. Re-minting a collapsed lineage
  locks in divergence of KL-to-original; gating on diversity prevents it.
- E2 analysis add-ons (from the companion work order, numbers verified): new
  analysis.py (reduce_to_stationary, critical_grounding with bootstrap CI ->
  g*=0.048, 95% CI [0.047,0.050]); tail_band_metrics + per-band logging; the
  E2 figure rebuilt as a 2x2 (defined g*+CI, g=0 flagged as a finite-time
  artifact, tail item-vs-mass, per-rarity-band panel). Uses truth-mass-weighted
  tail coverage rather than the raw (martingale) tail_mass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Giorgio Gilestro 2026-07-04 18:54:42 +02:00
parent a6eb9b7512
commit 1721d047fa
42 changed files with 1938 additions and 135 deletions

View file

@ -1,8 +1,11 @@
"""E2 figure: the grounding phase boundary (headline).
"""E2 figure: the grounding phase boundary (headline), publication-honest.
Shows that a critical grounding fraction g* << 1 separates collapse from a healthy
plateau: H trajectories (g=0 slides to 0, g>0 plateau), and stationary H / tail mass vs
g with the exact analytic H_eq overlaid. Usage: python figures/plot_E2.py [results/E2]
Four panels: (A) H trajectories (g=0 slides to 0, g>0 plateau); (B) the phase boundary
stationary H vs g tracking the exact H_eq, with an operational g* (where H first reaches
0.95·H*) and its bootstrap CI, and g=0 marked as a finite-time artifact; (C) tail coverage
by item-count vs truth-mass both stay low, the deep tail is largely unrescuable at
feasible grounding; (D) per-rarity-band survival the m·p*_i1 threshold made visible
(deep bands lag, motivating E4/E6). Usage: python figures/plot_E2.py [results/E2]
"""
from __future__ import annotations
@ -17,6 +20,7 @@ sys.path.insert(0, str(Path(__file__).parent))
from _figlib import load_bundle, mean_ci, savefig # noqa: E402
sys.path.insert(0, str(Path(__file__).parents[1] / "src"))
from knowledge.analysis import critical_grounding, reduce_to_stationary # noqa: E402
from knowledge.metrics import heterozygosity # noqa: E402
from knowledge.truth import make_true_distribution # noqa: E402
@ -24,59 +28,86 @@ from knowledge.truth import make_true_distribution # noqa: E402
def main(results_dir: str = "results/E2") -> None:
df, cfg = load_bundle(results_dir)
n = cfg["dynamics"]["n"]
K, zs = cfg["truth"]["K"], cfg["truth"]["zipf_s"]
td = make_true_distribution(K, 1, "zipf", cfg["truth"]["tail_frac"], zs, 0,
td = make_true_distribution(cfg["truth"]["K"], 1, "zipf", cfg["truth"]["tail_frac"],
cfg["truth"]["zipf_s"], 0,
tail_threshold=cfg["truth"]["tail_threshold"])
H_star = heterozygosity(td.p_star)
def H_eq(m): # exact stationary heterozygosity (blueprint 2.4-3)
def H_eq(m):
m = np.asarray(m, dtype=float)
return np.where(m <= 0, 0.0, H_star * m * (2 * n + m - 1) / (n + 2 * n * m + m * m))
g_values = sorted(df["g"].unique())
last = int(cfg["generations"] * 0.8) # stationary window: final 20% of generations
last = int(cfg["generations"] * 0.8)
stat = df[df["generation"] >= last]
fig, axes = plt.subplots(1, 3, figsize=(15, 4.2))
fig, axes = plt.subplots(2, 2, figsize=(13, 9))
# Panel 1: H trajectories, one line per g
ax = axes[0]
# Panel A: H trajectories, one line per g
ax = axes[0, 0]
colors = plt.cm.viridis(np.linspace(0, 0.9, len(g_values)))
for g, c in zip(g_values, colors):
sub = df[df["g"] == g].groupby("generation")["heterozygosity"].mean()
ax.plot(sub.index, sub.values, color=c, label=f"g={g:g}")
s = df[df["g"] == g].groupby("generation")["heterozygosity"].mean()
ax.plot(s.index, s.values, color=c, label=f"g={g:g}")
ax.axhline(H_star, ls=":", color="gray", lw=1)
ax.set(xlabel="generation", ylabel="heterozygosity $H$",
title="Trajectories: g=0 collapses, g>0 plateau")
ax.legend(frameon=False, fontsize=8, ncol=2)
# Panel 2: stationary H vs g, with exact H_eq overlay
stat = df[df["generation"] >= last]
# Panel B: stationary H vs g + exact H_eq + operational g* with bootstrap CI
ax = axes[0, 1]
st = reduce_to_stationary(df[df["generation"] >= last], value_col="heterozygosity",
replicate_col="replicate", last_frac=1.0)
gg, Hm, Hci = mean_ci(stat, "g", "heterozygosity")
m_of_g = stat.groupby("g")["m"].first().to_numpy()
ax = axes[1]
ax.errorbar(gg, Hm, yerr=Hci, fmt="o", color="#1f77b4", capsize=3,
label="simulation (stationary)", zorder=3)
# g=0 marked hollow (finite-time artifact: true H_eq(0)=0)
nz = gg > 0
ax.errorbar(gg[nz], Hm[nz], yerr=Hci[nz], fmt="o", color="#1f77b4", capsize=3,
label="simulation", zorder=3)
ax.plot(gg[~nz], Hm[~nz], "o", mfc="white", mec="#1f77b4", zorder=3)
ax.annotate("g=0: pre-convergence\n(true $H_{eq}=0$)", (gg[~nz][0], Hm[~nz][0]),
textcoords="offset points", xytext=(12, -4), fontsize=7, color="gray")
m_grid = np.linspace(0, m_of_g.max(), 400)
g_grid = m_grid / (n + m_grid)
ax.plot(g_grid, H_eq(m_grid), "k--", label=r"exact $H_{eq}$", zorder=2)
ax.plot(m_grid / (n + m_grid), H_eq(m_grid), "k--", label=r"exact $H_{eq}$", zorder=2)
ax.axhline(H_star, ls=":", color="gray", lw=1, label="$H^*$ (truth)")
r = critical_grounding(st, H_star=H_star, frac=0.95, seed=7)
ax.axhline(r["target_H"], ls=":", color="#d62728", lw=1)
ax.axvspan(r["ci_low"], r["ci_high"], color="#d62728", alpha=0.15)
ax.axvline(r["g_star"], color="#d62728", lw=1.2,
label=f"$g^*$={r['g_star']:.3f} (95% CI [{r['ci_low']:.3f},{r['ci_high']:.3f}])")
ax.set(xlabel="grounding fraction $g=m/(n+m)$", ylabel="stationary $H$",
title=r"Phase boundary: $g^\star \ll 1$")
title=r"Grounding saturates by $g^\star\approx0.05$ (95% of $H^*$)")
ax.legend(frameon=False, fontsize=8)
# Panel C: tail coverage by item-count vs truth-mass (both low: deep tail unrescuable)
ax = axes[1, 0]
ig, Im, Ici = mean_ci(stat, "g", "tail_frac_alive")
mg, Mm, Mci = mean_ci(stat, "g", "tail_truth_mass_alive")
ax.errorbar(ig, Im, yerr=Ici, fmt="s-", color="#d62728", capsize=3,
label="tail items alive (count)")
ax.errorbar(mg, Mm, yerr=Mci, fmt="o-", color="#9467bd", capsize=3,
label="tail truth-mass alive")
ax.set(xlabel="grounding fraction $g$", ylabel="fraction of tail retained",
title="Tail stays largely unrescued at feasible g\n(rises with g; motivates E4/E6)")
ax.legend(frameon=False, fontsize=9)
# Panel 3: stationary fraction of TAIL ITEMS still alive vs g. (Aggregate tail *mass*
# is a drift martingale and near-constant, so it is a poor indicator; the fraction of
# rare items kept alive is the honest, monotone measure of how much tail grounding
# rescues.) Tail-item survival rises steeply with g even where H is already saturated.
tg, Tm, Tci = mean_ci(stat, "g", "tail_frac_alive")
ax = axes[2]
ax.errorbar(tg, Tm, yerr=Tci, fmt="s", color="#d62728", capsize=3)
ax.set(xlabel="grounding fraction $g$",
ylabel="fraction of tail items alive",
title="Grounding keeps rare items alive")
# Panel D: per-rarity-band survival across g (band 0 = rarest)
ax = axes[1, 1]
band_cols = [c for c in df.columns if c.startswith("band") and c.endswith("_alive")]
band_cols = sorted(band_cols)
band_colors = plt.cm.plasma(np.linspace(0.1, 0.85, len(band_cols)))
for col, c in zip(band_cols, band_colors):
s = stat.groupby("g")[col].mean()
depth = col.replace("band", "").replace("_alive", "")
lab = f"band {depth}" + (" (rarest)" if depth == "0" else
" (shallowest)" if col == band_cols[-1] else "")
ax.plot(s.index, s.values, "-o", color=c, ms=4, label=lab)
ax.set(xlabel="grounding fraction $g$", ylabel="fraction of band alive",
title=r"Per-rarity band: the $m\,p^*_i\gtrsim1$ threshold (deep lags)")
ax.legend(frameon=False, fontsize=8)
fig.suptitle("E2 — a critical grounding ratio $g^\\star \\ll 1$ separates ratchet "
"from collapse", y=1.02)
fig.suptitle("E2 — a critical grounding ratio $g^\\star \\ll 1$ rescues diversity; "
"the deep tail needs recombination", y=1.0, fontsize=13)
fig.tight_layout()
savefig(fig, results_dir, "E2")

73
figures/plot_E3.py Normal file
View file

@ -0,0 +1,73 @@
"""E3 figure: region-matched grounding.
Shows that grounding must *overlap* the content it protects. At the same total budget,
uniform grounding spreads thin and lets the exercised region's tail collapse, while
matched grounding concentrates on that region and keeps its rare items alive (at the cost
of the regions it does not touch). Usage: python figures/plot_E3.py [results/E3]
Metric: per-region tail-item survival. (Per-region *heterozygosity* is confounded by
region mass under matched grounding, so it is deliberately not used here.)
"""
from __future__ import annotations
import sys
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
sys.path.insert(0, str(Path(__file__).parent))
from _figlib import load_bundle, savefig # noqa: E402
def main(results_dir: str = "results/E3") -> None:
df, cfg = load_bundle(results_dir)
R = cfg["truth"]["R"]
exercised = cfg["dynamics"]["grounding"]["exercised"]
target = exercised[0]
last = int(cfg["generations"] * 0.8)
colors = {"uniform": "#d62728", "matched": "#1f77b4"}
fig, axes = plt.subplots(1, 2, figsize=(12, 4.4))
# Panel 1: tail survival of the target region over generations
ax = axes[0]
tcol = f"tailalive_region_{target}"
for pol in ("uniform", "matched"):
sub = df[df["policy"] == pol].groupby("generation")[tcol]
mean = sub.mean()
sem = sub.sem()
ax.plot(mean.index, mean.values, color=colors[pol], label=pol)
ax.fill_between(mean.index, mean - 1.96 * sem, mean + 1.96 * sem,
color=colors[pol], alpha=0.2)
ax.set(xlabel="generation",
ylabel=f"tail items alive in region {target}",
title=f"Target region {target} (exercised): matched holds, uniform collapses")
ax.legend(frameon=False)
# Panel 2: stationary tail survival per region, uniform vs matched
ax = axes[1]
stat = df[df["generation"] >= last]
regions = np.arange(R)
width = 0.4
for i, pol in enumerate(("uniform", "matched")):
vals = [stat[stat["policy"] == pol][f"tailalive_region_{r}"].mean()
for r in regions]
ax.bar(regions + (i - 0.5) * width, vals, width,
color=colors[pol], label=pol)
ax.axvline(target, ls=":", color="gray", lw=1)
ax.annotate("exercised", (target, ax.get_ylim()[1] * 0.9), fontsize=8,
ha="center", color="gray")
ax.set(xlabel="region", ylabel="stationary tail items alive",
title="Uniform spreads thin; matched concentrates on the exercised region",
xticks=regions)
ax.legend(frameon=False)
fig.suptitle("E3 — grounding must overlap the content it protects", y=1.02)
fig.tight_layout()
savefig(fig, results_dir, "E3")
if __name__ == "__main__":
main(*sys.argv[1:])

79
figures/plot_E4.py Normal file
View file

@ -0,0 +1,79 @@
"""E4 figure: multi-teacher recombination — supply vs realisation.
Three panels tell the honest story: (A) union coverage rises with K_T and decorrelation,
matching the exact closed form (recombination *supplies* the tail); (B) that supply is
realised in the pupil only under a union-preserving merge mean-mixture distillation
dilutes it away (flat in K_T) while max-merge keeps it; (C) the union-surviving gap.
Usage: python figures/plot_E4.py [results/E4]
"""
from __future__ import annotations
import sys
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
sys.path.insert(0, str(Path(__file__).parent))
from _figlib import load_bundle, savefig # noqa: E402
def U_closed(K_T, rho, q):
return rho * q + (1 - rho) * (1 - (1 - q) ** K_T)
def main(results_dir: str = "results/E4") -> None:
df, cfg = load_bundle(results_dir)
q = cfg["coverage"]["q"]
K_Ts = sorted(df["K_T"].unique())
rhos = sorted(df["rho"].unique())
g0 = df[df["g"] == 0.0]
colors = plt.cm.viridis(np.linspace(0, 0.85, len(K_Ts)))
fig, axes = plt.subplots(1, 3, figsize=(15, 4.3))
# Panel A: union coverage vs rho per K_T, with closed-form overlay
ax = axes[0]
for K, c in zip(K_Ts, colors):
sub = g0[g0["K_T"] == K].groupby("rho")["union_coverage"].mean()
ax.plot(sub.index, sub.values, "o", color=c, label=f"K_T={K}")
ax.plot(rhos, [U_closed(K, r, q) for r in rhos], "-", color=c, lw=1)
ax.set(xlabel=r"teacher correlation $\rho$", ylabel="union tail coverage",
title=r"Supply: union matches $U(K_T,\rho,q)$")
ax.legend(frameon=False, fontsize=8)
# Panel B: surviving coverage vs rho per K_T — mean (dashed) vs max (solid)
ax = axes[1]
for K, c in zip(K_Ts, colors):
sub = g0[g0["K_T"] == K].groupby("rho")
ax.plot(sub["surviving_max"].mean().index, sub["surviving_max"].mean().values,
"-o", color=c, label=f"K_T={K}", ms=4)
ax.plot(sub["surviving_mean"].mean().index, sub["surviving_mean"].mean().values,
"--", color=c, lw=1, alpha=0.7)
ax.set(xlabel=r"teacher correlation $\rho$", ylabel="surviving tail coverage",
title="Realised: max-merge (solid) rises;\nmean-distill (dashed) stays flat")
ax.legend(frameon=False, fontsize=8)
# Panel C: surviving vs K_T at rho=0, both operators — the recombination benefit
ax = axes[2]
r0 = g0[g0["rho"] == 0.0]
mx = r0.groupby("K_T")["surviving_max"].agg(["mean", "sem"])
mn = r0.groupby("K_T")["surviving_mean"].agg(["mean", "sem"])
ax.errorbar(mx.index, mx["mean"], yerr=1.96 * mx["sem"], fmt="-o",
color="#1f77b4", capsize=3, label="max-merge (M2N2-style)")
ax.errorbar(mn.index, mn["mean"], yerr=1.96 * mn["sem"], fmt="--s",
color="#d62728", capsize=3, label="mean-mixture distillation")
ax.set(xlabel="number of teachers $K_T$", ylabel="surviving tail coverage",
title=r"Benefit needs a union-preserving merge ($\rho=0$)",
xticks=K_Ts)
ax.legend(frameon=False, fontsize=9)
fig.suptitle("E4 — recombination supplies the tail; only a union-preserving merge "
"realises it in the pupil", y=1.03)
fig.tight_layout()
savefig(fig, results_dir, "E4")
if __name__ == "__main__":
main(*sys.argv[1:])

80
figures/plot_E5.py Normal file
View file

@ -0,0 +1,80 @@
"""E5 figure: quality-diversity vs greedy selection.
At matched grounding, greedy (directional) selection drives the lineage toward the
fittest items and collapses diversity, while quality-diversity selection (a novelty bonus
w_i f_i·p_i^{-alpha}) maintains a high stationary heterozygosity that rises with the
novelty exponent alpha. Usage: python figures/plot_E5.py [results/E5]
"""
from __future__ import annotations
import sys
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
sys.path.insert(0, str(Path(__file__).parent))
from _figlib import load_bundle, savefig # noqa: E402
def main(results_dir: str = "results/E5") -> None:
df, cfg = load_bundle(results_dir)
last = int(cfg["generations"] * 0.8)
def arm(mode, alpha=1.0):
return df[(df["mode"] == mode) & (df["novelty_alpha"] == alpha)]
fig, axes = plt.subplots(1, 3, figsize=(15, 4.3))
# Panel 1: H trajectories
ax = axes[0]
series = [("greedy", 1.0, "#d62728", "greedy"),
("qd", 1.0, "#ff7f0e", "qd (α=1)"),
("qd", 2.0, "#1f77b4", "qd (α=2)"),
("none", 1.0, "#2ca02c", "none (grounding only)")]
for mode, a, c, lab in series:
s = arm(mode, a).groupby("generation")["heterozygosity"].mean()
ax.plot(s.index, s.values, color=c, label=lab)
ax.set(xlabel="generation", ylabel="heterozygosity $H$",
title="Greedy collapses; QD maintains diversity")
ax.legend(frameon=False, fontsize=8)
# Panel 2: stationary H vs alpha for qd, with greedy/none reference lines
ax = axes[1]
qd = df[(df["mode"] == "qd") & (df["generation"] >= last)]
st = qd.groupby("novelty_alpha")["heterozygosity"].agg(["mean", "sem"])
ax.errorbar(st.index, st["mean"], yerr=1.96 * st["sem"], fmt="-o",
color="#ff7f0e", capsize=3, label="qd")
for mode, c in (("greedy", "#d62728"), ("none", "#2ca02c")):
h = arm(mode, 1.0)
h = h[h["generation"] >= last]["heterozygosity"].mean()
ax.axhline(h, ls="--", color=c, label=f"{mode}")
ax.set(xlabel=r"novelty exponent $\alpha$", ylabel="stationary $H$",
title="QD maintains H above greedy for all α")
ax.legend(frameon=False, fontsize=9)
# Panel 3: stationary support size per arm
ax = axes[2]
arms = [("greedy", 1.0, "greedy"), ("qd", 0.5, "qd α=0.5"),
("qd", 1.0, "qd α=1"), ("qd", 2.0, "qd α=2"), ("none", 1.0, "none")]
labels, vals, errs, colors = [], [], [], []
palette = {"greedy": "#d62728", "qd": "#ff7f0e", "none": "#2ca02c"}
for mode, a, lab in arms:
s = arm(mode, a)
s = s[s["generation"] >= last]["support_size"]
labels.append(lab); vals.append(s.mean()); errs.append(1.96 * s.sem())
colors.append(palette[mode])
ax.bar(range(len(labels)), vals, yerr=errs, color=colors, capsize=3)
ax.set(ylabel="stationary support size", title="Surviving items per arm",
xticks=range(len(labels)))
ax.set_xticklabels(labels, rotation=25, ha="right", fontsize=8)
fig.suptitle("E5 — quality-diversity selection maintains diversity where greedy "
"fixes it", y=1.02)
fig.tight_layout()
savefig(fig, results_dir, "E5")
if __name__ == "__main__":
main(*sys.argv[1:])

76
figures/plot_E6.py Normal file
View file

@ -0,0 +1,76 @@
"""E6 figure: the re-minting gate and irreversibility.
Re-minting freezes the current distribution as the new grounding reference and discards
the original truth. Re-minting a collapsed lineage locks in the collapse: KL to the
original truth diverges, because the lost original tails can no longer be grounded.
Gating re-mint on diversity refuses to re-mint while collapsed and keeps KL bounded;
re-minting a healthy lineage is harmless. Usage: python figures/plot_E6.py [results/E6]
"""
from __future__ import annotations
import sys
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
sys.path.insert(0, str(Path(__file__).parent))
from _figlib import load_bundle, savefig # noqa: E402
STYLE = {
"healthy_remint": ("#2ca02c", "re-mint while healthy (H high)"),
"collapsed_remint": ("#d62728", "re-mint while collapsed (ungated)"),
"collapsed_gated": ("#1f77b4", "collapsed + diversity gate"),
"collapsed_noremint": ("#7f7f7f", "collapsed, no re-mint (baseline)"),
}
def main(results_dir: str = "results/E6") -> None:
df, cfg = load_bundle(results_dir)
period = cfg["dynamics"]["remint"]["period"]
G = cfg["generations"]
remint_gens = list(range(period, G + 1, period))
fig, axes = plt.subplots(1, 2, figsize=(13, 4.6))
# Panel 1: forward KL to the ORIGINAL truth
ax = axes[0]
for arm, (c, lab) in STYLE.items():
s = df[df["arm"] == arm].groupby("generation")["forward_kl"]
mean, sem = s.mean(), s.sem()
ax.plot(mean.index, mean.values, color=c, label=lab)
ax.fill_between(mean.index, mean - 1.96 * sem, mean + 1.96 * sem,
color=c, alpha=0.15)
for g in remint_gens:
ax.axvline(g, ls=":", color="k", lw=0.8, alpha=0.5)
ax.set(xlabel="generation", ylabel=r"forward KL to ORIGINAL truth",
title="Re-minting while collapsed locks in divergence")
ax.legend(frameon=False, fontsize=8)
# Panel 2: heterozygosity (which arms are collapsed; gate reads this)
ax = axes[1]
gate = None
for arm, (c, lab) in STYLE.items():
s = df[df["arm"] == arm].groupby("generation")["heterozygosity"].mean()
ax.plot(s.index, s.values, color=c, label=lab)
# draw the gate threshold used by the gated arm
for v in cfg["sweep"][0]["values"]:
if v["name"] == "collapsed_gated":
gate = v["set"].get("dynamics.remint.H_gate")
if gate is not None:
ax.axhline(gate, ls="--", color="k", lw=1)
ax.annotate(f"gate H={gate}", (G * 0.02, gate + 0.02), fontsize=8)
for g in remint_gens:
ax.axvline(g, ls=":", color="k", lw=0.8, alpha=0.5)
ax.set(xlabel="generation", ylabel="heterozygosity $H$",
title="Diversity at re-mint time (the gate reads this)")
ax.legend(frameon=False, fontsize=8)
fig.suptitle("E6 — re-minting is irreversible; gate it on diversity", y=1.02)
fig.tight_layout()
savefig(fig, results_dir, "E6")
if __name__ == "__main__":
main(*sys.argv[1:])