loop
Parent class for the integration loop.
Classes
| Name | Description |
|---|---|
| Loop | Define the integration loop |
| LoopEntry | One executable event in the integration loop |
Loop
loop.Loop(sim)Define the integration loop
The Loop handles the order in which each function is called in the sim. The order is defined in Loop.collect_funcs(), which searches through the sim and collects all methods to call, in order, in the integration loop.
Each type of module is called at a different time. Within each module type, they are called in the order listed. The default loop order is:
1. sim: start_step() # Initialize the sim, including plotting progress
2. all modules: start_step() # Initialize the modules, including the random number distribution
3. sim.custom: step() # Run any custom modules
4. sim.demographics: step() # Update the demographics, including adding new agents
5. sim.diseases: step_state() # Update the disease states, e.g. exposed -> infected
6. sim.connectors: step() # Run the connectors
7. sim.networks: step() # Run the networks, including adding/removing edges
8. sim.interventions: step() # Run the interventions
9. sim.diseases: step() # Run the diseases, including transmission
10. people: step_die() # Figure out who died on this timestep
11. people: update_results() # Update basic state results
12. all modules: update_results() # Update any results
13. sim.analyzers: step() # Run the analyzers
14. all modules: finish_step() # Do any final tidying
15. people: finish_step() # Clean up dead agents
16. sim: finish_step() # Increment the timestep
Methods
| Name | Description |
|---|---|
| collect_abs_tvecs | Collect time arrays for each module |
| collect_funcs | Collect all the callable functions (methods) that comprise the step |
| init | Parse the sim into the integration plan |
| insert | Insert a function into the loop plan at the specified location. |
| make_plan | Combine the module ordering and the time vectors into the integration plan |
| plan_metadata | Return the dataframe view of the plan used for matching and display |
| plot | Plot a diagram of all the events |
| plot_cpu | Plot the CPU time spent on each event; visualization of Loop.cpu_df. |
| plot_step_order | Plot the order of the module steps across timesteps – useful for debugging |
| run | Actually run the integration loop; usually called by sim.run() |
| run_one_step | Take a single step, i.e. call a single function; only used for debugging purposes. |
| shrink | Shrink the size of the loop for saving to disk |
| store_time | Store the current time in as high resolution as possible (only called when profiling) |
| to_df | Return a user-friendly version of the plan, omitting object columns |
collect_abs_tvecs
loop.Loop.collect_abs_tvecs()Collect time arrays for each module
Two parallel arrays are stored per module: abs_tvecs holds the ground-truth time objects (ss.date/ss.dur, used verbatim as each plan entry’s time), and abs_numvecs holds the canonical numeric representation (float years). The numeric arrays are what make_plan() uses to decide uniformity and to sort, so that date/dur objects are never compared during plan construction.
collect_funcs
loop.Loop.collect_funcs()Collect all the callable functions (methods) that comprise the step
init
loop.Loop.init()Parse the sim into the integration plan
insert
loop.Loop.insert(func, label=None, match_fn=None, before=False)Insert a function into the loop plan at the specified location.
The loop plan metadata view is a dataframe with columns including time (e.g. date('2025-05-05')), label (e.g. 'randomnet.step'), module (’randomnet'), and function name ('step'). By default, this method will match the conditions in the plan based on the criteria specified.
This functionality is similar to an analyzer or an intervention, but gives additional flexibility since can be inserted at (almost) any point in a sim.
Note: the loop must be initialized (sim.init()) before you can call this.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| func | func |
the function to insert; must take a single argument, sim |
required |
| label | str | the label (module.name) of the function to match; see sim.loop.to_df().label.unique() for choices |
None |
| match_fn | func |
if supplied, use this function to perform the matching on the plan dataframe, returning a boolean array or list of indices of matching rows (see example below) | None |
| before | bool | if true, insert the function before rather than after the match | False |
Examples
# Simple label matching with analyzer-like functionality
def check_pop_size(sim):
print(f'Population size is {len(sim.people)}')
sim = ss.Sim(diseases='sir', networks='random', demographics=True)
sim.init()
sim.loop.insert(check_pop_size, label='people.finish_step')
sim.run()
# Function-based matching with intervention-like functionality
def match_fn(plan):
past_2010 = plan.time > ss.date(2010)
is_step = (plan.label == 'sir.step') | (plan.label == 'randomnet.step')
return past_2010 * is_step
def update_betas(sim):
if not sim.metadata.get('updated'):
print(f'Updating beta values on {sim.now}')
sim.diseases.sis.beta = 0.1
sim.networks.randomnet.edges.beta[:] = 0.5
sim.metadata.updated = True
return
sim = ss.Sim(diseases='sis', networks='random')
sim.init()
sim.loop.insert(update_betas, match_fn=match_fn, before=True)
sim.run()make_plan
loop.Loop.make_plan()Combine the module ordering and the time vectors into the integration plan
The plan is the list of LoopEntry events in execution order: tick-major, and within each tick ordered by function order. When every module shares the sim’s canonical timeline (the common case) this order can be generated directly, with no sort. Otherwise a numeric sort is used. In neither case are date/dur objects compared (which is slow); the canonical numeric time vectors (abs_numvecs) are used instead.
plan_metadata
loop.Loop.plan_metadata()Return the dataframe view of the plan used for matching and display
plot
loop.Loop.plot(
simplify=False,
max_len=100,
fig_kw=None,
plot_kw=None,
scatter_kw=None,
)Plot a diagram of all the events
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| simplify | bool | if True, skip update_results and finish_step events, which are automatically applied | False |
| max_len | int | maximum number of entries to plot | 100 |
| fig_kw | dict | passed to plt.figure() |
None |
| plot_kw | dict | passed to plt.plot() |
None |
| scatter_kw | dict | passed to plt.scatter() |
None |
plot_cpu
loop.Loop.plot_cpu(bytime=True, max_entries=10, fig_kw=None, bar_kw=None)Plot the CPU time spent on each event; visualization of Loop.cpu_df.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| bytime | bool | if True, order events by total time rather than actual order | True |
| fig_kw | dict | passed to plt.figure() |
None |
| bar_kw | dict | passed to plt.bar() |
None |
plot_step_order
loop.Loop.plot_step_order(
which='default',
max_len=500,
plot_kw=None,
scatter_kw=None,
fig_kw=None,
legend_kw=None,
)Plot the order of the module steps across timesteps – useful for debugging when using different time units.
Note: generates a lot of data, best to debug with a small number of timesteps first!
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| which | dict | columns and values to filter to (default: {‘func_name’:‘step’}; if None, do not filter) | 'default' |
| max_len | int | maximum number of entries to plot | 500 |
| plot_kw | dict | passed to plt.plot() |
None |
| scatter_kw | dict | passed to plt.scatter() |
None |
| fig_kw | dict | passed to plt.figure() |
None |
| legend_kw | dict | passed to plt.legend() |
None |
Examples
sis = ss.SIS(dt=0.1)
net = ss.RandomNet(dt=0.5)
births = ss.Births(dt=1)
sim = ss.Sim(dt=0.1, dur=5, diseases=sis, networks=net, demographics=births)
sim.init()
sim.loop.plot_step_order()run
loop.Loop.run(until=None, verbose=None, profile=None)Actually run the integration loop; usually called by sim.run()
By default, per-entry CPU timing is not recorded and the plan DataFrame is not built at the end of the run – both are pure overhead (~15–25 ms for a bare 10-year sim) that most runs do not use. Pass profile=True to record per-entry timing (which to_df()/plot_cpu() then expose as the cpu_time column); otherwise cpu_time is NaN. The plan metadata (via to_df()) is always available on demand regardless.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| until | str / date | if supplied, stop after this date (used by sim.run_one_step) | None |
| verbose | bool | if True, print each function call as it runs | None |
| profile | bool | if True, record per-entry CPU timing (default: keep the current setting) | None |
run_one_step
loop.Loop.run_one_step()Take a single step, i.e. call a single function; only used for debugging purposes.
Compare sim.run_one_step(), which runs a full timestep (which involves multiple function calls).
shrink
loop.Loop.shrink()Shrink the size of the loop for saving to disk
store_time
loop.Loop.store_time()Store the current time in as high resolution as possible (only called when profiling)
to_df
loop.Loop.to_df()Return a user-friendly version of the plan, omitting object columns
The cpu_time column is populated only if the run recorded per-entry timing (i.e. sim.run(profile=True)); otherwise it is NaN. This is built lazily and cached in self.df/self.cpu_df.
LoopEntry
loop.LoopEntry(time, ti, func_order, func, label, module, func_name)One executable event in the integration loop
Note: slots=True is used primarily to reduce the memory footprint across the 10⁴–10⁵ instances created for a typical plan (no per-instance __dict__).