Diseases are the cornerstone of almost any Starsim analysis. In this tutorial, you’ll learn how to work with diseases in Starsim, starting with simple modifications and building up to creating your own custom disease models.
By the end of this tutorial, you’ll understand how to: - Modify parameters of existing diseases - Run simulations with multiple diseases - Create your own custom disease from scratch
Step 1: Modifying disease parameters
The easiest way to customize a disease is by changing its parameters. Much like sims or networks, a Disease can be customized by passing in a pars dictionary containing parameters. Let’s start with a simple SIR model and see how different parameters affect the simulation:
Notice how we modified several key parameters: - dur_inf=10: How long people stay infectious (10 years) - beta=0.2: Transmission probability per contact - init_prev=0.4: Starting with 40% of the population infected - p_death=0.2: 20% of infected people die
We already saw that this model creates results that are stored in sim.results.sir. The results can also be directly accessed via sir.results.
For more detail on any of the diseases that are in the Starsim library of diseases, please refer to the docstrings and source code of the disease files.
Step 2: Simulating multiple diseases
You can add multiple diseases to the same simulation, like so. Here we are making use of a “connector”. A connector is a module in Starsim that tells you how two things relate to one another - in this case, how HIV modifies a person’s transmissibility and susceptibility to SIS and vice versa. Unlike dieases, networks, interventions, etc., connectors don’t have any pre-specified location in the sim. Instead, they can be placed wherever they make the most sense (for example, a connector that mediated how two networks behaved might be placed at the beginning or end of the list of networks; for diseases, it might be placed at the beginning or end of the list of diseases).
import starsim as ssimport starsim.library as sslclass simple_hiv_sis(ss.Module):""" Simple connector whereby rel_sus to SIS doubles if CD4 count is <200"""def__init__(self, pars=None, label='HIV-SIS', **kwargs):super().__init__()self.define_pars( rel_trans_hiv =2, rel_trans_aids =5, rel_sus_hiv =2, rel_sus_aids =5, )self.update_pars(pars, **kwargs)returndef step(self):""" Specify how HIV increases SIS rel_sus and rel_trans """ sis =self.sim.people.sis hiv =self.sim.people.hiv p =self.pars sis.rel_sus[hiv.cd4 <500] = p.rel_sus_hiv sis.rel_sus[hiv.cd4 <200] = p.rel_sus_aids sis.rel_trans[hiv.cd4 <500] = p.rel_trans_hiv sis.rel_trans[hiv.cd4 <200] = p.rel_trans_aidsreturn# Make HIVhiv = ssl.diseases.HIV( beta = {'mf': [0.0008, 0.0004]}, # Specify transmissibility over the MF network init_prev =0.05,)# Make SISsis = ss.SIS( beta = {'mf': [0.05, 0.025]}, # Specify transmissibility over the MF network init_prev =0.025,)# Make the sim, including a connector between HIV and SIS:n_agents =5_000sim = ss.Sim(n_agents=n_agents, networks='mf', diseases=[simple_hiv_sis(), hiv, sis])sim.run()sim.plot('hiv')sim.plot('sis')
You can see how the two diseases interact - HIV creates a vulnerable population that’s more susceptible to SIS infection.
Step 3: Creating your own disease model
Now for the fun part - creating your own disease from scratch! Rather than starting from a blank page, the easiest approach is usually to inherit from one of the templates in diseases.py and change only what’s different.
Let’s turn the SIR model into an SIRS model, where immunity wanes: recovered agents eventually become susceptible again. This is what lets a disease settle into endemic transmission rather than burning through the population once and disappearing.
import numpy as npimport starsim as ssclass SIRS(ss.SIR):def__init__(self, pars=None, *args, **kwargs):super().__init__()self.define_pars( dur_imm = ss.lognorm_ex(mean=ss.years(10)), # How long immunity lasts after recovery )self.update_pars(pars, **kwargs)# SIR states are added automatically; here we add the time of losing immunityself.define_states( ss.FloatArr('ti_susceptible', label='Time of becoming susceptible again'), )returndef set_progression(self, uids):""" Schedule recovery and death as usual, then schedule the waning of immunity """super().set_progression(uids) recovering = uids[np.isfinite(self.ti_recovered[uids])] # Agents who are going to die have ti_recovered = NaNself.ti_susceptible[recovering] =self.ti_recovered[recovering] +self.pars.dur_imm.rvs(recovering)returndef step_state(self):""" Do the usual SIR transitions, then return agents whose immunity has waned to susceptible """super().step_state() waning = (self.recovered & (self.ti_susceptible <=self.ti)).uidsself.recovered[waning] =Falseself.susceptible[waning] =Truereturn
The new class includes the following main changes:
In __init__ we added the extra parameter (dur_imm) and state (ti_susceptible) that the model needs. We don’t need to redefine the SIR states, since we inherit them.
ss.SIR.set_prognoses() does two things: it calls set_infection() to make the agents infectious, then set_progression() to schedule what happens to them next. Waning immunity is part of the second, so that’s the only one we extend - we call super() to do the usual scheduling, then sample a waning time for everyone who is going to recover. Agents who are going to die instead have ti_recovered set to NaN, which is how we filter them out.
step_state() likewise calls super() and adds only the recovered → susceptible transition.
Unlike the SIR model, the SIRS model sustains transmission indefinitely, because the pool of susceptible agents is continually replenished as immunity wanes.
The other classic extension is to add an “exposed” state, for agents who are infected but not yet infectious. That one is built into Starsim as ss.SEIR, and it’s made in much the same way as SIRS above - except that it overrides set_infection() rather than set_progression(), so that agents enter exposed at the moment of transmission and infectious only after the latent period dur_exp has passed:
Note that an exposed agent doesn’t transmit, because transmission uses infectious, which in ss.SEIR is the I compartment alone. But an exposed agent does have the infection, so n_infected and prevalence count both E and I: in ss.SEIR, infected is derived as exposed | infectious. See Infected vs. infectious for why the two names come apart.
Exercises
Parameter exploration: Try different values of dur_imm in the SIRS model - how does it affect the epidemic curve?
SEIRS model: Apply the same waning immunity to ss.SEIR to make an SEIRS model.
Multi-strain model: Can you create a model with two strains of the same disease that provide partial cross-immunity?