SPB Git

spb/wp24_uqo

Public
18.6 KB · 288 lines python
1"""Tables and LaTeX number macros from solved scenarios.2Outputs: results/tables/tab02_calibration.tex, tab03_baseline.tex, tab04_scenarios.tex, tab05_decomposition.tex,3tab06_policy.tex, tab08_diagnostics.tex, numbers.tex ; results/data/scenario_summary.csv"""4import glob5import json6import os7import re8import sys910import numpy as np11import pandas as pd1213sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))14from agi_ge.analysis import campbell_shiller_decomposition, summary_by_year, valuation_ratios, welfare_cev  # noqa: E40215from agi_ge.calibration import load_targets  # noqa: E40216from agi_ge.solve import load_paths  # noqa: E4021718ROOT = os.path.join(os.path.dirname(__file__), "..")19SC = os.path.join(ROOT, "results", "scenarios")20TAB = os.path.join(ROOT, "results", "tables")21os.makedirs(TAB, exist_ok=True)22LAB = {"baseline": "Baseline", "no_ai": "AI frozen (everything at 2026)", "no_capability": "Capability frozen, hardware cheapens", "alpha_inf_x5": "Terminal compute requirements $\\times5$", "rapid_agi": "Rapid AGI (all tasks in 2032)",23       "fast_moore_cheap_energy": "Fast Moore ($g_{pQ}=0.34$), cheap energy", "fast_moore_energy_constrained": "Fast Moore, no efficiency gains",24       "moore_ends_2040": "Moore's law ends 2040 (floor 0.12)", "moore_floor_1e3": "Compute price floor $10^{-3}$", "moore_never_ends": "Moore's law never ends",25       "slow_alpha": "Slow capability ($g_\\alpha=0.05$)", "fast_alpha": "Fast capability ($g_\\alpha=0.20$)",26       "complements_leontief_like": "Strong complements ($\\sigma_N=\\sigma_T=0.4$)", "complements_cobb_douglas": "Cobb--Douglas ($\\sigma=1$)",27       "substitutes": "Substitutes ($\\sigma=1.5$)", "ai_monopoly": "AI monopoly (markup 1.5)", "citizen_fund_20": "Citizen fund (20\\% of equity)",28       "land_tax_50": "Land tax 50\\%", "compute_tax_30": "Compute tax 30\\%", "capital_tax_20": "Capital-income tax 20\\%",29       "foreign_ai_50": "50\\% foreign-owned compute", "low_risk_aversion": "$\\gamma=1$", "high_risk_aversion": "$\\gamma=4$",30       "scarce_land": "Scarce land (share 8\\%, $\\sigma_T=0.4$)", "severe_chip_depreciation": "Severe chip depreciation ($\\delta_Q=0.45$)",31       "reinstatement": "Expert task never automated"}323334_DIGITS = {"0": "Zero", "1": "One", "2": "Two", "3": "Three", "4": "Four", "5": "Five", "6": "Six", "7": "Seven", "8": "Eight", "9": "Nine"}353637def macro_name(s):38    """LaTeX-safe macro fragment: digits spelled out, non-letters removed."""39    s = "".join(_DIGITS.get(ch, ch) for ch in str(s))40    return re.sub(r"[^A-Za-z]", "", s)414243def fmt(x, d=2):44    if x is None or (isinstance(x, float) and not np.isfinite(x)):45        return "--"46    if abs(x) >= 1000:47        return f"{x:,.0f}".replace(",", "{,}")48    return f"{x:.{d}f}"495051def write_numbers(nums):52    with open(os.path.join(TAB, "numbers.tex"), "w") as f:53        f.write("% generated by scripts/50_tables.py — one macro per number used in the text\n")54        for k, v in nums.items():55            f.write(f"\\newcommand{{\\{k}}}{{{v}}}\n")565758def table_calibration(params, info):59    tg = load_targets()60    rows = []61    src = params.get("source", {})62    def add(name, sym, val, status, source):63        rows.append((name, sym, val, status, source))64    v = {k: t.value for k, t in tg.items()}65    st = {k: t.status for k, t in tg.items()}66    so = {k: t.source for k, t in tg.items()}67    add("Labor share of GDP, 2023", "", fmt(v["labor_share"], 3), st["labor_share"], "PWT via FRED")68    add("Nonresidential fixed assets / GDP (comparison)", "", fmt(v["KN_over_GDP"]), st["KN_over_GDP"], "BEA via FRED")69    add("Structures / GDP implied by income shares", "$K^N_0$", fmt(params["KN0"]), "calibrated", "residual of the income identity")70    add("Residential structures / GDP", "$K^H_0$", fmt(params["KH0"]), st["KH_over_GDP"], "BEA via FRED (derived)")71    add("Energy capital / GDP", "$K^E_0$", fmt(params["KE0"]), st["KE_over_GDP"], "assumption")72    add("Value of compute capital / GDP", "$p^Q_0K^Q_0$", fmt(v["VQ_over_GDP"], 3), st["VQ_over_GDP"], "IPE investment capitalized")73    add("Depreciation: structures, housing, compute, energy", "$\\delta_k$", f"{params['delta']['N']:.3f}, {params['delta']['H']:.3f}, {params['delta']['Q']:.2f}, {params['delta']['E']:.2f}", "data / assumed", "BEA via FRED; accounting lives")74    add("Adjustment costs", "$\\phi_k$", f"{params['phi']['N']:.0f}, {params['phi']['H']:.0f}, {params['phi']['Q']:.0f}, {params['phi']['E']:.0f}", "literature", "Cooper--Haltiwanger (2006) order")75    add("Real return in the frozen BGP", "$r^*$", fmt(v["r_star"], 3), st["r_star"], "between TIPS (1.96\\%) and returns on wealth")76    add("Trend growth per efficiency unit", "$g_Z$", fmt(v["g_Z"], 3), st["g_Z"], "real GDP growth 1990--2025 minus population")77    add("Curvature of utility", "$\\gamma$", fmt(v["gamma_u"], 1), "literature", "")78    add("Discount factor (implied)", "$\\beta$", fmt(params["beta"], 4), "calibrated", "$\\beta=(1+g_Z)^\\gamma/(1+r^*)$")79    add("Housing expenditure share (implied)", "$\\theta_H$", fmt(params["theta_H"], 3), "calibrated", "housing value added 12\\% of GDP")80    add("Land share of housing (implied)", "$\\nu_H$", fmt(params["nu_H"], 3), "calibrated", "housing Euler")81    add("Resource share in energy (implied)", "$\\eta$", fmt(params["eta"], 3), "calibrated", "energy Euler")82    add("Energy sector output / GDP", "", fmt(v["energy_share"], 3), st["energy_share"], "assumption (electricity 1.7\\% + other)")83    add("Data-center share of electricity, 2023", "", fmt(v["datacenter_energy_share"], 3), st["datacenter_energy_share"], "Shehabi et al. (2024)")84    add("Production land rent / GDP", "", fmt(v["land_rent_share"], 3), st["land_rent_share"], "assumption")85    add("Elasticities: tasks; tasks--structures; top nest", "$\\sigma_B,\\sigma_N,\\sigma_T$", f"{v['sigma_B']}, {v['sigma_N']}, {v['sigma_T']}", "literature", "Oberfield--Raval; Chirinko")86    add("Workers of skill $L$; skill premium", "$H_L$; $w_H/w_L$", f"{v['share_L_workers']}; {v['skill_premium']}", "assumed", "")87    add("CES task weights (implied)", "$\\nu_i$", ", ".join(f"{x:.3f}" for x in params["tasks"]["nu"]), "calibrated", "task wage shares")88    add("Compute per worker, task 1, 2026 (implied)", "$\\alpha_{1,0}$", fmt(info["alpha10"], 1), "calibrated", "compute Euler and task-1 tie")89    add("Implied compute share of task-1 input, 2026", "", src.get("compute_in_task1_implied", ""), "implied", "")90    add("Terminal compute per worker by task", "$\\alpha_i^\\infty$", ", ".join(f"{x:g}" for x in params["tasks"]["alpha_inf"]), "scenario", "normalization $\\alpha_1^\\infty=1$")91    add("2026 compute per worker by task (multiples of terminal)", "$\\alpha_{i,0}/\\alpha_i^\\infty$", ", ".join(f"{x:.0f}" for x in params["alpha0_mult"]), "calibrated / scenario", "")92    add("Capability progress; compute price decline; efficiency gains", "$g_\\alpha,g_{pQ},g_\\varepsilon$", f"{params['g_alpha']}, {params['g_pQ']}, {params['g_eps']}", "scenario", "Epoch: 0.34 for GPU FLOP/\\$")93    add("Floors (fraction of 2026): compute price; energy per unit", "$\\underline p/p_0,\\underline\\varepsilon/\\varepsilon_0$", f"{params['pQ_floor_ratio']:g}, {params['eps_floor_ratio']:g}", "scenario", "")94    with open(os.path.join(TAB, "tab02_calibration.tex"), "w") as f:95        f.write("\\begin{tabular}{@{}p{6.3cm}lp{3.2cm}lp{3.8cm}@{}}\n\\toprule\nObject & Symbol & Value & Status & Source\\\\\n\\midrule\n")96        for r in rows:97            f.write(" & ".join(str(x) for x in r) + "\\\\\n")98        f.write("\\bottomrule\n\\end{tabular}\n")99100101def table_baseline(P, years=(2026, 2030, 2040, 2050, 2075, 2100)):102    S = summary_by_year(P, years)103    rows = [("GDP (2026 = 1)", "GDP", 2), ("GDP growth (\\%)", "GDP_growth", 1), ("Labor share", "labor_share", 3),104            ("Compute income share", "compute_income_share", 3), ("Structures share", "structures_share", 3),105            ("Land share (both)", "land_share", 3), ("Energy share", "energy_share", 3),106            ("Wage $L$ per worker (2026 = 1)", "wL", 3), ("Wage $H$ per worker (2026 = 1)", "wH", 3),107            ("Real return $r$ (\\%)", "r", 1), ("Compute capacity (2026 = 1)", "KQ", 1), ("Value of compute / GDP", "VQ_over_GDP", 3),108            ("Wealth / GDP", "W_over_GDP", 2), ("Land price--rent ratio", "land_PR", 1), ("House price--rent ratio", "house_PR", 1),109            ("P/E compute", "PE_Q", 1), ("P/E structures", "PE_N", 1), ("Tobin's $q$ compute", "q_Q", 2),110            ("Share of energy used by compute", "energy_to_compute", 3), ("Investment / GDP", "inv_share", 3),111            ("Workers' consumption / GDP", "CW_over_GDP", 3), ("Tasks using compute (of 4)", "tasks_automated", 0)]112    with open(os.path.join(TAB, "tab03_baseline.tex"), "w") as f:113        f.write("\\begin{tabular}{@{}l" + "r" * len(years) + "@{}}\n\\toprule\n & " + " & ".join(str(y) for y in years) + "\\\\\n\\midrule\n")114        for lab, k, d in rows:115            vals = []116            for y in years:117                x = S[y][k]118                if k in ("GDP_growth", "r"):119                    x = 100 * x120                vals.append(fmt(x, d))121            f.write(lab + " & " + " & ".join(vals) + "\\\\\n")122        f.write("\\bottomrule\n\\end{tabular}\n")123    return S124125126def table_scenarios(allP, names, fname, years=(2050, 2100)):127    cols = [("labor_share", "Labor share", 3), ("wL", "$w_L$ (2026=1)", 3), ("GDP", "GDP (2026=1)", 1), ("r", "$r$ (\\%)", 1),128            ("W_over_GDP", "$W$/GDP", 1), ("land_PR", "Land P/R", 1), ("VQ_over_GDP", "$V^Q$/GDP", 3), ("CW_over_GDP", "$C_W$/GDP", 3)]129    recs = []130    with open(os.path.join(TAB, fname), "w") as f:131        f.write("\\begin{tabular}{@{}l" + "r" * (len(cols) * len(years)) + "@{}}\n\\toprule\n")132        f.write(" & " + " & ".join(f"\\multicolumn{{{len(cols)}}}{{c}}{{{y}}}" for y in years) + "\\\\\n")133        f.write("Scenario & " + " & ".join(c[1] for c in cols for _ in years) if False else "Scenario & " + " & ".join(c[1] for _ in years for c in cols) + "\\\\\n\\midrule\n")134        for n in names:135            if n not in allP:136                continue137            S = summary_by_year(allP[n], years)138            vals = []139            for y in years:140                for k, _, d in cols:141                    x = S[y][k]142                    x = 100 * x if k == "r" else x143                    vals.append(fmt(x, d))144                    recs.append(dict(scenario=n, year=y, stat=k, value=S[y][k]))145            f.write(LAB.get(n, n) + " & " + " & ".join(vals) + "\\\\\n")146        f.write("\\bottomrule\n\\end{tabular}\n")147    return recs148149150def table_decomposition(allP):151    if "baseline" not in allP or "no_ai" not in allP:152        return {}153    P1, P0 = allP["baseline"], allP["no_ai"]154    out = {}155    with open(os.path.join(TAB, "tab05_decomposition.tex"), "w") as f:156        f.write("\\begin{tabular}{@{}lrrr@{}}\n\\toprule\nClaim & Cash-flow channel & Discount-rate channel & Total\\\\\n\\midrule\n")157        for c, lab in [("land", "Production land"), ("housing_land", "Residential land"), ("N", "Structures"), ("Q", "Compute capital"), ("H", "Housing structures"), ("house", "Houses (structure + land)")]:158            try:159                d = campbell_shiller_decomposition(P1, P0, claim=c)160                f.write(f"{lab} & {fmt(100*d['cash_flow'],1)} & {fmt(100*d['discount_rate'],1)} & {fmt(100*d['total'],1)}\\\\\n")161                out[c] = d162            except Exception as e:  # noqa: BLE001163                print("decomp failed", c, e)164        f.write("\\bottomrule\n\\end{tabular}\n")165    return out166167168def table_policy(allP, params):169    names = ["baseline", "citizen_fund_20", "land_tax_50", "compute_tax_30", "capital_tax_20", "ai_monopoly", "foreign_ai_50"]170    if "baseline" not in allP:171        return {}172    P0 = allP["baseline"]173    beta, gamma = params["beta"], params["gamma_u"]174    out = {}175    with open(os.path.join(TAB, "tab06_policy.tex"), "w") as f:176        f.write("\\begin{tabular}{@{}lrrrrrrr@{}}\n\\toprule\nScenario & GDP 2050 & GDP 2100 & $w_L$ 2050 & $C_W$/GDP 2050 & $C_W$/GDP 2100 & CEV workers 2026--65 (\\%) & CEV capitalists 2026--65 (\\%)\\\\\n\\midrule\n")177        for n in names:178            if n not in allP:179                continue180            P = allP[n]181            S = summary_by_year(P, (2050, 2100))182            cevW = welfare_cev(P["X_W"], P["P_idx"], P0["X_W"], P0["P_idx"], beta, gamma)183            cevK = welfare_cev(P["X_K"], P["P_idx"], P0["X_K"], P0["P_idx"], beta, gamma)184            out[n] = dict(cevW=cevW, cevK=cevK, S=S)185            f.write(f"{LAB.get(n, n)} & {fmt(S[2050]['GDP'],2)} & {fmt(S[2100]['GDP'],1)} & {fmt(S[2050]['wL'],3)} & {fmt(S[2050]['CW_over_GDP'],3)} & {fmt(S[2100]['CW_over_GDP'],4)} & {fmt(100*cevW,1)} & {fmt(100*cevK,1)}\\\\\n")186        f.write("\\bottomrule\n\\end{tabular}\n")187    return out188189190def table_diagnostics(names):191    rows = []192    for n in names:193        mf = os.path.join(SC, f"{n}_meta.json")194        pf = os.path.join(SC, f"{n}_paths.npz")195        if not (os.path.exists(mf) and os.path.exists(pf)):196            continue197        meta = json.load(open(mf))198        P = load_paths(pf)199        res = np.abs(P["residuals"]).max()200        inc = np.abs(P["income_residual"] / P["GDP"]).max()201        hc = meta.get("horizon_check") or {}202        rows.append((LAB.get(n, n), meta["converged"], res, inc, hc.get("max_dev_r", np.nan), max(hc.get("max_log_dev_K", {np.nan: np.nan}).values()) if hc else np.nan, meta["seconds"]))203    with open(os.path.join(TAB, "tab08_diagnostics.tex"), "w") as f:204        f.write("\\begin{tabular}{@{}lcrrrrr@{}}\n\\toprule\nScenario & Converged & max $|$residual$|$ & max income-identity gap / GDP & horizon check: $\\Delta r$ & $\\Delta\\log K$ & seconds\\\\\n\\midrule\n")205        for r in rows:206            f.write(f"{r[0]} & {'yes' if r[1] else 'no'} & {r[2]:.1e} & {r[3]:.1e} & {r[4]:.1e} & {r[5]:.1e} & {r[6]:.0f}\\\\\n")207        f.write("\\bottomrule\n\\end{tabular}\n")208    return rows209210211if __name__ == "__main__":212    allP = {}213    for fpath in sorted(glob.glob(os.path.join(SC, "*_paths.npz"))):214        allP[os.path.basename(fpath).replace("_paths.npz", "")] = load_paths(fpath)215    print("scenarios:", list(allP))216    nums = {}217    if "baseline" in allP:218        params = json.load(open(os.path.join(SC, "baseline_params.json")))219        info = json.load(open(os.path.join(SC, "baseline_meta.json")))["calibration_info"]220        table_calibration(params, info)221        S = table_baseline(allP["baseline"])222        vr = valuation_ratios(allP["baseline"])223        P = allP["baseline"]224        nums.update({225            "WoverGDPbase": fmt(vr["W_over_GDP"][0], 1), "LandOverGDPbase": fmt((P["PT"][0] + P["PTH"][0]) / P["GDP"][0], 1),226            "PEstructuresbase": fmt(vr["PE_N"][0], 1), "PEcomputebase": fmt(vr["PE_Q"][0], 1),227            "rBase": fmt(100 * P["r"][1], 1), "AlphaOneBase": fmt(info["alpha10"], 1),228        })229        for y in (2030, 2040, 2050, 2075, 2100):230            nums[f"LS{macro_name(str(y))}"] = fmt(S[y]["labor_share"], 3)231            nums[f"wL{macro_name(str(y))}"] = fmt(S[y]["wL"], 2)232            nums[f"wH{macro_name(str(y))}"] = fmt(S[y]["wH"], 2)233            nums[f"GDP{macro_name(str(y))}"] = fmt(S[y]["GDP"], 1)234            nums[f"WY{macro_name(str(y))}"] = fmt(S[y]["W_over_GDP"], 1)235            nums[f"rr{macro_name(str(y))}"] = fmt(100 * S[y]["r"], 1)236            nums[f"CompShare{macro_name(str(y))}"] = fmt(S[y]["compute_income_share"], 3)237            nums[f"LandShare{macro_name(str(y))}"] = fmt(S[y]["land_share"], 3)238            nums[f"LandPR{macro_name(str(y))}"] = fmt(S[y]["land_PR"], 1)239            nums[f"CWshare{macro_name(str(y))}"] = fmt(S[y]["CW_over_GDP"], 2)240            nums[f"HousePR{macro_name(str(y))}"] = fmt(S[y]["house_PR"], 1)241        # years of automation (first year each task uses compute for > 50% of its input)242        Li, Qi, al = P["Li"], P["Qi"], P["alpha"]243        cs = (Qi / al) / (Li + Qi / al + 1e-300)244        for i, nm in enumerate(["One", "Two", "Three", "Four"]):245            idx = np.where(cs[:, i] > 0.5)[0]246            nums[f"AutoYear{nm}"] = str(int(P["years"][idx[0]])) if len(idx) else "never"247        pk = min(range(len(P["labor_share"])), key=lambda t: P["labor_share"][t] if P["years"][t] <= 2100 else 9)248        gmax = int(np.argmax(np.diff(np.log(P["GDP"]))[:100]))249        nums["GDPgrowthPeak"] = fmt(100 * np.diff(np.log(P["GDP"]))[gmax], 1)250        nums["GDPgrowthPeakYear"] = str(int(P["years"][gmax]))251        nums["rPeak"] = fmt(100 * np.max(P["r"][1:101]), 1)252        nums["rPeakYear"] = str(int(P["years"][int(np.argmax(P["r"][1:101]))]))253        nums["WYmin"] = fmt(np.min(vr["W_over_GDP"][:100]), 1)254        nums["WYminYear"] = str(int(P["years"][int(np.argmin(vr["W_over_GDP"][:100]))]))255        nums["LandPRmin"] = fmt(np.min(vr["land_PR"][:100]), 1)256    recs = table_scenarios(allP, ["baseline", "no_ai", "no_capability", "rapid_agi", "slow_alpha", "fast_alpha", "alpha_inf_x5", "fast_moore_cheap_energy", "fast_moore_energy_constrained",257                                  "moore_ends_2040", "moore_floor_1e3", "moore_never_ends", "complements_leontief_like", "complements_cobb_douglas",258                                  "substitutes", "scarce_land", "severe_chip_depreciation", "reinstatement", "low_risk_aversion", "high_risk_aversion"],259                          "tab04_scenarios.tex")260    pd.DataFrame(recs).to_csv(os.path.join(ROOT, "results", "data", "scenario_summary.csv"), index=False)261    for n, key in [("moore_ends_2040", "MooreEnds"), ("moore_floor_1e3", "MooreFloor"), ("alpha_inf_x5", "AlphaInfFive"), ("rapid_agi", "RapidAGI"), ("no_capability", "NoCapability")]:262        if n in allP:263            S = summary_by_year(allP[n], (2050, 2100))264            for y in (2050, 2100):265                nums[f"wL{key}{macro_name(str(y))}"] = fmt(S[y]["wL"], 3)266                nums[f"LS{key}{macro_name(str(y))}"] = fmt(S[y]["labor_share"], 3)267                nums[f"GDP{key}{macro_name(str(y))}"] = fmt(S[y]["GDP"], 1)268    dec = table_decomposition(allP)269    for c, d in dec.items():270        nums[f"Dec{macro_name(c)}CF"] = fmt(100 * d["cash_flow"], 0)271        nums[f"Dec{macro_name(c)}DR"] = fmt(100 * d["discount_rate"], 0)272        nums[f"Dec{macro_name(c)}Tot"] = fmt(100 * d["total"], 0)273    if "baseline" in allP:274        pol = table_policy(allP, params)275        for n, d in pol.items():276            nums[f"CEVW{macro_name(n)}"] = fmt(100 * d["cevW"], 1)277            nums[f"CEVK{macro_name(n)}"] = fmt(100 * d["cevK"], 1)278    rows = table_diagnostics(list(LAB))279    nums["NumScenarios"] = str(len(rows))280    nums["MaxResidualAll"] = f"{max(r[2] for r in rows):.0e}" if rows else "--"281    nums["MaxIncomeGapAll"] = f"{max(r[3] for r in rows):.0e}" if rows else "--"282    # keep previously generated HA numbers if present283    ha = os.path.join(ROOT, "results", "data", "ha_numbers.json")284    if os.path.exists(ha):285        nums.update(json.load(open(ha)))286    write_numbers(nums)287    print("numbers:", len(nums), "tables written")288
Files · main
Menu

Theme