Skip to content

Choose transition rules for supplied AR1 nodes - #626

Open
hmgaudecker wants to merge 41 commits into
codex/553-declared-target-validationfrom
codex/on-nodes-transition-rules
Open

hmgaudecker wants to merge 41 commits into
codex/553-declared-target-validationfrom
codex/on-nodes-transition-rules

Conversation

@hmgaudecker

@hmgaudecker hmgaudecker commented Oct 25, 2026 •

Copy link
Copy Markdown
Member

Models using supplied AR(1) state nodes can choose how transition probabilities are computed through TauchenAR1Process.on_nodes:

  • Midpoint bins preserve the existing default.
  • Innovation quadrature maps normal innovations linearly onto the supplied nodes.
  • Normalized node densities weight the supplied nodes by their conditional normal density.

Quadrature accepts a Gauss–Hermite node count or explicit standardized nodes and weights. Targets outside the state grid are clipped to its endpoints. Parameters and innovation scales can vary at runtime or with the current state.

Node-based simulation uses the selected transition matrix. Continuous simulation keeps its conditional normal draws.

The implementation also stabilizes density calculations and quadrature allocation over extreme numerical ranges. Targeted CPU and A100 tests cover transition probabilities, derivatives, Bellman expectations and simulation.

Density-rule derivatives use normalized scaled arithmetic. The stronger finite-row accuracy certificate is retained in private _lcm as a deliberately invoked developer/test oracle. It is documented with its input scope and success-or-rejection guarantee; it does not run during routine differentiation, solving or simulation at either runtime_checks setting. Routine derivatives do not carry that certificate's componentwise accuracy guarantee. The usual probability and finiteness checks remain controlled by runtime_checks.

Density stabilization adds compilation and memory cost in the measured small-model workload; this is not a performance estimate for every model.

Stacked on #625. Closes #586.

Validation: CPU CI · GPU CI.

@read-the-docs-community

read-the-docs-community Bot commented Oct 25, 2026 •

Copy link
Copy Markdown

@hmgaudecker
hmgaudecker changed the base branch from codex/607-engine-redesign to codex/553-declared-target-validation October 25, 2026 16:29

@mj023 mj023 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, no big errors that would have any impact on model execution etc. But I found some minor things that should be fixed and some cleanups that could be done before merging.

1. DensityDerivativeAccuracyError docstring promises a guard that doesn't run (src/lcm/exceptions.py:225)
The docstring says an out-of-budget derivative is rejected and that "under JIT the runtime callback reports this error". Routine AD never runs the certificate. For the row your own rejection test uses (family="central", rho), _certify_density_derivative_row raises, but jax.jit(jax.jacfwd(...)) returns 0 for the middle component instead of the reference -1.85e239, without any error. The case is extreme, but a public docstring shouldn't promise error control that doesn't exist. Could it be reworded to match the user guide, so it says the error comes only from the explicit developer certificate?

2. Test-only certificate is shipped and imported with the library (src/_lcm/processes/ar1.py:35)
density_derivative_admission.py (about 630 lines) is loaded on every import lcm, but only the private _certify_density_derivative_row oracle uses it. Since it doesn't run in solve, simulate or AD, could it live under tests/ next to test_density_derivative_accuracy.py, or at least be imported lazily inside the certify function? Then the public exception could move with it.

3. Unrelated CI change (.github/workflows/benchmark-pr.yml)
cancel-in-progress: false means a new push no longer cancels a running GPU benchmark for an outdated commit. That isn't mentioned in the description and seems unrelated to on_nodes. Was it intentional?

Cleanup

  • _checked_density_coefficients still carries the io_callback exemption and docstring, but nothing registers it as a callback; calling _certify_density_coefficients directly would remove the wrapper.
  • The user guide tells readers to call the private _lcm.processes.ar1._certify_density_derivative_row; that part could go in developer docs or just point at the test file.
  • _get_conditioned_weights_func reads fixed["rho"] in the new branch, though rho_fixed is already bound a few lines above.
  • The "Unknown on_nodes transition_rule" check can't fire, because beartype rejects a wrong Literal first, and a single TransitionRule alias would replace the four copies of that Literal.

hmgaudecker and others added 6 commits October 27, 2026 19:40
…t certificate

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LAZ63AjbZFLweWuJyPUs6S
The certificate runs only when a test hands it a row; solve, simulation and
routine differentiation never call it. It and DensityDerivativeAccuracyError
now live in tests/test_models/density_derivative_admission.py beside the other
oracles, so importing lcm no longer loads it. The io_callback adapter is gone:
the row entry point converts its operands and calls the keyword-only checker
directly. Docs point at the test file instead of private _lcm entry points.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LAZ63AjbZFLweWuJyPUs6S
The Literal appeared on the factory, the dataclass field and the quadrature
helper; all three now use one alias. The membership check in the helper could
not fire, since beartype on the factory refuses an unknown rule first; a test
pins that refusal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LAZ63AjbZFLweWuJyPUs6S
…tion' into codex/on-nodes-transition-rules

# Conflicts:
#	tests/candidate_certificate/sources.json
@hmgaudecker

Copy link
Copy Markdown
Member Author

Thanks, all addressed:

  1. DensityDerivativeAccuracyError docstring. Agreed — it overstated the guard. It now says the error comes only from a deliberate call to the certificate, and that solve, simulation and routine AD return an out-of-budget row without raising (1a56a1e). Since the certificate moved to the tests (see 2), the exception moved with it.

  2. Certificate shipped with the library. Moved. density_derivative_admission.py now lives in tests/test_models/ next to the other oracles, together with the row entry point and DensityDerivativeAccuracyError, so import lcm no longer loads it and the public exception is gone (b4b86ea).

  3. cancel-in-progress: false in benchmark-pr.yml. Agreed, that doesn't belong here. Reverted (ff2783f); false stays only in benchmark-main.yml.

Cleanups:

  • io_callback wrapper: removed; the entry point calls _certify_density_coefficients directly (b4b86ea).
  • User guide: it now points at the test file and the pytest command instead of the private function (b4b86ea).
  • rho_fixed: kept fixed["rho"] on purpose. rho_fixed is fixed.get("rho"), which is optional because the IID path has no rho, and ty rejects passing it to compute_transition_row. The subscript narrows the type and fails loudly if rho were ever absent.
  • Literal copies / dead check: one TransitionRule alias now, the unreachable check is deleted, and a test pins that an unknown rule is refused with GridInitializationError (6e6db8b).

hmgaudecker and others added 5 commits November 2, 2026 15:42
…es-transition-rules

# Conflicts:
#	src/_lcm/processes/ar1.py
#	tests/candidate_certificate/direct_flow.py
#	tests/candidate_certificate/sources.json
A process value beyond its outer nodes now follows a declared `off_grid`
rule: extrapolate along the edge segment (default, unchanged numbers), read
the edge node's row/continuation/policy (`clip_lookup`), additionally clip
the simulated state (`clip_state`), or refuse it (`raise`). A node draw keeps
sampling the edge row under every rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hs8mfXPYyEa24KaAp6BwCW
The on-nodes process compared every field except the retired keyword name, so
two declarations differing only in their draw mode read as distinct laws. Use
the current field name, update the density tests and the user guide to the
renamed keyword, and register the density test file with the CI workloads.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hs8mfXPYyEa24KaAp6BwCW
# Conflicts:
#	tests/candidate_certificate/direct_flow.py
#	tests/candidate_certificate/sources.json
#	tests/ci/ci-workloads.json
@codecov

codecov Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.91289% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 94.06%. Comparing base (c04f42b) to head (7b7d298).

Files with missing lines Patch % Lines
src/_lcm/processes/ar1.py 99.62% 1 Missing ⚠️
Additional details and impacted files
@@                           Coverage Diff                            @@
##           codex/553-declared-target-validation     #626      +/-   ##
========================================================================
+ Coverage                                 94.00%   94.06%   +0.06%     
========================================================================
  Files                                       658      660       +2     
  Lines                                    108613   109752    +1139     
========================================================================
+ Hits                                     102103   103240    +1137     
- Misses                                     6510     6512       +2     
Flag Coverage Δ
cpu-python 94.06% <99.91%> (+0.06%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

hmgaudecker and others added 2 commits November 2, 2026 18:08
…20261103

# Conflicts:
#	tests/candidate_certificate/direct_flow.py
#	tests/candidate_certificate/sources.json
#	tests/ci/ci-workloads.json
@github-actions

github-actions Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

Benchmark comparison (main → HEAD)

Comparing c8430c80 (main) → 7b7d2980 (HEAD)

Benchmark Statistic before after Ratio Alert
ACA (reduced) warm solve + simulate (reuses compiled code) 43.816 s 102.016 s 2.33 ❌
cold solve + simulate (first run, includes compilation) 791.91 s 813.76 s 1.03
peak CPU mem 7.54 GB 6.82 GB 0.90
Mahler-Yum warm solve + simulate (reuses compiled code) 5.853 s 6.080 s 1.04
cold solve + simulate (first run, includes compilation) 18.37 s 19.22 s 1.05
peak CPU mem 1.69 GB 1.66 GB 0.98
peak GPU mem: automatic solve + simulate 1.61 GB 1.61 GB 1.00
peak GPU mem: load saved solution + simulate 1.89 GB 1.89 GB 1.00
peak GPU mem: solve + save all persistable artifacts 571 MB 571 MB 1.00
Precautionary Savings - Solve execution time 259.9 ms 84.6 ms 0.33
first call (including compilation) 1.55 s 1.30 s 0.84
peak CPU mem 1.44 GB 1.44 GB 1.00
Precautionary Savings - Simulate execution time 900.0 ms 943.3 ms 1.05
peak GPU mem 324 MB 324 MB 1.00
first call (including compilation) 2.91 s 3.40 s 1.17 ❌
peak CPU mem 1.59 GB 1.57 GB 0.99
Precautionary Savings - Solve & Simulate execution time 866.3 ms 684.3 ms 0.79
peak GPU mem 1.50 GB 1.51 GB 1.00
first call (including compilation) 4.07 s 4.29 s 1.05
peak CPU mem 1.54 GB 1.53 GB 0.99
Precautionary Savings - Solve & Simulate (irreg) execution time 997.5 ms 812.8 ms 0.81
peak GPU mem 2.20 GB 2.21 GB 1.00
first call (including compilation) 4.20 s 4.46 s 1.06
peak CPU mem 1.54 GB 1.53 GB 0.99
Iskhakov et al. (2017) - GridSearch Solve execution time 363.4 ms 182.5 ms 0.50
peak GPU mem 120 MB 120 MB 1.00
first call (including compilation) 2.81 s 2.35 s 0.83
peak CPU mem 1.57 GB 1.63 GB 1.04
Iskhakov et al. (2017) - DC-EGM Solve execution time 651.1 ms 841.9 ms 1.29 ❌
first call (including compilation) 6.13 s 6.67 s 1.09
peak CPU mem 1.85 GB 1.97 GB 1.06
Iskhakov et al. (2017) - GridSearch Simulate execution time 1.011 s 946.8 ms 0.94
peak GPU mem 892 MB 891 MB 1.00
first call (including compilation) 6.54 s 6.93 s 1.06
peak CPU mem 1.70 GB 1.77 GB 1.04
Iskhakov et al. (2017) - DC-EGM Simulate execution time 1.115 s 1.281 s 1.15 ❌
peak GPU mem 891 MB 891 MB 1.00
first call (including compilation) 6.84 s 9.13 s 1.33 ❌
peak CPU mem 1.92 GB 2.08 GB 1.08
Collective Household - Construct execution time 735.8 ms 1.508 s 2.05 ❌
peak CPU mem 1.40 GB 1.43 GB 1.02
Collective Household - Solve execution time 481.2 ms 5.278 s 10.97 ❌
first call (including compilation) 2.88 s 56.02 s 19.44 ❌
peak CPU mem 1.46 GB 4.60 GB 3.15 ❌
Collective Household - Simulate (1000) execution time 1.189 s 32.898 s 27.67 ❌
first call (including compilation) 14.59 s 512.56 s 35.13 ❌
peak CPU mem 1.48 GB 23.66 GB 15.98 ❌
Collective Household - Simulate (100000) execution time 1.220 s nan µs nan
peak GPU mem 189 MB nan MB nan
first call (including compilation) 15.33 s nan s nan
peak CPU mem 1.56 GB nan MB nan
Reference Chain - Solve (1) execution time 217.7 ms 37.7 ms 0.17
first call (including compilation) 1.76 s 1.53 s 0.87
peak CPU mem 1.12 GB 1.16 GB 1.04
Reference Chain - Solve (8) execution time 492.7 ms 249.8 ms 0.51
first call (including compilation) 5.80 s 5.37 s 0.93
peak CPU mem 1.23 GB 1.28 GB 1.05
SimulationDispatch ('dissolution', 'off') warm simulation time per period and regime (ms) 45.77 ms 80.61 ms 1.76 ❌
backend compile requests on second simulation call 0
SimulationDispatch ('dissolution', 'progress') warm simulation time per period and regime (ms) 44.76 ms 82.30 ms 1.84 ❌
backend compile requests on second simulation call 0
SimulationDispatch ('multi_regime', 'off') warm simulation time per period and regime (ms) 49.92 ms 54.25 ms 1.09
backend compile requests on second simulation call 0
SimulationDispatch ('multi_regime', 'progress') warm simulation time per period and regime (ms) 48.43 ms 53.04 ms 1.10
backend compile requests on second simulation call 0

hmgaudecker added a commit that referenced this pull request Nov 4, 2026

This branch has not been deployed

No deployments
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.

TauchenAR1Process.on_nodes: let the caller choose the transition rule

2 participants