SPB Git

spb/wp25_uqo

Public
11.6 KB · 221 lines python
1"""Model A experiments: delegation threshold, aggregate model noise, efficiency benchmark and wedges, compute market,2ownership, savings-channel negative result.  Outputs: results/model_a/*.csv and results/figures/figA_*.pdf.34Run:  python3 scripts/10_model_a.py5"""6from __future__ import annotations78import json9import sys10from pathlib import Path1112import numpy as np13import pandas as pd1415sys.path.insert(0, str(Path(__file__).resolve().parents[1] / 'src'))16import matplotlib.pyplot as plt  # noqa: E4021718from age.analysis import plotstyle as ps  # noqa: E40219from age.equilibrium import lq_game as lq  # noqa: E40220from age.equilibrium import lq_map as m  # noqa: E4022122ROOT = Path(__file__).resolve().parents[1]23OUT = ROOT / 'results' / 'model_a'24FIG = ROOT / 'results' / 'figures'25OUT.mkdir(parents=True, exist_ok=True)26FIG.mkdir(parents=True, exist_ok=True)27ps.use()2829p = m.EconParams()30ss = m.steady_state(p)31pf = m.portfolio_lq_params(p, ss)32lqp = pf['lqp']33summary = dict(steady_state={k: float(v) for k, v in ss.items()}, portfolio_map={k: float(v) for k, v in pf.items() if k != 'lqp'})3435# ------------------------------------------------------------------ A1. savings channel (negative result)36lin = m.linearized_household(p, ss)37summary['savings_channel'] = {k: float(v) for k, v in lin.items() if np.isscalar(v)}38pd.DataFrame(dict(t=np.arange(len(lin['a_ge'])), a_ge=lin['a_ge'], a_pe=lin['a_pe'])).to_csv(OUT / 'savings_news_response.csv', index=False)3940# ------------------------------------------------------------------ A2. private value, delegation share, model noise vs D, M, tau_A41rows = []42for sigA in [0.08, 0.06, 0.04, 0.02]:43    for M in [1, 2, 4, 8, 1000]:44        pp = m.EconParams(sig_A_R=sigA, M=M)45        pfx = m.portfolio_lq_params(pp, ss)46        for D in np.linspace(0, 1, 41):47            mo = lq.moments(pfx['lqp'], D)48            rows.append(dict(sig_A=sigA, M=M, D=D, PV_goods=m.private_value_goods(pp, ss, pfx['lqp'], pfx, D),49                             V_noise=mo['V_noise'], V_track=mo['V_track'], V_disp=mo['V_disp'], Abar=mo['Abar'],50                             xi=mo['xi'], aA=mo['aA'], aH=mo['aH'],51                             W_rel=lq.welfare_decomposed(pfx['lqp'], D)['W_rel'] * pfx['Uxx_goods'],52                             wedge=lq.wedge(pfx['lqp'], D)))53dfA = pd.DataFrame(rows)54dfA.to_csv(OUT / 'game_moments.csv', index=False)5556# delegation equilibrium vs model quality and compute price57rows = []58for sigA in np.linspace(0.02, 0.095, 16):59    for pQ in [0.2, 0.6, 1.0]:60        for qbar in [0.002, 0.05]:61            pp = m.EconParams(sig_A_R=sigA, qbar=qbar)62            pfx = m.portfolio_lq_params(pp, ss)63            rows.append(dict(sig_A=sigA, pQ=pQ, qbar=qbar, D=m.delegation_share(pp, ss, pfx['lqp'], pfx, pQ),64                             fee=pQ * qbar + pp.pE * pp.ebar))65dfD = pd.DataFrame(rows)66dfD.to_csv(OUT / 'delegation_share.csv', index=False)6768# ------------------------------------------------------------------ A3. wedges: (a) non-pecuniary, (b) hand-to-mouth wage risk69rows = []70for D in np.linspace(0, 1, 41):71    for ups in [0.0, 0.5, 1.0, 1.5]:72        pfx = m.portfolio_lq_params(p, ss)73        lq_u = lq.LQParams(**{**pfx['lqp'].__dict__, 'upsilon': ups})74        rows.append(dict(D=D, upsilon=ups, wedge_goods=lq.wedge(lq_u, D) * pfx['Uxx_goods'],75                         PV_goods=lq.private_value(lq_u, D) * pfx['Uxx_goods']))76dfW = pd.DataFrame(rows)77# (b) hand-to-mouth workers: wage w ∝ K^alpha ; log-wage noise variance = alpha^2 V_noise ; CE loss = 1/2 gamma wbar Var78rows = []79for D in np.linspace(0.01, 1, 40):80    for M in [1, 2, 4, 8]:81        pfx = m.portfolio_lq_params(p, ss, M=M)82        h = 1e-483        dV = (lq.moments(pfx['lqp'], min(D + h, 1))['V_noise'] - lq.moments(pfx['lqp'], max(D - h, 0))['V_noise']) / (min(D + h, 1) - max(D - h, 0))84        for htm in [0.25, 0.5]:85            wedge_b = htm * 0.5 * p.gamma * ss['w'] * (p.alpha ** 2) * dV      # goods per unit of D86            rows.append(dict(D=D, M=M, htm=htm, wedge_b_goods=wedge_b, PV_goods=m.private_value_goods(p, ss, pfx['lqp'], pfx, D),87                             tax_over_fee=wedge_b / ss['fee']))88dfB = pd.DataFrame(rows)89dfW.to_csv(OUT / 'wedge_nonpecuniary.csv', index=False)90dfB.to_csv(OUT / 'wedge_hand_to_mouth.csv', index=False)9192# ------------------------------------------------------------------ A4. compute market: short run vs long run93rows = []94for KQ in [0.02, 0.05, 0.1]:95    ssk = m.steady_state(p, KQ=KQ)96    for qbar in np.linspace(0.0, 0.04, 41):97        pp = m.EconParams(qbar=qbar)98        pfx = m.portfolio_lq_params(pp, ssk)99        r = m.compute_market_short_run(pp, ssk, pfx['lqp'], pfx, KQ=KQ)100        rows.append(dict(KQ=KQ, qbar=qbar, regime='short run (fixed K_Q)', **{k: float(v) for k, v in r.items() if k != 'KQ'}))101        # long run: compute supplied at user cost -> production compute unaffected by delegation; K_Q expands102        pr_lr = pp.tasks.prices(0.0, ssk['K'], KQ, ssk['L'])103        D_lr = m.delegation_share(pp, ssk, pfx['lqp'], pfx, pr_lr['pQ'])104        rows.append(dict(KQ=KQ, qbar=qbar, regime='long run (elastic K_Q)', QP=KQ, D=D_lr, fee=pr_lr['pQ'] * qbar + pp.pE * pp.ebar,105                         KQ_total=KQ + qbar * D_lr, **{k: float(v) for k, v in pr_lr.items()}))106dfC = pd.DataFrame(rows)107dfC.to_csv(OUT / 'compute_market.csv', index=False)108109# ------------------------------------------------------------------ A5. ownership: two-class income shares vs automation110rows = []111for KQ in np.linspace(0.0, 2.0, 41):112    ssk = m.steady_state(p, KQ=KQ)113    compute_income = ssk['share_Q'] * ssk['Y']114    for n_own in [0.01, 0.10, 1.0]:115        owners_share = (ssk['share_K'] * n_own + ssk['share_Q'])  # owners hold compute + their pro-rata share of K116        rows.append(dict(KQ=KQ, iota=ssk['iota'], share_L=ssk['share_L'], share_Q=ssk['share_Q'], Y=ssk['Y'], w=ssk['w'],117                         n_owners=n_own, owners_income_share=owners_share))118dfO = pd.DataFrame(rows)119dfO.to_csv(OUT / 'ownership_shares.csv', index=False)120121# ------------------------------------------------------------------ A6. sensitivity heat-map: model-noise variance of wages over (sig_A, M)122grid_sA = np.linspace(0.02, 0.1, 17)123grid_M = [1, 2, 3, 4, 6, 8, 12, 16]124H = np.zeros((len(grid_sA), len(grid_M)))125Dgrid = np.zeros_like(H)126for i, sA in enumerate(grid_sA):127    for j, M in enumerate(grid_M):128        pp = m.EconParams(sig_A_R=sA, M=M)129        pfx = m.portfolio_lq_params(pp, ss)130        D = m.delegation_share(pp, ss, pfx['lqp'], pfx, 0.5)131        Dgrid[i, j] = D132        H[i, j] = p.alpha ** 2 * lq.moments(pfx['lqp'], D)['V_noise']      # Var(log wage) from model noise133pd.DataFrame(H, index=grid_sA, columns=grid_M).to_csv(OUT / 'heatmap_wage_noise.csv')134pd.DataFrame(Dgrid, index=grid_sA, columns=grid_M).to_csv(OUT / 'heatmap_D.csv')135136json.dump(summary, open(OUT / 'summary.json', 'w'), indent=1, default=float)137138# ================================================================== figures139# figA_pv: private value vs D for several model qualities (M=1) + model diversity140fig, axs = plt.subplots(1, 2, figsize=(7.2, 3.1))141for k, sigA in enumerate([0.08, 0.06, 0.04, 0.02]):142    d = dfA[(dfA.M == 1) & (dfA.sig_A == sigA)]143    axs[0].plot(d.D, 100 * d.PV_goods / ss['C'], color=ps.CAT[k], label=f'model noise s.d. {sigA:.2f}')144axs[0].set(xlabel='delegation share $D$', ylabel='private value of delegation (% of consumption)', title='(a) Private value of delegation')145axs[0].legend()146for k, M in enumerate([1, 2, 4, 8, 1000]):147    d = dfA[(dfA.M == M) & (dfA.sig_A == 0.04)]148    axs[1].plot(d.D, 1e4 * d.V_noise * p.alpha ** 2, color=ps.CAT[k], label=f'M = {M}' if M < 1000 else 'M → ∞')149axs[1].set(xlabel='delegation share $D$', ylabel='wage variance from model noise (×10⁴)', title='(b) Aggregate model noise ∝ $D^2/M$')150axs[1].legend()151ps.save(fig, FIG / 'figA_private_value_noise.pdf')152153# figA_threshold: D* vs model quality for compute prices154fig, ax = plt.subplots(figsize=(5.2, 3.2))155for k, (pQ, qbar) in enumerate([(0.2, 0.05), (0.6, 0.05), (1.0, 0.05)]):156    d = dfD[(dfD.pQ == pQ) & (dfD.qbar == qbar)]157    ax.plot(d.sig_A, d.D, color=ps.CAT[k], label=f'compute rental $p_Q$ = {pQ}')158ax.set(xlabel='foundation-model noise s.d. (lower = better model)', ylabel='equilibrium delegation share $D^*$', title='Delegation threshold (H1)')159ax.invert_xaxis()160ax.legend()161ps.save(fig, FIG / 'figA_threshold.pdf')162163# figA_wedge164fig, axs = plt.subplots(1, 2, figsize=(7.2, 3.1))165for k, ups in enumerate([0.0, 0.5, 1.0, 1.5]):166    d = dfW[dfW.upsilon == ups]167    axs[0].plot(d.D, 100 * d.wedge_goods / ss['C'], color=ps.CAT[k], label=f'υ = {ups}' + (' (CRS benchmark)' if ups == 1 else ''))168axs[0].axhline(0, color=ps.AXIS, lw=0.6)169axs[0].set(xlabel='delegation share $D$', ylabel='private − social marginal value (% of consumption)', title='(a) Wedge by curvature ratio υ')170axs[0].legend()171for k, M in enumerate([1, 2, 4, 8]):172    d = dfB[(dfB.M == M) & (dfB.htm == 0.5)]173    axs[1].plot(d.D, d.tax_over_fee, color=ps.CAT[k], label=f'M = {M}')174axs[1].set(xlabel='delegation share $D$', ylabel='Pigouvian delegation tax / fee', title='(b) Wage-risk wedge, 50% hand-to-mouth')175axs[1].legend()176ps.save(fig, FIG / 'figA_wedge.pdf')177178# figA_compute: short vs long run179fig, axs = plt.subplots(1, 3, figsize=(9.0, 3.0))180for k, KQ in enumerate([0.02, 0.05, 0.1]):181    for ls, reg in [('-', 'short run (fixed K_Q)'), ('--', 'long run (elastic K_Q)')]:182        d = dfC[(dfC.KQ == KQ) & (dfC.regime == reg)]183        axs[0].plot(d.qbar, d.pQ, ls, color=ps.CAT[k], label=f'$K_Q$={KQ}, {reg}' if k == 0 or ls == '-' else None)184        axs[1].plot(d.qbar, d.iota, ls, color=ps.CAT[k])185        axs[2].plot(d.qbar, d.share_L, ls, color=ps.CAT[k])186axs[0].set(xlabel='inference intensity $\\bar q$', ylabel='compute rental $p_Q$', title='(a) Compute price')187axs[1].set(xlabel='inference intensity $\\bar q$', ylabel='automated task share $\\iota$', title='(b) Automation')188axs[2].set(xlabel='inference intensity $\\bar q$', ylabel='labour share', title='(c) Labour share (H6)')189axs[0].legend(fontsize=7)190ps.save(fig, FIG / 'figA_compute_market.pdf')191192# figA_ownership193fig, ax = plt.subplots(figsize=(5.2, 3.2))194for k, n_own in enumerate([0.01, 0.10, 1.0]):195    d = dfO[dfO.n_owners == n_own]196    ax.plot(d.iota, d.owners_income_share, color=ps.CAT[k], label=f'owners = top {int(n_own*100)}%' if n_own < 1 else 'broad ownership')197ax.plot(dfO[dfO.n_owners == 1.0].iota, dfO[dfO.n_owners == 1.0].share_L, color=ps.TEXT2, lw=1, ls=':', label='labour share')198ax.set(xlabel='automated task share $\\iota$', ylabel='income share', title='Compute ownership and income concentration (Prop. 6)')199ax.legend()200ps.save(fig, FIG / 'figA_ownership.pdf')201202# figA_heatmap203fig, ax = plt.subplots(figsize=(5.4, 3.4))204im = ax.imshow(1e4 * H, origin='lower', aspect='auto', cmap=ps.SEQ,205               extent=[-0.5, len(grid_M) - 0.5, grid_sA[0], grid_sA[-1]])206ax.set_xticks(range(len(grid_M))); ax.set_xticklabels(grid_M)207ax.set(xlabel='number of independent foundation models $M$', ylabel='model noise s.d.', title='Wage variance from model noise at $D^*$ (×10⁴)')208ax.grid(False)209fig.colorbar(im, ax=ax, shrink=0.85)210ps.save(fig, FIG / 'figA_heatmap.pdf')211212# figA_savings (negative result): GE and PE news responses213fig, ax = plt.subplots(figsize=(5.2, 3.0))214ax.plot(np.arange(12), lin['a_ge'][:12], color=ps.CAT[0], label='general equilibrium')215ax.plot(np.arange(12), lin['a_pe'][:12], color=ps.CAT[1], label='partial equilibrium (prices fixed)')216ax.axhline(0, color=ps.AXIS, lw=0.6)217ax.set(xlabel='years after the news', ylabel='log capital response to a unit TFP news', title='Savings channel: a 1-log-point news moves savings by < 0.01')218ax.legend()219ps.save(fig, FIG / 'figA_savings_news.pdf')220print('Model A done:', summary['portfolio_map'], 'savings kappa0', lin['kappa0'])221
Files · main
Menu

Theme