Skip to content

Commit 714fee0

Browse files
hmgaudeckerclaude
andcommitted
Assets grid: subtract MAX_CONSUMPTION margin from the floor
With consumption now declared as `IrregSpacedGrid(n_points=N)` and points filled at runtime from `geomspace(consumption_floor, max_consumption, N)`, the grid clusters densely just above `consumption_floor`. At the lowest-asset / highest-OOP-shock corner, those near-floor consumption choices push `next_assets = cash_on_hand - OOP - consumption` slightly below the assets grid's old lower bound (`0` for the bare model, `-max_annual_labor_income` when wage_params are available). Out-of-bounds interpolation of next-period V then injects NaN, which propagates back through E[V] and eventually fails `validate_V`. Symptom on the production solve: `Value function at age 93 in regime 'retiree_oamc_forced_forcedout': 7317 of 207360 values are NaN`, with the `[NOTE]` showing E[V] NaN concentrated at the lowest assets indices and the highest hcc_transitory shock. Subtract `MAX_CONSUMPTION` from the assets floor to give a worst-case single-period drain margin. With 24 linspace points spanning the wider range, the per-point density change is negligible; the dead state and the bare-model fallback get the margin too. The asymmetry fix is the cheapest one — no change to the consumption grid type, no change to per-iteration parameters, no new constraints. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 31a0ad2 commit 714fee0

1 file changed

Lines changed: 18 additions & 3 deletions

File tree

src/aca_model/baseline/regimes/_common.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -262,10 +262,25 @@ def build_grids(
262262
sigma=1.0,
263263
)
264264

265-
assets_start = 0.0
265+
# Assets-grid lower bound includes a one-period margin below the
266+
# binding borrowing limit so that `next_assets = cash_on_hand - OOP -
267+
# consumption` stays inside the grid even at the worst-shock × low-
268+
# consumption corner. With the runtime log-spaced consumption grid
269+
# `geomspace(consumption_floor, max_consumption, n_points)`, choices
270+
# cluster densely just above `consumption_floor`, and at the lowest-
271+
# asset/highest-OOP-shock corner those choices push `next_assets`
272+
# slightly off the grid bottom — out-of-bounds interpolation of
273+
# next-period V then injects NaN that propagates through E[V].
274+
# Subtracting `MAX_CONSUMPTION` gives a worst-case single-period
275+
# drain margin; cheap at production grid sizes (24 linspace points
276+
# over the wider range).
277+
assets_start = -MAX_CONSUMPTION
266278
if wage_params is not None and _has_required_wage_keys(wage_params=wage_params):
267-
assets_start = -_compute_max_annual_labor_income(
268-
wage_params=wage_params, wage_res_grid=wage_res
279+
assets_start = (
280+
-_compute_max_annual_labor_income(
281+
wage_params=wage_params, wage_res_grid=wage_res
282+
)
283+
- MAX_CONSUMPTION
269284
)
270285

271286
return Grids(

0 commit comments

Comments
 (0)