- Wealth-concentration stock W with wealth-tax lever; converts into market and political channels only, moderated by checks (Q*S) - wealth_on switch reproduces the T03 model exactly (verified) - wealth.py: P3 supported and robust to conversion strength (full wealth tax lowers P by only 0.01-0.05 because membrane/lineage control does not run on wealth); effect on reachable channels depends on k_w - Tests for wealth switch and tax direction Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 1494246@bnt-lap001 Assistant-Session: caf6de6f-f9e4-47ce-a584-49baa271fa4b
71 lines
3.1 KiB
Python
71 lines
3.1 KiB
Python
"""Hypothesis tests P1 and P2 for the GADM-0.3 power channels.
|
||
|
||
P1: each channel becomes self-sustaining above a threshold. Test: sweep the
|
||
channel's initial value with everything else at the scenario initial state,
|
||
and look for a jump in its final value (bistability).
|
||
P2: membrane/lineage channels plus learning with use (R7) form a natural-
|
||
monopoly regime unless lineage diffusion L exceeds a critical value. Test:
|
||
sweep L for several learning amplifications lw.
|
||
|
||
Runs the T03 stage of GADM-0.3 (wealth_on = 0, via gadm03.t03_params()) so the
|
||
results in models/GADM-0.3.md § 2 stay reproducible after T04 added wealth.
|
||
|
||
Usage: python3 -B simulations/gadm02/power_channels.py
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
sys.dont_write_bytecode = True
|
||
|
||
import gadm03 as g # noqa: E402
|
||
from scenarios import load_all # noqa: E402
|
||
|
||
OUT = Path(__file__).resolve().parent / "results"
|
||
GRID = [i / 20 for i in range(21)]
|
||
|
||
|
||
def p1(sc: dict) -> list[str]:
|
||
lines = ["| Channel | Final value for initial 0.0, 0.2, …, 1.0 | Largest jump (initial) | Path-dependent |",
|
||
"|---|---|---|---|"]
|
||
for k in g.CHANNELS:
|
||
finals = [g.simulate(sc["params"], {**sc["init"], k: x})[-1][k] for x in GRID]
|
||
jumps = [(GRID[i], GRID[i + 1], finals[i + 1] - finals[i]) for i in range(len(GRID) - 1)]
|
||
big = max(jumps, key=lambda j: j[2])
|
||
bistable = max(finals) - min(finals) > 0.3 # end state depends on the start
|
||
thr = f"{big[0]:.2f}–{big[1]:.2f}" if bistable else "—"
|
||
spark = " ".join(f"{v:.2f}" for v in finals[::4])
|
||
lines.append(f"| {g.CHANNEL_NAMES[k]} (`{k}`) | {spark} | {thr} | {'yes' if bistable else 'no'} |")
|
||
return lines
|
||
|
||
|
||
def p2(sc: dict) -> list[str]:
|
||
lines = ["| lw (learning amplification) | final `Plin` for L = 0.0, 0.2, …, 1.0 | critical L (Plin < 0.5) |",
|
||
"|---|---|---|"]
|
||
for lw in (0.0, 2.0, 4.0):
|
||
finals = [g.simulate({**sc["params"], "L": L, "lw": lw}, sc["init"])[-1]["Plin"] for L in GRID]
|
||
crit = next((L for L, v in zip(GRID, finals) if v < 0.5), None)
|
||
lines.append(f"| {lw:.0f} | " + " ".join(f"{v:.2f}" for v in finals[::4])
|
||
+ f" | {crit:.2f} |" if crit is not None else f"| {lw:.0f} | "
|
||
+ " ".join(f"{v:.2f}" for v in finals[::4]) + " | none ≤ 1 |")
|
||
return lines
|
||
|
||
|
||
def main() -> None:
|
||
OUT.mkdir(exist_ok=True)
|
||
scs = load_all()
|
||
lines = [f"# {g.MODEL_VERSION}: power-channel hypotheses P1 and P2", "",
|
||
"Generated by `simulations/gadm02/power_channels.py` (T03 stage, wealth_on = 0).",
|
||
"Final values at t = 100 years.", ""]
|
||
for name in ("baseline", "generative_regime"):
|
||
sc = {**scs[name], "params": {**scs[name]["params"], **g.t03_params()}}
|
||
lines += [f"## {sc['title']}", "", "### P1: thresholds per channel", ""] + p1(sc) + [""]
|
||
lines += ["### P2: lineage diffusion vs. learning with use", ""] + p2(sc) + [""]
|
||
(OUT / "power_channels.md").write_text("\n".join(lines))
|
||
print("\n".join(lines))
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|