MachineSex/figures/plot_figS9_specialist_superparent.py
Giorgio Gilestro ab3dc10587 Restructure: descriptive tier and experiment names, paper/manuscript
- paper/pnas -> paper/manuscript (venue-neutral)
- configs/layer1 -> configs/inheritance, src/knowledge -> src/inheritance
  (imported as `inheritance`), make layer1 -> make inheritance; layer2 alias dropped
- inheritance and trained-network bundles named after the manuscript figure
  they feed (fig2_grounding_sweep, figS3_rebaselining, ...), or descriptively
  where they feed none; configs keep their `experiment:` value so parquet
  hashes are unchanged, only output.dir moves
- figure scripts, SI figure sources, notebooks, REPRODUCING.md, README and the
  SI Methods/tables updated; make clean no longer deletes tracked manifests;
  reproduce.sh hashes the s{seed}/ layouts too

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y64o8FKP7rCuXzC48pxpMm
2026-09-13 17:00:40 +01:00

67 lines
3.1 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.

"""E8 figure — the vertical claim: n-parent recombination exceeds any parent (FisherMuller).
The society headline. Decorrelated *parents* are specialists (expert on some loci, agnostic on the
rest); an *offspring* recombined from all of them can be fitter than any parent — capability that
*exceeds* every component, not just recovers a ceiling. Unlike biological sex there is no two-parent
limit, so capability climbs toward the optimum as the parent pool grows and decorrelates.
Two panels: (A) deployed capability (mode-genotype fitness) vs parent count at ρ=0 — sexual
recombination reaches the optimum (a genotype no parent had) while the best single parent and the
mean-mixture "model soup" plateau below; (B) the decorrelation control — sexual capability vs parent
count for ρ{0, 0.5, 1}: decorrelated parents (ρ=0) climb to the optimum, identical parents (ρ=1)
buy nothing. Reads only the committed bundle.
Usage: python figures/plot_figS9_specialist_superparent.py [results/figS9_specialist_superparent]
"""
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, mean_ci, savefig, letter_axes # noqa: E402
def main(results_dir: str = "results/figS9_specialist_superparent") -> None:
df, cfg = load_bundle(results_dir)
L = cfg["society"]["L"]
rhos = sorted(df["rho"].unique())
fig, axes = plt.subplots(1, 2, figsize=(13, 5))
# Panel A: best-parent vs average vs sexual, at rho=0.
ax = axes[0]
d0 = df[df["rho"] == 0.0]
for col, c, lab in [("best_parent", "#7f7f7f", "best single parent"),
("average", "#1f77b4", "average (model soup)"),
("sexual", "#d62728", "sexual recombination")]:
k, m, ci = mean_ci(d0, "K_T", col)
ax.errorbar(k, m, yerr=ci, fmt="-o", color=c, capsize=3, label=lab)
ax.axhline(L, ls=":", color="green", lw=1, label=f"optimum ($L$={L})")
ax.set(xlabel="number of parents $K_T$", ylabel="deployed capability (mode fitness)",
title="Recombination exceeds any parent (ρ=0):\nsexual reaches the optimum; soup & best-parent plateau")
ax.legend(frameon=False, fontsize=9)
# Panel B: sexual capability vs K_T for each rho (decorrelation control).
ax = axes[1]
colors = plt.cm.viridis(np.linspace(0, 0.8, len(rhos)))
for rho, c in zip(rhos, colors):
sub = df[df["rho"] == rho]
k, m, ci = mean_ci(sub, "K_T", "sexual")
ax.errorbar(k, m, yerr=ci, fmt="-o", color=c, capsize=3, label=fr"ρ={rho:g}")
ax.axhline(L, ls=":", color="green", lw=1, label=f"optimum ($L$={L})")
ax.set(xlabel="number of parents $K_T$", ylabel="sexual-recombination capability",
title="Decorrelation is the fuel:\nρ=0 climbs to the optimum; ρ=1 (clones) buy nothing")
ax.legend(frameon=False, fontsize=9)
fig.tight_layout()
letter_axes(fig)
savefig(fig, results_dir, "figS9_specialist_superparent")
if __name__ == "__main__":
main(*sys.argv[1:])