MachineSex/figures/plot_E3.py
Giorgio Gilestro 1721d047fa 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>
2026-07-04 18:54:42 +02:00

73 lines
2.8 KiB
Python

"""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:])