1"""Figures and numbers from the heterogeneous-agent runs (results/ha/*_ha.npz).2Outputs: results/figures/fig10_distribution.{pdf,png}, results/tables/tab07_distribution.tex, results/data/ha_numbers.json"""3import glob4import json5import os67import matplotlib8import numpy as np910matplotlib.use("Agg")11import matplotlib.pyplot as plt1213ROOT = os.path.join(os.path.dirname(__file__), "..")14HA = os.path.join(ROOT, "results", "ha")15FIG = os.path.join(ROOT, "results", "figures")16TAB = os.path.join(ROOT, "results", "tables")17plt.rcParams.update({"font.size": 9, "axes.spines.top": False, "axes.spines.right": False, "legend.frameon": False})18C = {"baseline": "#1f3b73", "no_ai": "#7f8c8d", "citizen_fund_20": "#27ae60", "rapid_agi": "#c0392b"}19LAB = {"baseline": "baseline", "no_ai": "AI frozen", "citizen_fund_20": "citizen fund 20%", "rapid_agi": "rapid AGI"}2021runs = {}22for f in sorted(glob.glob(os.path.join(HA, "*_ha.npz"))):23 n = os.path.basename(f).replace("_ha.npz", "")24 runs[n] = dict(np.load(f))25 runs[n]["meta"] = json.load(open(f.replace(".npz", ".json")))26print("HA runs:", list(runs))27if runs:28 fig, ax = plt.subplots(2, 3, figsize=(10.5, 5.6))29 for n, d in runs.items():30 y = d["years"]; m = y <= 210031 kw = dict(color=C.get(n, "k"), label=LAB.get(n, n))32 ax[0, 0].plot(y[m], d["gini"][m], **kw)33 ax[0, 1].plot(y[m], d["top10"][m], **kw)34 ax[0, 2].plot(y[m], d["top1"][m], **kw)35 ax[1, 0].plot(y[m], d["bottom50"][m], **kw)36 ax[1, 1].plot(y[m], 100 * d["r"][1:len(y) + 1][m], **kw)37 ax[1, 1].plot(y[m], 100 * d["r_twoclass"][1:len(y) + 1][m], color=C.get(n, "k"), ls=":", lw=0.8)38 ax[1, 2].plot(y[m], (d["X"] / d["GDP"][:len(y)])[m], **kw)39 ax[0, 0].set_title("Wealth Gini"); ax[0, 1].set_title("Top-10% wealth share"); ax[0, 2].set_title("Top-1% wealth share")40 ax[1, 0].set_title("Bottom-50% wealth share"); ax[1, 1].set_title("Real return: HA (solid) vs two-class (dotted), %")41 ax[1, 2].set_title("Household expenditure / GDP")42 ax[0, 0].legend(fontsize=7)43 for a in ax.ravel():44 a.set_xlim(2026, 2100)45 fig.tight_layout()46 for ext in ("pdf", "png"):47 fig.savefig(os.path.join(FIG, f"fig10_distribution.{ext}"), dpi=200, bbox_inches="tight")48 years = (2026, 2030, 2040, 2050, 2075, 2100)49 with open(os.path.join(TAB, "tab07_distribution.tex"), "w") as f:50 f.write("\\begin{tabular}{@{}ll" + "r" * len(years) + "@{}}\n\\toprule\nScenario & Statistic & " + " & ".join(str(y) for y in years) + "\\\\\n\\midrule\n")51 for n, d in runs.items():52 for stat, lab in [("gini", "Gini"), ("top10", "Top 10\\%"), ("top1", "Top 1\\%"), ("bottom50", "Bottom 50\\%")]:53 vals = []54 for yy in years:55 i = np.where(d["years"] == yy)[0]56 vals.append(f"{d[stat][i[0]]:.3f}" if len(i) else "--")57 f.write(f"{LAB.get(n, n) if stat == 'gini' else ''} & {lab} & " + " & ".join(vals) + "\\\\\n")58 vals = []59 for yy in years:60 i = np.where(d["years"] == yy)[0]61 vals.append(f"{100 * d['r'][i[0] + 1]:.1f}" if len(i) else "--")62 f.write(f" & $r$ (\\%) & " + " & ".join(vals) + "\\\\\n\\midrule\n")63 f.write("\\bottomrule\n\\end{tabular}\n")64 nums = {}65 if "baseline" in runs:66 d = runs["baseline"]; i0 = 067 nums["GiniBase"] = f"{d['gini'][0]:.2f}"; nums["TopTenBase"] = f"{d['top10'][0]:.2f}"; nums["TopOneBase"] = f"{d['top1'][0]:.2f}"68 nums["BottomFiftyBase"] = f"{d['bottom50'][0]:.3f}"; nums["betaHigh"] = f"{d['meta']['beta_high']:.4f}"69 for yy in (2050, 2100):70 i = int(np.where(d["years"] == yy)[0][0])71 yw = {2050: "TwoZeroFiveZero", 2100: "TwoOneZeroZero"}[yy]72 nums[f"Gini{yw}"] = f"{d['gini'][i]:.2f}"73 nums[f"TopTen{yw}"] = f"{d['top10'][i]:.2f}"74 nums[f"TopOne{yw}"] = f"{d['top1'][i]:.2f}"75 nums[f"rHA{yw}"] = f"{100 * d['r'][i + 1]:.1f}"76 nums[f"rTC{yw}"] = f"{100 * d['r_twoclass'][i + 1]:.1f}"77 nums["HAconverged"] = "yes" if d["meta"]["converged"] else "no"78 nums["HAmaxGap"] = f"{d['meta']['hist'][-1]:.0e}"79 with open(os.path.join(ROOT, "results", "data", "ha_numbers.json"), "w") as f:80 json.dump(nums, f, indent=1)81 print(nums)82