assembly-economics/simulations/gadm02/demand.py
tegwick 509a50eb04 ASSEMBLY-WP-0007 finished: T04 two-good demand split (genuine decoupling in the generative regime, sinks breach postponed; BR-010 resolved); residuals to WP-0018
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 23:17:36 +02:00

93 lines
4.6 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.

"""Two-good demand split: dematerialization and decoupling (ASSEMBLY-WP-0007-T04).
Tests (workplan T04):
1. Can VFAC rise while physical throughput falls, for reasons other than organism decline?
2. Does the generative regime's sink breach move out of the 100-year horizon?
Calibration check: throughput intensity per unit of output should fall by roughly the observed
dematerialization (EU resource productivity +1.6 %/year, 2000-2022, Eurostat).
Usage: python3 -B simulations/gadm02/demand.py
"""
from __future__ import annotations
import sys
from pathlib import Path
sys.dont_write_bytecode = True
import band_probe as bp # noqa: E402
import gadm05 as g5 # 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)
CASES = {"no demand split": {"dm_lam": 0.0}, "split (λ 0.05, ε 2)": {"dm_lam": 0.05, "dm_eps": 2.0},
"split, strong (λ 0.1, ε 4)": {"dm_lam": 0.1, "dm_eps": 4.0}}
def decoupled_windows(rows: list[dict], step: int = 1) -> float:
"""Share of 10-year windows with VFAC up, throughput down and K_Omega not falling."""
hits = n = 0
for i in range(0, len(rows) - step, step):
a, b = rows[i], rows[i + step]
n += 1
hits += b["VFAC"] > a["VFAC"] and b["thru"] < a["thru"] and b["Ko"] >= a["Ko"] - 1e-6
return hits / n
def intensity_trend(rows: list[dict], years: int = 10) -> float:
"""Average yearly change of throughput per unit of output over the first `years`."""
a, b = rows[0], rows[years // 10]
ia, ib = a["thru"] / a["Ystar"], b["thru"] / b["Ystar"]
return (ib / ia) ** (1 / years) - 1
def main() -> None:
OUT.mkdir(exist_ok=True)
scs = load_all()
var = {a: gr.variant(gr.BAND[a]) for a in ANCHORS}
res = {}
for sc in ("baseline", "generative_regime"):
for a in ANCHORS:
for cn, cd in CASES.items():
rows = g5.simulate_eco({**scs[sc]["params"], **var[a][0]}, {**scs[sc]["init"], **var[a][1]},
eco={"rm": "rest"}, demand=cd)
sink = next((r["t"] for r in rows if r["Es"] < g5.ECO_PARAMS["eco_floor"]), None)
res[(sc, a, cn)] = {"V": rows[-1]["VFAC"], "Cr": rows[-1]["Cr"], "dec": decoupled_windows(rows),
"sink": sink, "Es": rows[-1]["Es"], "it": intensity_trend(rows),
"thru": rows[-1]["thru"]}
lines = ["# GADM-0.5-dev: two-good demand split (dematerialization)", "",
"Generated by `simulations/gadm02/demand.py` (ASSEMBLY-WP-0007-T04). Ecological classes with",
"material replenishment at rest. Decoupled windows: 10-year windows with VFAC rising, throughput",
"falling and K_Ω not falling. Intensity trend: yearly change of throughput per unit of output,",
"years 0–10 (observed EU dematerialization ≈ −1.6 %/year).", "",
"| Scenario | Anchor | Case | VFAC t100 | rival demand share Cr t100 | intensity trend | decoupled windows | sinks breach | Es t100 |",
"|---|---|---|---|---|---|---|---|---|"]
for sc in ("baseline", "generative_regime"):
for a in ANCHORS:
for cn in CASES:
r = res[(sc, a, cn)]
lines.append(f"| {scs[sc]['title']} | {a} | {cn} | {r['V']:.3f} | {r['Cr']:.2f} | {100 * r['it']:+.1f} % | "
f"{r['dec']:.2f} | {('t ' + format(r['sink'], '.0f')) if r['sink'] is not None else '—'} | {r['Es']:.2f} |")
readings = []
for sc in ("baseline", "generative_regime"):
t = scs[sc]["title"]
readings.append((f"decoupled windows, split − none: {t}",
[res[(sc, a, 'split (λ 0.05, ε 2)')]["dec"] - res[(sc, a, 'no demand split')]["dec"] for a in ANCHORS]))
readings.append((f"ΔVFAC split − none: {t}",
[res[(sc, a, 'split (λ 0.05, ε 2)')]["V"] - res[(sc, a, 'no demand split')]["V"] for a in ANCHORS]))
readings.append((f"ΔEs t100 split − none: {t}",
[res[(sc, a, 'split (λ 0.05, ε 2)')]["Es"] - res[(sc, a, 'no demand split')]["Es"] for a in ANCHORS]))
lines += ["", "## Readings and robustness class", "", "| Reading | " + " | ".join(ANCHORS) + " | class |",
"|---|" + "---|" * (len(ANCHORS) + 1)]
for name, vals in readings:
lines.append(f"| {name} | " + " | ".join(f"{v:+.3f}" for v in vals) + f" | {bp.classify(vals, 'sign')} |")
lines.append("")
(OUT / "demand.md").write_text("\n".join(lines))
print("\n".join(lines))
if __name__ == "__main__":
main()