sim
The sim (ss.Sim)
This is what a user runs. It exists to gather the people, modules, and settings of a model in one object; to choose how to run it (the solver); and to hold the results. It works the same way for every kind of model.
Used with: agents and compartments; declared and coded modules
Classes
| Name | Description |
|---|---|
| Sim | The sim: a model, how to run it, and its results |
Sim
sim.Sim(
*models,
label=None,
people=None,
demographics=None,
connectors=None,
networks=None,
diseases=None,
interventions=None,
analyzers=None,
modules=None,
data=None,
pars=None,
copy_modules=True,
**kwargs,
)The sim: a model, how to run it, and its results
Every Starsim model, whatever its kind, is run with a sim. It gathers the people and the modules (ss.Disease, ss.Network, ss.Intervention, ss.Demographics, and others), runs them step by step (see ss.loop.Loop), or all at once in the compiled kernels when nothing needs to happen between the steps, and holds the results.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| model | Module / list / dict |
the model: a module or a list of modules, which are sorted by kind as for modules; transitions, alone or in a list (or in diseases=), are one disease, named from its states (e.g. sim.sir). Alternatively, the sim’s settings as an ss.SimPars or a dict, which can include any of the other arguments |
|
| pars | dict / SimPars |
the values of the model’s parameters by name, e.g. pars=dict(beta=0.1) or pars=fit.pars (see ss.par). As in Starsim 3, it can also hold settings of the sim (e.g. pars=dict(start=2000, dur=10, n_agents=1000, beta=0.1)): a name is a setting if no parameter of the model has it, and a keyword given directly wins over the same setting here. A whole ss.SimPars is taken as if given in place of the model. A name that is neither raises a KeyError when the sim is initialized |
None |
| label | str |
a name for the sim, shown in plots and printouts | None |
| people | People | the people to use (by default, the sim makes them) | None |
| demographics | str / Demographics / list / bool |
births, deaths, and migration: declared (ss.birth(), ss.death(), which run under every solver), or modules (e.g. ss.Births(), ss.Pregnancy()), or a list; True for births and deaths at default rates |
None |
| connectors | str / Connector / list |
as above, for connectors | None |
| networks | str / Network / list |
as above, for networks | None |
| interventions | str / Intervention / list |
as above, for interventions | None |
| diseases | str / Disease / list |
as above, for diseases | None |
| analyzers | str / Analyzer / list |
as above, for analyzers | None |
| modules | Module / list |
modules of any kind, sorted among demographics, diseases, etc.; a module of your own that is none of these (an ss.Module, or a function of the sim, which becomes its step()) is kept in sim.custom |
None |
| solver | str / Solver |
how a declared model is run: ‘auto’ (the default: chosen from the model; sim.solver_reason says why), ‘ode’ (deterministic and fastest), ‘binomial’ (stochastic compartments, a chain binomial), ‘gillespie’ (stochastic and exact, event by event), ‘tau’ (tau-leaping, a faster approximation to Gillespie), or ‘abm’ (agents, for detail about individuals). A solver can take options, e.g. ss.solvers.ode(integrator=, substeps=, dwells=) (dwells='exact' keeps each dwell time as it is, rather than as a chain of stages), ss.solvers.binomial(substeps=), or ss.solvers.abm(step=) (step='events' for agents in continuous time). A declared module can have its own way of running as agents, ss.Disease(..., solver=ss.solvers.abm(step='wholestep')), which the sim’s does not override. sim.solver is the one that runs, e.g. isinstance(sim.solver, ss.solvers.abm) |
|
| branching | bool |
run the declared model as a branching process, under any solver: infection takes no one from the uninfected and gives no one back, so the susceptible side stays as it would with no infection. This is useful for the early growth or extinction of an outbreak, and for the size of chains of transmission; as agents, only the infected are made, so a large population costs little. See sim.branching for the size and outcome of each run, and sim.run(until=) to stop one |
|
| unit | str |
the unit of bare numbers in declared transitions, e.g. ‘day’; if nothing in the model has a unit, time is in timesteps | |
| approximations | str |
what to do when a solver has to approximate part of the model (e.g. a dwell time run as stages): ‘note’ (the default) says so in one line (kept in sim.notes, and printed unless verbose=0); ‘allow’ keeps the note without printing it; and ‘error’ raises an error, for when the results must be exact |
|
| stratify | dict |
stratify every module by properties of people, e.g. dict(age=[0, 15, 65], risk={'low': 0.8, 'high': 0.2}), as sim.stratify() takes them |
|
| data | df |
data to compare the results with: a dataframe (or dict, or the name of a file) with a column “time” and columns named “module.result”, e.g. “hiv.infection”; it is plotted with the results, and is what sim.fit(), sim.log_density(), and sim.gradient() fit to |
None |
| copy_modules | bool |
by default, the sim runs a copy of each module it is given, so the same modules can be given to several sims (read the ones that ran from the sim, e.g. sim.sir); if False, it runs the modules themselves, which saves a copy (and lets you read them directly after the run), but then the sim can’t be run again or copied with sim.new(), and the modules can’t be given to another sim |
True |
| n_agents | int / float |
the number of agents, if the model runs as agents (default 10,000); more agents give less noisy results but run slower. Not for a compartmental run, which has no agents (give total_pop=) | |
| total_pop | int / float |
the number of people (default: one per agent); as agents, each stands for total_pop/n_agents of them (sim.pop_scale), and the results that count people are scaled up by it, so a large population can be modeled with fewer agents |
|
| rescale | bool / dict |
dynamic rescaling of agents, as in Covasim (default False): each agent starts standing for one person, and the number grows to sim.pop_scale as the agents are infected, with some made naive again each time. This keeps the detail of a small outbreak in a large population without an agent per person. True, or a dict of its options, dict(threshold=0.05, factor=1.2) (the defaults): the fraction of agents who are not naive that triggers rescaling, and the least factor by which the scale grows each time |
|
| start | float / str / date |
when the sim starts: a date (e.g. ‘2020-01-01’) or a year (a number from 1800 on, e.g. 2000), which give it a calendar; any other number (or a duration) is a time in the sim’s unit from 0 (default: 2000 for a model in years, else 0). With a calendar, every bare number given as a time (stop=, when=, the times of ss.schedule and ss.timeseries) is a year; without one, a time in the sim’s unit from 0. A duration given as a time (when=ss.days(10), until=, the start= of a module) is that long after the start with a calendar, else a time from 0 (day 10; the same when the start is 0) | |
| stop | float / str / date / dur |
when the sim ends, read as the start is: a date, a bare number (a year, or with no calendar a time in the sim’s unit from 0), or a duration from 0 (e.g. stop=ss.days(100), with no start; ss.years(2030) in a sim of calendar years is the year 2030); with no start, a year starts the sim in 2000, unless unit= is other than years (unit=‘day’, stop=3650 is 3,650 days from 0; default: a run of 100 days in days, else 50 of the sim’s unit) | |
| dur | float / dur |
the length of the run from its start, e.g. start=2000, dur=10 (a bare number is in years in a sim of calendar years, as a bare stop is, else in the sim’s unit) or start='2020-01-01', dur=ss.days(90); any two of the start, stop, and length define the third (stop=2035, dur=10 starts in 2025), and all three must agree |
|
| dt | float / dur |
the timestep, in the sim’s unit (default 1.0); a smaller step is more accurate but slower (sim.check_dt() shows how much the results depend on it) |
|
| rand_seed | int |
the random seed, from which every module’s random numbers are drawn; the same seed gives the same results, and different seeds give different runs of a stochastic model | |
| even_draws | bool |
for agents, space each call’s random numbers evenly, so that close to the expected number of agents move each time (default False). This makes runs much less variable (about 3 to 10 times less in the final size of a simple SIR), which helps when comparing scenarios with few runs; but outbreaks die out less often than they should, and the mean of a result can shift (e.g. the peak of an SIR on a network, by about 10%). Agents only, and needs ss.options.crn |
|
| keep_times | bool |
for a declared disease run as agents, keep the time each agent entered every state (e.g. ti_S) and the history of every transition (e.g. sir.infection.n_times), for your own code to read after the run, e.g. in an analyzer. By default, only those the model needs are kept, which saves memory and time (default: ss.options.keep_times, False) |
|
| aging | bool |
whether agents age (by default, they age if there are births or deaths) | |
| record_people | bool |
whether to record the results about people (alive, female, died, emigrated, etc.) every step (default True); False saves a little time in a sim that doesn’t need them, but some modules, e.g. demographics, rely on results.alive |
|
| step_order | str |
the order in which the modules step: ‘type’ (default) at the place of each kind of module in the loop (see ss.Loop), or ‘given’ in the order they were given (the model and modules= first, then the modules given by kind, e.g. diseases=), before the deaths and results of the step; ‘given’ is for when one module must act on what another did earlier in the same step. Analyzers, which read the step’s results, still step last |
|
| require_kernels | bool |
raise an error (ss.CapabilityError) if a compartmental sim can’t be run all at once in the compiled kernels, and would instead run step by step in plain Python, which can be 100 times slower; useful for making sure a fit or a large batch of runs stays fast (sim.describe() says why a sim can’t use them) |
|
| group_results | list |
the results to keep by group (e.g. by age), by name, e.g. ['I', 'cases'] or ['sir.I']; the others keep only their totals, which saves memory in a sim with many groups (default: all, at every few steps if every step would take more than about 100 MB) |
|
| verbose | int / float |
how much to print: 0 nothing, 1 the solver, notes, and progress (default), 2 everything (e.g. each step); a fraction is as 1, with progress at each such fraction of the run (e.g. 0.1 for every 10%) | |
| kwargs | dict |
the settings above, by name (they are kept in sim.pars, an ss.SimPars) |
Modules can be given by kind (diseases=, networks=, etc.), all together with modules=, or both: they are sorted by kind and merged.
Examples
S, I, R = ss.states(3)
sim = ss.Sim(ss.Disease(S >> I @ 0.3*I, I >> R @ ss.days(10), init_prev=0.01)) # A declared model; here run as an ODE, or give solver= (e.g. solver='abm')
sim = ss.Sim(diseases='sir', networks='random') # Agents on a network; equivalent to ss.demo()
sim = ss.Sim(diseases=ss.SIR(), networks=ss.RandomNet()) # The same, with objects instead of strings
sim = ss.Sim(diseases=['sir', ss.SIS()], networks=['random', 'degree']) # Lists, mixing strings and objects
sim = ss.Sim(modules=[ss.SIR(), ss.RandomNet()]) # Modules of several kinds, sorted by kindAttributes
| Name | Description |
|---|---|
| branching | For a run as a branching process (ss.Sim(branching=True)): what came of it, as an sc.obj (by module, if several |
| current_scale | The number of people each agent represents on the current timestep; varies over time if pars.rescale=True |
| label | The sim’s label, which names it in plots and printouts; kept as sim.pars.label |
| modules | Every module of the sim, in the order they are initialized |
| pop_scale | The number of people each agent stands for (with dynamic rescaling, at the end of the run): total_pop/n_agents as agents, and 1 in a compartmental run, which has no agents; set by total_pop= and n_agents= |
| started | Whether the run has begun, i.e. at least part of its first step has been taken (False before the first step, and again after sim.restart()) |
Methods
| Name | Description |
|---|---|
| add_module | Add a module to a sim that has already been initialized, and possibly run part-way |
| advance | Move the time to the step about to be taken, from the time point it starts at, where whatever it does is dated and recorded: for the sim, and the modules on its timeline (see ss.loop.Loop) |
| all_networks | The networks, by name, including those that a compartmental run replaces with contact rates (which are not run themselves) |
| check_dt | Rerun the sim with a smaller timestep, and report how much each result changes |
| check_model_keywords | A keyword that is not a setting of the sim but names a parameter of the model (e.g. ss.Sim(sir, beta=0.1)) is an error that says to give it in pars= |
| describe | The resolved model: every state, transition, and default, with each guess marked; what runs where (and if a compartmental run is taken a step at a time, why); and what each rate and dwell time is per step |
| diagram | The state-transition diagram of every declared disease, with how they act on each other (e.g. a modifier of one disease with a condition on another’s state, as a dotted arrow) |
| equations | The ODE systems of every declared disease, with symbolic parameter names, and how the diseases act on each other, as text that shows itself (ss.display.Text; print() it in a script); see ss.Disease.equations() |
| finalize | Compute final results |
| finalize_results | Scale the results and remove any “unused” results |
| finish_step | Finish the simulation timestep |
| fit | Fit every parameter that has bounds to every result that has data |
| get_module | Retrieve a single module |
| get_modules | Retrieve modules from the Sim |
| gradient | The derivative of results, or of a function of the run sim, with respect to parameters, at their current values |
| init | Perform all initializations for the sim |
| init_people | Initialize people within the sim |
| init_results | Make the results of the sim itself: the people’s (see ss.People.init_results()), and those of dynamic rescaling |
| lock_pars | Lock the sim and module parameters, so setting a parameter that doesn’t exist raises an error |
| log_density | The log of the posterior density of the fitted parameters, as a function in JAX of their values, with its gradient by reverse mode; see ss.gradients.LogDensity |
| new | A new, uninitialized sim: a copy of this one as it was when it was initialized, optionally with other values |
| optimize | Allocate spending between programs to reach a goal, keeping the total fixed; or find the least total that reaches a target |
| plot | Plot the results |
| products | The products of the interventions, each once; a list, not an ndict like the other kinds of module |
| profile | Profile the performance of the simulation: how long each call of the loop, each line, or each function takes |
| rescale | Dynamically rescale the population; only used if pars.rescale=True |
| restart | Take a sim back to its start, optionally with new values of its parameters or a new seed, without running it |
| result_scale | The factor to scale results by: a number, or an array with one value per timestep if pars.rescale=True |
| run | Run the model – the main method for running a simulation. |
| run_one_step | Run a single step of the sim (every module’s part of it), e.g. to look at the sim between steps while debugging |
| save | Save to disk as a gzipped pickle: the sim itself, with its results, read back by ss.load() |
| sensitivity | Which parameters matter: partial rank correlations of each parameter with bounds and each result, over samples within the bounds; see ss.fitting.sensitivity() |
| set_diagnostics | Store detailed diagnostics information in the sim |
| shrink | Make the sim much smaller, for keeping many sims in memory or saving them, by removing the people and other large objects (in place by default) |
| start_step | Start the step: only print progress, since everything else happens in the modules |
| stratify | Stratify every module in the sim by properties of people, e.g. sim.stratify(age=[0, 15, 65]) |
| summarize | A table of each result over the whole run, by its summarize_by, as msim.summarize() gives for a scenario run once |
| to_df | The results as a dataframe, with a column for each (e.g. sir_prevalence); see ss.Results.to_df() for the arguments |
| to_json | Export the results and the sim’s parameters as JSON |
| to_synthetic_data | Data drawn from the results of the run, as if observed: e.g. to check that a fit finds values that are known |
| what_modifies | Everything that modifies a quantity: each ss.modify(), and the efficacy of each product, as text that shows itself (ss.display.Text; print() it in a script) |
add_module
sim.Sim.add_module(module, key=None)Add a module to a sim that has already been initialized, and possibly run part-way
Usually modules are supplied when the sim is created; use this method to add one later, e.g. an intervention after running the sim to a certain date. It should be called between timesteps (not from within a module’s step()). The module’s results cover the whole sim, but are only filled in from the current timestep onwards.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| module | ss.Module |
the module to add | required |
| key | str |
the type of module, e.g. “interventions” (if None, determine from the module’s class) | None |
Examples
sim = ss.Sim(diseases='sis', networks='random', start='2000-01-01') # The default run of 100 days
sim.run(until='2000-02-01')
sim.add_module(ss.modify(sim.diseases.sis.pars.beta, multiply=0.5, when=ss.schedule(start='2000-03-01'))) # Not a delivery of a new product, which changes the model's structure
sim.run()advance
sim.Sim.advance()Move the time to the step about to be taken, from the time point it starts at, where whatever it does is dated and recorded: for the sim, and the modules on its timeline (see ss.loop.Loop)
all_networks
sim.Sim.all_networks()The networks, by name, including those that a compartmental run replaces with contact rates (which are not run themselves)
check_dt
sim.Sim.check_dt(factor=2)Rerun the sim with a smaller timestep, and report how much each result changes
In a run that takes whole steps this covers everything that depends on the size of the step: turning rates into probabilities, the delay of a step between modules, and the order of the modules. In a stochastic run the change also includes the difference between two runs.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| factor | int |
how many times smaller to make the timestep | 2 |
Returns
| Name | Type | Description |
|---|---|---|
The report, as text that shows itself (ss.display.Text), with the table behind it (a dataframe) as .table |
Examples
S, I, R = ss.states(3)
sir = ss.Disease(infection=S >> I @ 0.3*I, recovery=ss.Transition(I, R, dur=4), init_prev=0.01)
ss.Sim(sir, solver='binomial', unit='day', stop=100).check_dt()check_model_keywords
sim.Sim.check_model_keywords(given, kwargs)A keyword that is not a setting of the sim but names a parameter of the model (e.g. ss.Sim(sir, beta=0.1)) is an error that says to give it in pars=
describe
sim.Sim.describe()The resolved model: every state, transition, and default, with each guess marked; what runs where (and if a compartmental run is taken a step at a time, why); and what each rate and dwell time is per step
It is text that shows itself (ss.display.Text), as equations() is: in color at an interactive prompt in a terminal, as tables in a notebook, and as plain text with print(sim.describe()) (e.g. in a script) or str().
diagram
sim.Sim.diagram(kind='plot', **kwargs)The state-transition diagram of every declared disease, with how they act on each other (e.g. a modifier of one disease with a condition on another’s state, as a dotted arrow)
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| kind | str |
‘plot’ to draw it as a Matplotlib figure, or ‘text’ for the text of a Mermaid flowchart (see ss.Disease.diagram()) |
'plot' |
| kwargs | dict |
for a plot, passed to ss.return_fig() (e.g. filename=, do_show=) |
Example
sim = ss.Sim(diseases=[ss.SIR(), ss.SIS()])
sim.diagram()equations
sim.Sim.equations()The ODE systems of every declared disease, with symbolic parameter names, and how the diseases act on each other, as text that shows itself (ss.display.Text; print() it in a script); see ss.Disease.equations()
finalize
sim.Sim.finalize()Compute final results
finalize_results
sim.Sim.finalize_results()Scale the results and remove any “unused” results
finish_step
sim.Sim.finish_step()Finish the simulation timestep
fit
sim.Sim.fit(
pars=None,
data=None,
weights=None,
likelihood=None,
loss=None,
penalty=None,
method=None,
rand_seed=None,
verbose=None,
**kwargs,
)Fit every parameter that has bounds to every result that has data
Fits every parameter that has bounds (see ss.par) to every result that has data (see ss.Sim(data=...)). The original sim is not changed. The fitting itself is in ss.fitting, which is imported when it is first used.
A parameter can be one of a declared model, of a module written as code (ssl.HIV(beta=ss.par(...)), also a value in a dict parameter, fitted as e.g. cin_fn.k, or a setting kept as an attribute), a modifier’s amount (ss.modify(..., multiply=ss.par(...))), or a keyword argument of a function given as a module (def set_testing(sim, symp_prob=ss.par(0.1, bounds=[0.01, 0.5])), for a value set in code); one named alike in several modules is fitted with its module’s name, e.g. hpv16.rel_beta. A parameter whose value is an array, with bounds for each element (ss.par(np.array([...]), bounds=[lows, highs])), has each element fitted, as knots[0], …, except those whose bounds are equal. A data column names a result, as sim.results has it: e.g. prevalence, sir.prevalence, an analyzer’s by_age.cancers_0_15, a result nested deeper (hpv.genotype.cancers), or a column of a result with columns (sis.infection.alpha).
The fit uses either likelihoods or a loss: every data series has a likelihood (declared on its outcome, or given here), and the fit maximizes their sum; or none has, and it minimizes the loss. loss= is used in place of likelihoods declared on the outcomes (with a note), and likelihood=False switches them off. Some series with a likelihood and some without is an error, as is loss= with likelihood=.
By default, the fit finds the best values (a point estimate), by the method that suits the sim, and prints which and why:
- ‘l-bfgs-b’ for a deterministic sim, with gradients from JAX (see
ss.gradients), or by finite differences for one that JAX cannot run - ‘nelder-mead’ for one whose likelihood or priors JAX does not cover (e.g. a likelihood given as a function), or with one or two fitted parameters (where it is faster than gradients)
- ‘optuna’ for a stochastic sim, including an agent-based one
Other methods for a best fit are ‘powell’ (no gradients), ‘slsqp’ (with gradients, like ‘l-bfgs-b’), and ‘if2’ (iterated filtering, for the sims that ‘pmcmc’ fits: the maximum-likelihood values, from passes of a particle filter in which each particle’s values are perturbed by a shrinking random walk; see ss.fitting.Swarm). For the uncertainty in the values (a posterior), ask for one by name:
- ‘nuts’ (the No-U-Turn sampler), for a deterministic sim with gradients whose data series all have a likelihood; each chain starts from a draw near one of the best optima found from a scan over the bounds
- ‘pmcmc’ (particle MCMC), for a sim whose data series all have a likelihood: a random-walk Metropolis sampler on the likelihood estimated by a particle filter for a stochastic compartmental sim (see
ss.fitting.ParticleFilter), or on the exact likelihood for a deterministic sim (e.g. one JAX cannot run); each chain starts from a different one of the best points of a scan, and a run is kept with each draw,fit.trajectories, whichfit.project()continues - ‘abc-smc’ (approximate Bayesian computation by sequential Monte Carlo), for any stochastic sim, with no likelihood needed; its accuracy depends on the tolerance it reaches, and it gives no posterior for a deterministic sim
The priors are the parameters’ prior=, or uniform over their bounds.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| pars | list |
the names of the parameters to fit (default: all that have bounds) | None |
| data | df |
the data to fit to (default: the sim’s data) | None |
| weights | dict |
the relative weight of each data series (default: equal) | None |
| likelihood | ss.Dist/dict/bool |
the likelihood of every data series (e.g. ss.poisson()), or of each by its name (e.g. dict(prevalence=ss.normal(std=0.02))), in place of any declared on its outcome; or False, for none (the declared ones are not used, and the fit minimizes the loss). The fit then maximizes their sum (plus the log of the priors of the parameters that have one, for the methods that find a best fit: the most probable values) |
None |
| loss | str / func |
the measure of the mismatch to minimize, with no likelihoods (or in place of those declared on the outcomes): ‘nae’ (normalized absolute error, the default), ‘rmse’, or a function of the sim; any other name is an error | None |
| penalty | func |
a function of the parameter values (a dict by name, an array whole) returning a number added to what a best fit minimizes, e.g. a smoothness penalty, lambda pars: 10*np.sum(np.diff(pars['knots'], 2)**2) |
None |
| method | str/ss.fitting.method/list |
how to fit (see below); a method with settings is ss.fitting.method(), e.g. ss.fitting.method('optuna', n_trials=200) (the settings of each method, such as the number of runs per point for a stochastic sim, are listed there); a list of methods runs each from the best fit of the one before (a stage that does not converge stops the list, but not one whose convergence is not assessed, such as ABC-SMC’s; see ss.fitting.fit_stages()) |
None |
| rand_seed | int |
the random seed for the fitting method (default: the sim’s, so a fit gives the same answer each time) | None |
| verbose | bool |
whether to print progress | None |
Returns
| Name | Type | Description |
|---|---|---|
An ss.fitting.Fit, with the calibrated sim as fit.sim and the best-fit values as fit.pars |
Examples
beta = ss.par(0.1, bounds=[0.01, 0.5])
S, I, R = ss.states(3)
sir = ss.Disease(S >> I @ beta * I, I >> R @ ss.days(10), init_prev=0.01)
sim = ss.Sim(sir, data='cases.csv', start='2020-01-01', stop='2020-06-01')
fit = sim.fit() # A best fit
post = sim.fit(method='nuts', likelihood=ss.poisson()) # A posterior, with a likelihood for each data series
more = sim.fit(method=ss.fitting.method('nuts', draws=1000, chains=2), likelihood=ss.poisson()) # With settings of the method
fit.sim.plot()get_module
sim.Sim.get_module(query, die=True, match_case=False)Retrieve a single module
This method is guaranteed to return a single module if it succeeds, so can be relied on in places where this is expected to retrieve a specific module from the Sim (and retrieving no modules or multiple modules would be unexpected). Use Sim.get_modules() to retrieve all matching modules.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| query | type / str |
Query input supported by get_modules (either a Type e.g., ss.SIR) or a string that will match module names. |
required |
| die | bool |
if True (default), raises an exception unless exactly 1 module is found; else return None if no modules found | True |
| match_case | bool |
if query is a string, whether to match case (default False) |
False |
Returns
| Name | Type | Description |
|---|---|---|
| An ss.Module instance |
get_modules
sim.Sim.get_modules(query=None, match_case=False, as_dict=False)Retrieve modules from the Sim
Retrieve a module by name or by type. The query term can be the type of module to match e.g., ss.SIR, ss.Disease, ss.Intervention, or a string which is tested against module names, in which * matches anything (e.g. 'hiv*').
If the Sim is not initialized, this will search over any ss.Module instances contained in Sim.pars. Note that sim initialization might result in the creation of modules, therefore some modules may be retrieved after initialization but not before (e.g. diseases='sir' will not be registered as the module ss.SIR() until after initialization).
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| query | type / str |
The module type (e.g. ss.SIR) or a case-insensitive string (e.g. 'sir'); if None, return all modules |
None |
| match_case | bool |
if query is a string, whether to match case (default False) |
False |
| as_dict | bool |
only used if query=None, in which case return a dict rather than list of modules | False |
Returns
| Name | Type | Description |
|---|---|---|
A list of ss.Module instances. The list will be empty if no modules were found |
gradient
sim.Sim.gradient(results=None, pars=None)The derivative of results, or of a function of the run sim, with respect to parameters, at their current values
The ODE run is differentiated by JAX (see ss.gradients), so this needs JAX, a sim with solver='ode', and one that the kernels run from start to finish (see sim.describe()). The sim itself is not changed. A parameter with several values (a value for each level, ss.by(patch=[...]); an array; or a time series) has a derivative for each, named as sim.fit() names them (beta[a], beta[b], …).
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| results | str / list / func |
the results, by name ('I', or 'sir.I' where needed); or a function of the run sim that returns a number or an array (e.g. a cost); default: every result of the declared modules |
None |
| pars | str / list |
the parameters, by name (or one value of one, 'beta[a]'); default: every parameter that has bounds |
None |
Returns
| Name | Type | Description |
|---|---|---|
An sc.obj by parameter, then module, then result, of the derivative at each step; for a function, by parameter, of the derivative of what it returns |
Example
beta = ss.par(0.3, bounds=[0.1, 1])
S, I, R = ss.states(3)
sir = ss.Disease(S >> I @ beta * I, I >> R @ ss.days(8), init=dict(I=0.01), name='sir')
sim = ss.Sim(sir, unit='day', stop=100)
grad = sim.gradient('I')
grad.beta.sir.I # How the number infectious at each step changes with beta
sim.gradient(lambda sim: sim.results.sir.infection.sum()).beta # How the total infections change with itinit
sim.Sim.init(force=False, **kwargs)Perform all initializations for the sim
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| force | bool |
whether to overwrite sim attributes even if they already exist | False |
| kwargs | dict |
passed to ss.People() |
init_people
sim.Sim.init_people(**kwargs)Initialize people within the sim Sometimes the people are provided, in which case this just adds a few sim properties to them. Other time people are not provided and this method makes them.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| kwargs | dict |
passed to ss.People() |
init_results
sim.Sim.init_results()Make the results of the sim itself: the people’s (see ss.People.init_results()), and those of dynamic rescaling
lock_pars
sim.Sim.lock_pars(locked=True)Lock the sim and module parameters, so setting a parameter that doesn’t exist raises an error
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| locked | bool |
whether to lock the parameters | True |
log_density
sim.Sim.log_density(pars=None, likelihood=None)The log of the posterior density of the fitted parameters, as a function in JAX of their values, with its gradient by reverse mode; see ss.gradients.LogDensity
Needs JAX, a sim with solver='ode' that the kernels run whole, data, and a likelihood for every data series.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| pars | str / list |
the parameters, by name (default: every one with bounds); one with several values has a value for each, named as sim.fit() names them |
None |
| likelihood | dict |
the likelihood of each data series, if not declared on its outcome | None |
Example
ld = sim.log_density()
ld(ld.x0), ld.grad(ld.x0) # The value and its gradient at the current valuesnew
sim.Sim.new(pars=None, inplace=False, **kwargs)A new, uninitialized sim: a copy of this one as it was when it was initialized, optionally with other values
A sim that has not been initialized is copied as it is, with any changes made to it since it was made. Once a sim is initialized, its settings are those of its run: setting one (e.g. sim.pars.stop = 30 after sim.run()) raises, so give it here instead (sim.new(stop=30)). Other changes made to it then (e.g. to a module’s parameters) belong to that run only, and are not kept.
To run a finished sim again from the start, sim.run() is enough (and sim.restart(pars=...).run() with other values, which for a sim that is nothing but compartments is much faster than a new sim); sim.new() is for other settings, e.g. sim.new(solver='abm').run(). To copy a sim as it is, including its results, use sim.copy().
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| pars | dict |
values of model parameters by name, e.g. dict(beta=0.1), and of sim settings only if they were given as ss.par (e.g. ss.Sim(n_agents=ss.par(5000, bounds=[1000, 20000])), so they can be fitted) |
None |
| inplace | bool |
if True, this sim becomes the new one, rather than a new one being returned | False |
| kwargs | dict |
sim arguments to change, e.g. rand_seed=2, solver='abm', or interventions=...; None goes back to the default, e.g. n_agents=None for a compartmental run of an agent-based sim; also data=. A subclass’s own arguments (e.g. FPsim’s location=) are not taken again: make the subclass again for those. |
optimize
sim.Sim.optimize(
goal=None,
total=None,
start=None,
spend=None,
bounds=None,
target=None,
maxiter=50,
verbose=None,
**kwargs,
)Allocate spending between programs to reach a goal, keeping the total fixed; or find the least total that reaches a target
The spending of each program is a parameter of the model: one per program of an imported program book (sim.imported.programs), or any parameters, given by name with spend=. The search (SciPy’s SLSQP) is over the fractions of the total, and each point is a run (restarted, if the sim is nothing but compartments). With target=, the total is doubled until its best allocation reaches the target, then found by bisection between a total that reaches it and one that does not (to within 1%), assuming that more money never does worse. The sim itself is not changed. The search is in ss.fitting, which is imported when it is first used.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| goal | ss.fitting.maximize/ss.fitting.minimize |
what to make as large or as small as possible: a result (e.g. ss.fitting.maximize('all_vs', at=2030), or ss.fitting.minimize('hiv.new_infections')), or a function of the run sim |
None |
| total | float |
the total to allocate (default: the current total); with target=, the total the search starts from |
None |
| start | float / date |
when the new allocation takes effect (default: the start of the sim), read as any time is (see ss.connectors.to_time(): a bare number is a year if the sim has a calendar) |
None |
| spend | dict / list |
the spending parameters: names, or {program: name} (default: the imported programs) | None |
| bounds | tuple / dict |
the least and most a program can have, as amounts (default: 0 and the total); a dict gives them by program, e.g. dict(testing=(1e5, None)) (None for no limit), and a program not in it has the default |
None |
| target | float |
the value of the result to reach (at least this with ss.fitting.maximize(), at most with ss.fitting.minimize()) with the least total |
None |
| maxiter | int |
the most iterations of the search (of each total’s, with target=) |
50 |
| verbose | bool |
whether to print progress | None |
Returns an ss.fitting.Optimization.
Examples
sim = ss.import_from('hiv_framework.xlsx', format='atomica', databook='hiv_databook.xlsx', progbook='hiv_progbook.xlsx', programs_start=2020)
best = sim.optimize(ss.fitting.maximize('all_vs', at=2030), start=2020)
best.summarize()
least = sim.optimize(ss.fitting.maximize('all_vs', at=2030), start=2020, target=1.1*best.value) # The least money that reaches itplot
sim.Sim.plot(
key=None,
fig=None,
show_data=True,
show_skipped=False,
annualize=False,
show_interventions=True,
**kwargs,
)Plot the results
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| key | str / list |
the result to plot, e.g. 'sis.prevalence' (by default, all); if a list, exactly those |
None |
| fig | Figure |
an existing figure to plot into | None |
| style | str |
the plotting style to use | |
| show_data | bool |
plot the data, if the sim has any | True |
| show_skipped | bool |
also show the results that are left out by default (e.g. those that never change) | False |
| annualize | bool |
if True, resample results to annual values before plotting (each by its summarize_by; see ss.Result.resample()) |
False |
| show_interventions | bool / str |
mark when interventions and modifiers act: a dashed line at each round of a delivery, and at the start of routine delivery or of an ss.modify(); if 'fill', shade the span from their start to their stop instead (those on an event or condition are not marked) |
True |
| fig_kw | dict |
passed to sc.getrowscols(), then plt.subplots() and plt.figure() |
|
| plot_kw | dict |
passed to plt.plot() |
|
| data_kw | dict |
passed to plt.scatter(), for plotting the data |
|
| style_kw | dict |
passed to ss.style(), for controlling the detailed plotting style (default “starsim”; other options are “simple”, None, or any Matplotlib style) |
|
| **kwargs | dict |
known arguments (e.g. figsize, font) split between the above dicts; see ss.plot_args() for all valid options |
Examples
sim = ss.Sim(diseases='sis', networks='random').run()
# Basic usage
sim.plot()
# Plot a single result
sim.plot('sis.prevalence')
# Plot with a custom figure size, font, and style
sim.plot(figsize=(12,16), font='Raleway', style='fancy')products
sim.Sim.products()The products of the interventions, each once; a list, not an ndict like the other kinds of module
profile
sim.Sim.profile(kind='line', follow=None, plot=True, **kwargs)Profile the performance of the simulation: how long each call of the loop, each line, or each function takes
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| kind | str |
what to time: ‘loop’, how long each call of the loop takes (each module’s step, and so on), by running the sim itself a step at a time (then sim.loop.plot_cpu() and sim.loop.to_df()); ‘line’ (default), how long each line of the functions followed takes, with a line profiler (sc.profile()), on a copy of the sim; or ‘function’, how long every function the run calls takes, with a function profiler (sc.profile(kind='function')) |
'line' |
| follow | func / list |
for kind='line', the functions/methods to follow in detail (by default, those of the loop) |
None |
| plot | bool |
for kind='loop' and 'line', whether to plot time spent per module step |
True |
| **kwargs | dict |
passed to sc.profile() (for ‘line’) or sc.profile(kind='function') (for ‘function’, e.g. sort='selftime' or mintime=1e-3) |
Returns
| Name | Type | Description |
|---|---|---|
For ‘loop’, the loop (sim.loop, with to_df() and plot_cpu()); for ‘line’, an ss.debugtools.Profile; for ‘function’, an sc.cprofile |
Examples
import starsim as ss
net = ss.RandomNet()
sis = ss.SIS()
sim = ss.Sim(networks=net, diseases=sis)
sim.profile(kind='loop') # Which modules take the time
prof = sim.profile(follow=[net.add_pairs, sis.step]) # Which lines of these take it
prof = sim.profile(kind='function') # Which functions take itrescale
sim.Sim.rescale()Dynamically rescale the population; only used if pars.rescale=True
The scale factor starts at 1. Once the fraction of agents who are not naive (i.e. have ever been infected with any disease) exceeds the threshold (rescale=dict(threshold=)), the scale factor is increased by at least rescale=dict(factor=) (up to sim.pop_scale), and the same fraction of non-naive agents are made naive again (via disease.make_naive()), so that each agent now represents more people. Based on Covasim’s dynamic rescaling.
The people no agent stands for yet are counted as one number, results.unrepresented: pop_scale - 1 for each agent at the start, less the people the agents alive take on at each rescaling (from scale s to s', s' - s for each); the deaths and births of agents do not change it. So alive is scale people for each agent alive, plus these.
restart
sim.Sim.restart(pars=None, rand_seed=None)Take a sim back to its start, optionally with new values of its parameters or a new seed, without running it
sim.run() does this itself for a finished sim; restart() is for running it again with other values or another seed (sim.restart(pars=dict(beta=0.1), rand_seed=2).run()), for stepping it yourself from the start (sim.restart().run(until=2010)), or for a function that sets the values to try (as for ss.gradients.Density). The values given are kept for later runs (as sim.par_values), until changed again.
For a sim that is nothing but compartments (one that the kernels run from start to finish: see sim.describe()), this makes repeated runs many times faster; any other sim (or a change the compartments were not laid out for, such as a new distribution) is built again, as sim.new() would. ss.parallel(sim, n_runs=...) and sim.fit() use it where they can.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| pars | dict |
values of model parameters by name, e.g. dict(beta=0.1) |
None |
| rand_seed | int |
the seed for the next run | None |
Returns
| Name | Type | Description |
|---|---|---|
| The sim (itself, or itself built again) |
result_scale
sim.Sim.result_scale(module=None)The factor to scale results by: a number, or an array with one value per timestep if pars.rescale=True
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| module | ss.Module |
if provided, map the scale onto the module’s timevec (if different from the sim’s) | None |
run
sim.Sim.run(until=None, verbose=None, **kwargs)Run the model – the main method for running a simulation.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| until | date / str / float / dur / Expr / func |
run the steps up to and including the one that starts at this time (a duration is that long after the start, in a sim with a calendar); or a condition, checked at the end of each step, that stops and finalizes the run once it holds: an expression of the model (sir.I.n + sir.R.n >= 1000) or a function of the sim; the results after the stop are NaN, and sim.stopped is the last time point with results (the condition held at the end of its step) |
None |
| verbose | int |
how much to print: 0 nothing, 1 the solver, notes, and progress (default), 2 everything (e.g. each step) | None |
Returns
| Name | Type | Description |
|---|---|---|
The sim, with its results (e.g. ss.Sim(...).run().plot()) |
A sim that has finished is run again from the start, as it was before it was initialized. For a variant of it, give the changes first: sim.restart(pars=dict(beta=0.1), rand_seed=2).run() runs it again from the start with other values or another seed (for a sim that is nothing but compartments, without building it again, which makes repeated runs many times faster), and sim.new(solver='abm').run() builds a new sim with other settings. To run it many times, use ss.parallel(sim, n_runs=10); to time its parts, sim.profile(). To copy a sim as it is, including its results, use sim.copy().
Example
S, I, R = ss.states(3)
sir = ss.Disease(S >> I @ 0.3*I, ss.Transition(I, R, dur=8), init=dict(I=0.01))
sim = ss.Sim(sir, unit='day', stop=100)
peaks = [sim.restart(pars=dict(beta=beta)).run().results.sir.I.max() for beta in [0.2, 0.3, 0.4]]run_one_step
sim.Sim.run_one_step(verbose=None)Run a single step of the sim (every module’s part of it), e.g. to look at the sim between steps while debugging
sim.loop.run_one_step() instead runs one call of the loop, e.g. one module’s step().
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| verbose | bool |
how much the loop prints (not the sim) | None |
save
sim.Sim.save(filename=None, shrink=None, **kwargs)Save to disk as a gzipped pickle: the sim itself, with its results, read back by ss.load()
This is the checkpoint of a sim: saved before it has finished, it keeps its full state (it is not shrunk), so the loaded sim can continue the run. A pickle needs the same code (Starsim and any modules written as code) to be read. For the results as JSON, see sim.to_json().
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| filename | str |
the name or path of the file to save to (default: the sim’s label, or “starsim”, with “.sim”) | None |
| shrink | bool |
whether to shrink the sim before saving, which makes the file about 99% smaller (see sim.shrink()); by default, yes unless the sim is in the middle of a run |
None |
| kwargs | passed to sc.makefilepath() |
Returns
| Name | Type | Description |
|---|---|---|
| filename | str |
the validated absolute path to the saved file |
Examples
sim.save('my.sim')sensitivity
sim.Sim.sensitivity(**kwargs)Which parameters matter: partial rank correlations of each parameter with bounds and each result, over samples within the bounds; see ss.fitting.sensitivity()
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| kwargs | dict |
passed to ss.fitting.sensitivity() |
set_diagnostics
sim.Sim.set_diagnostics(rvs=True, states=True, detailed=False)Store detailed diagnostics information in the sim
Stores every random number produced during a sim run (by ss.Dist objects) and/or every state of every agent, at every timestep. This allows e.g. understanding exactly where two runs that should have been identical diverged. Results can be exported (in JSON format) and analyzed externally.
Although the states export could be used for analysis, users are encouraged to write an ss.Analyzer instead, as enabling diagnostics significantly degrades sim run time and memory usage.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| rvs | bool |
whether to enable diagnostics for random numbers/variates (default True) | True |
| states | bool |
as above, for people states (default True) | True |
| detailed | bool |
if true, store literally every random number and agent state (otherwise, use summary stats) (default False) | False |
Examples
# General settings -- use a small sim and few timesteps if possible
kw = dict(n_agents=100, start=0, stop=10, networks='random', diseases='sis')
# Simplest usage: store info and print states
sim = ss.Sim(**kw)
sim.set_diagnostics()
sim.run()
print(sim.diagnostics.states)
# Configure separately, with custom file export and saving all detail
sim = ss.Sim(**kw)
sim.set_diagnostics(rvs='diagnostics_rvs.json', states='diagnostics_states.json', detailed=True)
sim.run()
sim.diagnostics.export()shrink
sim.Sim.shrink(inplace=True, full=True, size_limit=1.0, base_size=30, die=True)Make the sim much smaller, for keeping many sims in memory or saving them, by removing the people and other large objects (in place by default)
The results are kept, so a shrunk sim can still be plotted and summarized. Each module is then checked: if it is still larger than size_limit*(base_size + n_timesteps) KB, not counting its results, it is most likely holding an array of its agents that it failed to let go of (e.g. a module of your own whose shrink() misses one).
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| inplace | bool |
whether to shrink this sim (default), or return a shrunk copy | True |
| full | bool |
whether to remove the people and the large parts of each module (otherwise, only the references that keep the sim from being freed from memory) | True |
| size_limit | float |
the size each module may have, in KB per timestep (None to skip the check) | 1.0 |
| base_size | float |
the size each module may have whatever the length of the sim, in timesteps’ worth of size_limit (for its parameters and the like) |
30 |
| die | bool |
whether a module that is too large raises an error (otherwise, a warning) | True |
Returns
| Name | Type | Description |
|---|---|---|
| The shrunk sim |
start_step
sim.Sim.start_step()Start the step: only print progress, since everything else happens in the modules
stratify
sim.Sim.stratify(**kwargs)Stratify every module in the sim by properties of people, e.g. sim.stratify(age=[0, 15, 65])
Takes the same arguments as ss.Disease.stratify(), and returns the sim.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| kwargs | dict |
each dimension and its levels (or ss.levels()), as for ss.Disease.stratify() |
summarize
sim.Sim.summarize(key=None, how=None)A table of each result over the whole run, by its summarize_by, as msim.summarize() gives for a scenario run once
By default, a flow (e.g. new infections) is totaled, a stock (e.g. the number infected) takes its final value, and a ratio (e.g. prevalence) is averaged; see ss.Result.summarize(). Results that are 0 throughout are kept, so the tables of different runs line up row for row.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| key | str |
if given, only this result (e.g. 'sir_infection' or 'sir.infection'): its summary, as a number (or the rows of its columns, for a 2D result) |
None |
| how | str / dict |
‘mean’, ‘median’, ‘sum’, ‘last’, ‘max’, ‘min’, or a function, to summarize every result that way; or a dict of these by result, named as for key (e.g. dict(sir_I='max') or {'sir.I': 'max'}), with the rest by their summarize_by |
None |
Returns
| Name | Type | Description |
|---|---|---|
A dataframe with a row for each result and one column, value; with key, that result’s summary |
Example
sim.summarize() # Every result
sim.summarize('sir_infection') # The total infections
sim.summarize('sir_I', how='max') # The peak number infectiousto_df
sim.Sim.to_df(**kwargs)The results as a dataframe, with a column for each (e.g. sir_prevalence); see ss.Results.to_df() for the arguments
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| kwargs | dict |
passed to ss.Results.to_df() |
to_json
sim.Sim.to_json(filename=None, keys=None, indent=2, **kwargs)Export the results and the sim’s parameters as JSON
This is a record of a run’s output, for reading elsewhere; it cannot be loaded back into a sim (see sim.save()).
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| filename | str |
the file to write to, if any | None |
| keys | str / list |
what to write: ‘pars’, ‘summary’, and/or ‘results’ (default all three) | None |
| indent | int |
the number of spaces to indent by, in the file | 2 |
| **kwargs | dict |
passed to sc.jsonify() |
Returns
| Name | Type | Description |
|---|---|---|
| The parameters, summary, and/or results as a dict (also written to the file, if one is given) |
Examples
json = sim.to_json() # Convert to a dict
sim.to_json('sim.json') # Write everything
sim.to_json('summary.json', keys='summary') # Just write the summaryto_synthetic_data
sim.Sim.to_synthetic_data(
results,
resample=None,
noise='poisson',
rand_seed=None,
)Data drawn from the results of the run, as if observed: e.g. to check that a fit finds values that are known
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| results | str / list |
the results, by name, e.g. ‘cases’ (or ‘sir.cases’ if more than one module has one of that name) | required |
| resample | str |
the period each value is over, e.g. ‘week’ for the total of a flow over each week | None |
| noise | str/ss.Dist |
‘poisson’ (the default), for counts drawn with each value as their mean; a likelihood as sim.fit() takes it, with each value as its mean, e.g. ss.nbinom(n=10) for counts with a variance of mean + mean²/10 (see ss.fitting.logpdf()); or None, for the values themselves |
'poisson' |
| rand_seed | int |
the random seed for the noise (default: the sim’s) | None |
Returns
| Name | Type | Description |
|---|---|---|
A dataframe with a column of times (date, or time in a sim without dates) and a column for each result, as ss.Sim(data=...) takes it |
Example
truth = ss.Sim(sir, pars=dict(beta=0.025), rand_seed=2, **pars).run()
data = truth.to_synthetic_data('cases', resample='week') # Weekly cases, with Poisson noise
data = truth.to_synthetic_data('cases', resample='week', noise=ss.nbinom(n=10)) # Overdispersed, as fitted with likelihood=ss.nbinom(n=10)
sim = ss.Sim(sir, data=data, **pars)what_modifies
sim.Sim.what_modifies(target)Everything that modifies a quantity: each ss.modify(), and the efficacy of each product, as text that shows itself (ss.display.Text; print() it in a script)
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| target | par / str |
the quantity, as for ss.modify(): e.g. sir.pars.beta, sir.rel_sus, or 'sir.pars.beta' |
required |
Functions
| Name | Description |
|---|---|
| demo | Create a simple demo simulation for Starsim |
demo
sim.demo(run=True, plot=True, summary=True, show=None, **kwargs)Create a simple demo simulation for Starsim
Defaults to using the SIR model with a random network, but these can be configured. On the network, β is per contact, so the demo’s SIR has β = 0.03 a day: with 10 contacts each, infectious for 10 days, an R0 of 3.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| run | bool |
whether to run the sim | True |
| summary | bool |
whether to print a summary of the results | True |
| plot | bool |
whether to plot the results | True |
| show | bool |
whether to show the plot (default: ss.options.show) |
None |
| kwargs | dict |
passed to ss.Sim() |
Examples
ss.demo() # Run, plot, and show results
ss.demo(diseases='sis', networks='degree') # Run with different defaults