I have been testing a nested reserving/projection pattern using lifelib's annuallife/TradLife_A_EX1 as a concrete example.
Problem
For each outer valuation time t, the model creates one or more dynamic InnerProj[t, ...] ItemSpaces and runs a full runoff projection.
modelx currently retains the calculated Cells and dependency graph for every inner ItemSpace, even though the outer model ultimately uses only a small number of scalar summary results.
For a nested reserving model this can make memory consumption grow with the accumulated outer × inner projections.
Proposed model structure
Rather than trying to infer which inner Cells should be retained, the proposal is to make the lifetime boundary explicit in the model structure.
The dynamic ItemSpace declares that its working cache is ephemeral:
_formula = lambda t0, risk=0, shock=0: {
"cache_lifetime": "ephemeral"
}
A child Space contains the results that should survive:
InnerProj[t0, risk, shock] ephemeral working region
├── runoff / cashflow / PV Cells scratch
└── Results persistent interface
└── pv_net_cf() retained result
In TradLife_A_EX1, for example, the outer formula changes from:
base_pv = InnerProj[t].pv_net_cf(t)
to:
base_pv = InnerProj[t].Results.pv_net_cf()
Results.pv_net_cf() can be zero-argument because the dynamic ItemSpace already carries t0, risk, and shock.
Proposed cache semantics
For an ephemeral ItemSpace:
- Normal inner Cells are cached normally while the nested projection is running.
- The
Results child Space defines the explicitly retained interface.
- Scratch remains available until all declared scalar
Results Cells for that ItemSpace have been materialized, so multiple results reuse the same inner calculation.
- Once the result set is complete, modelx retains the
Results values and removes the remaining calculated inner cache.
- Dependency paths through removed scratch values are summarized so normal modelx invalidation/recalculation still works.
- If an ordinary scratch Cell has an external consumer, release fails rather than silently treating it as another result.
- If not all declared
Results Cells are requested, the cache is conservatively retained.
I have been testing a nested reserving/projection pattern using lifelib's
annuallife/TradLife_A_EX1as a concrete example.Problem
For each outer valuation time
t, the model creates one or more dynamicInnerProj[t, ...]ItemSpaces and runs a full runoff projection.modelx currently retains the calculated Cells and dependency graph for every inner ItemSpace, even though the outer model ultimately uses only a small number of scalar summary results.
For a nested reserving model this can make memory consumption grow with the accumulated outer × inner projections.
Proposed model structure
Rather than trying to infer which inner Cells should be retained, the proposal is to make the lifetime boundary explicit in the model structure.
The dynamic ItemSpace declares that its working cache is ephemeral:
A child Space contains the results that should survive:
In
TradLife_A_EX1, for example, the outer formula changes from:to:
Results.pv_net_cf()can be zero-argument because the dynamic ItemSpace already carriest0,risk, andshock.Proposed cache semantics
For an ephemeral ItemSpace:
Resultschild Space defines the explicitly retained interface.ResultsCells for that ItemSpace have been materialized, so multiple results reuse the same inner calculation.Resultsvalues and removes the remaining calculated inner cache.ResultsCells are requested, the cache is conservatively retained.