Skip to content

Commit

Permalink
Add stop_after_walltime and stop_on_interrupt (NanoComp#860)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
ChristopherHogan authored and stevengj committed May 9, 2019
1 parent 3ea517a commit 06cec19
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 19 deletions.
12 changes: 10 additions & 2 deletions doc/docs/Python_User_Interface.md
Original file line number Diff line number Diff line change
Expand Up @@ -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):

Expand All @@ -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)`**
Expand Down
2 changes: 2 additions & 0 deletions python/meep.i
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
70 changes: 53 additions & 17 deletions python/simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import numbers
import os
import re
import signal
import subprocess
import sys
import warnings
Expand Down Expand Up @@ -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()
Expand All @@ -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)
Expand Down Expand Up @@ -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()
Expand Down
1 change: 1 addition & 0 deletions python/tests/simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -626,5 +626,6 @@ def mat_func(p):
_check(mp.Medium(), False)
_check(mat_func, False)


if __name__ == '__main__':
unittest.main()

0 comments on commit 06cec19

Please sign in to comment.