1"""SIMULATED — factorial experiments + ablations for H3. Usage: python -m wp35.simulations.run_experiments [--quick]2Design (pre-declared here before running): N x network x bandwidth x plan-entropy x shock x regime x seeds, plus an3admin-capacity factor for the two hierarchical regimes. Outputs results/simulated/runs_main.parquet, runs_ablation.parquet.4"""5from __future__ import annotations6import itertools, sys, time7from multiprocessing import Pool8from pathlib import Path9import pandas as pd10from wp35.simulations.model import Params, simulate1112ROOT = Path(__file__).resolve().parents[3]; OUT = ROOT / "results" / "simulated"; OUT.mkdir(parents=True, exist_ok=True)13SEEDS = list(range(8))14REGIMES = ["none", "norms", "records", "central", "federated"]151617def main_design(quick=False):18 Ns = [50, 200, 800] if not quick else [50, 200]19 cells = []20 for N, net, bw, zipf, shock, reg, seed in itertools.product(Ns, ["lattice", "smallworld", "random"], [2, 8], [0.0, 1.0], ["none", "distribution", "network"], REGIMES, SEEDS if not quick else SEEDS[:2]):21 base = dict(N=N, network=net, bandwidth=bw, zipf=zipf, shock_type=shock, shock_period=20 if shock != "none" else -1, regime=reg, seed=seed, T=40)22 if reg in ("central", "federated"):23 for ratio in [0.5, 2.0]:24 cells.append(Params(**base, admin_capacity_ratio=ratio))25 else:26 cells.append(Params(**base, admin_capacity_ratio=2.0))27 return cells282930def ablation_design(quick=False):31 cells = []32 seeds = SEEDS if not quick else SEEDS[:2]33 base = dict(N=200, network="lattice", bandwidth=8, zipf=1.0, T=40)34 for seed in seeds:35 for reg in REGIMES:36 r = 2.037 cells.append(Params(**base, regime=reg, seed=seed, admin_capacity_ratio=r, shock_type="none", shock_period=-1)) # reference38 # records without verification39 cells.append(Params(**base, regime="records", seed=seed, verify_records=False, admin_capacity_ratio=2.0))40 # code adaptation speed after a distribution shock41 for ad in [0, 10, 1000]:42 cells.append(Params(**base, regime="norms", seed=seed, shock_type="distribution", shock_period=20, adapt_delay=ad, shock_kl_target=2.0, admin_capacity_ratio=2.0))43 # horizon (break-even, Theorem 2) and learning cost44 for T in [10, 40, 160]:45 for lb in [0.0, 200.0, 1000.0]:46 for reg in ["none", "norms"]:47 cells.append(Params(**{**base, "T": T}, regime=reg, seed=seed, learn_bits=lb, admin_capacity_ratio=2.0))48 # break-even with a large, concentrated plan space (high coding gain)49 for T in [10, 40, 160]:50 for lb in [200.0, 1000.0]:51 for reg in ["none", "norms"]:52 cells.append(Params(**{**base, "T": T, "zipf": 1.5}, regime=reg, seed=seed, s=256, learn_bits=lb, admin_capacity_ratio=2.0))53 # heterogeneity of information (eps) for none vs norms54 for eps in [0.05, 0.25, 0.6]:55 for reg in ["none", "norms"]:56 cells.append(Params(**base, regime=reg, seed=seed, eps=eps, admin_capacity_ratio=2.0))57 # admin capacity sweep (phase diagram, Theorem 5) and branching58 for ratio in [0.25, 0.5, 0.75, 1.0, 1.5, 2.0, 4.0]:59 for reg in ["central", "federated"]:60 cells.append(Params(**base, regime=reg, seed=seed, admin_capacity_ratio=ratio))61 for b in [4, 16]:62 cells.append(Params(**base, regime="federated", seed=seed, branching=b, admin_capacity_ratio=1.0))63 # plan-set size / entropy sweep (coding gain, Theorem 1)64 for s_, z in [(16, 0.0), (64, 0.0), (256, 0.0), (64, 0.5), (64, 1.5), (64, 2.5)]:65 for reg in ["none", "norms"]:66 cells.append(Params(**{**base, "zipf": z}, regime=reg, seed=seed, s=s_, admin_capacity_ratio=2.0))67 return cells686970def _run(P):71 return simulate(P)727374def run(cells, path, n_jobs=12):75 t0 = time.time()76 with Pool(n_jobs) as pool:77 rows = pool.map(_run, cells, chunksize=16)78 df = pd.DataFrame(rows); df.to_parquet(path, index=False)79 print(f"{path.name}: {len(df)} runs in {time.time() - t0:.1f}s")80 return df818283def main():84 quick = "--quick" in sys.argv85 main_cells = main_design(quick); abl = ablation_design(quick)86 print("main design cells:", len(main_cells), "| ablation cells:", len(abl))87 run(main_cells, OUT / ("runs_main_quick.parquet" if quick else "runs_main.parquet"))88 run(abl, OUT / ("runs_ablation_quick.parquet" if quick else "runs_ablation.parquet"))899091if __name__ == "__main__":92 main()93