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
189 lines
8.6 KiB
Python
189 lines
8.6 KiB
Python
"""GADM-0.2 toy simulation (normalized stocks, Euler integration).
|
|
|
|
Implements models/GADM-0.2.md. Every stock lives in [0, 1]. Inflows are scaled
|
|
by headroom (1 - x) and outflows by level (x), so stocks stay bounded without
|
|
clipping. This is a hypothesis generator, not a forecast (GADM-0.1 § 27).
|
|
|
|
Standard library only, deterministic.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import math
|
|
|
|
MODEL_VERSION = "GADM-0.2"
|
|
TIME_UNIT = "year" # one period = one year (ASSEMBLY-WP-0005-T01); rates are per year
|
|
|
|
STOCKS = ("H", "X", "Kp", "Ko", "F", "G", "N", "Q", "P", "E", "S", "A")
|
|
|
|
INIT = {
|
|
"H": 0.50, # human capability
|
|
"X": 0.40, # assembly commons
|
|
"Kp": 0.50, # physical / conventional productive capacity
|
|
"Ko": 0.05, # information-organism capability (K_Omega)
|
|
"F": 0.40, # risk capacity
|
|
"G": 0.50, # effective agency
|
|
"N": 0.50, # diversity & composability
|
|
"Q": 0.50, # epistemic capacity
|
|
"P": 0.40, # domination concentration
|
|
"E": 0.50, # ecological headroom
|
|
"S": 0.50, # social cohesion
|
|
"A": 0.50, # attention headroom (unclaimed, self-directed attention)
|
|
}
|
|
|
|
PARAMS = {
|
|
# experimentation V (GADM-0.1 § 5) + organism term gated by Q (M2)
|
|
"v0": 2.5, "eH": 0.5, "eX": 0.5, "eF": 0.3, "eG": 0.4, "eN": 0.3,
|
|
"eQ": 0.3, "eP": 0.5, "vo": 0.4,
|
|
# selection channel weights (§ 6) + organism channel O
|
|
"wm": 0.35, "ws": 0.20, "wc": 0.15, "wp": 0.15, "wo": 0.30,
|
|
# claims on output (§ 9) + participation claims C_p
|
|
"wL0": 0.60, "auto": 0.6, "dshare": 0.10, "cp": 0.0, "od": 0.20,
|
|
# ASSEMBLY-WP-0016-T04: foreign demand for organism-enabled output (exports), dem += xport * K_Omega.
|
|
# 0 = closed economy (default; all earlier results unchanged).
|
|
"xport": 0.0,
|
|
# ASSEMBLY-WP-0007-T01: rival / non-rival output. phi_r = share of organism output that is rival
|
|
# (physical, limited by ecological headroom); 1 = GADM up to 0.4 (all output scaled by sqrt(E)).
|
|
# xn = rate at which non-rival output, as far as it is openly inheritable, flows into commons X.
|
|
"phi_r": 1.0, "xn": 0.0,
|
|
# ASSEMBLY-WP-0018-T04: organism selection quality falls with discovery concentration,
|
|
# O = Q (1 - disc) (1 - o_pd * Pd). 0 = GADM up to 0.5 (default).
|
|
"o_pd": 0.0,
|
|
# commons (§ 7)
|
|
"aX": 0.30, "rho0": 0.30, "rhoO": 0.40, "IX": 0.02, "dX": 0.04,
|
|
"lX": 0.06, "lXo": 0.06,
|
|
# productive capacity (§ 8) and organism capability (mapping note § 1)
|
|
"aK": 0.35, "dK": 0.05, "aO": 0.15, "ell": 0.25, "oseed": 0.01, "dO": 0.06,
|
|
# human capability (§ 11)
|
|
"h0": 0.08, "hA": 0.04, "hT": 0.03, "dH": 0.05, "desk": 0.06,
|
|
# risk capacity
|
|
"f0": 0.03, "fY": 0.08, "dF": 0.06,
|
|
# effective agency (§ 12)
|
|
"g1": 0.08, "g2": 0.05, "g3": 0.04, "g4": 0.04, "dG": 0.04,
|
|
"b1": 0.08, "b2": 0.06,
|
|
# diversity & composability (§ 16)
|
|
"n1": 0.30, "n2": 0.05, "dN": 0.04, "n3": 0.06, "n4": 0.08,
|
|
# epistemic capacity (§ 15)
|
|
"q0": 0.04, "q1": 0.05, "qo": 0.06, "dQ": 0.03, "q2": 0.06, "qx": 0.08,
|
|
"q3": 0.10,
|
|
# domination (§ 13) + membrane control and lineage concentration
|
|
"p1": 0.10, "p2": 0.06, "p3": 0.06, "p4": 0.06,
|
|
"p5": 0.10, "p6": 0.05, "p7": 0.06,
|
|
# ecology (§ 14)
|
|
"ereg": 0.05, "e1": 0.06, "e2": 0.03, "thr": 1.0,
|
|
# cohesion (§ 17), relaxation rate
|
|
"srate": 0.20,
|
|
# attention headroom (mapping note § 4)
|
|
"arec": 0.10, "aext": 0.12,
|
|
# institutional / organism-regime levers (scenario axes)
|
|
"m": 0.30, # membrane control held by users/communities (0..1)
|
|
"L": 0.30, # lineage diffusion (0 concentrated .. 1 diffuse)
|
|
"lin_open": 0.30, # openly inheritable lineages (commons vs enclosure)
|
|
"std": 0.50, # open standards / interoperability
|
|
"disc": 0.40, # discovery concentration
|
|
"expl": 0.40, # explainability / provenance enforcement
|
|
"x_att": 0.50, # attention-extraction regime (0 protected .. 1 extraction)
|
|
}
|
|
|
|
FLOORS = {"G": (">=", 0.20), "E": (">=", 0.20), "P": ("<=", 0.70), "A": (">=", 0.20)}
|
|
|
|
|
|
def auxiliaries(s: dict, p: dict) -> dict:
|
|
"""Flows and auxiliaries for state s under parameters p."""
|
|
H, X, Kp, Ko, F, G, N, Q, P, E, S, A = (s[k] for k in STOCKS)
|
|
K = 1 - (1 - Kp) * (1 - Ko)
|
|
phi = p.get("phi_r", 1.0)
|
|
y_r = (1 - (1 - Kp) * (1 - phi * Ko)) * math.sqrt(E) # rival output, limited by ecology
|
|
y_n = (1 - phi) * Ko * (1 - Kp) # non-rival output (sum = K at E = 1)
|
|
ystar = y_r + y_n
|
|
|
|
wl = p["wL0"] * (1 - p["auto"] * Ko)
|
|
cap = 1 - wl
|
|
priv = cap * max(0.0, 1 - p["dshare"] - p["cp"])
|
|
B = min(1.0, wl + cap * (p["dshare"] + p["cp"]) + priv * p["od"])
|
|
dem = (0.4 + 0.6 * B) * (0.6 + 0.4 * S) + p.get("xport", 0.0) * Ko
|
|
|
|
v_h = p["v0"] * (H ** p["eH"]) * (X ** p["eX"]) * (F ** p["eF"]) * (G ** p["eG"]) \
|
|
* (N ** p["eN"]) * (Q ** p["eQ"]) * ((1 - P) ** p["eP"])
|
|
V = min(1.0, v_h + p["vo"] * Ko * Q)
|
|
|
|
M = math.sqrt(A * dem)
|
|
O = Q * (1 - p["disc"]) * (1 - p.get("o_pd", 0.0) * s.get("Pd_ch", 0.0))
|
|
w = [p["wm"], p["ws"], p["wc"], p["wp"], p["wo"] * Ko]
|
|
q = [M, Q, N * (1 - P), S, O]
|
|
sigma = sum(a * b for a, b in zip(w, q)) / sum(w)
|
|
vsel = V * sigma
|
|
rho = min(1.0, p["rho0"] + p["rhoO"] * Ko * p["lin_open"])
|
|
return {"K": K, "Ystar": ystar, "Yr": y_r, "Yn": y_n, "B": B, "dem": dem, "V": V, "sigma": sigma,
|
|
"Vsel": vsel, "rho": rho, "wl": wl}
|
|
|
|
|
|
def derivatives(s: dict, p: dict) -> tuple[dict, dict]:
|
|
H, X, Kp, Ko, F, G, N, Q, P, E, S, A = (s[k] for k in STOCKS)
|
|
a = auxiliaries(s, p)
|
|
B, dem, V, vsel, rho = a["B"], a["dem"], a["V"], a["Vsel"], a["rho"]
|
|
m, L, lo, std = p["m"], p["L"], p["lin_open"], p["std"]
|
|
|
|
d = {}
|
|
d["X"] = (p["aX"] * rho * vsel + p["IX"] + p.get("xn", 0.0) * a["Yn"] * lo) * (1 - X) \
|
|
- (p["dX"] + p["lX"] * P + p["lXo"] * Ko * (1 - lo)) * X
|
|
d["Kp"] = p["aK"] * vsel * dem * (1 - Kp) - p["dK"] * Kp
|
|
d["Ko"] = (p["aO"] * vsel * dem + p["ell"] * Ko * dem * Q + p["oseed"]) * (1 - Ko) \
|
|
- p["dO"] * Ko
|
|
d["H"] = (p["h0"] * (0.5 * S + 0.5 * B) + p["hA"] * A + p["hT"] * X) * (1 - H) \
|
|
- (p["dH"] + p["desk"] * Ko * (1 - m)) * H
|
|
d["F"] = (p["f0"] + p["fY"] * a["Ystar"] * B) * (1 - F) - p["dF"] * F
|
|
d["G"] = (p["g1"] * B + p["g2"] * X * (1 - P) + p["g3"] * F + p["g4"] * Ko * m) * (1 - G) \
|
|
- (p["dG"] + p["b1"] * P + p["b2"] * Ko * (1 - m)) * G
|
|
d["N"] = (p["n1"] * V * (1 - P) + p["n2"] * X * std) * (1 - N) \
|
|
- (p["dN"] + p["n3"] * P + p["n4"] * Ko * (1 - L)) * N
|
|
d["Q"] = (p["q0"] + p["q1"] * X + p["qo"] * Ko * p["expl"]) * (1 - Q) \
|
|
- (p["dQ"] + p["q2"] * P + p["qx"] * Ko * (1 - p["expl"]) + p["q3"] * max(0.0, V - Q)) * Q
|
|
d["P"] = (p["p1"] * vsel * (1 - B) + p["p2"] * p["disc"] + p["p3"] * Ko * (1 - m)
|
|
+ p["p4"] * Ko * (1 - L)) * (1 - P) \
|
|
- (p["p5"] * Q * S + p["p6"] * X * std + p["p7"] * B) * P
|
|
d["E"] = p["ereg"] * (1 - E) - (p["e1"] * Kp * dem * p["thr"] + p["e2"] * Ko) * E
|
|
s_target = (G ** 0.4) * ((1 - P) ** 0.3) * (B ** 0.2) * (A ** 0.1)
|
|
d["S"] = p["srate"] * (s_target - S)
|
|
d["A"] = p["arec"] * (0.4 + 0.6 * B) * (1 - A) - p["aext"] * p["x_att"] * (0.2 + Ko) * A
|
|
return d, a
|
|
|
|
|
|
def vfac(s: dict, with_attention: bool = True) -> float:
|
|
"""Geometric-mean VFAC (equal exponents). Diagnostic, not a welfare function."""
|
|
f = [s["H"], s["X"], s["G"], s["N"], s["Q"], s["S"], s["E"], 1 - s["P"]]
|
|
if with_attention:
|
|
f.append(s["A"])
|
|
if min(f) <= 0.0:
|
|
return 0.0 # a collapsed foundational subsystem collapses the diagnostic
|
|
return math.exp(sum(math.log(x) for x in f) / len(f))
|
|
|
|
|
|
def simulate(params: dict | None = None, init: dict | None = None,
|
|
T: float = 100.0, dt: float = 0.1, record_every: int = 10) -> list[dict]:
|
|
p = {**PARAMS, **(params or {})}
|
|
s = {**INIT, **(init or {})}
|
|
unknown = set(p) - set(PARAMS)
|
|
if unknown:
|
|
raise KeyError(f"unknown parameters: {sorted(unknown)}")
|
|
rows = []
|
|
steps = int(round(T / dt))
|
|
for i in range(steps + 1):
|
|
d, a = derivatives(s, p)
|
|
if i % record_every == 0:
|
|
row = {"t": round(i * dt, 6), **s, **{k: a[k] for k in ("Ystar", "B", "V", "Vsel", "sigma")}}
|
|
row["VFAC"] = vfac(s)
|
|
row["VFAC01"] = vfac(s, with_attention=False)
|
|
rows.append(row)
|
|
if i < steps:
|
|
s = {k: min(1.0, max(0.0, s[k] + dt * d[k])) for k in STOCKS}
|
|
return rows
|
|
|
|
|
|
def floor_violations(rows: list[dict]) -> dict:
|
|
"""Share of recorded periods in which each constitutional floor is violated."""
|
|
out = {}
|
|
for k, (op, lim) in FLOORS.items():
|
|
bad = [r for r in rows if (r[k] < lim if op == ">=" else r[k] > lim)]
|
|
out[k] = len(bad) / len(rows)
|
|
return out
|