Skip to content

Say which numbers are a replication and which are calibration inputs; make MPCs mean something - #3

Merged
vahid-ahmadi merged 1 commit into
mainfrom
fix/replication-claims-and-mpc-measurement
Aug 12, 2026
Merged

Say which numbers are a replication and which are calibration inputs; make MPCs mean something#3
vahid-ahmadi merged 1 commit into
mainfrom
fix/replication-claims-and-mpc-measurement

Conversation

@vahid-ahmadi

Copy link
Copy Markdown
Contributor

The validation section was reporting inputs as results. Y=1, K=10,
r=1.25% quarterly, total wealth 14 and B=1.04 were listed as "the steady
state reproduces the paper's calibration targets". They are calibration
inputs: they are imposed, and (Z, alpha, mu_p, tau, phi) are backed out
to make them consistent. "Target 10, achieved 10" is a constant equalling
itself.

The real replication is the parameters solved for internally, which
reproduce ABRS (2021) Table B.III to the last published digit — beta
0.9762739 vs 0.976, chi1 6.4164196 vs 6.416, Z 0.4677898 vs 0.468, alpha
0.3299492 vs 0.33, mu_p 1.0152284 vs 1.015, tau 0.3560606 vs 0.356. chi1
is the strongest of these: it is solved numerically from a 6.5 starting
guess and lands on the published value. That table is now the headline,
gated by a test, and the imposed quantities are labelled as imposed.

Two related corrections. Market clearing was quoted at 3.2e-13, which is
a solver target; goods-market clearing at 3.3e-08 is untargeted (it
holds only by Walras' law) and is the honest measure — five orders
looser. And the upstream regression targets (A, B, UCE) that this repo
matches bit for bit are sequence-jacobian v1.0.0's own partial-
equilibrium test at the solver's chi1 = 6.5 guess, so they validate the
household code, not the calibration.

A published discrepancy that was not disclosed. Table B.III gives
phi = 2.073; this DAG's union_ss yields 1.7135, a 17% gap that does not
reconcile under either definition of UCE. It looks like a reporting
convention — phi is a pure normalisation making N=1 optimal, the wage
Phillips residual is 7e-15 here, and nothing else depends on it — but it
is unexplained, so it is written down and deliberately not asserted.

IRF magnitudes were claimed and cannot be. The README said signs
and magnitudes match the paper. ABRS (2021) publishes no figure
plotting Y/C/I/pi against these shocks, so there is nothing to match
magnitudes against. Signs and timing are gated; magnitudes are not
claimed.

MPCs were being measured at a kink the grid does not resolve. Just
over half the population sits exactly at the liquid borrowing constraint,
and the one-grid-step MPC is the policy slope right at that kink: it
rises monotonically from 0.076 at nB=50 to 0.100 at nB=150 with no
plateau, so the published number was a grid artefact. MPCs are now
defined out of a windfall of stated economic size (default 25% of mean
quarterly post-tax labour income, roughly the 2001/2008 US rebates the
empirical literature measures), which is stable across grids. The legacy
one-step difference is kept for the grid-sensitivity test and documented
as not quotable. Related: the hand-to-mouth share is an atom, not a
threshold — 51.2% hold exactly zero liquid assets, so the count is
insensitive to the cutoff over [0, 0.028], and hand_to_mouth_breakdown
now shows that rather than leaving the cutoff looking load-bearing.

Units trap, stated once. size is a level change for all three
shock kinds. That reads naturally for the policy rate and for G (Y_ss = 1,
so a level change in G is a share of GDP), and is a trap for
productivity: Z_ss = 0.468, so size=0.01 is a 2.14% TFP improvement,
not 1%. SHOCK_UNITS states this and every result carries
shock_pct_of_ss so the reading is never left to the caller.

A latent cache bug. The Jacobian cache keyed on id(ss) without
holding a reference, so a garbage-collected steady state could have its
address reused by a different one, which would then be handed the first
one's Jacobian. It keeps the reference now. PRODUCTION_CALIBRATION was
also passed by reference into a solver that writes its solved unknowns
back, letting the first solve seed every later one; it is copied.

Also: budget_residual is an arithmetic identity — tax_revenue is
defined as exactly that expression rearranged — so the test asserting it
is ~0 can catch a refactoring slip and nothing else, and now says so.
sequence-jacobian is pinned exactly and the numeric stack carries
major-version ceilings, because the replication asserts published digits
and a silent NumPy major bump must not be able to move them.

24 -> 43 test functions; 47 pass with --runslow, none removed.

Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com

🤖 Generated with Claude Code

… make MPCs mean something

**The validation section was reporting inputs as results.** Y=1, K=10,
r=1.25% quarterly, total wealth 14 and B=1.04 were listed as "the steady
state reproduces the paper's calibration targets". They are calibration
*inputs*: they are imposed, and (Z, alpha, mu_p, tau, phi) are backed out
to make them consistent. "Target 10, achieved 10" is a constant equalling
itself.

The real replication is the parameters solved for internally, which
reproduce ABRS (2021) Table B.III to the last published digit — beta
0.9762739 vs 0.976, chi1 6.4164196 vs 6.416, Z 0.4677898 vs 0.468, alpha
0.3299492 vs 0.33, mu_p 1.0152284 vs 1.015, tau 0.3560606 vs 0.356. chi1
is the strongest of these: it is solved numerically from a 6.5 starting
guess and lands on the published value. That table is now the headline,
gated by a test, and the imposed quantities are labelled as imposed.

Two related corrections. Market clearing was quoted at 3.2e-13, which is
a solver *target*; goods-market clearing at 3.3e-08 is untargeted (it
holds only by Walras' law) and is the honest measure — five orders
looser. And the upstream regression targets (A, B, UCE) that this repo
matches bit for bit are `sequence-jacobian` v1.0.0's own partial-
equilibrium test at the solver's chi1 = 6.5 *guess*, so they validate the
household code, not the calibration.

**A published discrepancy that was not disclosed.** Table B.III gives
phi = 2.073; this DAG's `union_ss` yields 1.7135, a 17% gap that does not
reconcile under either definition of UCE. It looks like a reporting
convention — phi is a pure normalisation making N=1 optimal, the wage
Phillips residual is 7e-15 here, and nothing else depends on it — but it
is unexplained, so it is written down and deliberately not asserted.

**IRF magnitudes were claimed and cannot be.** The README said signs
*and magnitudes* match the paper. ABRS (2021) publishes no figure
plotting Y/C/I/pi against these shocks, so there is nothing to match
magnitudes against. Signs and timing are gated; magnitudes are not
claimed.

**MPCs were being measured at a kink the grid does not resolve.** Just
over half the population sits exactly at the liquid borrowing constraint,
and the one-grid-step MPC is the policy slope right at that kink: it
rises monotonically from 0.076 at nB=50 to 0.100 at nB=150 with no
plateau, so the published number was a grid artefact. MPCs are now
defined out of a windfall of stated economic size (default 25% of mean
quarterly post-tax labour income, roughly the 2001/2008 US rebates the
empirical literature measures), which is stable across grids. The legacy
one-step difference is kept for the grid-sensitivity test and documented
as not quotable. Related: the hand-to-mouth share is an atom, not a
threshold — 51.2% hold exactly zero liquid assets, so the count is
insensitive to the cutoff over [0, 0.028], and `hand_to_mouth_breakdown`
now shows that rather than leaving the cutoff looking load-bearing.

**Units trap, stated once.** `size` is a level change for all three
shock kinds. That reads naturally for the policy rate and for G (Y_ss = 1,
so a level change in G is a share of GDP), and is a trap for
productivity: Z_ss = 0.468, so size=0.01 is a **2.14%** TFP improvement,
not 1%. `SHOCK_UNITS` states this and every result carries
`shock_pct_of_ss` so the reading is never left to the caller.

**A latent cache bug.** The Jacobian cache keyed on `id(ss)` without
holding a reference, so a garbage-collected steady state could have its
address reused by a different one, which would then be handed the first
one's Jacobian. It keeps the reference now. `PRODUCTION_CALIBRATION` was
also passed by reference into a solver that writes its solved unknowns
back, letting the first solve seed every later one; it is copied.

Also: `budget_residual` is an arithmetic identity — `tax_revenue` is
defined as exactly that expression rearranged — so the test asserting it
is ~0 can catch a refactoring slip and nothing else, and now says so.
`sequence-jacobian` is pinned exactly and the numeric stack carries
major-version ceilings, because the replication asserts published digits
and a silent NumPy major bump must not be able to move them.

24 -> 43 test functions; 47 pass with `--runslow`, none removed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@vahid-ahmadi
vahid-ahmadi merged commit 19a8e5e into main Aug 12, 2026
3 checks passed
@vahid-ahmadi
vahid-ahmadi deleted the fix/replication-claims-and-mpc-measurement branch August 12, 2026 14:38
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