Skip to content

Commit c084704

Browse files
authored
Merge pull request #229 from python-adaptive/feature/time-based-stop
add a time-base stopping criterion for runners
2 parents 59459cb + cf0ab59 commit c084704

File tree

3 files changed

+61
-0
lines changed

3 files changed

+61
-0
lines changed

adaptive/runner.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -712,6 +712,46 @@ def replay_log(learner, log):
712712
getattr(learner, method)(*args)
713713

714714

715+
# --- Useful runner goals
716+
717+
718+
def stop_after(*, seconds=0, minutes=0, hours=0):
719+
"""Stop a runner after a specified time.
720+
721+
For example, to specify a runner that should stop after
722+
5 minutes, one could do the following:
723+
724+
>>> runner = Runner(learner, goal=stop_after(minutes=5))
725+
726+
To stop a runner after 2 hours, 10 minutes and 3 seconds,
727+
one could do the following:
728+
729+
>>> runner = Runner(learner, goal=stop_after(hours=2, minutes=10, seconds=3))
730+
731+
Parameters
732+
----------
733+
seconds, minutes, hours : float, default: 0
734+
If more than one is specified, then they are added together
735+
736+
Returns
737+
-------
738+
goal : callable
739+
Can be used as the ``goal`` parameter when constructing
740+
a `Runner`.
741+
742+
Notes
743+
-----
744+
The duration specified is only a *lower bound* on the time that the
745+
runner will run for, because the runner only checks its goal when
746+
it adds points to its learner
747+
"""
748+
stop_time = time.time() + seconds + 60 * minutes + 3600 * hours
749+
return lambda _: time.time() > stop_time
750+
751+
752+
# -- Internal executor-related, things
753+
754+
715755
class SequentialExecutor(concurrent.Executor):
716756
"""A trivial executor that runs functions synchronously.
717757

adaptive/tests/test_runner.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# -*- coding: utf-8 -*-
22

33
import asyncio
4+
import time
45

56
import pytest
67

@@ -10,6 +11,7 @@
1011
BlockingRunner,
1112
SequentialExecutor,
1213
simple,
14+
stop_after,
1315
with_distributed,
1416
with_ipyparallel,
1517
)
@@ -103,6 +105,14 @@ def test_concurrent_futures_executor():
103105
)
104106

105107

108+
def test_stop_after_goal():
109+
seconds_to_wait = 0.2 # don't make this too large or the test will take ages
110+
start_time = time.time()
111+
BlockingRunner(Learner1D(linear, (-1, 1)), stop_after(seconds=seconds_to_wait))
112+
stop_time = time.time()
113+
assert stop_time - start_time > seconds_to_wait
114+
115+
106116
@pytest.mark.skipif(not with_ipyparallel, reason="IPyparallel is not installed")
107117
def test_ipyparallel_executor(ipyparallel_executor):
108118
learner = Learner1D(linear, (-1, 1))

docs/source/reference/adaptive.runner.extras.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
11
Runner extras
22
=============
33

4+
Stopping Criteria
5+
-----------------
6+
7+
Runners allow you to specify the stopping criterion by providing
8+
a ``goal`` as a function that takes the learner and returns a boolean: ``False``
9+
for "continue running" and ``True`` for "stop". This gives you a lot of flexibility
10+
for defining your own stopping conditions, however we also provide some common
11+
stopping conditions as a convenience.
12+
13+
.. autofunction:: adaptive.runner.stop_after
14+
415
Simple executor
516
---------------
617

0 commit comments

Comments
 (0)