Starsim v3.0 to v3.1 migration guide
Overview
This guide describes the breaking changes introduced in Starsim v3.1.0 and the steps required to migrate code from Starsim v3.0.x. It was mostly written by an LLM, and is mostly written for other LLMs.
Most of the changes are in the Pregnancy module and maternal networks. If your code does not use ss.Pregnancy, ss.MaternalNet, ss.PrenatalNet, or ss.PostnatalNet, you may only need to check the general changes in sections 1-3.
Note: If you are an LLM reading this, perform each of the following migrations one at a time and confirm with the user after each step.
General changes
1. People.(module_name) now returns the module, not a state container
Previously, people.sir returned a proxy object that exposed only module states (e.g. people.sir.infected). Now it returns a direct reference to the module itself, so all attributes – including dynamically computed @property values – are accessible.
What to change: In most cases, no change is needed – people.sir.infected still works. However, if your code relied on people.sir being a state-only namespace, be aware that it is now the full module object.
# v3.0 -- worked for states, but not for properties
people.sir.infected # OK
people.sir.infectious # AttributeError
# v3.1 -- both work
people.sir.infected # OK (state)
people.sir.infectious # OK (property)2. Network.prenatal and Network.postnatal attributes removed
These boolean attributes have been removed. Type checking is used instead.
What to change: Replace attribute checks with isinstance() checks.
# v3.0
for nw in sim.networks.values():
if nw.prenatal:
...
if nw.postnatal:
...
# v3.1
for nw in sim.networks.values():
if isinstance(nw, ss.PrenatalNet):
...
if isinstance(nw, ss.PostnatalNet):
...3. MaternalNet.add_pairs() argument names and signature changed
The arguments mother_inds and unborn_inds have been renamed to mother_uids and unborn_uids. The dur, start, and end arguments have been removed – edge lifespans are now managed by the Pregnancy module.
What to change: Update argument names and remove dur/start/end arguments.
# v3.0
net.add_pairs(mother_inds=mothers, unborn_inds=babies, dur=dur_preg, start=ti)
# v3.1
net.add_pairs(mother_uids=mothers, unborn_uids=babies)4. PostnatalNet is now a DynamicNetwork with a dur parameter
PostnatalNet is no longer a subclass of MaternalNet. It is now a DynamicNetwork that accepts an optional dur parameter for the postnatal period. Its add_pairs() method now takes mother_uids and infant_uids (not unborn_uids).
What to change: Update how you construct and interact with PostnatalNet.
# v3.0
postnatal = ss.PostnatalNet()
# Pairs were added by Pregnancy with dur/start/end
# v3.1
postnatal = ss.PostnatalNet(dur=ss.lognorm_ex(mean=ss.years(0.5), std=ss.years(0.5)))
# Pairs are automatically added at delivery by Pregnancy5. New BreastfeedingNet network
A new ss.BreastfeedingNet is available for modeling breastfeeding transmission. It automatically tracks breastfeeding status from the Pregnancy module. This is additive – no migration is required – but if you were previously using PostnatalNet to model breastfeeding transmission, consider switching.
# v3.1
sim = ss.Sim(
demographics=ss.Pregnancy(),
networks=[ss.PrenatalNet(), ss.BreastfeedingNet()],
diseases=ss.SIR(),
)Pregnancy module changes
6. fecund is now a computed property, not a BoolState
Previously fecund was a BoolState that was manually set to True/False. It is now a read-only @property that dynamically computes fecundity based on age and sex.
What to change: Remove any code that sets fecund directly.
# v3.0
pregnancy.fecund[uids] = False # Manually mark as infecund
pregnancy.fecund[uids] = True # Manually mark as fecund
# v3.1
# fecund is now read-only, computed from age, sex, and min_age/max_age
# Use pregnancy.infertile to mark agents as unable to conceive
pregnancy.infertile[uids] = True7. postpartum state removed; breastfeeding states added
The postpartum BoolState and associated dur_postpartum / ti_postpartum states have been removed. Breastfeeding is now tracked separately.
What to change: Replace postpartum references with breastfeeding or use PostnatalNet with a dur parameter.
# v3.0
pregnancy.postpartum # BoolState
pregnancy.dur_postpartum # FloatArr
pregnancy.ti_postpartum # FloatArr
# v3.1
pregnancy.breastfeeding # BoolState (mothers currently breastfeeding)
pregnancy.dur_breastfeed # FloatArr (duration of breastfeeding)
pregnancy.ti_stop_breastfeed # FloatArr (time breastfeeding stops)
pregnancy.breastfed # BoolState (newborns who were breastfed)8. child_uid state removed
The child_uid FloatArr has been removed. Unborn children are now found dynamically via people.parent.
What to change: Use pregnancy.find_unborn_children(parent_uids) instead.
# v3.0
child = pregnancy.child_uid[mother_uid]
# v3.1
children = pregnancy.find_unborn_children(mother_uids)9. make_p_fertility() renamed to make_p_conceive()
The method and associated distribution have been renamed.
What to change: Rename method calls and references.
# v3.0
p = pregnancy.make_p_fertility(eligible_uids)
pregnancy.pars.p_fertility # Bernoulli distribution
# v3.1
p = pregnancy.make_p_conceive(eligible_uids)
pregnancy._p_conceive # Bernoulli distribution (now private)10. p_neonatal_death replaced by p_survive_maternal_death
The semantics have been inverted and the name clarified. Despite its name, p_neonatal_death in v3.0 was only applied when a mother dies during pregnancy – it was the probability that the neonate also dies conditional on maternal death. It was never a general neonatal mortality parameter.
In v3.1, p_survive_maternal_death makes this conditional relationship explicit: it is the probability that an unborn child survives the mother’s death (default 0, meaning unborn children do not survive maternal death).
Note: Neither v3.0 nor v3.1 includes a built-in parameter for general neonatal mortality (e.g. from prematurity or birth complications). To model that, override the process_newborns() hook in a Pregnancy subclass.
What to change: Rename the parameter and invert the probability value.
# v3.0
pregnancy = ss.Pregnancy(p_neonatal_death=ss.bernoulli(0.8)) # 80% chance neonate dies given maternal death
# v3.1
pregnancy = ss.Pregnancy(p_survive_maternal_death=ss.bernoulli(0.2)) # 20% chance unborn survives maternal death11. dur_pregnancy is now a distribution, not a fixed duration
Previously, dur_pregnancy was a fixed value (default ss.years(0.75)). It is now an ss.choice distribution that draws from gestational weeks 32-42, and duration is assigned per-agent.
What to change: If you were relying on a fixed pregnancy duration or accessing self.pars.dur_pregnancy.years, update accordingly.
# v3.0
pregnancy = ss.Pregnancy(dur_pregnancy=ss.years(0.75))
# Internally: self.pars.dur_pregnancy.years was a single float
# v3.1
# Default draws from weeks 32-42; to override with a fixed duration:
pregnancy = ss.Pregnancy(dur_pregnancy=ss.choice(a=ss.weeks(np.arange(38, 43))))
# Per-agent durations are stored in: pregnancy.dur_pregnancy[uids] (a FloatArr)12. set_prognoses() removed; logic split across new methods
The set_prognoses() method has been removed. Its functionality is now split across make_pregnancies(), process_delivery(), and set_breastfeeding().
What to change: If you had a subclass that overrode set_prognoses(), move the logic to the appropriate new method.
# v3.0
class MyPregnancy(ss.Pregnancy):
def set_prognoses(self, uids):
super().set_prognoses(uids)
# Custom logic for new pregnancies
# v3.1
class MyPregnancy(ss.Pregnancy):
def make_pregnancies(self, uids):
embryo_counts = super().make_pregnancies(uids)
# Custom logic for new pregnancies
return embryo_counts13. do_step() and update_states() removed; step() restructured
The step() method no longer delegates to do_step() and update_states(). The burn-in logic has also been moved from step() to init_post(). The new step() calls a sequence of smaller, overridable methods.
What to change: If you had a subclass that overrode do_step() or update_states(), restructure to override the new methods.
# v3.0 step flow
step() -> do_step() -> update_states() + make_pregnancies() + make_embryos()
# v3.1 step flow
init_post() # Burn-in happens here now
step():
updates_pre() # Set base states, check breastfeeding
progress_pregnancies() # Advance gestational clock
process_delivery() # Handle births
set_rel_sus() # Update susceptibility
select_conceivers() # Find who conceives
make_pregnancies() # Set pregnancy states
make_embryos() # Create embryo agents
update_maternal_deaths()# Handle maternal deathsUse the _post_delivery() hook for custom post-delivery logic:
# v3.1
class MyPregnancy(ss.Pregnancy):
def _post_delivery(self, mother_uids, newborn_uids):
# Custom post-delivery logic here
pass14. make_embryos() return value changed
make_embryos() now returns a tuple (conceive_uids_with_repeats, new_uids) instead of just new_uids. The first element accounts for multiple embryos per pregnancy (e.g. twins).
What to change: Update any code that captures the return value.
# v3.0
new_uids = pregnancy.make_embryos(conceive_uids)
# v3.1
conceive_uids_with_repeats, new_uids = pregnancy.make_embryos(conceive_uids, embryo_counts)15. make_pregnancies() renamed to select_conceivers() for selection; make_pregnancies() now sets pregnancy states
The old make_pregnancies() both selected who conceives and set prognoses. These are now separate: select_conceivers() returns UIDs of women who will conceive, and make_pregnancies() sets pregnancy states and durations for those UIDs.
What to change: If you overrode make_pregnancies() in a subclass, split the logic.
# v3.0
class MyPregnancy(ss.Pregnancy):
def make_pregnancies(self):
# Custom selection + prognosis logic
# v3.1
class MyPregnancy(ss.Pregnancy):
def select_conceivers(self, uids=None):
# Custom selection logic
return conceive_uids
def make_pregnancies(self, uids):
# Custom prognosis logic
embryo_counts = super().make_pregnancies(uids)
return embryo_counts16. New parameters added to Pregnancy
The following new parameters are available via PregnancyPars. No migration is required, but you may want to use them:
| Parameter | Default | Description |
|---|---|---|
p_infertile |
ss.bernoulli(p=0) |
Primary infertility probability |
dur_breastfeed |
ss.lognorm_ex(mean=ss.years(0.75), std=ss.years(0.5)) |
Breastfeeding duration |
p_breastfeed |
ss.bernoulli(p=1) |
Probability of breastfeeding |
embryos_per_pregnancy |
ss.choice(a=[1,2], p=[1.0, 0.0]) |
Number of embryos (twins support) |
rr_ptb |
ss.normal(loc=1, scale=0.1) |
Baseline relative risk of pre-term birth |
rr_ptb_age |
Age-dependent array | Relative risk of pre-term birth by age |
p_survive_maternal_death |
ss.bernoulli(0) |
Probability unborn survives mother’s death |
trimesters |
[ss.weeks(13), ss.weeks(26)] |
Trimester boundaries |
17. New results added to Pregnancy
The following results are now available. No migration is required.
| Result | Description |
|---|---|
maternal_deaths |
Number of maternal deaths per timestep |
mmr |
Maternal mortality rate (per 100,000 births) |
tfr |
Total fertility rate |
n_fecund |
Number of fecund women |
n_fertile |
Number of fertile women |
n_susceptible |
Number of susceptible (non-pregnant fertile) women |
Age-specific fertility rates are available via pregnancy.asfr (a 2D array, not a standard Result).