Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add stop_after_walltime and stop_on_interrupt #860

Merged
merged 4 commits into from
May 9, 2019
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,9 +1530,9 @@ 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(step_functions..., until_after_sources=condition/time, stop_on_interrupt=False)`**
stevengj marked this conversation as resolved.
Show resolved Hide resolved
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`.

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)
stevengj marked this conversation as resolved.
Show resolved Hide resolved

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 @@ -2425,6 +2436,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()