MachineSex/figures/plot_E11.py
Giorgio Gilestro 0f7b775ae5 society: the dynamic Lamarckian society — the vertical claim (E11 / C3)
The culmination. A finite population of agents (genotypes, L loci) evolves
on a rugged NK landscape that IS reality (knowledge/dynamic_society.py),
composing the four operators the whole study built toward: grounding,
directed recombination (sex), quality-diversity selection, and mutation.
Grounding is made load-bearing via the consensus-conformity (self-
consumption) mechanism (GG decision): selection acts on
g*true_fitness + (1-g)*conformity, where conformity = agreement with the
population's own consensus, so at g=0 the society optimises fitting-the-
crowd rather than reality.

4-arm ablation (12 reps), each breaking distinctly, only the full society
climbing (global_opt ~ 0.79):
- full         0.78  climbs to the optimum, diversity maintained longest
- no_sex       0.77  can't recombine to escape local optima
- no_diversity 0.74  greedy: collapses diversity fastest, worse local optimum
- no_grounding 0.48  self-consumption collapse to an unfit consensus
                     (trains on the crowd -> confident-but-wrong mean;
                      conformity-true gap ~ 0.5)

This integrates E1-E6 + the learning kernel + E7-E10 into one system and
shows the Lamarckian society needs ALL of grounding + directed sex +
diversity: on a rugged landscape you need diversity to explore basins, sex
to recombine them, and grounding to select on reality -- remove any one and
you fail differently. Closes the C3 vertical claim analytically; the LLM
rung remains the eventual empirical instantiation.

New: knowledge/dynamic_society.py, configs/layer1/E11.yaml, figures/
plot_E11.py, README, tests/test_dynamic_society.py (+5). kind:
dynamic_society dispatch; make layer1 wired. 122 tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 12:34:01 +01:00

68 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.

"""E11 figure — the dynamic Lamarckian society: the vertical claim (C3).
A finite population of agents evolves on a rugged NK landscape (reality). The **full** society —
grounding + directed recombination (sex) + quality-diversity selection — climbs to the global optimum
while maintaining diversity longest. A 4-arm ablation shows every operator is load-bearing, each
breaking distinctly: **no_grounding** collapses to a fit-looking but actually-poor consensus
(self-consumption); **no_sex** plateaus (can't recombine to escape local optima); **no_diversity**
(greedy) collapses diversity fastest and stalls at a worse local optimum.
Three panels over generations: (A) best real capability — the vertical climb, full highest, no_grounding
crashing below the rest; (B) population diversity — full explores longest, no_grounding collapses
almost immediately; (C) the self-consumption signature — conformity minus true fitness (how far the
population's mutual agreement exceeds its real capability), largest for no_grounding. Reads only the
committed bundle.
Usage: python figures/plot_E11.py [results/E11]
"""
from __future__ import annotations
import sys
from pathlib import Path
import matplotlib.pyplot as plt
sys.path.insert(0, str(Path(__file__).parent))
from _figlib import load_bundle, mean_ci, savefig # noqa: E402
_ARMS = [("full", "#2ca02c", "full society"),
("no_sex", "#ff7f0e", "no sex (no recombination)"),
("no_diversity", "#9467bd", "no diversity (greedy)"),
("no_grounding", "#d62728", "no grounding (self-consumption)")]
def main(results_dir: str = "results/E11") -> None:
df, _ = load_bundle(results_dir)
arms = [a for a in _ARMS if a[0] in set(df["arm"].unique())]
g_opt = df["global_opt"].mean()
fig, axes = plt.subplots(1, 3, figsize=(16, 4.8))
def traj(ax, col, title, ylabel, hline=None):
for name, c, lab in arms:
sub = df[df["arm"] == name]
g, m, ci = mean_ci(sub, "generation", col)
ax.plot(g, m, "-", color=c, lw=1.9, label=lab)
ax.fill_between(g, m - ci, m + ci, color=c, alpha=0.15)
if hline is not None:
ax.axhline(hline[0], ls=":", color="gray", lw=1, label=hline[1])
ax.set(xlabel="generation", ylabel=ylabel, title=title)
ax.legend(frameon=False, fontsize=8)
traj(axes[0], "best_fitness", "The vertical climb: general capability\n"
"(full climbs highest; no-grounding collapses)", "best real fitness",
hline=(g_opt, "global optimum"))
traj(axes[1], "diversity", "Specialties maintained: diversity during search\n"
"(full explores longest; ablations collapse fast)", "population diversity")
traj(axes[2], "conformity_true_gap", "Self-consumption signature:\n"
"agreement minus real capability", "conformity true fitness")
fig.suptitle("E11 — the dynamic Lamarckian society: grounding + directed sex + diversity climb to "
"the optimum; remove any one and it breaks (the vertical claim, C3)", y=1.02, fontsize=12)
fig.tight_layout()
savefig(fig, results_dir, "E11")
if __name__ == "__main__":
main(*sys.argv[1:])