From 06cec198bcade148e6c7212b4c72f7a28312d35a Mon Sep 17 00:00:00 2001 From: Christopher Hogan Date: Thu, 9 May 2019 12:34:29 -0500 Subject: [PATCH] Add stop_after_walltime and stop_on_interrupt (#860) * Add stop_after_walltime and stop_on_interrupt * Fix doc typo [ci skip] * Let until take a list of stopping conditions * Fix docs for run --- doc/docs/Python_User_Interface.md | 12 +++++- python/meep.i | 2 + python/simulation.py | 70 +++++++++++++++++++++++-------- python/tests/simulation.py | 1 + 4 files changed, 66 insertions(+), 19 deletions(-) diff --git a/doc/docs/Python_User_Interface.md b/doc/docs/Python_User_Interface.md index 88b0050e6..61b215c2d 100644 --- a/doc/docs/Python_User_Interface.md +++ b/doc/docs/Python_User_Interface.md @@ -1530,11 +1530,11 @@ A common point of confusion is described in [The Run Function Is Not A Loop](The **`run(step_functions..., until=condition/time)`** — -Run the simulation until a certain time or condition, calling the given step functions (if any) at each timestep. The keyword argument `until` is *either* a number, in which case it is an additional time (in Meep units) to run for, *or* it is a function (of no arguments) which returns `True` when the simulation should stop. +Run the simulation until a certain time or condition, calling the given step functions (if any) at each timestep. The keyword argument `until` is *either* a number, in which case it is an additional time (in Meep units) to run for, *or* it is a function (of no arguments) which returns `True` when the simulation should stop. `until` can also be a list of stopping conditions which may include a number and additional functions. **`run(step_functions..., until_after_sources=condition/time)`** — -Run the simulation until all sources have turned off, calling the given step functions (if any) at each timestep. The keyword argument `until_after_sources` is either a number, in which case it is an *additional* time (in Meep units) to run for after the sources are off, *or* it is a function (of no arguments). In the latter case, the simulation runs until the sources are off *and* `condition` returns `True`. +Run the simulation until all sources have turned off, calling the given step functions (if any) at each timestep. The keyword argument `until_after_sources` is either a number, in which case it is an *additional* time (in Meep units) to run for after the sources are off, *or* it is a function (of no arguments). In the latter case, the simulation runs until the sources are off *and* `condition` returns `True`. Like `until` above, `until_after_sources` can take a list of stopping conditions. In particular, a useful value for `until_after_sources` or `until` is often `stop_when_field_decayed`, which is demonstrated in [Tutorial/Basics](Python_Tutorials/Basics.md#transmittance-spectrum-of-a-waveguide-bend): @@ -1544,6 +1544,14 @@ Return a `condition` function, suitable for passing to `until`/`until_after_sour Note that, if you make `decay_by` very small, you may need to increase the `cutoff` property of your source(s), to decrease the amplitude of the small high-frequency components that are excited when the source turns off. High frequencies near the [Nyquist frequency](https://en.wikipedia.org/wiki/Nyquist_frequency) of the grid have slow group velocities and are absorbed poorly by [PML](Perfectly_Matched_Layer.md). +**`stop_after_walltime(t)`** +— +Return a `condition` function, suitable for passing to `until`. Stops the simulation after `t` seconds of wall time have passed. + +**`stop_on_interrupt()`** +— +Return a `condition` function, suitable for passing to `until`. Instead of terminating when receiving a SIGINT or SIGTERM signal from the system, the simulation will abort time stepping and continue executing any code that follows the `run` function (e.g., outputting fields). + Finally, another run function, useful for computing ω(**k**) band diagrams, is: **`run_k_points(T, k_points)`** diff --git a/python/meep.i b/python/meep.i index 12a020f52..88240473f 100644 --- a/python/meep.i +++ b/python/meep.i @@ -1489,6 +1489,8 @@ PyObject *_get_array_slice_dimensions(meep::fields *f, const meep::volume &where scale_flux_fields, scale_force_fields, scale_near2far_fields, + stop_after_walltime, + stop_on_interrupt, stop_when_fields_decayed, synchronized_magnetic, to_appended, diff --git a/python/simulation.py b/python/simulation.py index c4f206419..613966f8a 100644 --- a/python/simulation.py +++ b/python/simulation.py @@ -5,6 +5,7 @@ import numbers import os import re +import signal import subprocess import sys import warnings @@ -1232,19 +1233,25 @@ def _run_until(self, cond, step_funcs): if self.fields is None: self.init_sim() - if isinstance(cond, numbers.Number): - stop_time = cond - t0 = self.round_time() + if not isinstance(cond, list): + cond = [cond] - def stop_cond(sim): - return sim.round_time() >= t0 + stop_time + for i in range(len(cond)): + if isinstance(cond[i], numbers.Number): + stop_time = cond[i] + t0 = self.round_time() - cond = stop_cond + def stop_cond(sim): + return sim.round_time() >= t0 + stop_time - step_funcs = list(step_funcs) - step_funcs.append(display_progress(t0, t0 + stop_time, self.progress_interval)) + cond[i] = stop_cond - while not cond(self): + step_funcs = list(step_funcs) + step_funcs.append(display_progress(t0, t0 + stop_time, self.progress_interval)) + else: + assert callable(cond[i]), "Stopping condition {} is not an integer or a function".format(cond[i]) + + while not any([x(self) for x in cond]): for func in step_funcs: _eval_step_func(self, func, 'step') self.fields.step() @@ -1266,16 +1273,20 @@ def _run_sources_until(self, cond, step_funcs): if self.fields is None: self.init_sim() - ts = self.fields.last_source_time() + if not isinstance(cond, list): + cond = [cond] - if isinstance(cond, numbers.Number): - new_cond = (ts - self.round_time()) + cond - else: - def f(sim): - return cond(sim) and sim.round_time() >= ts - new_cond = f + ts = self.fields.last_source_time() + new_conds = [] + for i in range(len(cond)): + if isinstance(cond[i], numbers.Number): + new_conds.append((ts - self.round_time()) + cond[i]) + else: + def f(sim): + return cond[i](sim) and sim.round_time() >= ts + new_conds.append(f) - self._run_until(new_cond, step_funcs) + self._run_until(new_conds, step_funcs) def _run_sources(self, step_funcs): self._run_sources_until(self, 0, step_funcs) @@ -2433,6 +2444,31 @@ def _stop(sim): return _stop +def stop_after_walltime(t): + start = mp.wall_time() + def _stop_after_walltime(sim): + if mp.wall_time() - start > t: + return True + return False + return _stop_after_walltime + + +def stop_on_interrupt(): + shutting_down = [False] + + def _signal_handler(sig, frame): + print("WARNING: System requested termination. Time stepping aborted.") + shutting_down[0] = True + + signal.signal(signal.SIGINT, _signal_handler) + signal.signal(signal.SIGTERM, _signal_handler) + + def _stop(sim): + return shutting_down[0] + + return _stop + + def synchronized_magnetic(*step_funcs): def _sync(sim, todo): sim.fields.synchronize_magnetic_fields() diff --git a/python/tests/simulation.py b/python/tests/simulation.py index 171e97cf5..6874d5a9a 100644 --- a/python/tests/simulation.py +++ b/python/tests/simulation.py @@ -626,5 +626,6 @@ def mat_func(p): _check(mp.Medium(), False) _check(mat_func, False) + if __name__ == '__main__': unittest.main()