Skip to content

Commit e6e354c

Browse files
authored
Allow terminating simulation programmatically (#1828)
1 parent 462d6b8 commit e6e354c

2 files changed

Lines changed: 29 additions & 1 deletion

File tree

src/tlo/performance.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ class MagpieOptions:
2424
save_sim: bool = False
2525
save_sim_freq: int = 6
2626
save_sim_on_end: bool = False
27+
wall_time_limit_minutes: float | None = None
2728

2829

2930
class Magpie(Module):
@@ -37,6 +38,7 @@ class Magpie(Module):
3738
save_sim=True, # save the simulation to a pickle file...
3839
save_sim_freq=3, # ...every n months
3940
save_sim_on_end=True # save the simulation to a pickle file at the end of the simulation
41+
wall_time_limit_minutes=60 # stop the simulation if it runs for more than n minutes (checked every month)
4042
)
4143
```
4244
@@ -63,6 +65,9 @@ def initialise_simulation(self, sim: Simulation):
6365
if self.options.save_sim:
6466
sim.schedule_event(SaveSimulation(self, self.options.save_sim_freq), sim.start_date)
6567

68+
if self.options.wall_time_limit_minutes is not None:
69+
sim.schedule_event(WallTimeLimit(self, self.options.wall_time_limit_minutes), sim.start_date)
70+
6671
def on_birth(self, mother_id, child_id):
6772
pass
6873

@@ -94,6 +99,23 @@ def apply(self, population):
9499
logger.info(key="stats", data=data)
95100

96101

102+
class WallTimeLimit(RegularEvent, PopulationScopeEventMixin):
103+
def __init__(self, module, wall_time_limit_minutes):
104+
super().__init__(module, frequency=DateOffset(months=1))
105+
self.wall_time_limit_minutes = wall_time_limit_minutes
106+
self.start_time = time.time()
107+
108+
def apply(self, population):
109+
now = time.time()
110+
elapsed_minutes = (now - self.start_time) / 60
111+
if elapsed_minutes >= self.wall_time_limit_minutes:
112+
logger.info(
113+
key="msg",
114+
data=f"Terminating simulation after {self.wall_time_limit_minutes} minutes of runtime"
115+
)
116+
self.sim.terminate()
117+
118+
97119
def make_pickle_filename(sim_date):
98120
strftime = "%Y%m%dT%H%M%S"
99121
timestamp = time.strftime(strftime)

src/tlo/simulation.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,9 @@ def __init__(
138138
# Whether simulation has been initialised
139139
self._initialised = False
140140

141+
# To allow for graceful termination
142+
self._terminate = False
143+
141144
def _configure_logging(
142145
self,
143146
filename: Optional[str] = None,
@@ -383,7 +386,7 @@ def run_simulation_to(self, *, to_date: Date) -> None:
383386
if self.show_progress_bar:
384387
progress_bar = self._initialise_progress_bar(to_date)
385388
while (
386-
len(self.event_queue) > 0 and self.event_queue.date_of_next_event < to_date
389+
len(self.event_queue) > 0 and self.event_queue.date_of_next_event < to_date and not self._terminate
387390
):
388391
event, date = self.event_queue.pop_next_event_and_date()
389392
if self.show_progress_bar:
@@ -506,6 +509,9 @@ def load_from_pickle(
506509
simulation._log_filepath = simulation._configure_logging(**log_config)
507510
return simulation
508511

512+
def terminate(self):
513+
self._terminate = True
514+
509515

510516
class EventQueue:
511517
"""A simple priority queue for events.

0 commit comments

Comments
 (0)