assembly-economics/simulations/gadm02/power_channels.py
tegwick c8329c6965 ASSEMBLY-WP-0005-T04: separate wealth from power (GADM-0.3)
- 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
2026-10-12 07:32:57 +02:00

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

"""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()