Skip to content

Commit 4f8ad99

Browse files
hmgaudeckerclaude
andcommitted
docs: continuous-outer inference design freeze (ADR + error budget)
PR 0 of the continuous-outer plan. Records, BEFORE any estimate is observed: the four governing decisions (paper/legacy_fortran split; central differences first, AD only as a cross-check; globally safeguarded outer search; solve and simulation share one continuous policy class), the scope boundaries and non-goals, the candidate bank's array-axis conventions, the finite collapse's tie-breaking semantics (keeper wins exact ties; earlier node beats later), and every release gate with reference thresholds that may later tighten but never loosen. The companion error-budget doc decomposes the central-difference error into its observable terms and freezes the derivative mesh-union protocol and the no-division-by-R simulation-variance convention. Base commit a1d9ca7 (feat/nested-nbegm-ez); the frozen finite baseline the ADR references landed with the candidate-bank commit. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PVSLmeMo6iWUTcNER7bvM6
1 parent b64f72b commit 4f8ad99

3 files changed

Lines changed: 181 additions & 0 deletions

File tree

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
# ADR: inference-grade continuous outer choice for `NNBEGM`
2+
3+
**Status:** accepted (design freeze) **Base commit:** `a1d9ca7` on
4+
`feat/nested-nbegm-ez`; work proceeds on `feat/continuous-outer`. **Scope:** this
5+
document freezes the decisions, scope boundaries, and release gates for the
6+
continuous-outer extension *before* any final estimate is observed, so the gates cannot
7+
drift toward whatever numbers later appear.
8+
9+
## Problem
10+
11+
`NNBEGM` selects its outer post-decision margin by a finite grid search, so every
12+
downstream object — simulated moments, their parameter Jacobian, and any standard error
13+
built on it — is piecewise constant in the parameters between grid reassignments. A
14+
finite-difference Jacobian of such a map measures grid artifacts, not derivatives, and
15+
the Mahler–Yum replication's inference layer correctly *refuses* to report on it. The
16+
extension makes the outer choice continuous (solve **and** simulation), then builds the
17+
numerical error budget that decides when a Jacobian, and only then an inference table,
18+
may be released.
19+
20+
## Governing decisions (non-negotiable)
21+
22+
1. **`paper` and `legacy_fortran` are separate configurations.** The canonical
23+
implementation follows the paper, appendix, equation map, and replication
24+
specification — not historical bugs. Historical-reproduction switches live in one
25+
manifest-visible object and can never leak into canonical estimation or standard
26+
errors.
27+
1. **The first inference implementation uses controlled central finite differences.**
28+
Automatic/implicit differentiation arrives later as a cross-check and performance
29+
layer, never as the sole evidence the Jacobian is correct. On AD/FD disagreement
30+
without a diagnosed central-difference failure, AD is rejected — not the finite
31+
difference.
32+
1. **The outer optimizer is globally safeguarded.** No global unimodality assumption:
33+
golden-section refinement runs only inside brackets identified on a global candidate
34+
mesh; the exact keeper is always evaluated separately; the best and second-best
35+
candidates are retained so tie margins are observable.
36+
1. **Solve and simulation use the same continuous policy class.** A continuous solve
37+
followed by grid-restricted simulation is not acceptable; simulation re-runs the same
38+
interpolant + safeguarded search off-grid.
39+
40+
## Scope boundaries
41+
42+
*First supported solver:* `NNBEGM(inner=NBEGM(...))` only. `NEGM` generalization is out
43+
of scope (candidate-specific endogenous grids need a separate design). *First supported
44+
structure:* one continuous liquid state, one inner consumption/saving action, one scalar
45+
continuous outer action, any already-supported discrete axes, an exact keeper branch,
46+
deterministic or analytically integrated fixed adjustment cost, no EV1 shock on the
47+
outer choice. *Non-goals for the first release:* multidimensional continuous
48+
optimization; globally differentiable policies; valid derivatives at active bounds,
49+
branch ties, or non-unique maxima; AD through simulation indicators; historical Fortran
50+
identity in canonical mode; standard errors merely because a numerical Jacobian has full
51+
rank.
52+
53+
## Array-axis conventions
54+
55+
The candidate bank stacks exact conditional inner solves along a **leading candidate
56+
axis** `C`:
57+
58+
```text
59+
outer_nodes (C,)
60+
V_arr (C, *V_shape) # V state order
61+
carry rows (C, *row_shape, P) # regime discrete states (V order),
62+
# passive continuous states,
63+
# discrete actions, then the
64+
# shared trailing grid axis P
65+
```
66+
67+
Carry rows follow the existing `EGMCarry` convention (leading regime discrete states in
68+
V state order, then passive continuous states, then discrete actions, shared trailing
69+
grid axis of static length). The keeper is **not** an entry in the bank: its outer
70+
action is state-dependent, so it stays a separate `KernelResult` and enters only at
71+
collapse.
72+
73+
## Collapse semantics (frozen, load-bearing)
74+
75+
The finite collapse must reproduce the incremental fold it replaces exactly, including
76+
tie-breaking:
77+
78+
- `V = max(V_keeper, max_j W_j)` via `jnp.fmax` — NaN-dead cells never poison a cell
79+
another candidate solves; a cell stays NaN only when every candidate is infeasible
80+
there.
81+
- Carry: pointwise winner per row entry with **strict** `>` against the running
82+
envelope, keeper first, then nodes in grid order. Hence the keeper wins exact ties,
83+
and an earlier node beats a later one. Any reimplementation that breaks this order
84+
breaks bit-identity on ties.
85+
86+
## Release gates (frozen before any estimate is observed)
87+
88+
No single threshold is proof of correctness; these are **joint necessary conditions**,
89+
and the reference numbers below may be tightened but never loosened after estimates
90+
exist.
91+
92+
*Solver gates:* zero unresolved outer intervals; zero simulation policy fallbacks; zero
93+
nonfinite reachable values; one further mesh/tolerance refinement moves every moment by
94+
`< 0.01 ×` its empirical SD and the effort policy by `< 1e-4`.
95+
96+
*Jacobian gates:* across accepted step / simulation-size / scramble / solver-tolerance
97+
designs — stable full column rank; every singular value moves `< 10 %`; no column moves
98+
`> 10 %` in moment-SD-normalized norm; non-negligible cells carry finite-difference
99+
signal `≥ 10 ×` their estimated simulation-noise SD; failing columns are marked
100+
unresolved, never zeroed.
101+
102+
*Inference-output gates:* recomputed per accepted design — CP-M SEs, one-step estimates,
103+
CI endpoints, AGS sensitivity; require SE relative range `< 5 %`, one-step and both
104+
CI-endpoint ranges `< 0.05 ×` the reference SE, AGS one-SD row relative-norm range
105+
`< 5 %`. AGS comparisons use `Λ · diag(σ̂_m)` (one-SD moment units), judged **rowwise
106+
per parameter**.
107+
108+
*Local-regularity gates:* refuse ordinary local-normal inference when the estimate is
109+
within five final step widths of a bound; the minimum singular value is numerically
110+
unresolved; material population mass lies within the numerical value-error bound of a
111+
keeper/adjuster tie or non-unique outer maximum; a targeted median has an unresolved
112+
atom; or the one-step estimate leaves the admissible region.
113+
114+
The full error-budget decomposition and its measurement plan live in
115+
[the outer-search error budget](outer_search_error_budget.md).
116+
117+
## Frozen finite baseline
118+
119+
The pre-refactor finite-`NNBEGM` behavior is frozen in
120+
`tests/data/n_nbegm_finite_baseline.npz`, captured at base commit `a1d9ca7` (x64, smooth
121+
two-asset toy, 3 periods, `outer_batch_size ∈ {0, 1, 4}`): every alive period's `V_arr`
122+
and all `EGMCarry` leaves, plus the public `Model.solve` output. The behavior-preserving
123+
candidate-bank refactor must reproduce it to `V` within `1e-12` and carries within
124+
`1e-11` (`tests/solution/test_n_nbegm_finite_baseline.py`).
125+
126+
## Implementation sequence
127+
128+
PR-numbered sequence (contract → candidate bank → interpolant/adaptive mesh → continuous
129+
carry → nested simulation policy → analytic fixed cost → Mahler wiring → random
130+
design/moment engine → parameterization/Jacobian → inference gate → custom JVP → legacy
131+
backend), with the candidate-bank refactor deliberately numerically neutral and gated on
132+
the frozen baseline above before any numerical-method change lands.
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Outer-search numerical error budget
2+
3+
Companion to the [continuous-outer ADR](adr_continuous_outer_inference.md). A central
4+
finite difference of the moment map decomposes as
5+
6+
```text
7+
Ĝ(h) − G = O(h²) parameter truncation
8+
+ O(ε_outer / h) outer search (mesh + bracket)
9+
+ O(ε_inner / h) inner NB-EGM solve
10+
+ O(ε_interp / h) outer interpolation
11+
+ O(ε_sim / h) finite simulation
12+
```
13+
14+
Every term must have an *observable* estimate before a Jacobian is accepted; "the steps
15+
agree with each other" alone is consistency, not accuracy — three steps can share a bias
16+
regime and agree while uniformly wrong.
17+
18+
| Error source | Measured by |
19+
| -------------------- | ------------------------------------------------------- |
20+
| parameter truncation | comparison of h, h/2, h/4 central differences |
21+
| outer search | tighter mesh and bracket tolerances |
22+
| inner NB-EGM | tighter savings-grid/envelope settings and Euler errors |
23+
| interpolation | exact midpoint validations (interpolant vs exact solve) |
24+
| simulation | independent scrambles and larger N |
25+
| branch ties | population mass near keeper/adjuster and local-max ties |
26+
| moment nonsmoothness | median atoms, empty groups, exact-equality mass |
27+
28+
All seven diagnostics are stored in the Jacobian manifest; inference refuses a manifest
29+
whose diagnostics are missing or whose configuration does not match the run being
30+
reported.
31+
32+
## Mesh freezing for derivatives
33+
34+
Adaptive node insertion is discrete, so the mesh must not differ arbitrarily between θ+h
35+
and θ−h. Derivative protocol: adapt at baseline θ and at pilot perturbations θ ±
36+
h_max·e_j; take the per-period union of all resulting nodes; sort, deduplicate, freeze;
37+
rerun baseline and every perturbation on the frozen mesh; verify the frozen mesh meets
38+
the interpolation-error criterion for every run; on any failure, augment the mesh and
39+
restart the entire derivative batch. The frozen nodes are stored in the Jacobian
40+
manifest.
41+
42+
## Simulation variance
43+
44+
The simulation allowance added to the empirical moment variance is the variance of **one
45+
production-sized simulated moment vector**, estimated from independent
46+
scrambles/replications; it is *not* divided by the number of auxiliary replications (the
47+
estimator's objective uses one simulation, not their average).

docs/myst.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ project:
7979
- file: development/setup.md
8080
- file: development/conventions.md
8181
- file: development/benchmarking.md
82+
- file: development/adr_continuous_outer_inference.md
83+
- file: development/outer_search_error_budget.md
8284
- file: credits.md
8385
title: Credits & Acknowledgments
8486
error_rules:

0 commit comments

Comments
 (0)