✨ Add optional mortality to calc_capacity - #421
Conversation
There was a problem hiding this comment.
Pull request overview
This PR extends laser.core.utils.calc_capacity() with optional mortality inputs so capacity sizing can target a peak-living (slots-reusable) bound rather than a births-only bound, and adds tests + changelog documentation for the new API.
Changes:
- Added keyword-only
deathratesandmortality_safety_factorparameters tocalc_capacity()with validation and updated docstring semantics. - Implemented mortality crediting (attenuating growth by a configurable fraction of projected deaths) and an
initial_popfloor for net-shrinking projections. - Added a comprehensive test block covering backward compatibility, monotonicity w.r.t.
mortality_safety_factor, validation, and keyword-only enforcement; updatedCHANGELOG.rst.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
src/laser/core/utils.py |
Adds mortality-aware capacity sizing parameters, validation, and estimation logic in calc_capacity(). |
tests/test_utils.py |
Adds unit tests for the new mortality feature and API surface behavior. |
CHANGELOG.rst |
Documents the new calc_capacity() mortality capability and related semantics. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
8cff9b6 to
6a912f1
Compare
jonathanhhb
left a comment
There was a problem hiding this comment.
LGTM. Walked through the math and ran a small mutation-test pass against the 8 new tests — introduced 9 plausible bugs in calc_capacity (sign flips, end-of-sim vs peak-across-time, dropped validation, removed * keyword-only marker, typo lamda_d from birthrates instead of deathrates, etc.). 7 of 9 were killed by at least one test, with peak_living_exceeds_end_of_sim_for_fluctuating_rates uniquely catching the most important one (using end-of-sim cumulative sums instead of the peak). The hand-computed peak for that test lands at ≈1.245M, well inside the [1.20M, 1.30M] band.
One optional ask before merge — the two floors at initial_pop (the inner np.maximum(np.max(cumsum, axis=0), 0.0) capping peak_exponent at 0, and the outer np.maximum(estimates, initial_pop)) are belt-and-suspenders: removing either one in isolation still passes every test, including test_calc_capacity_net_shrinking_floors_at_initial_pop, because in every net-shrinking scenario the surviving floor produces the same answer. Not a correctness bug — both genuinely prevent under-allocation — but worth either deduplicating, or adding a one-line comment noting they're intentionally redundant so a future refactor doesn't quietly strip a safety layer.
jonathanhhb
left a comment
There was a problem hiding this comment.
See previous comment.
`calc_capacity()` gains keyword-only parameters `deathrates` and `mortality_safety_factor` to compute a peak-living bound instead of a cumulative-births bound. When mortality is provided, the function credits a configurable fraction of projected deaths against births, with the remainder held back as headroom. Per-node estimates floor at `initial_pop` for net-shrinking projections. Math mirrors `calc_capacity_cdr` from razer.
Fluctuating CBR/CDR rates can produce a population peak well above the
end-of-simulation value. The previous formula used the end-of-sim
cumulative exponent, which would under-allocate the LaserFrame for
scenarios with intermediate spikes.
Now computes max_t cumsum(λ_b - death_credit × λ_d), floored at 0, and
uses that as the exponent. Monotonic-growth scenarios (births dominate
every tick) remain unchanged because peak == end when cumulative is
monotonic.
Includes two regression tests (spike-then-decline and monotonic case)
and
an illustrative script with plot under
misc/calc_capacity_peak_vs_end.{py,png}.
Also fix typo: "death sum" → "death rate" in the docstring.
The inner and outer floors in calc_capacity serve different purposes. Updated comments to explain their distinct roles and why both are intentionally kept despite redundancy.
6a912f1 to
89da321
Compare
calc_capacity()gains keyword-only parametersdeathratesandmortality_safety_factorto compute a peak-living bound instead of a cumulative-births bound. When mortality is provided, the function creditsa configurable fraction of projected deaths against births, with the remainder held back as headroom. Per-node estimates floor at
initial_popfor net-shrinking projections. Math mirrors
calc_capacity_cdrfrom razer.