MachineSex/paper/pnas/build.py
Giorgio Gilestro 96902e87f0 figures: publication-ready — unified, lettered, codename-free
paper/pnas/make_figs.py re-plots every panel directly from the committed
results artifacts into six single-file figures (figs/fig1..fig6.pdf):
no experiment codenames or suptitles (interpretation moved to captions),
bold panel letters, plain-language axis labels and legend entries, one
consistent style (8pt, no top/right spines). Panels: fig1 A-B (grounding
equilibrium + MNIST montage with its baked-in title cropped), fig2 A-B
(blending cancellation + Fisher-Muller), fig3 A-D (outbreeding, directed
recombination, mating breadth champion + diversity), fig4 A-C (society
ablation trajectories), fig5 A-F (speciation: analytic curve + cliff,
MLP decomposition + conflict sweep, LLM coherence + duration null),
fig6 A-D (seed-replicated merging, 7B-hard routing vs averaging,
predictive-test scatter, predictor comparison). build.py now places the
single PDFs; captions rewritten per lettered panel; in-text panel refs
updated (5B->5C-D, 5C->5E-F); stale stacked copies removed. Document
20pp -> 18pp.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BkRLcc18rwT2Lysu6PbG7v
2026-09-07 09:09:46 +01:00

192 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

r"""Build the PNAS-draft PDF from main.md (Markdown stays the source of truth).
Adapted from paper/arxiv/md2tex.py (same Markdown subset + pipe tables), with one addition: standalone
`*(FIG:name)*` markers place the publication figures produced by make_figs.py (unified, lettered,
codename-free panels re-plotted from the committed artifacts). Run make_figs.py before building when
results change.
Usage: python paper/pnas/build.py && (cd paper/pnas && tectonic main.tex)
"""
from __future__ import annotations
import re
import shutil
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
HERE = Path(__file__).resolve().parent
SRC = HERE / "main.md"
OUT = HERE / "body.tex"
# figure name -> (single publication PDF from make_figs.py, caption)
FIGURES: dict[str, tuple[list[str], str]] = {
"fig1": (["paper/pnas/figs/fig1.pdf"],
"Grounding is immigration. (A) Stationary diversity against the grounding fraction in the "
"minimal inheritance model: simulation (points, 95\\% CI) matches the exact immigration--drift "
"equilibrium (dashed). The equilibrium is smooth in $g$; $g \\approx 0.05$ marks the "
"operational threshold retaining 95\\% of source diversity in this setting (red line, "
"bootstrap CI shaded); the hollow point at $g=0$ is a finite-time value (the true equilibrium "
"is zero). (B) The same signs on real images: samples from a convolutional VAE retrained each "
"generation on its own output (rows: generations 0--15 of an ungrounded lineage) collapse "
"toward a single blurred mode; 10\\% grounding holds all thirty modes (quantified in SI)."),
"fig2": (["paper/pnas/figs/fig2.pdf"],
"Recombination in the minimal model: blending inheritance and the Fisher--Muller effect. "
"(A) Expected rare-capability survival in a child refit from $K$ uncorrelated parents: the "
"output-mean (blending) stays at the single-parent level --- the first-order cancellation --- "
"while the union operator (strongest source per item, renormalised, oracle-identified) rises "
"with parent count. (B) Multi-locus recombination of decorrelated specialists produces "
"offspring fitter than any parent, approaching the optimum as parents are added; the best "
"single parent and the blended average plateau below (mean $\\pm$ 95\\% CI)."),
"fig3": (["paper/pnas/figs/fig3.pdf"],
"Rugged (epistatic) landscapes: risk, remedy, and population structure. (A) Outbreeding "
"depression: the mean offspring of blindly recombined specialist parents falls below the best "
"parent, more steeply the more rugged the landscape (NK ruggedness $K$) and the higher the "
"recombination rate. (B) Screening candidate offspring against a verifier (directed "
"recombination) restores the gain at every ruggedness where blind recombination fails. "
"(C) Mating structure: the best champion arises at wide mate-pool breadth on smooth landscapes "
"and at intermediate breadth on rugged ones. (D) Wide breadth monotonically erodes population "
"diversity at every ruggedness (mean $\\pm$ 95\\% CI, 20 replicates)."),
"fig4": (["paper/pnas/figs/fig4.pdf"],
"The tested society: grounded evaluation, recombination, and diversity preservation make "
"complementary contributions. A finite agent population on a rugged NK landscape; selection "
"weights true fitness against conformity to the population consensus. (A) Best real fitness: "
"the full system approaches the global optimum; removing grounded evaluation collapses the "
"population onto a confident, unfit consensus; removing recombination or diversity "
"preservation strands it lower. (B) Population diversity. (C) The self-consumption signature: "
"conformity minus true fitness (mean $\\pm$ 95\\% CI, 12 replicates)."),
"fig5": (["paper/pnas/figs/fig5.pdf"],
"Model speciation at three tiers. (A) Analytic model: hybrid fitness tracks the parents while "
"lineages are compatible, then falls to inviability; the denser the incompatibilities, the "
"earlier the fall. (B) The isolation cliff: probability of hybrid inviability against "
"divergence, by incompatibility density. (C) Trained networks: the merge error barrier between "
"two MLPs before and after permutation-and-rescaling alignment --- the same-task/different-"
"start barrier is a coordinate artefact (removed by alignment); the conflicting-task barrier "
"is left essentially unchanged. (D) Sweeping the fraction of conflicting classes: the residual "
"barrier rises while merged-model accuracy falls from 0.97 to 0.03. (E) Language models (0.5B "
"LoRA children of a shared base): on shared ambiguous prompts each parent performs under its "
"own convention while the merged model falls below both --- function-specific hybrid "
"breakdown. (F) Divergence without conflict: over-training disjoint specialists from 1 to 12 "
"epochs produces no isolation; the merged model tracks or exceeds the parents throughout."),
"fig6": (["paper/pnas/figs/fig6.pdf"],
"The language-model tier. (A) Seed-replicated merging (0.5B, five seeds, fixed test sets; mean "
"$\\pm$ 95\\% CI): merged specialists exceed the best single specialist overall, and only "
"merged models are competent on every task family. (B) Hard, unsaturated tasks at 7B (single "
"run): the weight-average dilutes a fragile specialist below the best single parent; routing "
"among intact specialists preserves it. (C) The controlled predictive test (13 conditions "
"$\\times$ 3 seeds): pre-merge confidence-weighted functional conflict against merge penalty, "
"coloured by grid axis --- penalty concentrates on the conflict axis. (D) Predictor "
"comparison, $|$Spearman $\\rho|$ against merge penalty over the full grid: functional "
"measures carry signal, the tested weight-geometry baselines do not; paired differences "
"between predictors are not individually significant."),
}
UNICODE = {"": "---", "": "--", "": r"\(\rightarrow\)", "": r"\(\approx\)", "": r"\(\geq\)",
"": r"\(\gtrsim\)", "×": r"\(\times\)", "·": r"\(\cdot\)", "μ": r"\(\mu\)",
"ρ": r"\(\rho\)", "": r"\(\leq\)", "": r"\(\ll\)", "": r"\(\propto\)"}
SPECIALS = {"&": r"\&", "%": r"\%", "#": r"\#", "_": r"\_", "$": r"\$",
"~": r"\textasciitilde{}", "^": r"\textasciicircum{}"}
def esc(s: str) -> str:
s = s.replace("\\", r"\textbackslash{}")
for k, v in SPECIALS.items():
s = s.replace(k, v)
for k, v in UNICODE.items():
s = s.replace(k, v)
return s
def inline(s: str) -> str:
parts = re.split(r"(`[^`]*`)", s)
out = []
for p in parts:
if p.startswith("`") and p.endswith("`") and len(p) >= 2:
out.append(r"\texttt{" + esc(p[1:-1]) + "}")
else:
p = esc(p)
p = re.sub(r"\[([^\]]+)\]\((https?://[^)]+)\)", r"\\href{\2}{\1}", p)
p = re.sub(r"\*\*([^*]+)\*\*", r"\\textbf{\1}", p)
p = re.sub(r"\*([^*]+)\*", r"\\emph{\1}", p)
p = re.sub(r'"([^"]+)"', r"``\1''", p)
out.append(p)
return "".join(out)
def figure_env(name: str) -> str:
pdfs, caption = FIGURES[name]
src = ROOT / pdfs[0]
lines = [f"\\begin{{figure*}}[p]\\centering % {name}",
f"\\includegraphics[width=\\textwidth]{{figs/{src.name}}}",
f"\\caption{{{caption}}}\\label{{{name}}}",
"\\end{figure*}"]
return "\n".join(lines)
def convert(text: str) -> str:
lines = text.split("\n")
i = 0
while i < len(lines) and lines[i].strip() != "---":
i += 1
i += 1
blocks: list[list[str]] = []
cur: list[str] = []
for line in lines[i:]:
if line.strip() == "":
if cur:
blocks.append(cur); cur = []
else:
cur.append(line)
if cur:
blocks.append(cur)
def emit_table(block, out):
rows = [[c.strip() for c in line.strip().strip("|").split("|")] for line in block]
header, body = rows[0], rows[2:]
n = len(header)
widths = " ".join([f"p{{{0.92 / n:.3f}\\textwidth}}"] * n)
out += ["\\medskip\\noindent\\begin{center}\\footnotesize",
f"\\begin{{tabular}}{{{widths}}}", "\\hline",
" & ".join(inline(c) for c in header) + " \\\\ \\hline"]
for r in body:
r = (r + [""] * n)[:n]
out.append(" & ".join(inline(c) for c in r) + " \\\\[3pt]")
out += ["\\hline\\end{tabular}\\end{center}\\medskip", ""]
out: list[str] = []
for block in blocks:
first = block[0].strip()
m = re.match(r"^\*?\(FIG:(\w+)\)\*?$", first)
if m:
out.append(figure_env(m.group(1))); out.append("")
elif first.startswith("|") and len(block) >= 2 and set(block[1].strip()) <= set("|-: "):
emit_table(block, out)
elif first == "---" and len(block) == 1:
out.append("\\medskip\\hrule\\medskip"); out.append("")
elif first.startswith("## "):
out.append(f"\\section*{{{inline(first[3:])}}}"); out.append("")
elif first.startswith("### "):
out.append(f"\\subsection*{{{inline(first[4:])}}}"); out.append("")
elif re.match(r"^(- |\d+\. )", first):
env = "itemize" if first.startswith("- ") else "enumerate"
out.append(f"\\begin{{{env}}}")
items: list[str] = []
for l in block:
s = l.strip()
if re.match(r"^(- |\d+\. )", s):
items.append(re.sub(r"^(- |\d+\. )", "", s))
else:
items[-1] += " " + s
for it in items:
out.append("\\item " + inline(it.strip()))
out.append(f"\\end{{{env}}}"); out.append("")
else:
joined = re.sub(r"\s{2,}", " ", " ".join(l.strip() for l in block)).strip()
out.append(inline(joined)); out.append("")
return "\n".join(out) + "\n"
if __name__ == "__main__":
OUT.write_text(convert(SRC.read_text()))
print(f"wrote {OUT}")