- 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
58 lines
2.7 KiB
Python
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
|