--- a/code/econ_scenarios/production.py +++ b/code/econ_scenarios/production.py @@ -114,14 +114,27 @@ return Potential(dlnr, lnSL, lN, lnW, lnYL, lnK, math.nan) +# Structural-sensitivity pilot (The Unjournal, 29 Oct 2026): partial adjustment of the capital +# stock toward the paper's schedule. Each month the capital supplied is a weighted average of last +# month's stock and the schedule's level, (1 - lam) K_prev + lam e^{eps dlnr}, both as levels +# relative to the no-AI path. lam = 1 is the paper exactly; lam = 0 freezes the stock at its no-AI +# path. simulate() sets CAPITAL_ADJUSTMENT from its half-life argument and updates K_PREV. +CAPITAL_ADJUSTMENT = 1.0 +K_PREV = [1.0] + + def capital_supply(dlnr: float, assets: float, cal: Calibration) -> float: """Log gap of the capital supplied at rental gap ``dlnr``: the paper's schedule e^{eps dlnr} (Eq. 18) plus ``assets``, workers' extra assets over the no-AI capital stock, which is zero in the paper. At eps = inf the rental rate is pegged and the supply is whatever is demanded. Where negative extra assets exceed the schedule, at a rental rate far below the no-AI one, nothing is supplied and the log gap is minus infinity; the market clears at a higher rate. + + Pilot variant: the schedule's level enters with weight CAPITAL_ADJUSTMENT and last month's + stock with the rest (see the comment above); at weight 1 this is the paper's line unchanged. """ - level = math.exp(cal.eps * dlnr) + assets + lam = CAPITAL_ADJUSTMENT + level = (1.0 - lam) * K_PREV[0] + lam * math.exp(cal.eps * dlnr) + assets return math.log(level) if level > 0.0 else -math.inf --- a/code/econ_scenarios/simulate.py +++ b/code/econ_scenarios/simulate.py @@ -19,6 +19,7 @@ from .calibration import Calibration, Scenario, rate_from_fraction from .labor import SteadyState, hires, steady_state from .paths import AIState, ScenarioPaths +from . import production from .production import actual_economy, demand_given_wage, potential, shift_N if TYPE_CHECKING: @@ -298,6 +299,17 @@ return ell_next, U_next +def capital_adjustment(half_life_months: float) -> float: + """Monthly weight on the capital-supply schedule for a given half-life of the gap between the + stock and the schedule: 0 months is the paper (weight 1), infinity freezes the stock (weight 0). + """ + if half_life_months <= 0.0: + return 1.0 + if math.isinf(half_life_months): + return 0.0 + return 1.0 - 0.5 ** (1.0 / half_life_months) + + def simulate( scenario: Scenario, cal: Calibration | None = None, @@ -307,7 +319,33 @@ | Callable[[int, dict[str, list[float]], Callable[[float], float]], float] | None = None, freeze_after: float | None = None, + capital_half_life: float = 0.0, ) -> Simulation: + """The paper's monthly system, with the pilot's capital-adjustment switch. + + ``capital_half_life`` (months) sets how fast the capital stock closes its gap to the paper's + supply schedule; 0, the default, is the paper's instantaneous schedule. See ``_simulate``. + """ + saved = production.CAPITAL_ADJUSTMENT + production.CAPITAL_ADJUSTMENT = capital_adjustment(capital_half_life) + production.K_PREV[0] = 1.0 + try: + return _simulate(scenario, cal, horizon, level_form, worker_assets, freeze_after) + finally: + production.CAPITAL_ADJUSTMENT = saved + production.K_PREV[0] = 1.0 + + +def _simulate( + scenario: Scenario, + cal: Calibration | None = None, + horizon: float = 2030.0, + level_form: str = "exact", + worker_assets: np.ndarray + | Callable[[int, dict[str, list[float]], Callable[[float], float]], float] + | None = None, + freeze_after: float | None = None, +) -> Simulation: """Run the monthly system of Table A.1 from t0 through ``horizon``. ``level_form="first_order"`` uses the first-order rows of panel B for the reported potential @@ -384,6 +422,7 @@ S_C, S_N, H_C, H_N, f = _matching(ss, cal, mu, U, v_C, v_N) # Step 8: the actual economy at realized employment. act = actual_economy(x, dlnA, (ell[0], ell[1]), ss.ell0, cal, assets) + production.K_PREV[0] = math.exp(act.lnK) # pilot: next month adjusts from this stock lnW_avg = math.log( (math.exp(lnW_C) * ell[0] + math.exp(act.lnW_N) * ell[1]) / (ell[0] + ell[1]),