"""Post-solution analysis: valuation ratios, Campbell–Shiller decomposition along deterministic paths, wealth accounting, welfare (consumption-equivalent variations) and summary statistics by year.""" from __future__ import annotations import numpy as np KINDS = ("N", "Q", "E", "H") def valuation_ratios(P): """Per-period valuation objects for each capital type and for land/housing.""" out = {} T = len(P["Y"]) - 1 for k in KINDS: V, D, R, K = P[f"V_{k}"], P[f"D_{k}"], P[f"R_{k}"], P[f"K_{k}"] pk = P["pQ"] if k == "Q" else np.ones(T + 1) out[f"q_over_p_{k}"] = P[f"q_{k}"] / pk out[f"PE_{k}"] = V / np.where(R * K[:T + 1] > 0, R * K[:T + 1], np.nan) # price-earnings out[f"PD_{k}"] = V / np.where(D > 0, D, np.nan) # price-payout (if positive) out[f"V_over_GDP_{k}"] = V / P["GDP"] out["land_PR"] = P["PT"] / P["RTp"] # production land price / rent house_value = P["q_H"] * P["K_H"][1:T + 2] + P["PTH"] * 1.0 house_rent = P["RH"] * P["HS"] out["house_PR"] = house_value / house_rent out["house_land_share"] = P["PTH"] / house_value out["W_over_GDP"] = P["W"] / P["GDP"] out["wealth_comp"] = {k: P[f"V_{k}"] / P["W"] for k in KINDS} out["wealth_comp"]["land"] = (P["PT"] + P["PTH"]) / P["W"] return out def campbell_shiller_decomposition(P1, P0, claim="land", horizon=None): """Exact log price-dividend decomposition along deterministic paths. For a claim with price V_t and cash flow D_{t+1}: V_t = sum_{j>=1} D_{t+j} / prod_{i=1..j}(1+r_{t+i}). We compare scenario 1 to scenario 0 at t = 0 and attribute log(V1/V0) to the cash-flow path and the discount-rate path by the counterfactual: cash-flow effect = log V(D1, r0) - log V(D0, r0); discount-rate effect = log V(D1, r1) - log V(D1, r0). The two add up exactly to log(V1/V0) when both valuations use the same terminal convention (Gordon value at the horizon).""" def pv(D, r, g_term): T = min(len(D), len(r)) - 1 disc = np.cumprod(1.0 / (1.0 + r[1:T + 1])) rT = r[T] term = D[T] * (1 + g_term) / max(rT - g_term, 1e-4) * disc[-1] return float(np.sum(D[1:T + 1] * disc) + term) def cash_flow(P): if claim == "land": return P["RTp"] if claim == "housing_land": return P["RTHp"] if claim == "house": return P["RH"] * P["HS"] return P[f"D_{claim}"] # capital type: net payout D1, D0 = cash_flow(P1), cash_flow(P0) r1, r0 = P1["r"], P0["r"] g1 = D1[-1] / D1[-2] - 1 g0 = D0[-1] / D0[-2] - 1 V11, V00 = pv(D1, r1, g1), pv(D0, r0, g0) V10 = pv(D1, r0, g1) return dict(total=np.log(V11 / V00), cash_flow=np.log(V10 / V00), discount_rate=np.log(V11 / V10), V1=V11, V0=V00) def welfare_cev(x_path1, P_path1, x_path0, P_path0, beta, gamma, g=0.0, horizon=40): """Consumption-equivalent variation of path 1 relative to path 0 for a household with CRRA over x/P over the first `horizon` years (default 40: the remaining working life of a 2026 adult; with hand-to-mouth workers whose consumption goes to zero, infinite-horizon CEVs are dominated by the far future). Returns lambda such that scaling path 0 by (1+lambda) gives the utility of path 1.""" T = min(len(x_path1), len(x_path0), horizon) b = beta ** np.arange(T) def U(x, Pp): c = x[:T] / Pp[:T] if abs(gamma - 1) < 1e-10: return float(np.sum(b * np.log(c))) return float(np.sum(b * c ** (1 - gamma) / (1 - gamma))) U1, U0 = U(x_path1, P_path1), U(x_path0, P_path0) if abs(gamma - 1) < 1e-10: return np.exp((U1 - U0) / np.sum(b)) - 1 return (U1 / U0) ** (1 / (1 - gamma)) - 1 def summary_by_year(P, years=(2026, 2030, 2040, 2050, 2075, 2100)): """Dictionary year -> key statistics (used for tables).""" yrs = P["years"] T = len(P["Y"]) - 1 vr = valuation_ratios(P) rows = {} gf = P["gf"] for y in years: t = int(np.where(yrs == y)[0][0]) comp_share = P["R_Q"][t] * P["K_Q"][t] / P["GDP"][t] rows[y] = dict( GDP=P["GDP"][t] / P["GDP"][0], GDP_growth=(P["GDP"][min(t + 1, T)] / P["GDP"][t] - 1), labor_share=P["labor_share"][t], compute_income_share=comp_share, structures_share=P["R_N"][t] * P["K_N"][t] / P["GDP"][t], land_share=(P["RTp"][t] + P["RTHp"][t]) / P["GDP"][t], energy_share=(P["PE"][t] * P["E"][t]) / P["GDP"][t], wL=P["wL"][t] * gf[t] / (P["wL"][0] * gf[0]), wH=P["wH"][t] * gf[t] / (P["wH"][0] * gf[0]), r=P["r"][t + 1], A=P["A"][t], KQ=P["K_Q"][t], VQ_over_GDP=vr["V_over_GDP_Q"][t], W_over_GDP=vr["W_over_GDP"][t], land_PR=vr["land_PR"][t], house_PR=vr["house_PR"][t], PE_Q=vr["PE_Q"][t], PE_N=vr["PE_N"][t], q_N=vr["q_over_p_N"][t], q_Q=vr["q_over_p_Q"][t], util=P["util"][t], PE=P["PE"][t] / P["PE"][0], energy_to_compute=P["EQ"][t] / P["E"][t], inv_share=P["inv_goods"][t] / P["GDP"][t], CW_over_GDP=P["C_W"][t] / P["GDP"][t], CK_over_GDP=P["C_K"][t] / P["GDP"][t], workers_cons_level=P["C_W"][t] / P["C_W"][0], tasks_automated=int(np.sum(P["Qi"][t] > 1e-6 * max(P["Qtot"][t], 1e-12))), ) return rows def arrival_lottery_premia(P_ai, P_no, pi, beta, gamma, theta_H): """One-period lottery illustration of AGI-arrival risk (labelled as an approximation in the paper). At t=0 the economy learns with probability pi that the AI path (P_ai) is realized and with 1-pi the frozen path (P_no). Both paths share the 2026 stocks. The capitalists' expenditure x_0 is taken from the frozen path (status quo), the branch SDFs are M_b = beta (x_1^b/x_0)^{-gamma} (P_1^b/P_0)^{gamma-1}, and each claim's 2026 price is sum_b pi_b M_b (payout_1^b + V_1^b). Returns risk-free rate, expected returns and premia by claim. The exercise ignores the feedback of uncertainty on 2026 investment and consumption.""" x0 = P_no["X_K"][0] P0 = P_no["P_idx"][0] branches = [(pi, P_ai), (1 - pi, P_no)] M = [b * beta * (P["X_K"][1] / x0) ** (-gamma) * (P["P_idx"][1] / P0) ** (gamma - 1) for b, P in branches] Rf = 1.0 / sum(M) out = dict(Rf=Rf - 1.0) claims = {k: (lambda P, k=k: (P[f"D_{k}"][1] + P[f"V_{k}"][1])) for k in KINDS} claims["land"] = lambda P: P["RTp"][1] + P["PT"][1] claims["housing_land"] = lambda P: P["RTHp"][1] + P["PTH"][1] for name, f in claims.items(): pay = [f(P) for _, P in branches] V0 = sum(m * p for m, p in zip(M, pay)) ER = sum(b * p for (b, _), p in zip(branches, pay)) / V0 out[name] = dict(V0=V0, expected_return=ER - 1.0, premium=ER - Rf, payoff_ai=pay[0], payoff_no=pay[1]) return out