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
93 lines
4.6 KiB
Python
93 lines
4.6 KiB
Python
"""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()
|