assembly-economics/simulations/gadm02/band_probe.py
tegwick 1b0a28361e ASSEMBLY-WP-0009-T01/T02: adoption band B1-B4 and probe across it
- gadm04: optional self-enhancement loop (selfenh, default off)
- grounding.py: band anchors slow/central/fast/aggressive (B4 selfenh 3.0)
- band_probe.py: 36 readings across the band - 22 robust, 7 quantitative
  shifts, 7 qualitative breaks; core claims robust; moderate reforms suffice
  only under conservative adoption; under B4 lineage control dominates even
  in the generative regime (structural candidate)
- models/AdoptionBand.md, research/BreakRegister.md (BR-001..004)

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 10:58:47 +02:00

139 lines
6.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.

"""Probe scenarios across the adoption band B1-B4 (ASSEMBLY-WP-0009-T02).
For every scenario and band anchor, compute key readings and classify each as
robust, quantitative shift or qualitative break (research/ScenarioBandMethod.md
§ 5).
Usage: python3 -B simulations/gadm02/band_probe.py
"""
from __future__ import annotations
import itertools
import sys
from pathlib import Path
sys.dont_write_bytecode = True
import gadm03 as g3 # noqa: E402
import gadm04 as g # noqa: E402
import grounding as gr # noqa: E402
from scenarios import load_all # noqa: E402
OUT = Path(__file__).resolve().parent / "results"
ANCHORS = list(gr.BAND) # B1..B4
FLOORS = {"G": (">=", 0.2), "E": (">=", 0.2), "P": ("<=", 0.7), "A": (">=", 0.2)}
GRID = {"lineage": ({"L": 0.1, "lin_open": 0.1}, {"L": 0.8, "lin_open": 0.8}),
"membrane": ({"m": 0.2}, {"m": 0.8}),
"attention": ({"x_att": 0.9}, {"x_att": 0.2})}
def classify(values: list, kind: str) -> str:
"""kind: 'sign' (numbers), 'label' (categorical), 'bool'."""
if kind == "label" or kind == "bool":
return "robust" if len(set(values)) == 1 else "QUALITATIVE BREAK"
signs = {(v > 0) - (v < 0) for v in values}
if len(signs) > 1:
return "QUALITATIVE BREAK"
lo, hi = min(abs(v) for v in values), max(abs(v) for v in values)
return "quantitative shift" if lo > 0 and hi / lo > 1.5 else "robust"
def shape(traj: list[float]) -> str:
peak = max(traj)
if traj[-1] >= 0.9 * peak:
return "saturating"
return "peak-and-decline"
def main() -> None:
OUT.mkdir(exist_ok=True)
scs = load_all()
names = list(scs)
var = {a: gr.variant(gr.BAND[a]) for a in ANCHORS}
runs = {a: {n: g.simulate({**scs[n]["params"], **var[a][0]}, {**scs[n]["init"], **var[a][1]})
for n in names} for a in ANCHORS}
fin = {a: {n: runs[a][n][-1] for n in names} for a in ANCHORS}
lines = [f"# {g.MODEL_VERSION}: scenarios across the adoption band", "",
"Generated by `simulations/gadm02/band_probe.py`. Anchors B1–B4 from",
"`grounding.py` (`research/ScenarioBandMethod.md` § 2). t = 100 years unless noted.", "",
"## K_Ω path per anchor (baseline)", "", "| Anchor | t 5 | t 10 | t 25 | t 50 | t 100 | shape |",
"|---|---|---|---|---|---|---|"]
shapes = []
for a in ANCHORS:
tr = [r["Ko"] for r in runs[a]["baseline"]]
shapes.append(shape(tr))
lines.append(f"| {a} ({gr.BAND[a]}) | " + " | ".join(f"{tr[t]:.2f}" for t in (5, 10, 25, 50, 100))
+ f" | {shapes[-1]} |")
lines += ["", "## Scenario VFAC / P per anchor", "",
"| Scenario | " + " | ".join(ANCHORS) + " |", "|---|" + "---|" * len(ANCHORS)]
for n in names:
lines.append(f"| {scs[n]['title']} | " + " | ".join(
f"{fin[a][n]['VFAC']:.3f} / {fin[a][n]['P']:.2f}" for a in ANCHORS) + " |")
readings = []
def add(name, values, kind, fmt=lambda v: f"{v:+.3f}" if isinstance(v, float) else str(v)):
readings.append((name, [fmt(v) for v in values], classify(values, kind)))
order = {a: sorted(names, key=lambda n: -fin[a][n]["VFAC"]) for a in ANCHORS}
add("best scenario", [scs[order[a][0]]["title"] for a in ANCHORS], "label")
add("worst scenario", [scs[order[a][-1]]["title"] for a in ANCHORS], "label")
add("lowest domination", [scs[min(names, key=lambda n: fin[a][n]["P"])]["title"] for a in ANCHORS], "label")
for n in names:
if n != "baseline":
add(f"VFAC {scs[n]['title']} − baseline",
[fin[a][n]["VFAC"] - fin[a]["baseline"]["VFAC"] for a in ANCHORS], "sign")
for n in names:
dom = []
for a in ANCHORS:
ch = max(g3.CHANNELS, key=lambda k: fin[a][n][k])
dom.append(g3.CHANNEL_NAMES[ch])
add(f"dominant domination channel: {scs[n]['title']}", dom, "label")
for n in names:
viol = []
for a in ANCHORS:
v = [k for k, (op, lim) in FLOORS.items()
if (fin[a][n][k] < lim if op == ">=" else fin[a][n][k] > lim)]
viol.append(",".join(v) or "none")
add(f"floor violations at t 100: {scs[n]['title']}", viol, "label")
eff = {a: [] for a in ANCHORS}
for a in ANCHORS:
out = {}
for lv in itertools.product((0, 1), repeat=3):
ex = {}
for ax, l in zip(GRID, lv):
ex.update(GRID[ax][l])
out[lv] = g.simulate({**scs["baseline"]["params"], **var[a][0], **ex},
{**scs["baseline"]["init"], **var[a][1]})[-1]["VFAC"]
eff[a] = [sum((1 if lv[i] else -1) * v for lv, v in out.items()) / 4 for i in range(3)]
for i, ax in enumerate(GRID):
add(f"organism lever effect: {ax}", [eff[a][i] for a in ANCHORS], "sign")
add("lever order lineage > membrane > attention",
[eff[a][0] > eff[a][1] > eff[a][2] > 0 for a in ANCHORS], "bool")
mem = scs["generative_concentrated_lineages"]
p3 = []
for a in ANCHORS:
lo = g.simulate({**mem["params"], **var[a][0], "wtax": 0.0}, {**mem["init"], **var[a][1]})[-1]["P"]
hi = g.simulate({**mem["params"], **var[a][0], "wtax": 1.0}, {**mem["init"], **var[a][1]})[-1]["P"]
p3.append(hi - lo)
add("P3: wealth tax ΔP (membrane-dominated)", p3, "sign")
add("P3 holds (|ΔP| < 0.06)", [abs(x) < 0.06 for x in p3], "bool")
add("K_Ω trajectory shape (baseline)", shapes, "label")
lines += ["", "## Readings and robustness class", "",
"| Reading | " + " | ".join(ANCHORS) + " | class |", "|---|" + "---|" * (len(ANCHORS) + 1)]
for name, vals, cls in readings:
lines.append(f"| {name} | " + " | ".join(vals) + f" | {cls} |")
n_break = sum(1 for _n, _v, c in readings if c == "QUALITATIVE BREAK")
n_shift = sum(1 for _n, _v, c in readings if c == "quantitative shift")
lines += ["", f"{len(readings)} readings: {len(readings) - n_break - n_shift} robust, "
f"{n_shift} quantitative shifts, {n_break} qualitative breaks.", ""]
(OUT / "band_probe.md").write_text("\n".join(lines))
print("\n".join(lines))
if __name__ == "__main__":
main()