The review's core instruments adopted: the interpretation/explanation/ prediction ladder is now explicit in §1 (with the decisive pre-merge epistasis-prediction test stated as the open bar, not claimed); identity claims scoped (WF exact only in the minimal model, with the learning-kernel deviation cited against ourselves; Muller's ratchet scoped to the irreversible arm — recombination reassembles only what survives); "nobody has / none imports / theory outrun" removed; merge-don't-average given explicit operator boundaries (output-mean vs weight-average vs routing vs max-with-oracle; budgets; oracle; capacity handoff to speciation); a "what these experiments do and do not establish" scope block added to the speciation section (conflict floor is information-theoretic, not genetic; epistasis-cliff + snowball = hypotheses at the neural tier; emergent DMIs = flagship hypothesis, bounded by our null); "control theory" -> "framework" (subtitle included); §3/§11 overstatements fixed (frozen core != frozen behaviour; Baldwin echo, not identity; operational vs archival irreversibility); claims-at-a-glance table (status/assumptions/evidence/limits) added to §13. Reviewer's framing sentence adopted as the stated core contribution. Accessible version calibrated to match. md2tex gains pipe- table support; PDF rebuilds clean (22 pp). Lessons recorded. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BkRLcc18rwT2Lysu6PbG7v
177 lines
7.7 KiB
Python
177 lines
7.7 KiB
Python
r"""Deterministic Markdown -> LaTeX converter for the arXiv preprint (paper-specific, not general).
|
||
|
||
Converts `paper/the-evolution-of-sex-for-ai.md` into `body.tex`, which `main.tex` inputs. Kept
|
||
deliberately dumb and auditable: the paper uses a small Markdown subset (##/### headings, bold,
|
||
italics, inline code, links, bullet/numbered lists, one blockquote, horizontal rules, and
|
||
`(Figure: \`path\`.)` figure references), and this script handles exactly that subset. Re-run after
|
||
editing the Markdown; the Markdown remains the source of truth.
|
||
|
||
Usage: python paper/arxiv/md2tex.py
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import re
|
||
from pathlib import Path
|
||
|
||
SRC = Path(__file__).resolve().parents[1] / "the-evolution-of-sex-for-ai.md"
|
||
OUT = Path(__file__).resolve().parent / "body.tex"
|
||
|
||
# Figure references in the text -> (graphics file under figs/, caption).
|
||
FIGURES = {
|
||
"results/E14/E14.png": ("figs/E14.pdf",
|
||
"Mating systems (E14): the best mate-pool breadth shrinks as skills get more entangled. "
|
||
"(A) best fitness peaks at intermediate breadth on rugged landscapes; (B) the population mean "
|
||
"is monotonically favoured by promiscuity; (C) diversity is monotonically destroyed by it."),
|
||
"results/E12/E12.png": ("figs/E12.pdf",
|
||
"Model speciation, analytic (E12): hybrid fitness vs divergence traces compatible $\\rightarrow$ "
|
||
"outbreeding depression $\\rightarrow$ inviability; the isolation cliff arrives earlier the "
|
||
"denser the incompatibilities (epistasis), and damage grows super-linearly (the Orr--Turelli "
|
||
"snowball)."),
|
||
"results/speciation_real/speciation_real.png": ("figs/speciation_real.pdf",
|
||
"Model speciation in real weights (E13). (A) the merge barrier decomposed by alignment "
|
||
"strength: the independent-init barrier is a coordinate artefact (removed by alignment); the "
|
||
"conflict barrier survives even the full function-preserving symmetry group. (B) the isolation "
|
||
"cliff: residual barrier rises and hybrid accuracy falls ($0.97 \\rightarrow 0.03$) with "
|
||
"functional conflict. (C) the pre-registered emergent test: divergent-but-compatible "
|
||
"specialists develop no isolation at any divergence --- the merge instead rescues them "
|
||
"(Fisher--Muller)."),
|
||
}
|
||
|
||
UNICODE = {
|
||
"—": "---", "–": "--", "→": r"\(\rightarrow\)", "≈": r"\(\approx\)",
|
||
"≥": r"\(\geq\)", "×": r"\(\times\)", "·": r"\(\cdot\)", "μ": r"\(\mu\)",
|
||
}
|
||
|
||
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:
|
||
"""Escape + convert inline markup. Code spans are protected, then bold, italic, links."""
|
||
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) # straight quotes -> LaTeX quotes
|
||
out.append(p)
|
||
return "".join(out)
|
||
|
||
|
||
def figure_block(md_path: str) -> str:
|
||
gfx, caption = FIGURES[md_path]
|
||
return ("\\begin{figure*}[t]\\centering\n"
|
||
f"\\includegraphics[width=\\textwidth]{{{gfx}}}\n"
|
||
f"\\caption{{{caption}}}\n\\end{{figure*}}\n")
|
||
|
||
|
||
def convert(text: str) -> str:
|
||
"""Block-based conversion: soft-wrapped lines are joined per paragraph/item BEFORE inline
|
||
conversion, so bold/italic/code spans and figure pointers crossing a line break work."""
|
||
fig_queue: list[str] = []
|
||
|
||
def fig_sub(m):
|
||
path = m.group(1)
|
||
if path in FIGURES:
|
||
fig_queue.append(figure_block(path))
|
||
return ""
|
||
return m.group(0)
|
||
|
||
lines = text.split("\n")
|
||
i = 0
|
||
# Skip the header block (title/subtitle/author) up to and including the first horizontal rule:
|
||
# main.tex composes the title page itself.
|
||
while i < len(lines) and lines[i].strip() != "---":
|
||
i += 1
|
||
i += 1
|
||
|
||
# Group into blocks separated by blank lines; a block is a heading, rule, quote, list, or paragraph.
|
||
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_para(joined: str, out: list[str]) -> None:
|
||
joined = re.sub(r"\(Figure: `([^`]+)`\.?\)", fig_sub, joined)
|
||
joined = re.sub(r"\s{2,}", " ", joined).strip()
|
||
if joined:
|
||
out.append(inline(joined))
|
||
out.append("")
|
||
while fig_queue:
|
||
out.append(fig_queue.pop(0)); out.append("")
|
||
|
||
def emit_table(block: list[str], out: list[str]) -> None:
|
||
"""Pipe table -> small-font tabular with wrapped paragraph columns (full text width)."""
|
||
rows = [[c.strip() for c in line.strip().strip("|").split("|")] for line in block]
|
||
header, body = rows[0], [r for r in rows[2:]] # rows[1] is the |---| separator
|
||
n = len(header)
|
||
widths = " ".join([f"p{{{0.92 / n:.3f}\\textwidth}}"] * n)
|
||
out.append("\\medskip\\noindent\\begin{center}\\footnotesize") # non-floating: stays in place
|
||
out.append(f"\\begin{{tabular}}{{{widths}}}")
|
||
out.append("\\hline")
|
||
out.append(" & ".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.append("\\hline\\end{tabular}\\end{center}\\medskip")
|
||
out.append("")
|
||
|
||
out: list[str] = []
|
||
for block in blocks:
|
||
first = block[0].strip()
|
||
if 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 first.startswith("> "):
|
||
joined = " ".join(l.strip().lstrip("> ").strip() for l in block)
|
||
out.append("\\begin{quote}" + inline(joined) + "\\end{quote}"); 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] = items[-1] + " " + s # soft-wrapped continuation of the item
|
||
for it in items:
|
||
it = re.sub(r"\(Figure: `([^`]+)`\.?\)", fig_sub, it)
|
||
out.append("\\item " + inline(it.strip()))
|
||
out.append(f"\\end{{{env}}}"); out.append("")
|
||
while fig_queue:
|
||
out.append(fig_queue.pop(0)); out.append("")
|
||
else:
|
||
emit_para(" ".join(l.strip() for l in block), out)
|
||
return "\n".join(out) + "\n"
|
||
|
||
|
||
if __name__ == "__main__":
|
||
OUT.write_text(convert(SRC.read_text()))
|
||
print(f"wrote {OUT}")
|