MachineSex/paper/arxiv/md2tex.py
Giorgio Gilestro 58e6c74609 paper: claim-narrowing revision from the external review
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
2026-09-06 15:14:28 +01:00

177 lines
7.7 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"""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}")