Repository navigation
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #212 +/- ##
==========================================
+ Coverage 93.07% 93.86% +0.79%
==========================================
Files 71 74 +3
Lines 2598 3128 +530
==========================================
+ Hits 2418 2936 +518
- Misses 180 192 +12 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
Check out this pull request on See visual diffs & provide feedback on Jupyter Notebooks. Powered by ReviewNB |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Overview
model_config["continuous_states"]took a fixed grid, you can now pass a function ofthe discrete state under the new
continuous_grid_functionsargument and get adifferent grid per state. If you do decide to supply a function than the former grid
entry must be
None.to the budget equation. Grids live on the state-choice space because the solution
does;
choiceas an argument, however, is allowed only inbudget_constraint.income_shock_previous_period→income_shock: the shock is part of thisperiod's state. Continuous laws of motion are uniformly defined as end-of-last-period
→ this-period state, which also fixes what
states_initialmust contain.period's choice. State-choice space is denser than state space; if
budget_constraintdoes not declare
choice, the law of motion is evaluated on unique child statesagain and gathered back out.
1. Supplying a function instead of a grid
continuous_grid_functionsis a new top-level argument tosetup_model/create_model_dict. It accepts an entry forassets_end_of_period,assets_begin_of_period, or any additional continuous state, and that entry is afunction of the discrete state-choice evaluated once per state-choice at model build —
in place of the single global array that name would otherwise carry.
The function and the config entry are paired, and the pairing is enforced both ways. A
name left as an array in
model_config["continuous_states"]and not given a functionkeeps exactly the old behaviour, skipped by the new machinery entirely since a constant
grid is trivially consistent everywhere. A name given a function must be declared
Nonethere instead — no default array at all, the grid comes only from the function —and declaring
Nonewithout supplying a function raises, as does supplying both. Thename must appear in
continuous_stateseither way; the functions themselves go in thetop-level
continuous_grid_functionsargument, sincemodel_configholds data andnever callables.
assets_end_of_periodobeys the same rule as every other name. It briefly did not —its length sizes the fues tuning params (
n_constrained_points_to_add,n_total_wealth_grid), whichcheck_model_config.pycomputed eagerly, beforecontinuous_grid_functionsis processed and before any state-choice exists to pin adeferred length against. Those are now deferred too, resolved by
resolve_tuning_params_from_assets_end_of_periodonce the length is pinned. The upperenvelope, which reads them, is built after the model structure rather than alongside the
other model functions, so it is constructed from resolved sizes rather than closing over
pending ones. The same move dropped a dead
compute_upper_envelopefrom thealternative-simulation path, which nothing read.
Noneis what costs the code. Sizes that used to be known at config time —n_continuous_state_combinations,n_total_wealth_grid— cannot be computed before areal state-choice exists to evaluate the function against, so
check_model_config.pyleaves them unresolved and they are merged back once the state-choice space is built.
The length is pinned by the first state-choice's evaluation and every other
state-choice's grid is validated against it: grids may differ in their values, never in
their size, so solution containers stay rectangular. Save/load needed the mirror image —
load_model_dictrebuildsmodel_configfrom the raw user config, unresolving thosesizes, while taking the state-choice space from the pickle, so the lengths are re-pinned
from one state-choice there.
New module:
pre_processing/model_structure/continuous_state_grids.py.2. State-choice-specific, and why
choiceis budget-onlyGrids are indexed by state-choice, not by state, because that is where the solution
itself lives (
value_solved/policy_solved/endog_grid_solved). A grid functionmay therefore vary across the choices available in one state.
Declaring
choiceas an argument is a separate question, and the answer narrowed overthe course of the branch. An earlier iteration let any transition function take it; it
is now only
budget_constraint, and_check_choice_not_declaredraises at modelbuild for the others. The reason is timing: every other state variable is fixed before
the choice is made, so a choice argument there has no meaning. Wealth is the one
deliberate exception, and it is what makes choice-specific wages of the current choice available
for consumption.
choiceinbudget_constraintmeans the choice made in the period whose wealth is being computed.next_period_continuous_stategets the previous choice the ordinary way, as the statevariable
lagged_choice, as this is part of the states.Because grids are per-state-choice while the continuation value is computed once per
child, something has to decide whose grid feeds the transition into a shared child.
That is the representative parent: any one of the parent state-choices mapping into
that child. Picking arbitrarily is legitimate because
check_continuous_grid_consistency_across_shared_childrenverifies once at model-buildtime that all of them agree on their own grid — checked at build rather than defended
against at solve time.
3.
income_shock, and the timing of continuous laws of motionThe shock is realized in the period whose wealth the budget equation computes, so it is
part of that period's state, not the previous one's. The old name
income_shock_previous_perioddescribed a habit of the shipped example models — incomeearned against
lagged_choice, so a retiring agent still collects last period's wage —rather than anything the framework requires. The two non-state arguments of
budget_constraintare deliberately asymmetric in timing and the names now say so:asset_end_of_previous_periodreally is carried over fromt-1,income_shockisrealized in
t.Generalized: every continuous law of motion maps end-of-last-period quantities to a
this-period state. The consequence for simulation is that
states_initialtakesassets_end_of_previous_period, notassets_begin_of_period— what agents carryinto period 0, not what they have to spend in it. Period 0 then runs the budget
equation like every other period and stops being a special case, and the simulated panel
becomes self-consistent row by row: applying your budget constraint to the previous
period's
savingsand this period's ownincome_shockcolumn reproduces this period'sassets_begin_of_period.Both old names now raise a pointed
ValueErrorrather than being silentlyreinterpreted or surfacing as a bare
KeyErrorfrom the signature filter. Templates,toy models and tests are updated. Simulation also computes beginning-of-period wealth
for every choice before the choice is made and gathers the realized one afterwards —
the counterpart to a choice-dependent budget equation — and
alternative_sim_functionsrecomputes choice-dependence from the alternative budgetequation rather than inheriting it.
4. Sparsity reintroduced when the budget is choice-independent
State-choice space is denser than state space, and evaluating the law of motion on it
does redundant work: unless the budget equation depends on the current choice, every
state-choice sharing a child state computes a bit-identical transition.
calc_law_of_motionis now the single entry point every caller uses, dispatching on onemodel-static flag,
budget_depends_on_choice:choiceThese are not two implementations of the transition math — the coarse branch calls the
fine one with deduplicated rows. Since the branch is known at setup, only that branch's
dedup arrays are bundled and threaded through the backward-induction scan; the other is
never built.
5. What fell out of the single entry point
Having one entry point forced two identities apart that had been conflated, and the
separation fixed a latent bug. A transition into a child now distinguishes:
Pulling the proxy apart from the child is behavioural, not plumbing. With a value-reuse
proxy — a sparsity condition redirecting every dead state to one last-period death slot,
say — the law of motion now runs on the child's real state, with its real age and
experience, while only the value/policy lookup follows the proxy to where that value was
stored. Previously the proxy identity leaked into the transition. The user-facing
corollary is now in the sparsity docs: a living parent re-evaluates the periodic
utilityandmarginal_utilityat the proxied child's own identity during EGM, so thosemust branch on the terminal state and agree with
utility_functions_final_period, orevery parent with a dead child has a wrong Euler equation. (That branch fires for every
period except the last two, which bypass the periodic functions.)
Two cleanups come with it. The final period no longer runs a duplicated transition
path — it goes through
calc_law_of_motionlike everything else, with its own deduparrays built in the new
batches/last_two_periods.py. And the batch dedup logic,duplicated identically across both batch-size algorithms (
algo_batch_size.py'slargest_block and
single_segment.py's period_max), is one implementation inbatches/child_state_dedup.pythat also exposes the state-level dedup section 4 needs.6. Two additions riding along
dcegm.get_solve_from_small_models(newinterfaces/combined_solve.py, exportedfrom
dcegm/__init__.py) — for models whose pooled solve does not fit in GPU memory.Split the model by hand into sub-models whose discrete state-choice spaces tile the big
one exactly, typically one per invariant type (education × sex) where the problem is
block-diagonal and each block is an exact standalone sub-problem. You get back a
solve(params) -> model_solvedfor the big model that solves each block and scatters itinto the big container, so the full solution is only ever stored in one piece, never
solved in one. Sequential by default, one block's device arrays live at a time;
parallel=Truedispatches blocks round-robin acrossjax.devices(). The tiling isverified to be an exact partition rather than assumed.
interpolation/interpnd_regular.py— N-dimensional regular-grid interpolation, forreading stored solutions back on their own (possibly state-specific) regular grids under
Druedahl-Jorgensen.
Docs
Two new pages:
background/choice_dependent_sparsity.rst(whysparsity_conditiontakesno
choice, and the recipe for each of the two distinct things people mean when they askfor it) and
development/internals/law_of_motion.rst(evaluation granularity, parent vs.child vs. proxy, writing a choice-dependent budget equation). Updated:
sparsity_conditions.rstwith the proxied-terminal-state utility requirement, andpractitioner_guide.rstwith the initial-assets contract and choice-dependent budgets.Both new pages carry an AI-drafted warning banner — worth deciding whether that stays on
merge, as is whether
internals/state_specific_continuous_grids_plan.md, a 237-lineworking plan, should ship at all.
Tests
continuous_grid_functions,None-declared grids), batch dedup consistencytest_state_specific_grids_config.pytest_state_specific_grids_law_of_motion.pysolve()/simulate()/choice_values_for_statestest_state_specific_grids_end_to_end.pytest_state_specific_grids_reference.pytests/sparse_death/test_death_proxy_euler.py,tests/sparse_death/test_sparse_death_period_grid.pytest_initial_assets_contract.pytest_combined_solve.pytest_interpnd_regular_own_grids.py,test_interpolation.pytest_load_save.pytest_two_period_continuous_experience.py,test_law_of_motion.pyBreaking changes
budget_constraint:income_shock_previous_period→income_shock(old name raises).states_initial:assets_begin_of_period→assets_end_of_previous_period(old key raises).budget_constraintdeclaringchoiceraises at model build.