1717action-grid argmax value of the continuous action, subject to a post-read
1818feasibility check (in-support, finite, positive, within the intrinsic budget).
1919
20- The gate exists because the stored policy is the **solve-phase** optimum of
21- one conditional problem, and a linear read is faithful only where the row
22- carries the coordinates and branch topology it interpolates over. Kept on the
23- grid-argmax path:
20+ Regimes with discrete actions publish one conditional row per discrete-action
21+ combination — value and policy per branch, on that branch's own endogenous
22+ grid. Simulation then *re-decides* the branch at the subject's state: each
23+ branch's conditional value is interpolated at that branch's own resources
24+ (discrete-only constraints mask infeasible branches to `-inf`), the feasible
25+ branch of highest interpolated value wins, and only the winner's policy is
26+ read. The value read uses the cubic Hermite interpolant with the
27+ `marginal_utility` row as exact node slopes — the same convention the solve
28+ uses to publish values — so the ranking the re-decision sees is the ranking
29+ the solve convention implies.
30+
31+ The gate exists because the stored rows are the **solve-phase** optimum of
32+ one conditional problem each, and a read is faithful only where the rows
33+ carry the coordinates and branch topology they are interpolated over. Kept on
34+ the grid-argmax path:
2435- regimes with any `Phased` declaration — a phase-variant utility, budget,
2536 transition, or state domain (not only `H`, e.g. naive present bias) makes
2637 the stored policy solve the wrong simulate-phase FOC or puts the policy
2738 rows on the wrong coordinates;
28- - regimes with discrete actions — the branch is chosen from grid-restricted
29- Q values, and a branch whose refined conditional optimum lies between
30- action-grid nodes can lose that comparison yet win after continuous
31- refinement, so the refined policy could be paired with the wrong branch;
39+ - regimes whose upper-envelope backend does not certify every crossing —
40+ only MSS inserts the exact segment crossing by construction; FUES decides
41+ segment identity by a slope-threshold heuristic (no labels from the
42+ kernel), so its row can silently bridge a missed switch, and RFC/LTM leave
43+ switches between retained nodes;
3244- regimes with a passive continuous state — each row is the envelope policy
3345 conditional on one passive node, so blending rows across a passive-dimension
3446 branch switch would read an action from neither branch;
3951 the per-node solve publishes one point per exogenous asset node, not a
4052 crossing-complete row, so interpolating across nodes would mix branches;
4153- regimes with EV1 taste shocks, whose realized draws perturb the decision.
42- Publishing conditional values and re-deciding the branch at the simulated
43- state lifts the discrete-action, passive, process, and asset-row exclusions
44- (the tracked follow-up).
54+ Publishing per-passive-node / per-process-node conditional values and
55+ re-deciding across those axes the way the discrete-action axis already is
56+ lifts the passive, process, and asset-row exclusions (the tracked follow-up).
4557
4658Unlike the rolling `EGMCarry` (the cross-period continuation channel, overwritten
4759each period), this is saved for every solved period and travels to `simulate`
48- alongside the value-function arrays. Its `endog_grid` rows are shared with the
49- period's carry; only the `policy` row is additional state.
60+ alongside the value-function arrays. Its rows are shared with the period's
61+ carry; only the `policy` row is additional state.
5062"""
5163
5264from collections .abc import Sequence
@@ -64,17 +76,18 @@ class EGMSimPolicy:
6476
6577 Leading axes match the regime's combo layout (discrete states, then passive
6678 states, then discrete actions, as in `EGMCarry`); the trailing axis is the
67- static refined-grid length. Both rows are NaN-padded in lockstep in the tail.
79+ static refined-grid length. All rows are NaN-padded in lockstep in the tail.
6880 """
6981
7082 endog_grid : FloatND
7183 """Endogenous grid in resources space, NaN-padded in the tail.
7284
7385 Shared with the period's `EGMCarry.endog_grid`; weakly ascending per row.
74- Under a crossing-inserting upper-envelope backend (`fues`, `mss`) the
75- envelope-switch abscissae are duplicated with one-sided policy copies —
76- the topology the off-grid read requires; RFC/LTM rows leave switches
77- between retained nodes and do not qualify for the read.
86+ Under MSS the envelope-switch abscissae are duplicated with one-sided
87+ policy copies — the topology the off-grid read requires; FUES rows are
88+ not certified crossing-complete (segment identity is a slope-threshold
89+ heuristic) and RFC/LTM rows leave switches between retained nodes, so
90+ neither qualifies for the read.
7891 """
7992
8093 policy : FloatND
@@ -89,6 +102,15 @@ class EGMSimPolicy:
89102 the branch at the subject's state.
90103 """
91104
105+ marginal_utility : FloatND
106+ """Marginal utility at `endog_grid` (NaN on padding slots).
107+
108+ Shared with the period's `EGMCarry.marginal_utility`: the value row's
109+ exact node slope by the envelope theorem. Simulation passes it as the
110+ slope row of the cubic Hermite value read, matching the interpolation
111+ convention the solve publishes values under.
112+ """
113+
92114 row_discrete_state_names : tuple [StateName , ...] = ()
93115 """Names of the leading discrete-state row axes, in axis order."""
94116
@@ -100,7 +122,7 @@ class EGMSimPolicy:
100122 """Names of the discrete-action row axes, after the passive states."""
101123
102124
103- _EGM_SIM_POLICY_ARRAY_FIELDS = ("endog_grid" , "policy" , "value" )
125+ _EGM_SIM_POLICY_ARRAY_FIELDS = ("endog_grid" , "policy" , "value" , "marginal_utility" )
104126_EGM_SIM_POLICY_STATIC_FIELDS = (
105127 "row_discrete_state_names" ,
106128 "row_passive_state_names" ,
0 commit comments