- 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
139 lines
6.1 KiB
Python
139 lines
6.1 KiB
Python
"""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()
|