Related
Issue: [[20260122-solutions-attached-to-periods-vs-separate-list|CDC #10: solutions attached to periods vs separate list]]
Parent spec: [[spec_0.1h-periods-nests|spec 0.1h — periods and nests]]
Connector spec: [[spec_0.1lA-connector-unification|spec 0.1lA — connector unification]] (interleaved nest representation)
Discount stage: [[spec_0.1hA-discount-stage|spec 0.1hA — discount stage]]
Syntax rules: [[05-periods-models|0.4 Periods and models]]
Theory: [[periods-nests-theory|Composition as twister (foundation)]]
Spec 0.1hB — Solutions attached to periods (lifecycle enrichment)¶
1. Decision¶
Solutions are attached to the period that produced them, not stored in a separate parallel list. A period progresses through a lifecycle of increasing concreteness:
Each step enriches the same object. "Solve" is another transformation in the pipeline, not the creation of a separate artifact.
2. Problem with separate lists¶
The current spec_0.1h pattern uses parallel lists:
This scatters related information: solutions[h] is the solution for periods[h], but the alignment is implicit. Combined with twisters as a third list, there are three parallel sequences to keep in sync — a source of off-by-one and misalignment bugs.
With connectors now external and interleaved ([[spec_0.1lA-connector-unification|spec 0.1lA]]), and solutions attached to periods (this spec), the nest simplifies to a single interleaved sequence:
Each P_h carries its own lifecycle state — definition, methods, calibration, and (once solved) solution objects. No parallel lists, no alignment ambiguity.
3. Period lifecycle¶
A period object progresses through these stages:
| Lifecycle stage | What is attached | Pipeline step |
|---|---|---|
| parsed | Symbolic stage objects with equations and symbols | parse_stage() |
| methodized | Method schemes (VFI/EGM/etc.) on each stage | methodize() |
| configured | Settings values (grid sizes, tolerances) | configure() |
| calibrated | Parameter values (β, ρ, R, ...) | calibrate() |
| solved | Value functions, policy functions, grids | solver |
| simulated | Population distributions, moments | forward simulation |
Each step produces a new (or enriched) period object. The period's status indicates how far through the pipeline it has progressed.
3.1 Status tracking¶
Or equivalently, the presence of specific attributes:
period.stages # always present (parsed)
period.methods # present after methodize
period.settings # present after configure
period.calibration # present after calibrate
period.solution # present after solve
period.simulation # present after simulate
The exact mechanism (status field vs attribute presence) is an implementation detail. The key invariant is: a period carries all information about itself at any point in the pipeline.
3.2 Solution object¶
The period.solution dict contains the solver outputs for each stage in the period:
period.solution = {
"port_stage": {
"V": <interpolant>, # value function V(k)
"policy": <interpolant>, # policy ς(k)
"grid": <array>, # k grid
},
"cons_stage": {
"V": <interpolant>, # value function V(m)
"policy": <interpolant>, # policy c(m)
"dV": <interpolant>, # marginal value dV(m) (if EGM)
"grid": <array>, # m grid
},
"discount_stage": {
"V": <interpolant>, # V_disc(x) = β · V_cont(x)
},
}
4. Nest representation (updated)¶
Combining [[spec_0.1lA-connector-unification|spec 0.1lA]] (interleaved connectors) with this spec (solutions on periods), the nest is:
# The nest is an interleaved sequence — no parallel lists
nest = [P_0, c_01, P_1, c_12, P_2, ..., c_{H-1,H}, P_H]
Where:
- P_h is a period object carrying its full lifecycle state (definition through solution)
- c_{h,h+1} is a between-period connector (:=: field isomorphism)
- P_0 is terminal, P_H is initial (backward-indexed)
4.1 Accretive build + solve¶
The accretive loop from [[spec_0.1h-periods-nests|spec 0.1h]] now operates on a single growing sequence:
nest = []
For h = 0, 1, ..., H:
1. Build P_h (parse → methodize → configure → calibrate)
2. Get continuation:
- h=0: terminal condition
- h>0: pull back from P_{h-1}.solution via connector
3. Solve P_h → attaches P_h.solution
4. Append connector + P_h to nest
After the loop, nest is a complete interleaved sequence of solved periods and connectors.
4.2 Re-solving¶
To re-solve with different parameters (e.g. a new β):
# Re-calibrate and re-solve P_h (does not affect other periods' definitions)
P_h = calibrate(P_h, new_params) # returns enriched period (status → calibrated)
P_h = solve(P_h, continuation) # returns enriched period (status → solved)
# P_h in the nest is updated in place (or replaced)
The period object is the single source of truth — there is no separate solutions list to update.
5. Backward compatibility¶
Existing code that uses nest["solutions"][h] can be adapted with a thin accessor:
# Compatibility shim (if needed during transition)
def get_solution(nest, h):
"""Extract solution for period h from interleaved nest."""
period = get_period(nest, h)
return period.solution
6. Acceptance criteria¶
- [ ] Period object supports
.solutionattribute (attached by solver) - [ ] Period object supports
.statusor equivalent lifecycle indicator - [ ] Nest is a single interleaved sequence (no separate
solutionslist) - [ ] Accretive build+solve loop works with enriched period objects
- [ ] [[20260122-solutions-attached-to-periods-vs-separate-list|CDC #10]] marked resolved
- [ ] Existing
explore/scripts updated (or shim provided)