- aext recalibrated to 0.275 against ATUS and social-media time-use data; baseline attention headroom now ~0.49 instead of rising to 0.65 - T03/T04 stages stay reproducible (t03_params, t04_params; verified) - organism_grid.py: factorial analysis; lineage ownership is the strongest organism lever, then membrane control, then attention protection (which acts on A, not on power); lineage x membrane complementary at baseline; organism governance without attention protection raises attention draw 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
169 lines
7.2 KiB
Python
169 lines
7.2 KiB
Python
"""GADM-0.3 (power channels): domination P split into six channel sub-stocks.
|
|
|
|
Implements models/GADM-0.3.md § 2 (ASSEMBLY-WP-0005-T03). Everything except P
|
|
is GADM-0.2 unchanged (gadm02.py is reused). P becomes an auxiliary:
|
|
|
|
P = 1 - prod_k (1 - P_k) ** (1/6) (equal-weight geometric aggregation)
|
|
|
|
The plain product 1 - prod(1 - P_k) was tried first and rejected: with six
|
|
channels it compounds moderate values (six channels at 0.5 give P = 0.98) and
|
|
drove P to 1 in almost every scenario. The weighted form keeps the property
|
|
that one fully dominant channel yields P = 1, without compounding.
|
|
|
|
Each channel follows
|
|
dP_k = (d_k + r_k * P_k**2) * (1 - P_k) - c_k * C_k * P_k
|
|
with driver d_k, superlinear self-reinforcement r_k * P_k**2 (increasing
|
|
returns to control, which allows threshold behaviour, hypothesis P1) and a
|
|
counter C_k scaled by c_k. The c_k are calibrated so that every channel starts
|
|
in equilibrium in the baseline (see calibrate()).
|
|
|
|
Wealth separated from power (ASSEMBLY-WP-0005-T04): a wealth-concentration
|
|
stock W grows with privately captured returns and shrinks through decay and a
|
|
wealth tax. It converts into the market and political channels at rate
|
|
kappa = k_w * (1 - Q*S): checks reduce conversion. Membrane and lineage control
|
|
do not depend on wealth. Set wealth_on = 0 to reproduce the T03 model.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import math
|
|
|
|
import gadm02 as g2
|
|
|
|
MODEL_VERSION = "GADM-0.3-power"
|
|
TIME_UNIT = g2.TIME_UNIT
|
|
|
|
CHANNELS = ("Pm", "Pi", "Pd", "Pmem", "Plin", "Ppol")
|
|
CHANNEL_NAMES = {
|
|
"Pm": "market", "Pi": "infrastructure", "Pd": "discovery/attention",
|
|
"Pmem": "membrane", "Plin": "lineage", "Ppol": "political/institutional",
|
|
}
|
|
STOCKS = tuple(k for k in g2.STOCKS if k != "P") + CHANNELS + ("W",)
|
|
|
|
INIT = {k: v for k, v in g2.INIT.items() if k != "P"}
|
|
INIT.update({"Pm": 0.50, "Pi": 0.45, "Pd": 0.50, "Pmem": 0.05, "Plin": 0.05, "Ppol": 0.50})
|
|
INIT["W"] = 0.50 # wealth concentration
|
|
|
|
PARAMS = dict(g2.PARAMS)
|
|
PARAMS.update({
|
|
# drivers
|
|
"pi": 0.06, # infrastructure control grows with capacity K where standards are closed
|
|
"ppol": 0.10, # economic control converts into political influence
|
|
# self-reinforcement (increasing returns to control)
|
|
"r_m": 0.30, "r_i": 0.30, "r_d": 0.40, "r_mem": 0.30, "r_lin": 0.30, "r_pol": 0.30,
|
|
"lw": 2.0, # learning-with-use amplification of lineage self-reinforcement (R7)
|
|
# wealth separated from power (T04)
|
|
"wealth_on": 1.0, # 0 reproduces the T03 power-channel model
|
|
"a_w": 0.10, # accumulation of privately captured returns
|
|
"d_w": 0.03, # dissipation (inheritance splitting, failure, competition)
|
|
"t_w": 0.10, # wealth-tax effectiveness
|
|
"wtax": 0.20, # wealth-tax lever (0..1)
|
|
"k_w": 0.04, # wealth → market/political control conversion
|
|
# attention extraction recalibrated against time-use evidence (T05):
|
|
# GADM-0.2's 0.12 let baseline attention headroom rise to 0.65; 0.275 keeps
|
|
# it near 0.45 (engagement media already take ~half of leisure time).
|
|
"aext": 0.275,
|
|
# counter coefficients: calibrated, see calibrate()
|
|
"c_m": 0.0794, "c_i": 0.3117, "c_d": 0.2815, "c_mem": 0.1354, "c_lin": 0.1372, "c_pol": 0.4011,
|
|
})
|
|
|
|
|
|
def total_p(s: dict) -> float:
|
|
return 1 - math.prod((1 - s[k]) ** (1 / len(CHANNELS)) for k in CHANNELS)
|
|
|
|
|
|
def _channel_terms(s: dict, p: dict, a: dict) -> dict:
|
|
"""Return {channel: (driver, r_eff, counter)}."""
|
|
Ko, Q, S, X, N = s["Ko"], s["Q"], s["S"], s["X"], s["N"]
|
|
B, vsel, K = a["B"], a["Vsel"], a["K"]
|
|
conv = p["wealth_on"] * p["k_w"] * (1 - Q * S) * s["W"] # wealth bought into control
|
|
econ_ch = ("Pm", "Pi", "Pd", "Pmem", "Plin")
|
|
econ = 1 - math.prod((1 - s[k]) ** (1 / len(econ_ch)) for k in econ_ch)
|
|
return {
|
|
"Pm": (p["p1"] * vsel * (1 - B) + conv, p["r_m"], B + N),
|
|
"Pi": (p["pi"] * K * (1 - p["std"]), p["r_i"], X * p["std"] + 0.1),
|
|
"Pd": (p["p2"] * p["disc"] * (0.5 + 0.5 * Ko), p["r_d"], (1 - p["disc"]) * Q + 0.1),
|
|
"Pmem": (p["p3"] * Ko * (1 - p["m"]), p["r_mem"], p["m"] + 0.1),
|
|
"Plin": (p["p4"] * Ko * (1 - p["L"]), p["r_lin"] * (1 + p["lw"] * Ko * Q), p["L"] + 0.1),
|
|
"Ppol": (p["ppol"] * econ * (1 - B) + conv, p["r_pol"], Q * S),
|
|
}
|
|
|
|
|
|
COEF = {"Pm": "c_m", "Pi": "c_i", "Pd": "c_d", "Pmem": "c_mem", "Plin": "c_lin", "Ppol": "c_pol"}
|
|
|
|
|
|
def derivatives(s: dict, p: dict) -> tuple[dict, dict]:
|
|
s2 = {k: v for k, v in s.items() if k not in CHANNELS}
|
|
s2["P"] = total_p(s)
|
|
d, a = g2.derivatives(s2, p)
|
|
d.pop("P")
|
|
for k, (drv, r, ctr) in _channel_terms(s, p, a).items():
|
|
d[k] = (drv + r * s[k] ** 2) * (1 - s[k]) - p[COEF[k]] * ctr * s[k]
|
|
W = s["W"]
|
|
d["W"] = p["a_w"] * (1 - a["B"]) * a["Ystar"] * (1 - W) - (p["d_w"] + p["t_w"] * p["wtax"]) * W
|
|
return d, a
|
|
|
|
|
|
def channel_roots(s: dict, p: dict, k: str, n: int = 10000) -> list[float]:
|
|
"""Equilibria of channel k with every other stock frozen at state s."""
|
|
s2 = {x: v for x, v in s.items() if x not in CHANNELS}
|
|
s2["P"] = total_p(s)
|
|
drv, r, ctr = _channel_terms(s, p, g2.auxiliaries(s2, p))[k]
|
|
c = p[COEF[k]]
|
|
f = lambda x: (drv + r * x * x) * (1 - x) - c * ctr * x # noqa: E731
|
|
xs = [i / n for i in range(1, n)]
|
|
return sorted({round(b, 3) for a, b in zip(xs, xs[1:]) if f(a) * f(b) <= 0})
|
|
|
|
|
|
def channel_slope(s: dict, p: dict, k: str, h: float = 1e-5) -> float:
|
|
"""d(dP_k)/dP_k at state s; negative means the channel is locally stable."""
|
|
up, dn = dict(s), dict(s)
|
|
up[k] += h
|
|
dn[k] -= h
|
|
return (derivatives(up, p)[0][k] - derivatives(dn, p)[0][k]) / (2 * h)
|
|
|
|
|
|
def calibrate(params: dict | None = None, init: dict | None = None) -> dict:
|
|
"""Counter coefficients that put every channel in equilibrium at t = 0."""
|
|
p = {**PARAMS, **(params or {})}
|
|
s = {**INIT, **(init or {})}
|
|
s2 = {k: v for k, v in s.items() if k not in CHANNELS}
|
|
s2["P"] = total_p(s)
|
|
a = g2.auxiliaries(s2, p)
|
|
out = {}
|
|
for k, (drv, r, ctr) in _channel_terms(s, p, a).items():
|
|
out[COEF[k]] = round((drv + r * s[k] ** 2) * (1 - s[k]) / (ctr * s[k]), 4)
|
|
return out
|
|
|
|
|
|
def t03_params() -> dict:
|
|
"""Parameter overrides that reproduce the T03 power-channel model (no wealth)."""
|
|
return {"wealth_on": 0.0, "aext": g2.PARAMS["aext"], **calibrate({"wealth_on": 0.0})}
|
|
|
|
|
|
def t04_params() -> dict:
|
|
"""Parameter overrides that reproduce the T04 stage (wealth, old attention)."""
|
|
return {"aext": g2.PARAMS["aext"]}
|
|
|
|
|
|
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, "P": total_p(s),
|
|
**{k: a[k] for k in ("Ystar", "B", "V", "Vsel", "sigma")}}
|
|
vs = {**row}
|
|
row["VFAC"] = g2.vfac(vs)
|
|
row["VFAC01"] = g2.vfac(vs, 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
|