Skip to content

Global code - #32

Open
Daniel-Jensen wants to merge 18 commits into
mainfrom
bocola-rewrite
Open

Global code #32
Daniel-Jensen wants to merge 18 commits into
mainfrom
bocola-rewrite

Conversation

@Daniel-Jensen

Copy link
Copy Markdown
Owner

No description provided.

Daniel-Jensen and others added 18 commits July 6, 2026 16:14
…TATE.md

Relocates all standalone Python model files into code/global/ and removes the
old flat code/ layout. Updates STATE.md with the full fix log (BUG-1 through
ISSUE-5), post-fix Walras residuals, BD mechanism results, and the W-G1
structural limitation explanation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Introduces risk_branch.py (two-branch expectations, bond decomposition),
full test suite under tests/, and calibration/bank/transition/government
updates supporting the CK–Bocola pass-through experiment.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…mprovements in clarity

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment out the Cole-Kehoe sunspot section in main.py (flag off, call
and CLI override disabled) so the default pipeline runs SS + TFP only.
Includes accumulated model/calibration/plot refinements and the
sunspot transition study notes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Cole-Kehoe/Bocola risk-only experiment was near-inert and wrong-signed:
a persistent rise in priced default probability RAISED consumption and barely
moved output. Forensics (docs/sunspot_transition_study.md §8) traced this to
three compounding causes, each fixed here.

1. Priced event was ~6x too mild. delta_b/recovery had been reverted to
   0.25/0.80 (~1y duration, 20% haircut); pure repricing was -5.3% vs -30.1%
   at the documented 0.036/0.45 (7y GR/DE duration, Greek PSI haircut).
   -> calibration.py: restored delta_b=0.036, recovery_rate=0.45.

2. The GK bank block ABSORBED the shock instead of amplifying it. In the
   feared default branch capital lost far less than bonds, so capital was the
   "safe haven", two-branch pricing drove the IC multiplier mu NEGATIVE (the
   always-binding IC violated in equilibrium), the pricing wedge lambda*mu/Omega
   collapsed and propped bond prices up, and the imposed IC equality with
   alpha falling forced an equilibrium bank-RECAPITALIZATION boom (n, I, Q up).
   -> capital.py: def_capital_quality_D (GK capital-quality loss, quality0
      hook applied once at branch h=0) so capital is NOT the branch safe
      haven; base-path mu_D is positive again. xi_K=0.05 (0.10 tested/rejected:
      over-compresses mu, flips C_D[0] positive).

3. Output had no transmission channel. Impact Y is pinned by the static GHH
   labour identity; no spread ever entered production (a -30% bond crash /
   -20% bank equity moved Y_D only -0.2%).
   -> firms.py + transition.py: Neumeyer-Perri working-capital wedge
      (zeta_wc=1), firms pre-finance the wage bill at rdep(-1)+lambda*mu/Omega;
      the wedge enters labour demand, financing income routes to household
      dividends (goods-market identity preserved; zeta=0 nests exactly).

Branch feasibility (the documented PSI event wipes out bank equity):
- risk_branch.py: restored the haircut feasibility ladder (as homotopy /
  fallback) AND a contingent government recapitalization (recap_share_D=0.5,
  HFSF/EFSF analogue) financed by branch issuance; attempt order
  full -> full+recap -> ladder(+recap); branch["rescue_mode"] reports which.
  The full PSI event is now priced. _shifted_y0 scales the Kap block by the
  capital-quality profile so branch probes clear the Jermann penalty wall.
- government.py: recap_path plumbing; Bohn rule now responds to the SURVIVING
  stock (taxing the pre-haircut stock was a ~31%-of-GDP one-quarter tax spike
  that alone made full-event branches infeasible; a no-op when def_real=0).
- bank.py: recap_D/F equity injection added to retained net worth (not gross
  income); threaded through transition._inner_economy.

Guardrails / calibration:
- transition.py: mu-monitor warns when the IC multiplier goes negative on a
  solved path (out["mu_min_D/F"]); surfaced in main.py's CK table.
- calibration.py: T back to 200; centerpiece sunspot 1%*0.95^t.
- steady_state.py/firms.py: SS working-capital dividend routing (SS Walras 2e-10).

Results (sunspot 1%*0.95^t, T=200): 12/13 CLAUDE.md sign criteria pass -
Q_bD[0] -5.7%, n_D[0] -3.4%, n_F[0] -2.4%, Y_D[0] down, C_D[0] down, lending
spread +343bp, risk premium +73bp, base-path mu_D=+0.010>0, rescue full+recap.
Remaining item (risk-on n_D[0] above risk-off) is the M1 deposit-rate channel,
kept as a test warning pending the deferred union deposit market.

Tests: test_risk_channel re-promotes the two directional asserts (risk-on
Q_bD[0]<off, mu_D>0) and adds branch-budget/surv_d and quality0/zeta nesting
asserts; all of test_ss_identities, test_bank_block, test_fast_kernels,
test_signs_bocola, test_transition_walras, test_risk_channel pass.

Docs: CLAUDE.md, docs/STATE.md, docs/sunspot_transition_study.md §8 updated.

This commit also lands accumulated standalone-model infrastructure that was
already uncommitted in the working tree (solvers.py parallel-FD-Jacobian
Newton + jac_cache, fast_kernels.py numba EGM/distribution kernels with numpy
fallback, and the distribution/household/plots/trade refinements they depend
on) — transition.py imports solvers.py, so these are required for a working
build.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Make solve_default_branch price a single fixed feared event instead of
searching, and update the runtime function reference to match this and the
rest of the 2026-07-15 rework.

Branch behaviour (risk_branch.py, calibration.py):
- The branch now does ONE deterministic solve of a fixed event:
  branch_haircut_scale (new, default 1.0 = full Greek-PSI haircut) with the
  government recap on (recap_share_D > 0). No search on the common path.
- The old feasibility ladder (scan over scales 0.075…1.0) is demoted to an
  opt-in fallback, branch_use_ladder (new, default False). If the fixed
  event is infeasible the branch RAISES with an actionable hint (raise
  recap_share_D, lower branch_haircut_scale, or enable the ladder) instead
  of silently searching.
- Removes a wasted probe: the previous attempt order tried the full event
  WITHOUT recap first, which always failed at the PSI calibration and burned
  a full transition solve every risk round.
- Drops the now-unused target_scale/target_mode plumbing from
  solve_default_branch and its caller.

Verified identical to the previous behaviour: rescue_mode=full+recap,
scale=1.0, Q_bD[0] -5.67%, n_D[0] -3.44%, n_F[0] -2.39%, min mu_D +0.010 —
the full+recap event is exactly what the old ladder landed on.

Docs (docs/function_reference.md + rebuilt .pdf): updated every entry the
2026-07-15 rework touched — solve_default_branch (was still stating "the
feasibility ladder was removed … recapitalization is not modelled"),
solve_capital_path (quality0 capital-quality loss), govt_transition (recap
outlays + Bohn rule on the surviving stock), steady_state_firm/solve_firm_path
(working-capital wedge), _inner_economy (wedge, recap threading, mu-monitor),
and solve_transition (accept_tol, mu_min returns, and the actual Newton +
jac_cache solver, which the doc still described as the old hybr/krylov path).
CLAUDE.md and docs/sunspot_transition_study.md note the ladder is now opt-in.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Codebase-wide readability pass across every module in code/global/, plus a
small correctness fix for an edge case the previous commit (d1bd6eb) missed.

Comment/docstring cleanup (behaviour-preserving):
- Every function now opens with a single one-line summary comment; the long
  multi-line explanations were trimmed to short inline notes only where the
  logic is non-obvious.
- Removed all triple-quoted docstrings (module + function).
- Removed stale objects: beta_guess_D/F and the rk_D/F_guess seeds (always
  overwritten in solve_steady_state before use), recap_share_F (no F default
  branch — only recap_share_D is read), the T_arr duplicate of T in
  transition.py, and the dead ss["ss_cap_D"] guard in plots.py.
- Executable code preserved line-for-line. Verified byte-identical: SS
  (Y_ss=1, Q_bD_ss=0.8780, n_ss=3.2476), TFP transition (goods_D=2.16e-13,
  min_mu_D=-0.0307), and the sunspot risk-ON path (Q_bD[0]=-5.67%,
  n_D[0]=-3.44%, min_mu_D=+0.0101) all match the pre-cleanup values exactly.
  ~1,400 comment lines removed (files ~20-30% shorter).

Zero-shock branch guard (risk_branch.solve_transition_ck_risk):
- When there is no priced risk anywhere (def_price ≡ 0), return the risk-off
  path and skip the default-branch solve. This is exactly correct — pi ≡ 0
  nests the risk-neutral model — and avoids a fragile branch solve from the
  healthy SS state, which needs the (now off-by-default) feasibility ladder
  as a homotopy. Fixes test_zero_shock_with_risk_machinery, which regressed
  when d1bd6eb turned the ladder off (that commit was validated only on the
  sunspot path, missing this edge case).

Tests: test_ss_identities, test_bank_block, test_fast_kernels,
test_signs_bocola, test_transition_walras, and all of test_risk_channel
(incl. the re-enabled zero-shock test) pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ut branch

- Fix Ω-kernel weight swap: Ω = β[f + (1−f)α′] (Bocola Prop. 1; f = exit
  share, weight 1−f ≈ 0.95 on the franchise value — was 0.05). beta_inter
  0.96 → 0.99 (≈ β_hh); recalibrated SS: α_ss = 1.24, λ = 0.31.
- Occasionally-binding IC: capital-market residual is now the
  Fischer-Burmeister complementarity 0 ≤ μ ⊥ αn − λ·assets ≥ 0; portfolio
  shares and branch inits on actual net worth. No new unknowns.
- Default risk is exogenous (Bocola eqs. 11-12): π_t is an input path;
  Cole-Kehoe zones, zone iterations, and solve_transition_ck deleted.
- Pure-haircut feared event (recovery 0.45 on the whole claim): output-cost,
  ξ_K, and recap patches now diagnostic flags, all 0. Feasible via a
  numerical recap-share warm-start continuation; branch rk_d < 0 without ξ_K
  (capital no longer the safe haven), branch μ > 0 throughout.
- Removed: F-side default machinery, chi_tilt, sdf_mode empirical/model +
  kappa_d, branch ladder/haircut scale, anchored entrant mode.
- solve_transition_ck_risk → solve_transition_risk(pi_D_path=...); tests and
  CLAUDE.md updated. Full pipeline 66s; risk fixed point converges in 7
  rounds; complementarity ~1e-12 on all solved paths.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Production uses the stock carried into t: Y_t = Z_t K_{t-1}^a N_t^{1-a};
  mpk on the lagged vintage, matching the bank claim return
  rk_t = (mpk_t + (1-d)Q_t)/Q_{t-1} exactly. Impact output moves through
  hours alone (identity Y^_0 = (1-a)N^_0, tested to 1e-12).
- quality0 (branch capital-quality loss) now hits Y_0 directly (GK11).
- cap_profit re-derived: pure Jermann rents Q(K_t-(1-d)K_lag) - I; the mpk
  reconciliation term existed only for the old contemporaneous timing.
- Headline experiment (pi = 1%*0.95^t): Y_D[0] -0.32% (was +0.07%),
  I_D[0] -1.16% (was +1.77%) - the impact investment boom is gone.
- New tests: impact-composition identity + headline comovement case.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- The two national deposit clearings become (i) ONE union-wide clearing in
  D-good units and (ii) deposit-UIP (1+rdep_D) = (1+rdep_F)p'/p - the
  flexible-price image of a single union rate with national inflation
  differentials (BKK/Baxter-Crucini single-traded-bond margin; integrated
  GK bank funding as in Dedola-Karadi-Lombardo 2013). Zero-profit interbank
  pass-through -> no Walras leak (goods_F stays at the grid floor).
- Cross-border deposit position out['nfa_dep_D'] is the new absorption
  margin (breaks the national S=I trap behind the M1 comovement problem).
- Stage-2 SS: beta_F = beta_D (symmetric-SS doctrine) with an F-market
  clearing assert; beta value unchanged.
- Unknown layout unchanged (7T); bank and household blocks untouched.
- Headline experiment (pi = 1%*0.95^t, risk-neutral): Y_D[0] -0.22%,
  I_D[0] -1.00%; income-SDF sign gate no longer trips in the risk suite.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… flags

Reduce comment noise and dead code across code/global/ without changing
model behavior (arithmetic verified bit-identical on every hot path:
steady_state_bank over 1980 inputs, bank_backward on the TPI floor path,
the CB price floor over 20k draws).

- Comment convention: every module and function carries exactly one leading
  ALL-CAPS comment; further explanation is lowercase # notes on the specific
  hard line. Docstrings and **bold** markers removed.
- New prints.py: all console reporting (SS table, residual checks, risk and
  TPI diagnostic tables, lending_spread_bps). main.py is now orchestration
  only; model blocks no longer format output.
- market_residuals moved into transition.py as the single definition of the
  clearing diagnostics, shared by prints.py and the tests (was duplicated in
  tests/common.py as transition_residuals).
- Removed switched-off machinery that was all 0/inert at the baseline:
  def_output_cost_D, def_output_rho_D, def_capital_quality_D and its quality0
  capital plumbing, recap_share_D, the pin_rdep deposit-rate diagnostic, the
  hybr_factor argument, capital_branch_summary, and the standalone
  hm_bond_price_ss/hm_bond_return_ss helpers (inlined). The recap continuation
  survives as _RECAP_LADDER (numerical warm-start scaffolding only).
- Docs (CLAUDE.md, function_reference.md, STATE.md) updated to match.

Tests: ss_identities, bank_block, fast_kernels, transition_walras,
signs_bocola, risk_channel all pass. Full main.py pipeline (SS -> TFP ->
risk -> TPI) converges at the documented 1% headline. The pre-existing TPI
solver fragility at larger shocks is unchanged (test_tpi on_date=5 and
main.py at PI_SHOCK=0.02 stall in the base solve).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…nel opt-in

code/global's two-branch risk channel produces an expansionary artifact
(the crisis-state bank levers into capital as a safe haven); code/bocola2016/
builds a standalone reference solution — Smolyak time-iteration over the
true recursive competitive equilibrium — to validate against instead of the
finite-horizon branch approximation. main.py now defaults to the liquidity-
channel-only headline (RUN_RISK_TWO_BRANCH=False, RUN_TPI=False) with the
two-branch run kept available behind a flag; prints.py handles both modes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…alibration fixes

Add a recursive global equilibrium solve for the frozen two-country model,
replacing the perfect-foresight MIT transition + representative post-default
branch with a genuine recursive law of motion:

- state_grid.py: Smolyak sparse grid + Chebyshev basis (Krueger-Kubler 2004),
  square collocation, agreed 8-state box builder.
- point_map.py: single-point period map -- the per-period image of
  transition.py's 7-unknown stacked system; P sufficient for N (no separate
  household-wealth state).
- expectations.py: two-branch Gauss-Hermite quadrature over (eps_s, d') that
  replaces risk_branch.py's representative branch -- the bad state is the SAME
  fitted rules at a reachable point.
- decision_rules.py: per-regime Chebyshev coefficient layer (SOLVE7 + DERIVED4).
- household_ks.py: 1-moment Krusell-Smith household layer over the aggregate
  state, internally consistent (no separate perceived law of motion).
- recursive_residual.py / recursive_main.py: pointwise time-iteration driver
  (Coleman operator, not a monolithic Newton -- far less stiff at the
  occasionally-binding kink).
- recursive_experiment.py: the pass-through experiment -- elevated priced
  default probability lowers Y_D and C_D persistently, the sign the
  representative branch could not deliver.
- tests/: state-grid exactness + recursive nesting regression.

Calibration realignment to Bocola (2016) Tables 1-2:
- leverage^bg 4.0 -> 5.0 and spread 200bp -> 8bp (mu^bg = 0.001, barely-binding
  SS); the fold that blocked lev 5 was a high-spread artifact.
- delta_b 0.036 -> 0.056 (Bocola's pi, HM/CE perpetuity maturing fraction).
- B_gov set so D-bank D-sovereign holdings are 7.6% of assets (Bocola exp^bg,
  Table B1). The old 3.722 misread "93% of bank EQUITY" as debt/GDP, tripling
  exposure and making default expansionary via a huge fiscal windfall.
- phi_lamb -> gamma_tau = 1.0 (Bocola's debt-level tax rule).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Push the standalone Bocola (2016) reference solve toward his Figure-5
magnitudes and document the residual numerical obstacle honestly.

- full_rotated.py / simulate_full.py: PCA-decorrelated 6-state grid + best-
  iterate tracking. Cuts the infeasible-corner fraction 53% -> 13% and
  strengthens the pass-through (Q_b -19% at p^d 2.5%, near Bocola's ~-15%).
- rotated_grid.py: generalise rotated_from_path to any number of trailing
  exogenous dims, with optional exog_lo/exog_hi box overrides.
- time_iteration_full.py: warm start from saved coefficients + best-iterate
  return (the two-regime near-unit-root iteration is not globally
  contractive -- it reaches a good near-fixed-point then oscillates away).
- expectations.py: softplus floor on next-period investment so Q_K' is
  differentiable everywhere (the hard np.where(I'>0) kink made the FD
  Jacobian noisy); BGP stays a rest point to 1e-12.
- newton_collocation.py / full_continue.py: Newton on the stacked collocation
  residual with Fischer-Burmeister complementarity smoothing (mu an explicit
  unknown). FB-Newton unstalls where the axis-aligned solve was stuck
  (residual 0.037 -> 0.0053) but floors at ~5e-3 on a handful of hard anchored
  corners coupled to the interior through the global fit.
- docs: honest state of the replication -- the financial pass-through is
  replicated and correctly signed; matching the real-side magnitudes needs a
  corner-decoupling solver (local/FE basis or tuned Smolyak+filter) plus the
  Neumeyer-Perri working-capital wedge.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant