Skip to content

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:

parsed → methodized → configured → calibrated → solved → simulated

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:

nest = {"periods": [], "twisters": [], "solutions": []}

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:

nest = [P_0, connector_1, P_1, connector_2, P_2, ..., P_H]

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

period.status    # one of: "parsed", "methodized", "configured", "calibrated", "solved", "simulated"

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 .solution attribute (attached by solver)
  • [ ] Period object supports .status or equivalent lifecycle indicator
  • [ ] Nest is a single interleaved sequence (no separate solutions list)
  • [ ] 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)