---
title: The Sim object
jupyter: python3
---


As noted on the Model Structure page, the `Sim` is the main object for _doing_ things with Starsim. Once you've created your modules for diseases and interventions, you will add them to the `Sim`, which will ensure that everything gets run in a sensible order, and that everything is nicely accessible afterwards.

On this page, we explain what happens when you create a `Sim`, and the particular order of steps taken when a `Sim` is run. We then provide a few common examples of how to create and run a `Sim` and when/why you might choose each one.

## Creating and initializing a Sim
When you first create a sim, e.g. with `sim = ss.Sim()`, the resulting `Sim` object will be mostly empty with a few pre-set defaults. Most of the initialization is done when `sim.init()` is called. This completes the initialization with the following steps:

* Validating the parameters;
* Adding a `Time` module to the sim (stored under `sim.t`), which looks after converting parameters between timesteps;
* Adding `People` to the `Sim`: if these are not supplied directly, Starsim will automatically add `People` with default age/sex structure;
* Adding all the parameters and results from each `Module` to the `Sim`, and adding the states from each `Module` to the `People`; 
* Initializing any distributions contained anywhere within the parameters or states.

A `Sim` is automatically initialized if `sim.run()` is called.

## Running a Sim
### Order of operations
An important function of the `Sim` class is that it controls the order in which the different steps of each module are executed. Starsim allows users lots of flexibility regarding the timesteps for individual modules. For example, a common usage pattern is that you may only want to update your demographic modules once a year, but you may want to update your disease modules once per month, week, or day. The `Sim` class stores a plan for updating each module in the right order and with the right frequency. This is stored within a specialized `Loop` class housed within the `Sim`. Users typically won't interact with this class directly, but it is worth knowing the order in which updates are made on each timestep.

1. `Modules` have a `start_step` method which is called first. This usually just advances random number generators forward. 
2. Any `Demographic` modules have their `step` method called
3. Any `Disease` modules have their `step_state` method called
4. Any `Connectors` have their `step` method called
5. Any `Networks` have their `step` method called
6. Any `Interventions` have their `step` method called
7. Any `Disease` modules have their `step` method called
8. The `People` class has its `step_die` method called: this updates any people who died on this time step. Deaths are handled by modules, not by `People` directly, but `step_die` method assembles all the causes of death across disease modules and demographic modules and reconciles them. This ensures that each agent's death is uniquely attributable to a cause.
9. `Results` are updated for the `People` and the `Modules`
10. Any `Analyzer` modules have their `step` method called
11. Calls are made to `finish_step` for the `Modules`, the `People`, and the `Sim`.

If you want to see more details, have a look at `sim.loop.to_df()` (after calling `sim.init()`) and it will show a dataframe of every step in the simulation. You can also use `sim.loop.plot()` to visualize each step.

Note that there are two separate update calls for diseases, `step_state` and then `step`. The `step_state` method happens _before_ transmission, and the `step` method typically handles transmission. The `step_state` method typically handles disease progression, as in this example from the SIR model:


```{python}
def step_state(self):
    """ Progress infectious -> recovered """
    recovered = (self.infected & (self.ti_recovered <= self.ti)).uids
    self.infected[recovered] = False
    self.susceptible[recovered] = True
    self.update_immunity()
    return
```

To illustrate the ordering of all these updates, let's consider an example of a woman infected with pre-symptomatic COVID who, in the course of a single busy day, started developing COVID symptoms, took a COVID test, moved out of her parents' house and into her husband's house, and became pregnant. Starsim's model update logic would order her day as follows: (1) become pregnant, (2) develop symptoms, (3) move house, (4) get COVID test, (5) transmit COVID to anyone still in her contact networks, which at this point would include her husband but not her parents.

There are a few principles that guide the logic about the update ordering. Updating the demographics first makes sense because we want to make sure we capture mother-to-child transmission. Suppose that during one month, a woman became pregnant and also acquired malaria; we want to ensure that the effects of malaria on her pregnancy are captured. Updating the network before disease transmission occurs is fairly arbitrary, but it means that transmission can occur with new partners, which is particularly relevant for sexually transmitted infections, especially when it's an instantaneously-dissolving partnership.

### Printing progress
It can be useful to see how the `Sim` is progressing, and the `sim.run()` method accepts a `verbose` argument that controls how frequently to provide updates. With `verbose=1`, you will see updates on progree on every timestep; with `verbose=0.1` you will see updates every 10 timesteps, and so on. If you are running monthly or daily timesteps you can also use verbose=1/12 or 1/365 to avoid having too much printed output in your Python terminal. You can also use `verbose=-1`, which will only print output when the sim is complete.

## Starting and stopping a run

Simulations can be partially run, stopped, and restarted using the `sim.run(until=)` argument. This can be useful for a few different reasons:

- To inspect the state of a simulation for mid-run (although you can also use an analyzer or the Python debugger for this)
- To modify the state of a simulation before continuing (although you can also use an intervention for this)
- To run part of a simulation only once, before copying it and running different scenarios (although note that copying a simulation can often take almost as much time as running it!)

Of these, the third use case is probably the most common. The example below illustrates how to do this.

```{python}
import sciris as sc
import starsim as ss
ss.options(jupyter=True)

# Define custom intervention
class sis_vaccine(ss.Intervention):

    def __init__(self, start=2040, eff=1.0):
        super().__init__()
        self.start = start
        self.eff = eff
        return

    def step(self):
        sis = self.sim.diseases.sis
        if sis.now == self.start:
            sis.rel_trans[:] *= 1-self.eff
        return

# Default parameters
pars = dict(
    start = 2000,
    stop = 2050,
    diseases = 'sis',
    networks = 'random',
    verbose = 0,
)

# Define list of efficacies to run
effs = [0.0, 0.2, 0.5, 0.8, 1.0]


#%% Standard parallel run
T = sc.timer()
sims = []

for eff in effs:
    s = ss.Sim(pars, label=f'Efficacy={eff}', interventions=sis_vaccine(eff=eff))
    sims.append(s)

sims = ss.parallel(sims)
T.toc('Runtime using default')


#%% Shortcut using "until"
T = sc.timer()
sim = ss.Sim(pars, interventions=sis_vaccine())
sim.run(until=2039)
sims = []

for eff in effs:
    s = sim.copy()
    s.label = f'Efficacy={eff}'
    s.interventions[0].eff = eff
    sims.append(s)

sims = ss.parallel(sims)
T.toc('Runtime using "until"')

sims.plot()
ss.show()
```

Here, there is relatively little time saved by using `until`; however, with a longer burn-in period, or more scenarios, the time savings may be greater.

You can also use this approach to modify the parameters of a simulation before continuing:

```{python}
import starsim as ss

base_sim = ss.Sim(diseases='sis', networks='random', start=2000, stop=2100, verbose=False)
base_sim.run(until=2030)

sims = []
betas = [0.01, 0.02, 0.05, 0.10]
for beta in betas:
    sim = base_sim.copy()
    sim.diseases.sis.pars.beta = beta
    sim.label = f'beta={beta}'
    sims.append(sim)

msim = ss.parallel(sims)
msim.plot()
```

