One of the main reasons people don’t use ABMs is because they can be very slow. While “vanilla Starsim” is quite fast (10,000 agents running for 100 timesteps should take about a second), custom modules, if not properly written, can be quite slow.
The first step of fixing a slow module is to identify the problem. To do this, Starsim includes some built-in profiling tools.
Initializing sim with 10000 agents
Profiling 15 function(s):
<bound method Sim.run of Sim(n=10000; 2000.01.01—2020.01.01; networks=randomnet; diseases=sis)>
<bound method Sim.start_step of Sim(n=10000; 2000.01.01—2020.01.01; networks=randomnet; diseases=sis [...]
<function Module.start_step at 0x7fa66f00ee80>
<function Module.start_step at 0x7fa66f00ee80>
<bound method SIS.step_state of sis(pars=[init_prev, beta, dur_inf, waning, imm_boost]; states=[susc [...]
<bound method RandomExactNet.step of randomnet(n_edges=50000; pars=[n_contacts, dur, beta, age_range [...]
<bound method Infection.step of sis(pars=[init_prev, beta, dur_inf, waning, imm_boost]; states=[susc [...]
<bound method People.step_die of People(n=10000; age=30.2±17.4)>
<bound method People.update_results of People(n=10000; age=30.2±17.4)>
<bound method Network.update_results of randomnet(n_edges=50000; pars=[n_contacts, dur, beta, age_ra [...]
<function SIS.update_results at 0x7fa66eea4680>
<function Module.finish_step at 0x7fa66f00f060>
<function Module.finish_step at 0x7fa66f00f060>
<bound method People.finish_step of People(n=10000; age=30.2±17.4)>
<bound method Sim.finish_step of Sim(n=10000; 2000.01.01—2020.01.01; networks=randomnet; diseases=si [...]
Running 2000.01.01 ( 0/21) (0.00 s) ———————————————————— 5%
Running 2010.01.01 (10/21) (0.15 s) ••••••••••—————————— 52%
Running 2020.01.01 (20/21) (0.23 s) •••••••••••••••••••• 100%
Elapsed time: 0.238 s
—————————————————————————————————————————————————————————————————————
Profile of networks.Network.update_results: 0.000599612 s (0.251895%)
—————————————————————————————————————————————————————————————————————
Total time: 0.000599612 s
File: /home/runner/work/starsim/starsim/starsim/networks.py
Function: Network.update_results at line 332
Line # Hits Time Per Hit % Time Line Contents
==============================================================
332 def update_results(self):
333 """ Store the number of edges in the network """
334 21 166249.0 7916.6 27.7 super().update_results()
335 21 424027.0 20191.8 70.7 self.results['n_edges'][self.ti] = len(self)
336 21 9336.0 444.6 1.6 return
————————————————————————————————————————————————————————————
Profile of people.People.finish_step: 0.00102667 s (0.4313%)
————————————————————————————————————————————————————————————
Total time: 0.00102667 s
File: /home/runner/work/starsim/starsim/starsim/people.py
Function: People.finish_step at line 542
Line # Hits Time Per Hit % Time Line Contents
==============================================================
542 def finish_step(self):
543 """ Remove dead agents and run post-step updates """
544 21 683951.0 32569.1 66.6 self.remove_dead()
545 21 334226.0 15915.5 32.6 self.update_post()
546 21 8497.0 404.6 0.8 return
———————————————————————————————————————————————————————————————
Profile of diseases.SIS.update_results: 0.00328669 s (1.38072%)
———————————————————————————————————————————————————————————————
Total time: 0.00328669 s
File: /home/runner/work/starsim/starsim/starsim/diseases.py
Function: SIS.update_results at line 1071
Line # Hits Time Per Hit % Time Line Contents
==============================================================
1071 @ss.required()
1072 def update_results(self):
1073 """ Store the population immunity (susceptibility) """
1074 21 2274162.0 108293.4 69.2 super().update_results()
1075 21 1003315.0 47776.9 30.5 self.results['rel_sus'][self.ti] = self.rel_sus.mean()
1076 21 9216.0 438.9 0.3 return
—————————————————————————————————————————————————————————
Profile of people.People.step_die: 0.0034464 s (1.44782%)
—————————————————————————————————————————————————————————
Total time: 0.0034464 s
File: /home/runner/work/starsim/starsim/starsim/people.py
Function: People.step_die at line 502
Line # Hits Time Per Hit % Time Line Contents
==============================================================
502 def step_die(self):
503 """ Carry out any deaths or removals that took place this timestep """
504 21 3071125.0 146244.0 89.1 death_uids = ((self.ti_dead <= self.sim.ti) | (self.ti_removed <= self.sim.ti)).uids
505 21 112843.0 5373.5 3.3 self.alive[death_uids] = False # Whilst not dead, removed agents should not be included in alive totals
506
507 # Execute deaths that took place this timestep (i.e., changing the `alive` state of the agents). This is executed
508 # before analyzers have run so that analyzers are able to inspect and record outcomes for agents that died this timestep
509 42 190793.0 4542.7 5.5 for disease in self.sim.diseases():
510 21 21024.0 1001.1 0.6 if isinstance(disease, ss.Disease):
511 21 39893.0 1899.7 1.2 disease.step_die(death_uids)
512
513 21 10717.0 510.3 0.3 return death_uids
———————————————————————————————————————————————————————————————
Profile of people.People.update_results: 0.00528902 s (2.2219%)
———————————————————————————————————————————————————————————————
Total time: 0.00528902 s
File: /home/runner/work/starsim/starsim/starsim/people.py
Function: People.update_results at line 531
Line # Hits Time Per Hit % Time Line Contents
==============================================================
531 def update_results(self):
532 """ Record per-timestep population counts into the simulation results """
533 21 41106.0 1957.4 0.8 ti = self.sim.ti
534 21 13046.0 621.2 0.2 res = self.sim.results
535 63 629500.0 9992.1 11.9 for state in self.auto_state_list: # Count each auto-generated BoolState result, e.g. n_alive, n_female
536 42 1067108.0 25407.3 20.2 res[f'n_{state.name}'][ti] = np.count_nonzero(getattr(self, state.name))
537 21 1463848.0 69707.0 27.7 res.new_deaths[ti] = np.count_nonzero(self.ti_dead == ti)
538 21 1244021.0 59239.1 23.5 res.new_emigrants[ti] = np.count_nonzero(self.ti_removed == ti)
539 21 820044.0 39049.7 15.5 res.cum_deaths[ti] = res.new_deaths[ti] if ti == 0 else res.cum_deaths[ti-1] + res.new_deaths[ti]
540 21 10349.0 492.8 0.2 return
——————————————————————————————————————————————————————
Profile of sim.Sim.start_step: 0.00646342 s (2.71526%)
——————————————————————————————————————————————————————
Total time: 0.00646342 s
File: /home/runner/work/starsim/starsim/starsim/sim.py
Function: Sim.start_step at line 589
Line # Hits Time Per Hit % Time Line Contents
==============================================================
589 def start_step(self):
590 """ Start the step -- only print progress; all actual changes happen in the modules """
591
592 # Set the time and if we have reached the end of the simulation, then do nothing
593 21 11582.0 551.5 0.2 if self.complete:
594 errormsg = 'Simulation already complete'
595 raise AlreadyRunError(errormsg)
596
597 # Print out progress if needed
598 21 4560149.0 217150.0 70.6 self.elapsed = self.timer.toc(output=True)
599 21 14397.0 685.6 0.2 if self.verbose: # Print progress
600 21 10193.0 485.4 0.2 t = self.t
601 21 327063.0 15574.4 5.1 simlabel = f'"{self.label}": ' if self.label else ''
602 21 977411.0 46543.4 15.1 string = f' Running {simlabel}{t.now("str")} ({t.ti:2.0f}/{t.npts}) ({self.elapsed:0.2f} s) '
603 21 17611.0 838.6 0.3 if self.verbose >= 1:
604 sc.heading(string)
605 21 12335.0 587.4 0.2 elif self.verbose > 0:
606 21 23085.0 1099.3 0.4 if not (t.ti % int(1.0 / self.verbose)):
607 3 499063.0 166354.3 7.7 sc.progressbar(t.ti + 1, t.npts, label=string, length=20, newline=True)
608 21 10531.0 501.5 0.2 return
————————————————————————————————————————————————————————————
Profile of modules.Module.start_step: 0.0154721 s (6.49976%)
————————————————————————————————————————————————————————————
Total time: 0.0154721 s
File: /home/runner/work/starsim/starsim/starsim/modules.py
Function: Module.start_step at line 840
Line # Hits Time Per Hit % Time Line Contents
==============================================================
840 @required()
841 def start_step(self):
842 """ Tasks to perform at the beginning of the step """
843 42 37247.0 886.8 0.2 if self.finalized:
844 errormsg = f'The module {self._debug_name} has already been run. Did you mean to copy it before running it?'
845 raise RuntimeError(errormsg)
846 42 531252.0 12648.9 3.4 if self.dists is not None and ss.options.crn: # Will be None if no distributions are defined; jumping is a no-op under crn=False (see Dist.jump), so skip the per-dist loop entirely
847 42 14881347.0 354317.8 96.2 self.dists.jump_dt() # Advance random number generators forward for calls on this step
848 42 22222.0 529.1 0.1 return
—————————————————————————————————————————————————————————
Profile of diseases.SIS.step_state: 0.016982 s (7.13407%)
—————————————————————————————————————————————————————————
Total time: 0.016982 s
File: /home/runner/work/starsim/starsim/starsim/diseases.py
Function: SIS.step_state at line 1026
Line # Hits Time Per Hit % Time Line Contents
==============================================================
1026 def step_state(self):
1027 """ Progress infectious -> recovered """
1028 21 2341080.0 111480.0 13.8 recovered = (self.infected & (self.ti_recovered <= self.ti)).uids
1029 21 146023.0 6953.5 0.9 self.infected[recovered] = False
1030 21 80671.0 3841.5 0.5 self.susceptible[recovered] = True
1031 21 14399989.0 685713.8 84.8 self.update_immunity()
1032 21 14217.0 677.0 0.1 return
————————————————————————————————————————————————————————————
Profile of networks.RandomExactNet.step: 0.052012 s (21.85%)
————————————————————————————————————————————————————————————
Total time: 0.052012 s
File: /home/runner/work/starsim/starsim/starsim/networks.py
Function: RandomExactNet.step at line 709
Line # Hits Time Per Hit % Time Line Contents
==============================================================
709 def step(self):
710 """ If dynamic, replace expired edges; otherwise, do nothing """
711 21 318967.0 15188.9 0.6 if self.pars.dynamic:
712 21 51682617.0 2.46e+06 99.4 super().step()
713 21 10378.0 494.2 0.0 return
—————————————————————————————————————————————————————————
Profile of diseases.Infection.step: 0.129243 s (54.2944%)
—————————————————————————————————————————————————————————
Total time: 0.129243 s
File: /home/runner/work/starsim/starsim/starsim/diseases.py
Function: Infection.step at line 246
Line # Hits Time Per Hit % Time Line Contents
==============================================================
246 def step(self):
247 """
248 Perform key infection updates, including infection and setting prognoses
249 """
250 # Create new cases
251 21 98638734.0 4.7e+06 76.3 new_cases, sources, networks = self.infect() # TODO: store outputs in self or use objdict rather than 3 returns
252
253 # Set prognoses
254 21 16585.0 789.8 0.0 if len(new_cases):
255 21 30559030.0 1.46e+06 23.6 self.set_outcomes(new_cases, sources)
256 21 15398.0 733.2 0.0 if self.infection_log:
257 self.infection_log.add_data(new_cases, network=networks)
258
259 21 13164.0 626.9 0.0 return new_cases, sources, networks
Figure(672x480)
/home/runner/work/starsim/starsim/starsim/loop.py:613: RuntimeWarning:
No CPU timing was recorded: run with sim.run(profile=True) (or loop.run(profile=True)) to populate cpu_time.
ss.warn('No CPU timing was recorded: run with sim.run(profile=True) (or loop.run(profile=True)) to populate cpu_time.')
This graph (which is a shortcut to sim.loop.plot_cpu()) shows us how much time each step in the integration loop takes. We can get line-by-line detail of where each function is taking time, though:
prof.disp(maxentries=5)
——————————————————————————————————————————————————————
Profile of sim.Sim.start_step: 0.00646342 s (2.71526%)
——————————————————————————————————————————————————————
Total time: 0.00646342 s
File: /home/runner/work/starsim/starsim/starsim/sim.py
Function: Sim.start_step at line 589
Line # Hits Time Per Hit % Time Line Contents
==============================================================
589 def start_step(self):
590 """ Start the step -- only print progress; all actual changes happen in the modules """
591
592 # Set the time and if we have reached the end of the simulation, then do nothing
593 21 11582.0 551.5 0.2 if self.complete:
594 errormsg = 'Simulation already complete'
595 raise AlreadyRunError(errormsg)
596
597 # Print out progress if needed
598 21 4560149.0 217150.0 70.6 self.elapsed = self.timer.toc(output=True)
599 21 14397.0 685.6 0.2 if self.verbose: # Print progress
600 21 10193.0 485.4 0.2 t = self.t
601 21 327063.0 15574.4 5.1 simlabel = f'"{self.label}": ' if self.label else ''
602 21 977411.0 46543.4 15.1 string = f' Running {simlabel}{t.now("str")} ({t.ti:2.0f}/{t.npts}) ({self.elapsed:0.2f} s) '
603 21 17611.0 838.6 0.3 if self.verbose >= 1:
604 sc.heading(string)
605 21 12335.0 587.4 0.2 elif self.verbose > 0:
606 21 23085.0 1099.3 0.4 if not (t.ti % int(1.0 / self.verbose)):
607 3 499063.0 166354.3 7.7 sc.progressbar(t.ti + 1, t.npts, label=string, length=20, newline=True)
608 21 10531.0 501.5 0.2 return
————————————————————————————————————————————————————————————
Profile of modules.Module.start_step: 0.0154721 s (6.49976%)
————————————————————————————————————————————————————————————
Total time: 0.0154721 s
File: /home/runner/work/starsim/starsim/starsim/modules.py
Function: Module.start_step at line 840
Line # Hits Time Per Hit % Time Line Contents
==============================================================
840 @required()
841 def start_step(self):
842 """ Tasks to perform at the beginning of the step """
843 42 37247.0 886.8 0.2 if self.finalized:
844 errormsg = f'The module {self._debug_name} has already been run. Did you mean to copy it before running it?'
845 raise RuntimeError(errormsg)
846 42 531252.0 12648.9 3.4 if self.dists is not None and ss.options.crn: # Will be None if no distributions are defined; jumping is a no-op under crn=False (see Dist.jump), so skip the per-dist loop entirely
847 42 14881347.0 354317.8 96.2 self.dists.jump_dt() # Advance random number generators forward for calls on this step
848 42 22222.0 529.1 0.1 return
—————————————————————————————————————————————————————————
Profile of diseases.SIS.step_state: 0.016982 s (7.13407%)
—————————————————————————————————————————————————————————
Total time: 0.016982 s
File: /home/runner/work/starsim/starsim/starsim/diseases.py
Function: SIS.step_state at line 1026
Line # Hits Time Per Hit % Time Line Contents
==============================================================
1026 def step_state(self):
1027 """ Progress infectious -> recovered """
1028 21 2341080.0 111480.0 13.8 recovered = (self.infected & (self.ti_recovered <= self.ti)).uids
1029 21 146023.0 6953.5 0.9 self.infected[recovered] = False
1030 21 80671.0 3841.5 0.5 self.susceptible[recovered] = True
1031 21 14399989.0 685713.8 84.8 self.update_immunity()
1032 21 14217.0 677.0 0.1 return
————————————————————————————————————————————————————————————
Profile of networks.RandomExactNet.step: 0.052012 s (21.85%)
————————————————————————————————————————————————————————————
Total time: 0.052012 s
File: /home/runner/work/starsim/starsim/starsim/networks.py
Function: RandomExactNet.step at line 709
Line # Hits Time Per Hit % Time Line Contents
==============================================================
709 def step(self):
710 """ If dynamic, replace expired edges; otherwise, do nothing """
711 21 318967.0 15188.9 0.6 if self.pars.dynamic:
712 21 51682617.0 2.46e+06 99.4 super().step()
713 21 10378.0 494.2 0.0 return
—————————————————————————————————————————————————————————
Profile of diseases.Infection.step: 0.129243 s (54.2944%)
—————————————————————————————————————————————————————————
Total time: 0.129243 s
File: /home/runner/work/starsim/starsim/starsim/diseases.py
Function: Infection.step at line 246
Line # Hits Time Per Hit % Time Line Contents
==============================================================
246 def step(self):
247 """
248 Perform key infection updates, including infection and setting prognoses
249 """
250 # Create new cases
251 21 98638734.0 4.7e+06 76.3 new_cases, sources, networks = self.infect() # TODO: store outputs in self or use objdict rather than 3 returns
252
253 # Set prognoses
254 21 16585.0 789.8 0.0 if len(new_cases):
255 21 30559030.0 1.46e+06 23.6 self.set_outcomes(new_cases, sources)
256 21 15398.0 733.2 0.0 if self.infection_log:
257 self.infection_log.add_data(new_cases, network=networks)
258
259 21 13164.0 626.9 0.0 return new_cases, sources, networks
(Note that the names of the functions here refer to the actual functions called, which may not match the graph above. That’s because, for example, ss.SIS does not define its own step() method, but instead inherits step() from Infection. In the graph, this is shown as sis.step(), but is listed in the table as Infection.step(). This is because it’s referring to the actual code being run, so refers to where those lines of code exist in the codebase; there is no code corresponding to SIS.step() since it’s just inherited from Infection.step().)
If you want more detail, you can also define custom functions to follow. For example, we can see that ss.SIS.infect() takes the most time in ss.SIS.step(), so let’s profile that:
Initializing sim with 10000 agents
Profiling 1 function(s):
<function Infection.infect at 0x7fa66ee7a660>
Running 2000.01.01 ( 0/21) (0.00 s) ———————————————————— 5%
Running 2010.01.01 (10/21) (0.07 s) ••••••••••—————————— 52%
Running 2020.01.01 (20/21) (0.15 s) •••••••••••••••••••• 100%
Elapsed time: 0.155 s
————————————————————————————————————————————————————————————
Profile of diseases.Infection.infect: 0.0523842 s (33.8634%)
————————————————————————————————————————————————————————————
Total time: 0.0523842 s
File: /home/runner/work/starsim/starsim/starsim/diseases.py
Function: Infection.infect at line 302
Line # Hits Time Per Hit % Time Line Contents
==============================================================
302 def infect(self):
303 """
304 Determine who gets infected on this timestep via transmission on the network
305
306 Computes the effective transmissibility and susceptibility, calls `infect_route()`
307 for each network (or other route), and then removes duplicate infections with
308 `finalize_infections()`. A multi-strain disease can override this method to loop
309 over strains, calling `infect_route()` with per-strain values.
310 """
311 21 960163.0 45722.0 1.8 betamap = self.validate_beta()
312
313 # Compute effective transmissibility and susceptibility directly on the raw
314 # (full-length) arrays. This avoids the gather/scatter, full-length astype copy,
315 # and extra wrapper allocations of the Arr math operators; edges only ever index
316 # living agents, so stale raw values for inactive agents are never used.
317 21 724086.0 34480.3 1.4 rel_trans = self.rel_trans.asnew(self.infectious.raw * self.rel_trans.raw, copy=False)
318 21 506615.0 24124.5 1.0 rel_sus = self.rel_sus.asnew(self.susceptible.raw * self.rel_sus.raw, copy=False)
319
320 21 13785.0 656.4 0.0 new_cases = []
321 21 9897.0 471.3 0.0 sources = []
322 21 9549.0 454.7 0.0 networks = []
323 42 163074.0 3882.7 0.3 for i, (nkey,route) in enumerate(self.sim.networks.items()):
324 21 180038.0 8573.2 0.3 betas = betamap[ss.standardize_netkey(nkey)]
325 21 47514432.0 2.26e+06 90.7 target_uids, source_uids, network_ids = self.infect_route(i, route, betas, rel_trans, rel_sus)
326 21 13808.0 657.5 0.0 new_cases.append(target_uids)
327 21 9799.0 466.6 0.0 sources.append(source_uids)
328 21 9718.0 462.8 0.0 networks.append(network_ids)
329
330 21 2269193.0 108056.8 4.3 return self.finalize_infections(new_cases, sources, networks)
————————————————————————————————————————————————————————————
Profile of diseases.Infection.infect: 0.0523842 s (33.8634%)
————————————————————————————————————————————————————————————
Total time: 0.0523842 s
File: /home/runner/work/starsim/starsim/starsim/diseases.py
Function: Infection.infect at line 302
Line # Hits Time Per Hit % Time Line Contents
==============================================================
302 def infect(self):
303 """
304 Determine who gets infected on this timestep via transmission on the network
305
306 Computes the effective transmissibility and susceptibility, calls `infect_route()`
307 for each network (or other route), and then removes duplicate infections with
308 `finalize_infections()`. A multi-strain disease can override this method to loop
309 over strains, calling `infect_route()` with per-strain values.
310 """
311 21 960163.0 45722.0 1.8 betamap = self.validate_beta()
312
313 # Compute effective transmissibility and susceptibility directly on the raw
314 # (full-length) arrays. This avoids the gather/scatter, full-length astype copy,
315 # and extra wrapper allocations of the Arr math operators; edges only ever index
316 # living agents, so stale raw values for inactive agents are never used.
317 21 724086.0 34480.3 1.4 rel_trans = self.rel_trans.asnew(self.infectious.raw * self.rel_trans.raw, copy=False)
318 21 506615.0 24124.5 1.0 rel_sus = self.rel_sus.asnew(self.susceptible.raw * self.rel_sus.raw, copy=False)
319
320 21 13785.0 656.4 0.0 new_cases = []
321 21 9897.0 471.3 0.0 sources = []
322 21 9549.0 454.7 0.0 networks = []
323 42 163074.0 3882.7 0.3 for i, (nkey,route) in enumerate(self.sim.networks.items()):
324 21 180038.0 8573.2 0.3 betas = betamap[ss.standardize_netkey(nkey)]
325 21 47514432.0 2.26e+06 90.7 target_uids, source_uids, network_ids = self.infect_route(i, route, betas, rel_trans, rel_sus)
326 21 13808.0 657.5 0.0 new_cases.append(target_uids)
327 21 9799.0 466.6 0.0 sources.append(source_uids)
328 21 9718.0 462.8 0.0 networks.append(network_ids)
329
330 21 2269193.0 108056.8 4.3 return self.finalize_infections(new_cases, sources, networks)
(Note: you can only follow functions that are called as part of sim.run() this way. To follow other functions, such as those run by sim.init(), you can use sc.profile() directly.)
Debugging
When figuring out what your sim is doing – whether it’s doing something it shouldn’t be, or not doing something it should – sim.loop is your friend. It shows everything that will happen in the sim, and in what order:
time ti func_order label module func_name cpu_time
0 2000 0 0 sim.start_step sim start_step NaN
1 2000 0 1 randomnet.start_step randomnet start_step NaN
2 2000 0 2 sis.start_step sis start_step NaN
3 2000 0 3 sis.step_state sis step_state NaN
4 2000 0 4 randomnet.step randomnet step NaN
5 2000 0 5 sis.step sis step NaN
6 2000 0 6 people.step_die people step_die NaN
7 2000 0 7 people.update_results people update_results NaN
8 2000 0 8 randomnet.update_results randomnet update_results NaN
9 2000 0 9 sis.update_results sis update_results NaN
10 2000 0 10 randomnet.finish_step randomnet finish_step NaN
11 2000 0 11 sis.finish_step sis finish_step NaN
12 2000 0 12 people.finish_step people finish_step NaN
13 2000 0 13 sim.finish_step sim finish_step NaN
14 2001 1 0 sim.start_step sim start_step NaN
15 2001 1 1 randomnet.start_step randomnet start_step NaN
16 2001 1 2 sis.start_step sis start_step NaN
17 2001 1 3 sis.step_state sis step_state NaN
18 2001 1 4 randomnet.step randomnet step NaN
19 2001 1 5 sis.step sis step NaN
20 2001 1 6 people.step_die people step_die NaN
21 2001 1 7 people.update_results people update_results NaN
22 2001 1 8 randomnet.update_results randomnet update_results NaN
23 2001 1 9 sis.update_results sis update_results NaN
24 2001 1 10 randomnet.finish_step randomnet finish_step NaN
25 2001 1 11 sis.finish_step sis finish_step NaN
26 2001 1 12 people.finish_step people finish_step NaN
27 2001 1 13 sim.finish_step sim finish_step NaN
28 2002 2 0 sim.start_step sim start_step NaN
29 2002 2 1 randomnet.start_step randomnet start_step NaN
30 2002 2 2 sis.start_step sis start_step NaN
31 2002 2 3 sis.step_state sis step_state NaN
32 2002 2 4 randomnet.step randomnet step NaN
33 2002 2 5 sis.step sis step NaN
34 2002 2 6 people.step_die people step_die NaN
35 2002 2 7 people.update_results people update_results NaN
36 2002 2 8 randomnet.update_results randomnet update_results NaN
37 2002 2 9 sis.update_results sis update_results NaN
38 2002 2 10 randomnet.finish_step randomnet finish_step NaN
39 2002 2 11 sis.finish_step sis finish_step NaN
40 2002 2 12 people.finish_step people finish_step NaN
41 2002 2 13 sim.finish_step sim finish_step NaN
As you can see, it’s a lot – this is only three timesteps and two modules, and it’s already 41 steps.
The typical way to do debugging is to insert breakpoints or print statements into your modules for custom debugging (e.g., to print a value), or to use analyzers for heavier-lift debugging. Starsim also lets you manually modify the loop by inserting “probes” or other arbitrary functions. For example, if you wanted to check the population size after each time the People object is updated:
Initializing sim with 10000 agents
Running 2000 ( 0/11) (0.00 s) •——————————————————— 9%
Population size is 10074
Population size is 10191
Population size is 10281
Population size is 10376
Population size is 10454
Population size is 10556
Population size is 10652
Population size is 10742
Population size is 10853
Population size is 10946
Running 2010 (10/11) (0.06 s) •••••••••••••••••••• 100%
Population size is 11041
Initializing sim with 10000 agents
Running 2000 ( 0/11) (0.00 s) •——————————————————— 9%
Population size is 10179
Population size is 10287
Population size is 10379
Population size is 10485
Population size is 10559
Population size is 10665
Population size is 10750
Population size is 10857
Population size is 10951
Population size is 11066
Running 2010 (10/11) (0.06 s) •••••••••••••••••••• 100%
Population size is 11163
However, inserting functions directly in the loop gives you more control over their exact placement, whereas analyzers are always executed last in the timestep.
The loop also has methods for visualizing itself. You can get a simple representation of the loop with loop.plot():
sim.loop.plot()
Figure(672x480)
Or a slightly more detailed one with loop.plot_step_order():
sim.loop.plot_step_order()
Figure(672x480)
This is especially useful if your simulation has modules with different timesteps, e.g.:
Initializing sim with 10000 agents
Figure(672x480)
(Note: this is a 3D plot, so it helps if you can plot it in a separate window interactively to be able to move it around, rather than just in a notebook.)