MachineSex/tests/test_dynamic_society.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

58 lines
2.7 KiB
Python

"""Dynamic-society tests (pure NumPy) — the culminating vertical claim (E11 / C3).
Cover the finite-population operators (consensus, conformity, novelty) and the four ablation
behaviours: the full society climbs to near the optimum; removing grounding collapses it to an unfit
consensus (self-consumption); removing sex or diversity leaves it stuck below the full society.
"""
from __future__ import annotations
import numpy as np
from inheritance.dynamic_society import _conformity, _consensus, _novelty, run_dynamic_society
def _run(arm_overrides: dict, seed: int = 0, gens: int = 50):
base = {"L": 10, "K": 6, "N": 50, "g": 0.85, "mu": 0.03, "novelty": 0.5,
"n_off": 100, "recomb_rate": 0.2, "sex": True, "select": "qd"}
base.update(arm_overrides)
return run_dynamic_society({"society": base, "generations": gens}, seed=seed)
def test_consensus_and_conformity():
pop = np.array([[1, 1, 0, 0], [1, 0, 0, 1], [1, 1, 1, 0]], dtype=np.int8)
cons = _consensus(pop)
assert np.array_equal(cons, [1, 1, 0, 0]) # majority vote per locus
conf = _conformity(pop, cons)
assert np.isclose(conf[0], 1.0) # agent 0 == consensus
assert conf.min() >= 0.0 and conf.max() <= 1.0
def test_novelty_is_zero_for_clones_and_high_for_spread():
clones = np.ones((4, 8), dtype=np.int8)
assert np.allclose(_novelty(clones), 0.0) # identical -> no diversity
spread = np.array([[0] * 8, [1] * 8], dtype=np.int8)
assert np.allclose(_novelty(spread), 1.0) # opposite -> maximal diversity
def test_full_society_climbs_toward_optimum():
df = _run({})
go = df["global_opt"].iloc[0]
assert df["best_fitness"].iloc[-1] > df["best_fitness"].iloc[0] + 0.05 # it climbs
assert df["best_fitness"].iloc[-1] > 0.9 * go # ... to near the optimum
def test_no_grounding_collapses_to_unfit_consensus():
full = _run({})["best_fitness"].iloc[-1]
dry = _run({"g": 0.0})
assert dry["best_fitness"].iloc[-1] < full - 0.1 # far below the grounded society
assert dry["diversity"].iloc[-1] < 0.05 # diversity collapsed
assert dry["conformity_true_gap"].iloc[-1] > 0.3 # agreement >> real capability (delusion)
def test_ablations_stay_below_the_full_society():
full = _run({})["best_fitness"].iloc[-1]
no_sex = _run({"sex": False})["best_fitness"].iloc[-1]
no_div = _run({"select": "greedy", "novelty": 0.0})["best_fitness"].iloc[-1]
assert no_sex <= full + 1e-6 and no_div <= full + 1e-6 # neither beats the full society
assert min(no_sex, no_div) < full # ... and at least one is strictly worse