Skip to content

Add solver-config classes, builder registry, and DC-EGM build-time validation#377

Closed
hmgaudecker wants to merge 12 commits into
feat/fues-kernelfrom
feat/dcegm-solver-config
Closed

Add solver-config classes, builder registry, and DC-EGM build-time validation#377
hmgaudecker wants to merge 12 commits into
feat/fues-kernelfrom
feat/dcegm-solver-config

Conversation

@hmgaudecker

Copy link
Copy Markdown
Member

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) and DCEGM — pure-config frozen dataclasses, no engine internals. Regime gains a solver field (default_factory=BruteForce, fully backward compatible).
  • _lcm/solution/registry.py: SOLVER_KERNEL_BUILDERS maps type(regime.solver) to the per-period kernel builder; process_regimes dispatches 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, no isinstance chains.
  • _lcm/egm/validation.py: the DC-EGM model contract, enforced at Model construction — 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), default H, deterministic Euler transition, grid hygiene, target-regime compatibility, and numeric spot checks (post_decision ≈ R − c, (u′)⁻¹(u′(c)) ≈ c, monotonicity).
  • A DCEGM-configured model constructs and validates, but solve()/simulate() raise a clear NotImplementedError until 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):

🤖 Generated with Claude Code

hmgaudecker and others added 2 commits June 10, 2026 12:17
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>
@read-the-docs-community

read-the-docs-community Bot commented Jun 10, 2026

Copy link
Copy Markdown

@github-actions

github-actions Bot commented Jun 10, 2026

Copy link
Copy Markdown

Benchmark comparison (main → HEAD)

Comparing 64cf042c (main) → 55200a24 (HEAD)

Benchmark Statistic before after Ratio Alert
aca-baseline execution time 15.286 s 14.812 s 0.97
peak GPU mem 581 MB 581 MB 1.00
compilation time 311.45 s 342.94 s 1.10
peak CPU mem 7.83 GB 7.67 GB 0.98
aca-baseline-debug execution time 72.232 s 76.960 s 1.07
peak GPU mem 581 MB 581 MB 1.00
compilation time 404.14 s 448.42 s 1.11
peak CPU mem 7.63 GB 7.70 GB 1.01
Mahler-Yum execution time 4.868 s 4.838 s 0.99
peak GPU mem 520 MB 520 MB 1.00
compilation time 11.92 s 12.07 s 1.01
peak CPU mem 1.60 GB 1.61 GB 1.00
Precautionary Savings - Solve execution time 27.6 ms 25.3 ms 0.92
peak GPU mem 8 MB 8 MB 1.00
compilation time 1.52 s 1.58 s 1.04
peak CPU mem 1.15 GB 1.16 GB 1.01
Precautionary Savings - Simulate execution time 98.4 ms 96.9 ms 0.98
peak GPU mem 162 MB 162 MB 1.00
compilation time 3.83 s 3.90 s 1.02
peak CPU mem 1.35 GB 1.35 GB 1.00
Precautionary Savings - Solve & Simulate execution time 124.7 ms 119.2 ms 0.96
peak GPU mem 566 MB 566 MB 1.00
compilation time 5.07 s 5.08 s 1.00
peak CPU mem 1.32 GB 1.33 GB 1.00
Precautionary Savings - Solve & Simulate (irreg) execution time 229.2 ms 234.6 ms 1.02
peak GPU mem 2.18 GB 2.18 GB 1.00
compilation time 5.24 s 5.35 s 1.02
peak CPU mem 1.37 GB 1.38 GB 1.00
IskhakovEtAl2017Simulate execution time 324.3 ms
compilation time 5.24 s
peak CPU mem 1.33 GB
IskhakovEtAl2017Solve execution time 46.5 ms
compilation time 0.72 s
peak CPU mem 1.15 GB
IskhakovEtAl2017SimulateGpuPeakMem peak GPU mem 342 MB
IskhakovEtAl2017SolveGpuPeakMem peak GPU mem 67 MB

hmgaudecker and others added 5 commits June 10, 2026 18:05
# 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
@hmgaudecker hmgaudecker changed the base branch from example/iskhakov-et-al-2017 to feat/fues-kernel June 11, 2026 12:50
@hmgaudecker

Copy link
Copy Markdown
Member Author

Coordination heads-up for the stack: #379 is now merged into #373's branch (feat/phase-variant-states) and the combined PR is reviewed there — #374's base has been retargeted accordingly; feat/canonical-broadcast-grammar will be deleted once nothing points at it.

One upcoming change that touches your edits: the review fixes on #373 delete EffectiveUserRegime (_lcm/regime_building/effective.py becomes finalize.py with a finalize_regimes function returning plain, complete Regimes; erased alias FinalizedUserRegime marks post-merge signatures). On your next cascade, take the deletion in any effective.py conflict — your solver/taste_shocks fields ride along automatically once there is no field-copy list. model.user_regimes then holds plain Regimes; isinstance(..., Regime) keeps working everywhere.

🤖 Generated with Claude Code

hmgaudecker and others added 5 commits June 11, 2026 22:07
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>
@hmgaudecker

Copy link
Copy Markdown
Member Author

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.

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