Skip to main content

sim

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 kind

Attributes

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, which fit.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 it
init
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 values
new
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 it
plot
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 it
rescale
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 infectious
to_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 summary
to_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