Add solver-config classes, builder registry, and DC-EGM build-time validation#377
Add solver-config classes, builder registry, and DC-EGM build-time validation#377hmgaudecker wants to merge 12 commits into
Conversation
Red-spec commit: tests/test_dcegm_validation.py defines the build-time DC-EGM contract (one parametrized case per rule), with the brute-force/DC-EGM equivalent-spec builders in tests/test_models/deterministic/dcegm_variants.py and the retirement-only concave oracle model in retirement_only.py. The file importorskips on lcm.solvers until the solver-config API lands. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…lidation. - `lcm.solvers`: frozen kw-only `BruteForce` and `DCEGM` configuration dataclasses (beartype-checked via REGIME_CONF), exported from `lcm`. - `Regime.solver` field, defaulting to `BruteForce()` so every existing model is byte-identical. - `_lcm/solution/registry.py`: `SOLVER_KERNEL_BUILDERS` maps `type(regime.solver)` to a per-period kernel builder; the brute-force builder is the unchanged max-Q-over-a build, the DCEGM builder emits stubs that raise `NotImplementedError` at solve time. `Model.solve`/ `Model.simulate` raise the same error up front so the message is about the solver, not about params the EGM machinery would consume. - `_lcm/egm/validation.py`: the full DC-EGM build-time contract — state/action classification, required functions, default-H identity, post-decision signature, constraint/utility/resources DAG-ancestry rules, deterministic Euler transition routed through the post-decision function, savings-node-stage independence, grid hygiene, cross-regime target compatibility, and numeric spot checks (consumption recovery, resources monotonicity, inverse-marginal-utility round-trip via jax.grad) on small samples outside jit. Called from `validate_model_inputs` (before the generic unused-variable check, so the contract-specific message wins) and from `process_regimes` for direct engine callers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Benchmark comparison (main → HEAD)Comparing
|
# Conflicts: # src/_lcm/model_processing.py # src/_lcm/regime_building/processing.py # src/lcm/__init__.py # src/lcm/regime.py
# Conflicts: # src/_lcm/regime_building/effective.py
Model-level broadcast widens regime slots with None masks; the effective regimes the validation runs on have masks resolved away, so grid and function reads filter to the concrete values. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
# Conflicts: # src/_lcm/egm/__init__.py # src/_lcm/model_processing.py # src/_lcm/regime_building/processing.py # src/lcm/__init__.py # src/lcm/regime.py
|
Coordination heads-up for the stack: #379 is now merged into #373's branch ( One upcoming change that touches your edits: the review fixes on #373 delete 🤖 Generated with Claude Code |
The kernels need every grid's points at model construction (savings nodes, carry shapes, numeric spot checks), and carry rows are selected by integer indexing along whole discrete axes. Grid hygiene therefore rejects runtime-supplied points on the savings grid, the Euler state grid, and the continuous action grid, and batching/distribution on any discrete state or action grid — each with a message naming the regime and the grid's role. A remaining runtime grid reaching the numeric spot checks surfaces as a contextual ModelInitializationError instead of a raw grid error. Also document the savings grid's accuracy role on the solver config and pin the brute-target test's match to the rule's message. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Folded into #378 (base retargeted to feat/fues-kernel): the solver-config classes, registry, and build-time validation are best reviewed against the concave EGM kernel that relies on them. The branch feat/dcegm-solver-config remains in the stack's history; no commits are lost. |
Second DC-EGM prequel (stacked on #374; see also #375, #376): the public solver abstraction and the full DC-EGM build-time validation, with the solver itself still to come.
lcm.solvers:BruteForce(default) andDCEGM— pure-config frozen dataclasses, no engine internals.Regimegains asolverfield (default_factory=BruteForce, fully backward compatible)._lcm/solution/registry.py:SOLVER_KERNEL_BUILDERSmapstype(regime.solver)to the per-period kernel builder;process_regimesdispatches through it (the brute-force builder is the previous_build_max_Q_over_a_per_period, moved verbatim). Adding a solver = one public config class + one registered builder, noisinstancechains._lcm/egm/validation.py: the DC-EGM model contract, enforced atModelconstruction — state/action classification (exactly one Euler state + one continuous action; process states exempt; passive continuous states rejected pointing at the upcoming extension), required functions (resources, post-decision,inverse_marginal_utility), DAG-ancestor rules (transition reaches wealth/consumption only through the post-decision function; utility independent of the state; constraints discrete-only; savings-node-stage functions independent of the decision), defaultH, deterministic Euler transition, grid hygiene, target-regime compatibility, and numeric spot checks (post_decision ≈ R − c,(u′)⁻¹(u′(c)) ≈ c, monotonicity).DCEGM-configured model constructs and validates, butsolve()/simulate()raise a clearNotImplementedErroruntil the EGM step lands.Tests were written first: 14 spec tests covering one violation per contract rule, a non-DCEGM-target rule, and a brute-force regression (explicit
solver=BruteForce()solves identically to the default).Merge-integration notes (deliberate, to resolve when the siblings land):
get_max_Q_over_awith taste-shock kwargs; the registry's brute-force builder call site needs those threaded after the merge.src/_lcm/egm/__init__.py(trivial union).🤖 Generated with Claude Code