ASSEMBLY-WP-0006-T03: global sensitivity on GADM-0.4

- gsa.py: any model (--model), parallel (--jobs), channel counters
  recalibrated per sample; reproduces the GADM-0.2 analysis exactly
- GADM-0.4: additive at baseline, but governance levers (m, L, r_lin)
  interact strongly in the generative regime; commons investment now the
  top baseline driver; organism capability level confirmed as top
  uncertainty; explainability gains weight via power channels

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
This commit is contained in:
tegwick 2026-10-12 08:45:06 +02:00
parent 2c6d6d3ec9
commit ffff0b3a3c
7 changed files with 227 additions and 21 deletions

View file

@ -19,7 +19,8 @@ simulation. No install step.
```bash
python3 -m unittest discover -s simulations/gadm02 # must pass before committing model changes
cd simulations/gadm02 && python3 run_scenarios.py && python3 sensitivity.py
cd simulations/gadm02 && python3 gsa.py # global sensitivity, ~5 min
cd simulations/gadm02 && python3 -B gsa.py --jobs 8 # global sensitivity GADM-0.2, ~1.5 min
cd simulations/gadm02 && python3 -B gsa.py --model gadm04 --jobs 8 # GADM-0.4, ~12 min
cd simulations/gadm02 && python3 -B power_channels.py # GADM-0.3 power channels (T03 stage)
cd simulations/gadm02 && python3 -B wealth.py # GADM-0.3 wealth vs. power
cd simulations/gadm02 && python3 -B organism_grid.py # GADM-0.3 organism grid

View file

@ -8,7 +8,7 @@ the code is the reference if the two ever disagree.
**Status:** in development, exploratory. Implemented so far: durable human
capability (§ 1, ASSEMBLY-WP-0006-T01), grounded organism depreciation and
robustness checks (§ 2, T02).
robustness checks (§ 2, T02), global sensitivity analysis (§ 3, T03).
GADM-0.4 builds on the GADM-0.3 cohort model (`GADM-0.3.md` § 5), which stays
unchanged and reproducible in `gadm03c.py`. Time unit: one year.
@ -106,10 +106,65 @@ Details and sources: `ParameterGrounding.md`.
are essentially unchanged: L3 half-time 17 years, final ΔH working age +0.032;
L1 ΔH −0.028 (ageing, strong mentoring) against −0.038 (stable, none).
---
## 3. Global sensitivity analysis (T03)
Same method as `GADM-0.2.md` § 5.1: Morris screening over all parameters, then
Sobol indices (S1 Saltelli 2010 centred, ST Jansen, N = 256, bootstrap 95 %
intervals) for the top 10 plus the reuse bundle; ±50 % ranges. New for
GADM-0.4:
- the channel counters c_* are recalibrated for every sample (not varied), so
each sample starts in equilibrium;
- the switch `wealth_on` is fixed;
- evaluation is parallel (`--jobs 8`). The parallel code reproduces the
GADM-0.2 analysis exactly.
Full tables: `simulations/gadm02/results/gsa_gadm04.md`.
| | Baseline | Generative regime |
|---|---|---|
| Top ST | `IX` 0.25, `dO` 0.23, `q0` 0.10, `lX` 0.10, `dX` 0.08 | `dO` 0.17, `expl` 0.17, `m` 0.16, `IX` 0.13, `q0` 0.12 |
| ΣS1 (analysed set) | 1.03 | 0.89 |
| Reuse bundle ΣS1 / ΣST | 0.30 / 0.28 | 0.20 / 0.26 |
| Morris σ ≥ μ* (strong interaction / non-linearity) | none | `L` (σ 0.051 vs. μ* 0.036), `r_lin` (0.043 vs. 0.042), `m` (0.057 vs. 0.062) |
For comparison, GADM-0.2: baseline top ST was `dO` 0.34, `p7` 0.13, `ell` 0.10;
generative top ST was `m` 0.27, `L` 0.13, `ereg` 0.13; ΣS1 ≈ 1.04–1.06.
### 3.1 Readings
1. **Additivity holds at baseline, but breaks for the governance levers in
the generative regime.** That is where the superlinear power channels
operate near their thresholds. Lineage diffusion `L` acts almost entirely
through interactions (S1 ≈ 0, ST 0.04), together with the lineage
self-reinforcement `r_lin`: it matters where it pushes the lineage channel
across its tipping point (GADM-0.3 P1/P2). One-at-a-time analysis is no
longer adequate for these levers.
2. **The commons cluster now drives the baseline.** Commons investment `IX`,
enclosure `lX` and obsolescence `dX` together carry about 0.43 of total
ST. In GADM-0.2, the reuse bundle carried about 0.11. Two things changed:
durable capability makes X's contribution to formation persist, and the
power channels make infrastructure control respond to X and standards. The
bundle's effect is mostly **IX alone**, without interaction (ΣST ≈ ΣS1).
So the GADM-0.2 correction ("reuse is not an interaction bundle") still
stands.
3. **The level of organism capability is a top uncertainty.** `dO` stays among
the top two drivers in both scenarios (ST 0.17–0.23), even after grounding.
Varying `dO` with investment held fixed moves the organism level, which is
exactly the parameter `ParameterGrounding.md` lists as ungrounded. Grounding
that level is the most valuable next empirical step.
4. **Explainability gains weight in the generative regime** (ST 0.17, up from
0.06 in GADM-0.2). With power channels, epistemic capacity Q does double
duty: it checks the political channel and feeds the discovery counter. So
`expl` acts on power as well as on knowledge.
## Reproduce
```bash
python3 -B -m unittest discover -s simulations/gadm02
cd simulations/gadm02 && python3 -B life_cycle.py --model gadm04
cd simulations/gadm02 && python3 -B robustness.py # T02 robustness checks
cd simulations/gadm02 && python3 -B gsa.py --model gadm04 --jobs 8 # T03, ~12 min on 8 cores
```

View file

@ -59,7 +59,8 @@ Readings:
## Open grounding needs
1. The level of organism capability K_Ω (AI capital share, task-automation
share).
share). Confirmed as a top uncertainty by the GADM-0.4 Sobol analysis
(`GADM-0.4.md` § 3, reading 3).
2. The channel self-reinforcement r_k (persistence of market leadership,
platform tipping evidence).
3. The wealth → influence conversion k_w (political-economy evidence on

View file

@ -17,7 +17,7 @@ bytecode cache can mask same-size changes made within the same second.
python3 -m unittest discover -s simulations/gadm02 # tests
cd simulations/gadm02 && python3 run_scenarios.py # → results/summary.md + CSVs
cd simulations/gadm02 && python3 sensitivity.py # → results/sensitivity.md
cd simulations/gadm02 && python3 gsa.py # → results/gsa.md (~5 min; --quick)
cd simulations/gadm02 && python3 -B gsa.py --jobs 8 # → results/gsa.md (--model gadm04 → gsa_gadm04.md; --quick)
```
Results under `results/` are committed so that model documents can cite them.

View file

@ -10,18 +10,32 @@ reading in models/GADM-0.2.md can be tested directly.
Ranges: each parameter varies ±50 % around its scenario value; levers are
clipped to [0, 1]; zero-valued parameters vary in [0, 0.1].
Usage: python3 simulations/gadm02/gsa.py [--quick]
Usage: python3 -B simulations/gadm02/gsa.py [--quick] [--model gadm04] [--jobs 8]
For models with power channels (gadm03, gadm03c, gadm04), the channel counter
coefficients c_* are not varied but recalibrated for every sample, so each
sample starts in equilibrium; the wealth_on switch is not varied.
"""
from __future__ import annotations
import importlib
import multiprocessing as mp
import random
import statistics
import sys
from pathlib import Path
import gadm02 as g
from scenarios import load_all
sys.dont_write_bytecode = True
import gadm03 # noqa: E402
from scenarios import load_all # noqa: E402
MODEL = sys.argv[sys.argv.index("--model") + 1] if "--model" in sys.argv else "gadm02"
JOBS = int(sys.argv[sys.argv.index("--jobs") + 1]) if "--jobs" in sys.argv else 1
g = importlib.import_module(MODEL)
CHANNELS = "Pm" in g.STOCKS
FIXED = {"wealth_on"} | ({c for c in gadm03.COEF.values()} if CHANNELS else set())
OUT = Path(__file__).resolve().parent / "results"
LEVERS = {"m", "L", "lin_open", "std", "disc", "expl", "x_att", "auto", "dshare", "cp", "od"}
@ -44,21 +58,52 @@ def model(base: dict, rng: dict, names: list[str], u: list[float], init: dict) -
for k, x in zip(names, u):
lo, hi = rng[k]
p[k] = lo + x * (hi - lo)
if CHANNELS:
p.update(gadm03.calibrate(p))
return g.simulate(p, init)[-1]["VFAC"]
_CTX: dict = {}
def _init(ctx: dict) -> None:
_CTX.update(ctx)
def _eval(u: list[float]) -> float:
return model(_CTX["base"], _CTX["rng"], _CTX["names"], u, _CTX["init"])
def evaluate(base, rng, names, init, points: list[list[float]]) -> list[float]:
"""Evaluate many sample points, in parallel when --jobs > 1."""
if JOBS <= 1:
return [model(base, rng, names, u, init) for u in points]
ctx = {"base": base, "rng": rng, "names": names, "init": init}
with mp.Pool(JOBS, initializer=_init, initargs=(ctx,)) as pool:
return pool.map(_eval, points, chunksize=8)
def morris(base, init, rng, r, rnd):
names = list(base)
names = [n for n in base if n not in FIXED]
k, delta = len(names), 2 / 3
effects = {n: [] for n in names}
for _ in range(r):
trajectories = []
for _ in range(r): # draw all trajectories first (same RNG order as before)
x = [rnd.choice((0.0, 1 / 3)) for _ in range(k)]
y = model(base, rng, names, x, init)
for i in rnd.sample(range(k), k):
order = rnd.sample(range(k), k)
pts = [list(x)]
for i in order:
x[i] += delta
y2 = model(base, rng, names, x, init)
effects[names[i]].append((y2 - y) / delta)
y = y2
pts.append(list(x))
trajectories.append((order, pts))
flat = [pt for _o, pts in trajectories for pt in pts]
ys = evaluate(base, rng, names, init, flat)
pos = 0
for order, pts in trajectories:
y = ys[pos:pos + len(pts)]
pos += len(pts)
for j, i in enumerate(order):
effects[names[i]].append((y[j + 1] - y[j]) / delta)
res = []
for n, ee in effects.items():
res.append((n, statistics.fmean(abs(e) for e in ee), statistics.fmean(ee), statistics.pstdev(ee)))
@ -70,11 +115,10 @@ def sobol(base, init, rng, names, n, rnd, boot=200):
k = len(names)
A = [[rnd.random() for _ in range(k)] for _ in range(n)]
B = [[rnd.random() for _ in range(k)] for _ in range(n)]
fA = [model(base, rng, names, a, init) for a in A]
fB = [model(base, rng, names, b, init) for b in B]
fAB = []
for i in range(k):
fAB.append([model(base, rng, names, A[j][:i] + [B[j][i]] + A[j][i + 1:], init) for j in range(n)])
pts = A + B + [A[j][:i] + [B[j][i]] + A[j][i + 1:] for i in range(k) for j in range(n)]
ys = evaluate(base, rng, names, init, pts)
fA, fB = ys[:n], ys[n:2 * n]
fAB = [ys[2 * n + i * n: 2 * n + (i + 1) * n] for i in range(k)]
def indices(idx):
a = [fA[j] for j in idx]
@ -107,6 +151,8 @@ def main() -> None:
OUT.mkdir(exist_ok=True)
scs = load_all()
lines = [f"# {g.MODEL_VERSION} global sensitivity analysis", "",
*([("Channel counters c_* are recalibrated per sample (not varied); "
"wealth_on is fixed."), ""] if CHANNELS else []),
"Generated by `simulations/gadm02/gsa.py`" + (" (--quick)" if quick else "") + ".",
f"Output: final VFAC (t = 100 {g.TIME_UNIT}s). Parameters vary ±50 % around the scenario",
"value (levers clipped to [0, 1]; zero-valued parameters in [0, 0.1]).",
@ -141,7 +187,8 @@ def main() -> None:
lines += ["", f"Sum of S1 over analysed parameters: {tot1:.3f}. Reuse bundle: "
f"ΣS1 = {b1:.3f}, ΣST = {bt:.3f}.", ""]
print(f"done {name}", file=sys.stderr)
(OUT / ("gsa-quick.md" if quick else "gsa.md")).write_text("\n".join(lines))
stem = "gsa" if MODEL == "gadm02" else f"gsa_{MODEL}"
(OUT / (f"{stem}-quick.md" if quick else f"{stem}.md")).write_text("\n".join(lines))
print("\n".join(lines))

View file

@ -0,0 +1,102 @@
# GADM-0.4-dev global sensitivity analysis
Channel counters c_* are recalibrated per sample (not varied); wealth_on is fixed.
Generated by `simulations/gadm02/gsa.py`.
Output: final VFAC (t = 100 years). Parameters vary ±50 % around the scenario
value (levers clipped to [0, 1]; zero-valued parameters in [0, 0.1]).
Morris: 20 trajectories, 4 levels. Sobol: S1 Saltelli (2010, centred), ST Jansen, N = 256,
bootstrap 95 % intervals, seed 20261012. Parameters outside the Sobol set stay at
their scenario values.
μ* ranks importance. A σ that is large relative to μ* indicates
interactions or non-linearity. ST − S1 is the share of variance due to
interactions involving that parameter.
## Baseline (current trends)
### Morris screening (top 15)
| Rank | Parameter | μ* | μ | σ |
|---|---|---|---|---|
| 1 | `IX` | 0.0520 | +0.0520 | 0.0119 |
| 2 | `dO` | 0.0503 | +0.0503 | 0.0209 |
| 3 | `q0` | 0.0410 | +0.0410 | 0.0185 |
| 4 | `dX` | 0.0333 | -0.0333 | 0.0096 |
| 5 | `n1` | 0.0328 | +0.0328 | 0.0122 |
| 6 | `ell` | 0.0301 | -0.0301 | 0.0159 |
| 7 | `lX` | 0.0275 | -0.0275 | 0.0061 |
| 8 | `q2` | 0.0263 | -0.0263 | 0.0142 |
| 9 | `g1` | 0.0260 | +0.0260 | 0.0070 |
| 10 | `arec` | 0.0250 | +0.0250 | 0.0046 |
| 11 | `b1` | 0.0225 | -0.0225 | 0.0049 |
| 12 | `wL0` | 0.0216 | +0.0216 | 0.0080 |
| 13 | `r_lin` | 0.0210 | -0.0210 | 0.0098 |
| 14 | `aext` | 0.0198 | -0.0198 | 0.0059 |
| 15 | `r_mem` | 0.0197 | -0.0197 | 0.0092 |
Reuse bundle Morris ranks: `rho0` 29, `IX` 1, `lin_open` 44, `std` 54
### Sobol indices
| Parameter | S1 | S1 95 % CI | ST | ST 95 % CI | ST − S1 |
|---|---|---|---|---|---|
| `IX` | 0.260 | [0.184, 0.355] | 0.249 | [0.214, 0.291] | -0.011 |
| `dO` | 0.169 | [0.077, 0.246] | 0.228 | [0.182, 0.275] | +0.059 |
| `q0` | 0.100 | [0.057, 0.150] | 0.097 | [0.082, 0.117] | -0.003 |
| `lX` | 0.140 | [0.101, 0.189] | 0.096 | [0.078, 0.115] | -0.044 |
| `dX` | 0.109 | [0.059, 0.162] | 0.079 | [0.066, 0.097] | -0.030 |
| `n1` | 0.019 | [-0.037, 0.078] | 0.079 | [0.065, 0.098] | +0.060 |
| `ell` | 0.070 | [0.027, 0.121] | 0.077 | [0.062, 0.097] | +0.007 |
| `g1` | 0.030 | [-0.015, 0.068] | 0.070 | [0.056, 0.084] | +0.040 |
| `arec` | 0.017 | [-0.028, 0.064] | 0.062 | [0.052, 0.073] | +0.045 |
| `q2` | 0.067 | [0.031, 0.103] | 0.050 | [0.042, 0.059] | -0.016 |
| `rho0` | 0.030 | [0.008, 0.048] | 0.017 | [0.014, 0.021] | -0.013 |
| `std` | 0.009 | [-0.007, 0.024] | 0.006 | [0.004, 0.007] | -0.003 |
| `lin_open` | 0.005 | [-0.006, 0.017] | 0.004 | [0.003, 0.005] | -0.001 |
Sum of S1 over analysed parameters: 1.025. Reuse bundle: ΣS1 = 0.304, ΣST = 0.277.
## Generative regime
### Morris screening (top 15)
| Rank | Parameter | μ* | μ | σ |
|---|---|---|---|---|
| 1 | `m` | 0.0624 | +0.0546 | 0.0565 |
| 2 | `r_lin` | 0.0423 | -0.0419 | 0.0431 |
| 3 | `IX` | 0.0410 | +0.0410 | 0.0288 |
| 4 | `dO` | 0.0388 | +0.0388 | 0.0245 |
| 5 | `n1` | 0.0365 | +0.0365 | 0.0175 |
| 6 | `L` | 0.0361 | +0.0197 | 0.0508 |
| 7 | `q0` | 0.0350 | +0.0350 | 0.0261 |
| 8 | `expl` | 0.0347 | +0.0347 | 0.0156 |
| 9 | `aX` | 0.0297 | +0.0297 | 0.0203 |
| 10 | `dX` | 0.0292 | -0.0292 | 0.0173 |
| 11 | `q1` | 0.0286 | +0.0286 | 0.0160 |
| 12 | `q2` | 0.0275 | -0.0275 | 0.0142 |
| 13 | `arec` | 0.0267 | +0.0267 | 0.0069 |
| 14 | `ereg` | 0.0267 | +0.0267 | 0.0130 |
| 15 | `g1` | 0.0265 | +0.0265 | 0.0086 |
Reuse bundle Morris ranks: `rho0` 26, `IX` 3, `lin_open` 31, `std` 22
### Sobol indices
| Parameter | S1 | S1 95 % CI | ST | ST 95 % CI | ST − S1 |
|---|---|---|---|---|---|
| `dO` | 0.147 | [0.076, 0.204] | 0.171 | [0.134, 0.203] | +0.023 |
| `expl` | 0.165 | [0.103, 0.235] | 0.166 | [0.139, 0.196] | +0.000 |
| `m` | 0.095 | [0.022, 0.182] | 0.156 | [0.092, 0.230] | +0.061 |
| `IX` | 0.098 | [0.030, 0.152] | 0.129 | [0.102, 0.162] | +0.032 |
| `q0` | 0.119 | [0.074, 0.192] | 0.118 | [0.096, 0.144] | -0.001 |
| `n1` | 0.075 | [0.023, 0.127] | 0.092 | [0.076, 0.113] | +0.017 |
| `std` | 0.051 | [0.009, 0.091] | 0.066 | [0.052, 0.079] | +0.015 |
| `aX` | 0.044 | [0.009, 0.078] | 0.060 | [0.044, 0.075] | +0.016 |
| `dX` | 0.043 | [-0.002, 0.095] | 0.057 | [0.047, 0.070] | +0.014 |
| `r_lin` | 0.016 | [-0.014, 0.045] | 0.048 | [0.029, 0.070] | +0.032 |
| `L` | -0.013 | [-0.047, 0.013] | 0.039 | [0.017, 0.071] | +0.052 |
| `rho0` | 0.032 | [0.001, 0.061] | 0.038 | [0.030, 0.051] | +0.006 |
| `lin_open` | 0.022 | [-0.004, 0.051] | 0.027 | [0.022, 0.035] | +0.006 |
Sum of S1 over analysed parameters: 0.894. Reuse bundle: ΣS1 = 0.203, ΣST = 0.262.

View file

@ -51,7 +51,7 @@ literature- or data-based ranges. Re-run the affected readings.
```task
id: ASSEMBLY-WP-0006-T03
status: progress
status: done
priority: medium
state_hub_task_id: "c33dd30b-a015-55d6-ba75-61dc3d7a1b49"
```