assembly-economics/simulations/gadm02/test_gadm04.py
tegwick 56649466ac ASSEMBLY-WP-0009-T03: sentiment and legitimacy loop in GADM-0.4 (opt-in); legitimacy.py; AdoptionBand § 3; BR-005
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 14913@bnt-lap001
Assistant-Session: add7ef69-b901-4756-9a67-34cf1125d96f
2026-10-13 14:17:21 +02:00

97 lines
3.9 KiB
Python

"""Sanity tests for GADM-0.4 durable capability.
Usage: python3 -B -m unittest discover -s simulations/gadm02
"""
import unittest
import gadm04 as g
from scenarios import load_all
class TestDurableCapability(unittest.TestCase):
def test_bounded(self):
for name, sc in load_all().items():
for row in g.simulate(sc["params"], sc["init"]):
for k in g.STOCKS:
if not k.startswith("N"):
self.assertGreaterEqual(row[k], 0.0, (name, k))
self.assertLessEqual(row[k], 1.0, (name, k))
def test_initial_effective_capability_matches_gadm03(self): # H only; W and A differ (T04)
import gadm03c
self.assertAlmostEqual(g.simulate(T=0)[0]["H"], gadm03c.simulate(T=0)[0]["H"])
def test_initial_state_is_equilibrium_for_wealth_attention_and_channels(self):
d, _ = g.derivatives(dict(g.INIT), g.PARAMS)
for k in ("W", "Aw", "As", "Gw", "Gs", "Pm", "Pi", "Pd", "Pmem", "Plin", "Ppol"):
self.assertLess(abs(d[k]), 1e-4, k)
def test_cohort_agency_off_reproduces_aggregate_model(self):
# T04 state: baseline VFAC 0.320 with aggregate agency
self.assertAlmostEqual(g.simulate({"cohort_G": 0.0})[-1]["VFAC"], 0.320, places=3)
def test_aggregate_agency_is_adult_weighted(self):
r = g.simulate(T=0)[0]
w, s_ = r["n_w"], r["n_s"]
self.assertAlmostEqual(r["G"], (w * r["Gw"] + s_ * r["Gs"]) / (w + s_))
def test_calibration_matches_params(self):
cal = g.calibrate()
for k, v in cal.items():
self.assertAlmostEqual(g.PARAMS[k], v, places=6, msg=k)
def test_education_reaches_working_age_with_a_lag(self):
lo, hi = g.simulate({"edu": 1.0}), g.simulate({"edu": 1.5})
diff = [(a["t"], b["Hw"] - a["Hw"]) for a, b in zip(lo, hi)]
final = diff[-1][1]
self.assertGreater(final, 0.0)
half = next(t for t, x in diff if x >= 0.5 * final)
self.assertGreaterEqual(half, 10) # L3: a substantial lag ...
self.assertLessEqual(half, 30) # ... but within a generation
class TestGrounding(unittest.TestCase):
def test_grounded_variants_start_in_equilibrium(self):
import grounding as gr
for name in ("central", "slow"):
params, init = gr.variant(name)
d, _ = g.derivatives({**g.INIT, **init}, {**g.PARAMS, **params})
for k in gr.EQ_KEYS + ("Pm", "Pi", "Pd", "Pmem", "Plin", "Ppol"):
self.assertLess(abs(d[k]), 1e-3, (name, k))
def test_band_is_ordered_by_adoption_speed(self):
import grounding as gr
ko = [g.simulate(*gr.variant(gr.BAND[a]))[10]["Ko"] for a in ("B1", "B2", "B3", "B4")]
self.assertEqual(ko, sorted(ko))
def test_self_enhancement_off_by_default(self):
self.assertEqual(g.PARAMS["selfenh"], 0.0)
def test_grounded_adoption_is_slower_than_default(self):
import grounding as gr
p, i = gr.variant("central")
self.assertLess(g.simulate(p, i)[10]["Ko"], g.simulate()[10]["Ko"])
def test_legitimacy_loop_off_by_default(self):
self.assertEqual(g.PARAMS["sent"], 0.0)
r = g.simulate(T=10)[-1]
self.assertEqual((r["Sig"], r["Lg"]), (0.5, 0.5))
def test_legitimacy_loop_without_response_is_neutral(self):
# sentiment switched on but with zero gain stays neutral and leaves K_Omega unchanged
on = g.simulate({"sent": 1.0, "sig_gain": 0.0}, T=30)
off = g.simulate(T=30)
self.assertAlmostEqual(on[-1]["Sig"], 0.5, places=9)
self.assertAlmostEqual(on[-1]["Ko"], off[-1]["Ko"], places=9)
def test_legitimacy_loop_damps_aggressive_adoption(self):
import grounding as gr
p, i = gr.variant("aggressive")
off = max(r["Ko"] for r in g.simulate(p, i, T=30))
on = max(r["Ko"] for r in g.simulate({**p, "sent": 1.0}, i, T=30))
self.assertLess(on, off)
if __name__ == "__main__":
unittest.main()