... | ... |
@@ -31,7 +31,7 @@ def notebook_extension(*, _inline_js=True): |
31 | 31 |
_holoviews_enabled = True |
32 | 32 |
except ModuleNotFoundError: |
33 | 33 |
warnings.warn( |
34 |
- "holoviews is not installed; plotting " "is disabled.", RuntimeWarning |
|
34 |
+ "holoviews is not installed; plotting is disabled.", RuntimeWarning |
|
35 | 35 |
) |
36 | 36 |
|
37 | 37 |
# Load ipywidgets |
... | ... |
@@ -42,7 +42,7 @@ def notebook_extension(*, _inline_js=True): |
42 | 42 |
_ipywidgets_enabled = True |
43 | 43 |
except ModuleNotFoundError: |
44 | 44 |
warnings.warn( |
45 |
- "ipywidgets is not installed; live_info " "is disabled.", RuntimeWarning |
|
45 |
+ "ipywidgets is not installed; live_info is disabled.", RuntimeWarning |
|
46 | 46 |
) |
47 | 47 |
|
48 | 48 |
# Enable asyncio integration |
... | ... |
@@ -233,11 +233,10 @@ def live_info(runner, *, update_interval=0.5): |
233 | 233 |
|
234 | 234 |
def _table_row(i, key, value): |
235 | 235 |
"""Style the rows of a table. Based on the default Jupyterlab table style.""" |
236 |
- style_odd = "text-align: right; padding: 0.5em 0.5em; line-height: 1.0;" |
|
237 |
- style_even = style_odd + "background: var(--md-grey-100);" |
|
238 |
- template = '<tr><th style="{style}">{key}</th><th style="{style}">{value}</th></tr>' |
|
239 |
- style = style_odd if i % 2 == 1 else style_even |
|
240 |
- return template.format(style=style, key=key, value=value) |
|
236 |
+ style = "text-align: right; padding: 0.5em 0.5em; line-height: 1.0;" |
|
237 |
+ if i % 2 == 1: |
|
238 |
+ style += " background: var(--md-grey-100);" |
|
239 |
+ return f'<tr><th style="{style}">{key}</th><th style="{style}">{value}</th></tr>' |
|
241 | 240 |
|
242 | 241 |
|
243 | 242 |
def _info_html(runner): |
color the overhead between red and green
Bas Nijholt authored on 18/12/2019 18:26:30 • Joseph Weston committed on 18/12/2019 18:26:30... | ... |
@@ -250,10 +250,14 @@ def _info_html(runner): |
250 | 250 |
"finished": "green", |
251 | 251 |
}[status] |
252 | 252 |
|
253 |
+ overhead = runner.overhead() |
|
254 |
+ red_level = max(0, min(int(255 * overhead / 100), 255)) |
|
255 |
+ overhead_color = "#{:02x}{:02x}{:02x}".format(red_level, 255 - red_level, 0) |
|
256 |
+ |
|
253 | 257 |
info = [ |
254 | 258 |
("status", f'<font color="{color}">{status}</font>'), |
255 | 259 |
("elapsed time", datetime.timedelta(seconds=runner.elapsed_time())), |
256 |
- ("overhead", f"{runner.overhead():.2f}%"), |
|
260 |
+ ("overhead", f'<font color="{overhead_color}">{overhead:.2f}%</font>'), |
|
257 | 261 |
] |
258 | 262 |
|
259 | 263 |
with suppress(Exception): |
Based on the default style of the Jupyterlab table.
Bas Nijholt authored on 18/12/2019 17:36:23 • GitHub committed on 18/12/2019 17:36:23... | ... |
@@ -228,14 +228,16 @@ def live_info(runner, *, update_interval=0.5): |
228 | 228 |
|
229 | 229 |
runner.ioloop.create_task(update()) |
230 | 230 |
|
231 |
- display( |
|
232 |
- ipywidgets.HBox( |
|
233 |
- (status, cancel), |
|
234 |
- layout=ipywidgets.Layout( |
|
235 |
- border="solid 1px", width="200px", align_items="center" |
|
236 |
- ), |
|
237 |
- ) |
|
238 |
- ) |
|
231 |
+ display(ipywidgets.VBox((status, cancel))) |
|
232 |
+ |
|
233 |
+ |
|
234 |
+def _table_row(i, key, value): |
|
235 |
+ """Style the rows of a table. Based on the default Jupyterlab table style.""" |
|
236 |
+ style_odd = "text-align: right; padding: 0.5em 0.5em; line-height: 1.0;" |
|
237 |
+ style_even = style_odd + "background: var(--md-grey-100);" |
|
238 |
+ template = '<tr><th style="{style}">{key}</th><th style="{style}">{value}</th></tr>' |
|
239 |
+ style = style_odd if i % 2 == 1 else style_even |
|
240 |
+ return template.format(style=style, key=key, value=value) |
|
239 | 241 |
|
240 | 242 |
|
241 | 243 |
def _info_html(runner): |
... | ... |
@@ -260,11 +262,10 @@ def _info_html(runner): |
260 | 262 |
with suppress(Exception): |
261 | 263 |
info.append(("latest loss", f'{runner.learner._cache["loss"]:.3f}')) |
262 | 264 |
|
263 |
- template = '<dt class="ignore-css">{}</dt><dd>{}</dd>' |
|
264 |
- table = "\n".join(template.format(k, v) for k, v in info) |
|
265 |
+ table = "\n".join(_table_row(i, k, v) for i, (k, v) in enumerate(info)) |
|
265 | 266 |
|
266 | 267 |
return f""" |
267 |
- <dl> |
|
268 |
+ <table> |
|
268 | 269 |
{table} |
269 |
- </dl> |
|
270 |
+ </table> |
|
270 | 271 |
""" |
... | ... |
@@ -136,6 +136,7 @@ def live_plot(runner, *, plotter=None, update_interval=2, name=None, normalize=T |
136 | 136 |
|
137 | 137 |
streams = [hv.streams.Stream.define("Next")()] |
138 | 138 |
dm = hv.DynamicMap(plot_generator(), streams=streams) |
139 |
+ dm.cache_size = 1 |
|
139 | 140 |
|
140 | 141 |
if normalize: |
141 | 142 |
# XXX: change when https://github.com/pyviz/holoviews/issues/3637 |
... | ... |
@@ -96,7 +96,7 @@ def live_plot(runner, *, plotter=None, update_interval=2, name=None, normalize=T |
96 | 96 |
|
97 | 97 |
Parameters |
98 | 98 |
---------- |
99 |
- runner : `Runner` |
|
99 |
+ runner : `~adaptive.Runner` |
|
100 | 100 |
plotter : function |
101 | 101 |
A function that takes the learner as a argument and returns a |
102 | 102 |
holoviews object. By default ``learner.plot()`` will be called. |
... | ... |
@@ -16,8 +16,10 @@ _plotly_enabled = False |
16 | 16 |
def notebook_extension(*, _inline_js=True): |
17 | 17 |
"""Enable ipywidgets, holoviews, and asyncio notebook integration.""" |
18 | 18 |
if not in_ipynb(): |
19 |
- raise RuntimeError('"adaptive.notebook_extension()" may only be run ' |
|
20 |
- 'from a Jupyter notebook.') |
|
19 |
+ raise RuntimeError( |
|
20 |
+ '"adaptive.notebook_extension()" may only be run ' |
|
21 |
+ "from a Jupyter notebook." |
|
22 |
+ ) |
|
21 | 23 |
|
22 | 24 |
global _async_enabled, _holoviews_enabled, _ipywidgets_enabled |
23 | 25 |
|
... | ... |
@@ -26,54 +28,60 @@ def notebook_extension(*, _inline_js=True): |
26 | 28 |
_holoviews_enabled = False # After closing a notebook the js is gone |
27 | 29 |
if not _holoviews_enabled: |
28 | 30 |
import holoviews |
29 |
- holoviews.notebook_extension('bokeh', logo=False, inline=_inline_js) |
|
31 |
+ |
|
32 |
+ holoviews.notebook_extension("bokeh", logo=False, inline=_inline_js) |
|
30 | 33 |
_holoviews_enabled = True |
31 | 34 |
except ModuleNotFoundError: |
32 |
- warnings.warn("holoviews is not installed; plotting " |
|
33 |
- "is disabled.", RuntimeWarning) |
|
35 |
+ warnings.warn( |
|
36 |
+ "holoviews is not installed; plotting " "is disabled.", RuntimeWarning |
|
37 |
+ ) |
|
34 | 38 |
|
35 | 39 |
# Load ipywidgets |
36 | 40 |
try: |
37 | 41 |
if not _ipywidgets_enabled: |
38 |
- import ipywidgets |
|
42 |
+ import ipywidgets # noqa: F401 |
|
43 |
+ |
|
39 | 44 |
_ipywidgets_enabled = True |
40 | 45 |
except ModuleNotFoundError: |
41 |
- warnings.warn("ipywidgets is not installed; live_info " |
|
42 |
- "is disabled.", RuntimeWarning) |
|
46 |
+ warnings.warn( |
|
47 |
+ "ipywidgets is not installed; live_info " "is disabled.", RuntimeWarning |
|
48 |
+ ) |
|
43 | 49 |
|
44 | 50 |
# Enable asyncio integration |
45 | 51 |
if not _async_enabled: |
46 |
- get_ipython().magic('gui asyncio') |
|
52 |
+ get_ipython().magic("gui asyncio") # noqa: F821 |
|
47 | 53 |
_async_enabled = True |
48 | 54 |
|
49 | 55 |
|
50 | 56 |
def ensure_holoviews(): |
51 | 57 |
try: |
52 |
- return importlib.import_module('holoviews') |
|
58 |
+ return importlib.import_module("holoviews") |
|
53 | 59 |
except ModuleNotFoundError: |
54 |
- raise RuntimeError('holoviews is not installed; plotting is disabled.') |
|
60 |
+ raise RuntimeError("holoviews is not installed; plotting is disabled.") |
|
55 | 61 |
|
56 | 62 |
|
57 | 63 |
def ensure_plotly(): |
58 | 64 |
global _plotly_enabled |
59 | 65 |
try: |
60 | 66 |
import plotly |
67 |
+ |
|
61 | 68 |
if not _plotly_enabled: |
62 | 69 |
import plotly.graph_objs |
63 | 70 |
import plotly.figure_factory |
64 | 71 |
import plotly.offline |
72 |
+ |
|
65 | 73 |
# This injects javascript and should happen only once |
66 | 74 |
plotly.offline.init_notebook_mode() |
67 | 75 |
_plotly_enabled = True |
68 | 76 |
return plotly |
69 | 77 |
except ModuleNotFoundError: |
70 |
- raise RuntimeError('plotly is not installed; plotting is disabled.') |
|
78 |
+ raise RuntimeError("plotly is not installed; plotting is disabled.") |
|
71 | 79 |
|
72 | 80 |
|
73 | 81 |
def in_ipynb(): |
74 | 82 |
try: |
75 | 83 |
# If we are running in IPython, then `get_ipython()` is always a global |
76 |
- return get_ipython().__class__.__name__ == 'ZMQInteractiveShell' |
|
84 |
+ return get_ipython().__class__.__name__ == "ZMQInteractiveShell" |
|
77 | 85 |
except NameError: |
78 | 86 |
return False |
79 | 87 |
|
... | ... |
@@ -83,8 +91,7 @@ def in_ipynb(): |
83 | 91 |
active_plotting_tasks = dict() |
84 | 92 |
|
85 | 93 |
|
86 |
-def live_plot(runner, *, plotter=None, update_interval=2, |
|
87 |
- name=None, normalize=True): |
|
94 |
+def live_plot(runner, *, plotter=None, update_interval=2, name=None, normalize=True): |
|
88 | 95 |
"""Live plotting of the learner's data. |
89 | 96 |
|
90 | 97 |
Parameters |
... | ... |
@@ -135,14 +142,17 @@ def live_plot(runner, *, plotter=None, update_interval=2, |
135 | 142 |
# is fixed. |
136 | 143 |
dm = dm.map(lambda obj: obj.opts(framewise=True), hv.Element) |
137 | 144 |
|
138 |
- cancel_button = ipywidgets.Button(description='cancel live-plot', |
|
139 |
- layout=ipywidgets.Layout(width='150px')) |
|
145 |
+ cancel_button = ipywidgets.Button( |
|
146 |
+ description="cancel live-plot", layout=ipywidgets.Layout(width="150px") |
|
147 |
+ ) |
|
140 | 148 |
|
141 | 149 |
# Could have used dm.periodic in the following, but this would either spin |
142 | 150 |
# off a thread (and learner is not threadsafe) or block the kernel. |
143 | 151 |
|
144 | 152 |
async def updater(): |
145 |
- event = lambda: hv.streams.Stream.trigger(dm.streams) # XXX: used to be dm.event() |
|
153 |
+ event = lambda: hv.streams.Stream.trigger( # noqa: E731 |
|
154 |
+ dm.streams |
|
155 |
+ ) # XXX: used to be dm.event() |
|
146 | 156 |
# see https://github.com/pyviz/holoviews/issues/3564 |
147 | 157 |
try: |
148 | 158 |
while not runner.task.done(): |
... | ... |
@@ -152,7 +162,7 @@ def live_plot(runner, *, plotter=None, update_interval=2, |
152 | 162 |
finally: |
153 | 163 |
if active_plotting_tasks[name] is asyncio.Task.current_task(): |
154 | 164 |
active_plotting_tasks.pop(name, None) |
155 |
- cancel_button.layout.display = 'none' # remove cancel button |
|
165 |
+ cancel_button.layout.display = "none" # remove cancel button |
|
156 | 166 |
|
157 | 167 |
def cancel(_): |
158 | 168 |
with suppress(KeyError): |
... | ... |
@@ -177,7 +187,7 @@ def should_update(status): |
177 | 187 |
# i.e. we're offline for 12h, with an update_interval of 0.5s, |
178 | 188 |
# and without the reduced probability, we have buffer_size=86400. |
179 | 189 |
# With the correction this is np.log(86400) / np.log(1.1) = 119.2 |
180 |
- return 1.1**buffer_size * random.random() < 1 |
|
190 |
+ return 1.1 ** buffer_size * random.random() < 1 |
|
181 | 191 |
except Exception: |
182 | 192 |
# We catch any Exception because we are using a private API. |
183 | 193 |
return True |
... | ... |
@@ -190,16 +200,19 @@ def live_info(runner, *, update_interval=0.5): |
190 | 200 |
visualized in a Jupyter notebook. |
191 | 201 |
""" |
192 | 202 |
if not _holoviews_enabled: |
193 |
- raise RuntimeError("Live plotting is not enabled; did you run " |
|
194 |
- "'adaptive.notebook_extension()'?") |
|
203 |
+ raise RuntimeError( |
|
204 |
+ "Live plotting is not enabled; did you run " |
|
205 |
+ "'adaptive.notebook_extension()'?" |
|
206 |
+ ) |
|
195 | 207 |
|
196 | 208 |
import ipywidgets |
197 | 209 |
from IPython.display import display |
198 | 210 |
|
199 | 211 |
status = ipywidgets.HTML(value=_info_html(runner)) |
200 | 212 |
|
201 |
- cancel = ipywidgets.Button(description='cancel runner', |
|
202 |
- layout=ipywidgets.Layout(width='100px')) |
|
213 |
+ cancel = ipywidgets.Button( |
|
214 |
+ description="cancel runner", layout=ipywidgets.Layout(width="100px") |
|
215 |
+ ) |
|
203 | 216 |
cancel.on_click(lambda _: runner.cancel()) |
204 | 217 |
|
205 | 218 |
async def update(): |
... | ... |
@@ -212,43 +225,47 @@ def live_info(runner, *, update_interval=0.5): |
212 | 225 |
await asyncio.sleep(0.05) |
213 | 226 |
|
214 | 227 |
status.value = _info_html(runner) |
215 |
- cancel.layout.display = 'none' |
|
228 |
+ cancel.layout.display = "none" |
|
216 | 229 |
|
217 | 230 |
runner.ioloop.create_task(update()) |
218 | 231 |
|
219 |
- display(ipywidgets.HBox( |
|
220 |
- (status, cancel), |
|
221 |
- layout=ipywidgets.Layout(border='solid 1px', |
|
222 |
- width='200px', |
|
223 |
- align_items='center'), |
|
224 |
- )) |
|
232 |
+ display( |
|
233 |
+ ipywidgets.HBox( |
|
234 |
+ (status, cancel), |
|
235 |
+ layout=ipywidgets.Layout( |
|
236 |
+ border="solid 1px", width="200px", align_items="center" |
|
237 |
+ ), |
|
238 |
+ ) |
|
239 |
+ ) |
|
225 | 240 |
|
226 | 241 |
|
227 | 242 |
def _info_html(runner): |
228 | 243 |
status = runner.status() |
229 | 244 |
|
230 |
- color = {'cancelled': 'orange', |
|
231 |
- 'failed': 'red', |
|
232 |
- 'running': 'blue', |
|
233 |
- 'finished': 'green'}[status] |
|
245 |
+ color = { |
|
246 |
+ "cancelled": "orange", |
|
247 |
+ "failed": "red", |
|
248 |
+ "running": "blue", |
|
249 |
+ "finished": "green", |
|
250 |
+ }[status] |
|
234 | 251 |
|
235 | 252 |
info = [ |
236 |
- ('status', f'<font color="{color}">{status}</font>'), |
|
237 |
- ('elapsed time', datetime.timedelta(seconds=runner.elapsed_time())), |
|
238 |
- ('overhead', f'{runner.overhead():.2f}%'), |
|
253 |
+ ("status", f'<font color="{color}">{status}</font>'), |
|
254 |
+ ("elapsed time", datetime.timedelta(seconds=runner.elapsed_time())), |
|
255 |
+ ("overhead", f"{runner.overhead():.2f}%"), |
|
239 | 256 |
] |
240 | 257 |
|
241 | 258 |
with suppress(Exception): |
242 |
- info.append(('# of points', runner.learner.npoints)) |
|
259 |
+ info.append(("# of points", runner.learner.npoints)) |
|
243 | 260 |
|
244 | 261 |
with suppress(Exception): |
245 |
- info.append(('latest loss', f'{runner.learner._cache["loss"]:.3f}')) |
|
262 |
+ info.append(("latest loss", f'{runner.learner._cache["loss"]:.3f}')) |
|
246 | 263 |
|
247 | 264 |
template = '<dt class="ignore-css">{}</dt><dd>{}</dd>' |
248 |
- table = '\n'.join(template.format(k, v) for k, v in info) |
|
265 |
+ table = "\n".join(template.format(k, v) for k, v in info) |
|
249 | 266 |
|
250 |
- return f''' |
|
267 |
+ return f""" |
|
251 | 268 |
<dl> |
252 | 269 |
{table} |
253 | 270 |
</dl> |
254 |
- ''' |
|
271 |
+ """ |
... | ... |
@@ -127,7 +127,7 @@ def live_plot(runner, *, plotter=None, update_interval=2, |
127 | 127 |
else: |
128 | 128 |
yield plotter(runner.learner) |
129 | 129 |
|
130 |
- steams = [hv.streams.Stream.define("Next")()] |
|
130 |
+ streams = [hv.streams.Stream.define("Next")()] |
|
131 | 131 |
dm = hv.DynamicMap(plot_generator(), streams=streams) |
132 | 132 |
|
133 | 133 |
if normalize: |
see https://github.com/pyviz/holoviews/issues/3637
Bas Nijholt authored on 25/04/2019 23:07:57... | ... |
@@ -132,6 +132,8 @@ def live_plot(runner, *, plotter=None, update_interval=2, |
132 | 132 |
dm = hv.DynamicMap(plot_generator(), streams=streams) |
133 | 133 |
|
134 | 134 |
if normalize: |
135 |
+ # XXX: change when https://github.com/pyviz/holoviews/issues/3637 |
|
136 |
+ # is fixed. |
|
135 | 137 |
dm = dm.map(lambda obj: obj.opts(framewise=True), hv.Element) |
136 | 138 |
|
137 | 139 |
cancel_button = ipywidgets.Button(description='cancel live-plot', |
... | ... |
@@ -84,7 +84,8 @@ def in_ipynb(): |
84 | 84 |
active_plotting_tasks = dict() |
85 | 85 |
|
86 | 86 |
|
87 |
-def live_plot(runner, *, plotter=None, update_interval=2, name=None): |
|
87 |
+def live_plot(runner, *, plotter=None, update_interval=2, |
|
88 |
+ name=None, normalize=True): |
|
88 | 89 |
"""Live plotting of the learner's data. |
89 | 90 |
|
90 | 91 |
Parameters |
... | ... |
@@ -99,6 +100,8 @@ def live_plot(runner, *, plotter=None, update_interval=2, name=None): |
99 | 100 |
Name for the `live_plot` task in `adaptive.active_plotting_tasks`. |
100 | 101 |
By default the name is None and if another task with the same name |
101 | 102 |
already exists that other `live_plot` is canceled. |
103 |
+ normalize : bool |
|
104 |
+ Normalize (scale to fit) the frame upon each update. |
|
102 | 105 |
|
103 | 106 |
Returns |
104 | 107 |
------- |
... | ... |
@@ -106,8 +109,10 @@ def live_plot(runner, *, plotter=None, update_interval=2, name=None): |
106 | 109 |
The plot that automatically updates every `update_interval`. |
107 | 110 |
""" |
108 | 111 |
if not _holoviews_enabled: |
109 |
- raise RuntimeError("Live plotting is not enabled; did you run " |
|
110 |
- "'adaptive.notebook_extension()'?") |
|
112 |
+ raise RuntimeError( |
|
113 |
+ "Live plotting is not enabled; did you run " |
|
114 |
+ "'adaptive.notebook_extension()'?" |
|
115 |
+ ) |
|
111 | 116 |
|
112 | 117 |
import holoviews as hv |
113 | 118 |
import ipywidgets |
... | ... |
@@ -123,8 +128,12 @@ def live_plot(runner, *, plotter=None, update_interval=2, name=None): |
123 | 128 |
else: |
124 | 129 |
yield plotter(runner.learner) |
125 | 130 |
|
126 |
- dm = hv.DynamicMap(plot_generator(), |
|
127 |
- streams=[hv.streams.Stream.define('Next')()]) |
|
131 |
+ steams = [hv.streams.Stream.define("Next")()] |
|
132 |
+ dm = hv.DynamicMap(plot_generator(), streams=streams) |
|
133 |
+ |
|
134 |
+ if normalize: |
|
135 |
+ dm = dm.map(lambda obj: obj.opts(framewise=True), hv.Element) |
|
136 |
+ |
|
128 | 137 |
cancel_button = ipywidgets.Button(description='cancel live-plot', |
129 | 138 |
layout=ipywidgets.Layout(width='150px')) |
130 | 139 |
|
... | ... |
@@ -14,7 +14,7 @@ _ipywidgets_enabled = False |
14 | 14 |
_plotly_enabled = False |
15 | 15 |
|
16 | 16 |
|
17 |
-def notebook_extension(): |
|
17 |
+def notebook_extension(*, _inline_js=True): |
|
18 | 18 |
"""Enable ipywidgets, holoviews, and asyncio notebook integration.""" |
19 | 19 |
if not in_ipynb(): |
20 | 20 |
raise RuntimeError('"adaptive.notebook_extension()" may only be run ' |
... | ... |
@@ -27,7 +27,7 @@ def notebook_extension(): |
27 | 27 |
_holoviews_enabled = False # After closing a notebook the js is gone |
28 | 28 |
if not _holoviews_enabled: |
29 | 29 |
import holoviews |
30 |
- holoviews.notebook_extension('bokeh', logo=False) |
|
30 |
+ holoviews.notebook_extension('bokeh', logo=False, inline=_inline_js) |
|
31 | 31 |
_holoviews_enabled = True |
32 | 32 |
except ModuleNotFoundError: |
33 | 33 |
warnings.warn("holoviews is not installed; plotting " |
See https://github.com/pyviz/holoviews/issues/3564
and https://github.com/python-adaptive/adaptive/issues/166
... | ... |
@@ -132,11 +132,13 @@ def live_plot(runner, *, plotter=None, update_interval=2, name=None): |
132 | 132 |
# off a thread (and learner is not threadsafe) or block the kernel. |
133 | 133 |
|
134 | 134 |
async def updater(): |
135 |
+ event = lambda: hv.streams.Stream.trigger(dm.streams) # XXX: used to be dm.event() |
|
136 |
+ # see https://github.com/pyviz/holoviews/issues/3564 |
|
135 | 137 |
try: |
136 | 138 |
while not runner.task.done(): |
137 |
- dm.event() |
|
139 |
+ event() |
|
138 | 140 |
await asyncio.sleep(update_interval) |
139 |
- dm.event() # fire off one last update before we die |
|
141 |
+ event() # fire off one last update before we die |
|
140 | 142 |
finally: |
141 | 143 |
if active_plotting_tasks[name] is asyncio.Task.current_task(): |
142 | 144 |
active_plotting_tasks.pop(name, None) |
See https://www.python.org/dev/peps/pep-0008/#imports
Bas Nijholt authored on 26/11/2018 13:39:12Because after closing the notebook and then opening it, the javascript
is gone. Without this change, one could not load it again.
... | ... |
@@ -3,6 +3,7 @@ import importlib |
3 | 3 |
import asyncio |
4 | 4 |
from contextlib import suppress |
5 | 5 |
import datetime |
6 |
+import random |
|
6 | 7 |
import warnings |
7 | 8 |
|
8 | 9 |
|
... | ... |
@@ -150,6 +151,24 @@ def live_plot(runner, *, plotter=None, update_interval=2, name=None): |
150 | 151 |
return dm |
151 | 152 |
|
152 | 153 |
|
154 |
+def should_update(status): |
|
155 |
+ try: |
|
156 |
+ # Get the length of the write buffer size |
|
157 |
+ buffer_size = len(status.comm.kernel.iopub_thread._events) |
|
158 |
+ |
|
159 |
+ # Make sure to only keep all the messages when the notebook |
|
160 |
+ # is viewed, this means 'buffer_size == 1'. However, when not |
|
161 |
+ # viewing the notebook the buffer fills up. When this happens |
|
162 |
+ # we decide to only add messages to it when a certain probability. |
|
163 |
+ # i.e. we're offline for 12h, with an update_interval of 0.5s, |
|
164 |
+ # and without the reduced probability, we have buffer_size=86400. |
|
165 |
+ # With the correction this is np.log(86400) / np.log(1.1) = 119.2 |
|
166 |
+ return 1.1**buffer_size * random.random() < 1 |
|
167 |
+ except Exception: |
|
168 |
+ # We catch any Exception because we are using a private API. |
|
169 |
+ return True |
|
170 |
+ |
|
171 |
+ |
|
153 | 172 |
def live_info(runner, *, update_interval=0.5): |
154 | 173 |
"""Display live information about the runner. |
155 | 174 |
|
... | ... |
@@ -172,7 +191,12 @@ def live_info(runner, *, update_interval=0.5): |
172 | 191 |
async def update(): |
173 | 192 |
while not runner.task.done(): |
174 | 193 |
await asyncio.sleep(update_interval) |
175 |
- status.value = _info_html(runner) |
|
194 |
+ |
|
195 |
+ if should_update(status): |
|
196 |
+ status.value = _info_html(runner) |
|
197 |
+ else: |
|
198 |
+ await asyncio.sleep(0.05) |
|
199 |
+ |
|
176 | 200 |
status.value = _info_html(runner) |
177 | 201 |
cancel.layout.display = 'none' |
178 | 202 |
|
... | ... |
@@ -7,28 +7,42 @@ import warnings |
7 | 7 |
|
8 | 8 |
|
9 | 9 |
_async_enabled = False |
10 |
-_plotting_enabled = False |
|
10 |
+_holoviews_enabled = False |
|
11 |
+_ipywidgets_enabled = False |
|
12 |
+_plotly_enabled = False |
|
11 | 13 |
|
12 | 14 |
|
13 | 15 |
def notebook_extension(): |
16 |
+ """Enable ipywidgets, holoviews, and asyncio notebook integration.""" |
|
14 | 17 |
if not in_ipynb(): |
15 | 18 |
raise RuntimeError('"adaptive.notebook_extension()" may only be run ' |
16 | 19 |
'from a Jupyter notebook.') |
17 | 20 |
|
18 |
- global _plotting_enabled |
|
19 |
- _plotting_enabled = False |
|
21 |
+ global _async_enabled, _holoviews_enabled, _ipywidgets_enabled |
|
22 |
+ |
|
23 |
+ # Load holoviews |
|
24 |
+ try: |
|
25 |
+ if not _holoviews_enabled: |
|
26 |
+ import holoviews |
|
27 |
+ holoviews.notebook_extension('bokeh', logo=False) |
|
28 |
+ _holoviews_enabled = True |
|
29 |
+ except ModuleNotFoundError: |
|
30 |
+ warnings.warn("holoviews is not installed; plotting " |
|
31 |
+ "is disabled.", RuntimeWarning) |
|
32 |
+ |
|
33 |
+ # Load ipywidgets |
|
20 | 34 |
try: |
21 |
- import ipywidgets |
|
22 |
- import holoviews |
|
23 |
- holoviews.notebook_extension('bokeh', logo=False) |
|
24 |
- _plotting_enabled = True |
|
35 |
+ if not _ipywidgets_enabled: |
|
36 |
+ import ipywidgets |
|
37 |
+ _ipywidgets_enabled = True |
|
25 | 38 |
except ModuleNotFoundError: |
26 |
- warnings.warn("holoviews and (or) ipywidgets are not installed; plotting " |
|
39 |
+ warnings.warn("ipywidgets is not installed; live_info " |
|
27 | 40 |
"is disabled.", RuntimeWarning) |
28 | 41 |
|
29 |
- global _async_enabled |
|
30 |
- get_ipython().magic('gui asyncio') |
|
31 |
- _async_enabled = True |
|
42 |
+ # Enable asyncio integration |
|
43 |
+ if not _async_enabled: |
|
44 |
+ get_ipython().magic('gui asyncio') |
|
45 |
+ _async_enabled = True |
|
32 | 46 |
|
33 | 47 |
|
34 | 48 |
def ensure_holoviews(): |
... | ... |
@@ -38,6 +52,22 @@ def ensure_holoviews(): |
38 | 52 |
raise RuntimeError('holoviews is not installed; plotting is disabled.') |
39 | 53 |
|
40 | 54 |
|
55 |
+def ensure_plotly(): |
|
56 |
+ global _plotly_enabled |
|
57 |
+ try: |
|
58 |
+ import plotly |
|
59 |
+ if not _plotly_enabled: |
|
60 |
+ import plotly.graph_objs |
|
61 |
+ import plotly.figure_factory |
|
62 |
+ import plotly.offline |
|
63 |
+ # This injects javascript and should happen only once |
|
64 |
+ plotly.offline.init_notebook_mode() |
|
65 |
+ _plotly_enabled = True |
|
66 |
+ return plotly |
|
67 |
+ except ModuleNotFoundError: |
|
68 |
+ raise RuntimeError('plotly is not installed; plotting is disabled.') |
|
69 |
+ |
|
70 |
+ |
|
41 | 71 |
def in_ipynb(): |
42 | 72 |
try: |
43 | 73 |
# If we are running in IPython, then `get_ipython()` is always a global |
... | ... |
@@ -72,7 +102,7 @@ def live_plot(runner, *, plotter=None, update_interval=2, name=None): |
72 | 102 |
dm : `holoviews.core.DynamicMap` |
73 | 103 |
The plot that automatically updates every `update_interval`. |
74 | 104 |
""" |
75 |
- if not _plotting_enabled: |
|
105 |
+ if not _holoviews_enabled: |
|
76 | 106 |
raise RuntimeError("Live plotting is not enabled; did you run " |
77 | 107 |
"'adaptive.notebook_extension()'?") |
78 | 108 |
|
... | ... |
@@ -126,7 +156,7 @@ def live_info(runner, *, update_interval=0.5): |
126 | 156 |
Returns an interactive ipywidget that can be |
127 | 157 |
visualized in a Jupyter notebook. |
128 | 158 |
""" |
129 |
- if not _plotting_enabled: |
|
159 |
+ if not _holoviews_enabled: |
|
130 | 160 |
raise RuntimeError("Live plotting is not enabled; did you run " |
131 | 161 |
"'adaptive.notebook_extension()'?") |
132 | 162 |
|
... | ... |
@@ -176,7 +176,7 @@ def _info_html(runner): |
176 | 176 |
with suppress(Exception): |
177 | 177 |
info.append(('latest loss', f'{runner.learner._cache["loss"]:.3f}')) |
178 | 178 |
|
179 |
- template = '<dt>{}</dt><dd>{}</dd>' |
|
179 |
+ template = '<dt class="ignore-css">{}</dt><dd>{}</dd>' |
|
180 | 180 |
table = '\n'.join(template.format(k, v) for k, v in info) |
181 | 181 |
|
182 | 182 |
return f''' |
... | ... |
@@ -20,7 +20,7 @@ def notebook_extension(): |
20 | 20 |
try: |
21 | 21 |
import ipywidgets |
22 | 22 |
import holoviews |
23 |
- holoviews.notebook_extension('bokeh') |
|
23 |
+ holoviews.notebook_extension('bokeh', logo=False) |
|
24 | 24 |
_plotting_enabled = True |
25 | 25 |
except ModuleNotFoundError: |
26 | 26 |
warnings.warn("holoviews and (or) ipywidgets are not installed; plotting " |
... | ... |
@@ -56,21 +56,21 @@ def live_plot(runner, *, plotter=None, update_interval=2, name=None): |
56 | 56 |
|
57 | 57 |
Parameters |
58 | 58 |
---------- |
59 |
- runner : Runner |
|
59 |
+ runner : `Runner` |
|
60 | 60 |
plotter : function |
61 | 61 |
A function that takes the learner as a argument and returns a |
62 |
- holoviews object. By default learner.plot() will be called. |
|
62 |
+ holoviews object. By default ``learner.plot()`` will be called. |
|
63 | 63 |
update_interval : int |
64 | 64 |
Number of second between the updates of the plot. |
65 | 65 |
name : hasable |
66 | 66 |
Name for the `live_plot` task in `adaptive.active_plotting_tasks`. |
67 |
- By default the name is `None` and if another task with the same name |
|
68 |
- already exists that other live_plot is canceled. |
|
67 |
+ By default the name is None and if another task with the same name |
|
68 |
+ already exists that other `live_plot` is canceled. |
|
69 | 69 |
|
70 | 70 |
Returns |
71 | 71 |
------- |
72 |
- dm : holoviews.DynamicMap |
|
73 |
- The plot that automatically updates every update_interval. |
|
72 |
+ dm : `holoviews.core.DynamicMap` |
|
73 |
+ The plot that automatically updates every `update_interval`. |
|
74 | 74 |
""" |
75 | 75 |
if not _plotting_enabled: |
76 | 76 |
raise RuntimeError("Live plotting is not enabled; did you run " |
... | ... |
@@ -174,6 +174,9 @@ def _info_html(runner): |
174 | 174 |
with suppress(Exception): |
175 | 175 |
info.append(('# of points', runner.learner.npoints)) |
176 | 176 |
|
177 |
+ with suppress(Exception): |
|
178 |
+ info.append(('latest loss', f'{runner.learner._cache["loss"]:.3f}')) |
|
179 |
+ |
|
177 | 180 |
template = '<dt>{}</dt><dd>{}</dd>' |
178 | 181 |
table = '\n'.join(template.format(k, v) for k, v in info) |
179 | 182 |
|
... | ... |
@@ -1,6 +1,7 @@ |
1 | 1 |
# -*- coding: utf-8 -*- |
2 | 2 |
import importlib |
3 | 3 |
import asyncio |
4 |
+from contextlib import suppress |
|
4 | 5 |
import datetime |
5 | 6 |
from pkg_resources import parse_version |
6 | 7 |
import warnings |
... | ... |
@@ -110,10 +111,8 @@ def live_plot(runner, *, plotter=None, update_interval=2, name=None): |
110 | 111 |
cancel_button.layout.display = 'none' # remove cancel button |
111 | 112 |
|
112 | 113 |
def cancel(_): |
113 |
- try: |
|
114 |
+ with suppress(KeyError): |
|
114 | 115 |
active_plotting_tasks[name].cancel() |
115 |
- except KeyError: |
|
116 |
- pass |
|
117 | 116 |
|
118 | 117 |
active_plotting_tasks[name] = runner.ioloop.create_task(updater()) |
119 | 118 |
cancel_button.on_click(cancel) |
... | ... |
@@ -172,10 +171,8 @@ def _info_html(runner): |
172 | 171 |
('overhead', f'{runner.overhead():.2f}%'), |
173 | 172 |
] |
174 | 173 |
|
175 |
- try: |
|
174 |
+ with suppress(Exception): |
|
176 | 175 |
info.append(('# of points', runner.learner.npoints)) |
177 |
- except Exception: |
|
178 |
- pass |
|
179 | 176 |
|
180 | 177 |
template = '<dt>{}</dt><dd>{}</dd>' |
181 | 178 |
table = '\n'.join(template.format(k, v) for k, v in info) |
... | ... |
@@ -161,19 +161,15 @@ def live_info(runner, *, update_interval=0.5): |
161 | 161 |
def _info_html(runner): |
162 | 162 |
status = runner.status() |
163 | 163 |
|
164 |
- stat_color = {'cancelled': 'orange', |
|
165 |
- 'failed': 'red', |
|
166 |
- 'running': 'blue', |
|
167 |
- 'finished': 'green'}[status] |
|
168 |
- |
|
169 |
- performance = runner.performance() |
|
170 |
- perf_color = 'green' if performance > 1 else 'red' |
|
164 |
+ color = {'cancelled': 'orange', |
|
165 |
+ 'failed': 'red', |
|
166 |
+ 'running': 'blue', |
|
167 |
+ 'finished': 'green'}[status] |
|
171 | 168 |
|
172 | 169 |
info = [ |
173 |
- ('status', f'<font color="{stat_color}">{status}</font>'), |
|
170 |
+ ('status', f'<font color="{color}">{status}</font>'), |
|
174 | 171 |
('elapsed time', datetime.timedelta(seconds=runner.elapsed_time())), |
175 |
- ('performance', f'<font color="{perf_color}">{performance:.1f}</font>'), |
|
176 |
- ('efficiency', f'{runner.efficiency():.2f}%'), |
|
172 |
+ ('overhead', f'{runner.overhead():.2f}%'), |
|
177 | 173 |
] |
178 | 174 |
|
179 | 175 |
try: |
... | ... |
@@ -173,6 +173,7 @@ def _info_html(runner): |
173 | 173 |
('status', f'<font color="{stat_color}">{status}</font>'), |
174 | 174 |
('elapsed time', datetime.timedelta(seconds=runner.elapsed_time())), |
175 | 175 |
('performance', f'<font color="{perf_color}">{performance:.1f}</font>'), |
176 |
+ ('efficiency', f'{runner.efficiency():.2f}%'), |
|
176 | 177 |
] |
177 | 178 |
|
178 | 179 |
try: |
... | ... |
@@ -161,15 +161,18 @@ def live_info(runner, *, update_interval=0.5): |
161 | 161 |
def _info_html(runner): |
162 | 162 |
status = runner.status() |
163 | 163 |
|
164 |
- color = {'cancelled': 'orange', |
|
165 |
- 'failed': 'red', |
|
166 |
- 'running': 'blue', |
|
167 |
- 'finished': 'green'}[status] |
|
164 |
+ stat_color = {'cancelled': 'orange', |
|
165 |
+ 'failed': 'red', |
|
166 |
+ 'running': 'blue', |
|
167 |
+ 'finished': 'green'}[status] |
|
168 |
+ |
|
169 |
+ performance = runner.performance() |
|
170 |
+ perf_color = 'green' if performance > 1 else 'red' |
|
168 | 171 |
|
169 | 172 |
info = [ |
170 |
- ('status', f'<font color="{color}">{status}</font>'), |
|
173 |
+ ('status', f'<font color="{stat_color}">{status}</font>'), |
|
171 | 174 |
('elapsed time', datetime.timedelta(seconds=runner.elapsed_time())), |
172 |
- (f'efficiency', f'{runner.efficiency():.1f}%'), |
|
175 |
+ ('performance', f'<font color="{perf_color}">{performance:.1f}</font>'), |
|
173 | 176 |
] |
174 | 177 |
|
175 | 178 |
try: |
... | ... |
@@ -166,13 +166,10 @@ def _info_html(runner): |
166 | 166 |
'running': 'blue', |
167 | 167 |
'finished': 'green'}[status] |
168 | 168 |
|
169 |
- t_total = runner.elapsed_time() |
|
170 |
- efficiency = (t_total - runner.time_ask_tell) / t_total * 100 |
|
171 |
- |
|
172 | 169 |
info = [ |
173 | 170 |
('status', f'<font color="{color}">{status}</font>'), |
174 |
- ('elapsed time', datetime.timedelta(seconds=t_total)), |
|
175 |
- (f'efficiency', f'{efficiency:.1f}%'), |
|
171 |
+ ('elapsed time', datetime.timedelta(seconds=runner.elapsed_time())), |
|
172 |
+ (f'efficiency', f'{runner.efficiency():.1f}%'), |
|
176 | 173 |
] |
177 | 174 |
|
178 | 175 |
try: |
... | ... |
@@ -166,9 +166,13 @@ def _info_html(runner): |
166 | 166 |
'running': 'blue', |
167 | 167 |
'finished': 'green'}[status] |
168 | 168 |
|
169 |
+ t_total = runner.elapsed_time() |
|
170 |
+ efficiency = (t_total - runner.time_ask_tell) / t_total * 100 |
|
171 |
+ |
|
169 | 172 |
info = [ |
170 | 173 |
('status', f'<font color="{color}">{status}</font>'), |
171 |
- ('elapsed time', datetime.timedelta(seconds=runner.elapsed_time())), |
|
174 |
+ ('elapsed time', datetime.timedelta(seconds=t_total)), |
|
175 |
+ (f'efficiency', f'{efficiency:.1f}%'), |
|
172 | 176 |
] |
173 | 177 |
|
174 | 178 |
try: |
> This is deprecated in traitlets 4.2. This error will be raised in a future release of traitlets.
Bas Nijholt authored on 24/05/2018 07:47:31... | ... |
@@ -23,7 +23,7 @@ def notebook_extension(): |
23 | 23 |
holoviews.notebook_extension('bokeh') |
24 | 24 |
_plotting_enabled = True |
25 | 25 |
except ModuleNotFoundError: |
26 |
- warnings.warn("holoviews and ipywidgets are not installed; plotting " |
|
26 |
+ warnings.warn("holoviews and (or) ipywidgets are not installed; plotting " |
|
27 | 27 |
"is disabled.", RuntimeWarning) |
28 | 28 |
|
29 | 29 |
global _async_enabled |
This provides a better UX than leaving a useless button dangling.
Joseph Weston authored on 19/02/2018 14:45:12... | ... |
@@ -80,6 +80,9 @@ def live_plot(runner, *, plotter=None, update_interval=2, name=None): |
80 | 80 |
import ipywidgets |
81 | 81 |
from IPython.display import display |
82 | 82 |
|
83 |
+ if name in active_plotting_tasks: |
|
84 |
+ active_plotting_tasks[name].cancel() |
|
85 |
+ |
|
83 | 86 |
def plot_generator(): |
84 | 87 |
while True: |
85 | 88 |
if not plotter: |
... | ... |
@@ -89,7 +92,8 @@ def live_plot(runner, *, plotter=None, update_interval=2, name=None): |
89 | 92 |
|
90 | 93 |
dm = hv.DynamicMap(plot_generator(), |
91 | 94 |
streams=[hv.streams.Stream.define('Next')()]) |
92 |
- |
|
95 |
+ cancel_button = ipywidgets.Button(description='cancel live-plot', |
|
96 |
+ layout=ipywidgets.Layout(width='150px')) |
|
93 | 97 |
|
94 | 98 |
# Could have used dm.periodic in the following, but this would either spin |
95 | 99 |
# off a thread (and learner is not threadsafe) or block the kernel. |
... | ... |
@@ -103,12 +107,7 @@ def live_plot(runner, *, plotter=None, update_interval=2, name=None): |
103 | 107 |
finally: |
104 | 108 |
if active_plotting_tasks[name] is asyncio.Task.current_task(): |
105 | 109 |
active_plotting_tasks.pop(name, None) |
106 |
- |
|
107 |
- global active_plotting_tasks |
|
108 |
- if name in active_plotting_tasks: |
|
109 |
- active_plotting_tasks[name].cancel() |
|
110 |
- |
|
111 |
- active_plotting_tasks[name] = asyncio.get_event_loop().create_task(updater()) |
|
110 |
+ cancel_button.layout.display = 'none' # remove cancel button |
|
112 | 111 |
|
113 | 112 |
def cancel(_): |
114 | 113 |
try: |
... | ... |
@@ -116,11 +115,10 @@ def live_plot(runner, *, plotter=None, update_interval=2, name=None): |
116 | 115 |
except KeyError: |
117 | 116 |
pass |
118 | 117 |
|
119 |
- cancel_button = ipywidgets.Button(description='cancel live-plot', |
|
120 |
- layout=ipywidgets.Layout(width='150px')) |
|
118 |
+ active_plotting_tasks[name] = runner.ioloop.create_task(updater()) |
|
121 | 119 |
cancel_button.on_click(cancel) |
122 |
- display(cancel_button) |
|
123 | 120 |
|
121 |
+ display(cancel_button) |
|
124 | 122 |
return dm |
125 | 123 |
|
126 | 124 |
|
... | ... |
@@ -140,7 +138,7 @@ def live_info(runner, *, update_interval=0.5): |
140 | 138 |
status = ipywidgets.HTML(value=_info_html(runner)) |
141 | 139 |
|
142 | 140 |
cancel = ipywidgets.Button(description='cancel runner', |
143 |
- layout=ipywidgets.Layout(width='100px')) |
|
141 |
+ layout=ipywidgets.Layout(width='100px')) |
|
144 | 142 |
cancel.on_click(lambda _: runner.cancel()) |
145 | 143 |
|
146 | 144 |
async def update(): |
... | ... |
@@ -148,17 +146,17 @@ def live_info(runner, *, update_interval=0.5): |
148 | 146 |
await asyncio.sleep(update_interval) |
149 | 147 |
status.value = _info_html(runner) |
150 | 148 |
status.value = _info_html(runner) |
149 |
+ cancel.layout.display = 'none' |
|
151 | 150 |
|
152 | 151 |
runner.ioloop.create_task(update()) |
153 | 152 |
|
154 |
- hbox = ipywidgets.HBox( |
|
153 |
+ display(ipywidgets.HBox( |
|
155 | 154 |
(status, cancel), |
156 | 155 |
description='Runner stats', |
157 | 156 |
layout=ipywidgets.Layout(border='solid 1px', |
158 | 157 |
width='200px', |
159 | 158 |
align_items='center'), |
160 |
- ) |
|
161 |
- return display(hbox) |
|
159 |
+ )) |
|
162 | 160 |
|
163 | 161 |
|
164 | 162 |
def _info_html(runner): |
Now we raise a custom error telling the user to install holoviews
Joseph Weston authored on 19/02/2018 14:44:19... | ... |
@@ -1,4 +1,5 @@ |
1 | 1 |
# -*- coding: utf-8 -*- |
2 |
+import importlib |
|
2 | 3 |
import asyncio |
3 | 4 |
import datetime |
4 | 5 |
from pkg_resources import parse_version |
... | ... |
@@ -30,6 +31,13 @@ def notebook_extension(): |
30 | 31 |
_async_enabled = True |
31 | 32 |
|
32 | 33 |
|
34 |
+def ensure_holoviews(): |
|
35 |
+ try: |
|
36 |
+ return importlib.import_module('holoviews') |
|
37 |
+ except ModuleNotFounderror: |
|
38 |
+ raise RuntimeError('holoviews is not installed; plotting is disabled.') |
|
39 |
+ |
|
40 |
+ |
|
33 | 41 |
def in_ipynb(): |
34 | 42 |
try: |
35 | 43 |
# If we are running in IPython, then `get_ipython()` is always a global |
... | ... |
@@ -147,8 +147,8 @@ def live_info(runner, *, update_interval=0.5): |
147 | 147 |
(status, cancel), |
148 | 148 |
description='Runner stats', |
149 | 149 |
layout=ipywidgets.Layout(border='solid 1px', |
150 |
- width='200px', |
|
151 |
- align_items='center'), |
|
150 |
+ width='200px', |
|
151 |
+ align_items='center'), |
|
152 | 152 |
) |
153 | 153 |
return display(hbox) |
154 | 154 |
|
Also move all non-essential requirements to 'extras_require'.
Joseph Weston authored on 16/02/2018 20:49:15... | ... |
@@ -4,55 +4,41 @@ import datetime |
4 | 4 |
from pkg_resources import parse_version |
5 | 5 |
import warnings |
6 | 6 |
|
7 |
-from IPython import get_ipython |
|
8 |
-import ipykernel |
|
9 |
-from ipykernel.eventloops import register_integration |
|
10 | 7 |
|
8 |
+_async_enabled = False |
|
9 |
+_plotting_enabled = False |
|
11 | 10 |
|
12 |
-# IPython event loop integration |
|
13 | 11 |
|
14 |
-if parse_version(ipykernel.__version__) < parse_version('4.7.0'): |
|
15 |
- # XXX: remove this function when we depend on ipykernel>=4.7.0 |
|
16 |
- @register_integration('asyncio') |
|
17 |
- def _loop_asyncio(kernel): |
|
18 |
- """Start a kernel with asyncio event loop support. |
|
19 |
- Taken from https://github.com/ipython/ipykernel/blob/fa814da201bdebd5b16110597604f7dabafec58d/ipykernel/eventloops.py#L294""" |
|
20 |
- loop = asyncio.get_event_loop() |
|
12 |
+def notebook_extension(): |
|
13 |
+ if not in_ipynb(): |
|
14 |
+ raise RuntimeError('"adaptive.notebook_extension()" may only be run ' |
|
15 |
+ 'from a Jupyter notebook.') |
|
21 | 16 |
|
22 |
- def kernel_handler(): |
|
23 |
- loop.call_soon(kernel.do_one_iteration) |
|
24 |
- loop.call_later(kernel._poll_interval, kernel_handler) |
|
17 |
+ global _plotting_enabled |
|
18 |
+ _plotting_enabled = False |
|
19 |
+ try: |
|
20 |
+ import ipywidgets |
|
21 |
+ import holoviews |
|
22 |
+ holoviews.notebook_extension('bokeh') |
|
23 |
+ _plotting_enabled = True |
|
24 |
+ except ModuleNotFoundError: |
|
25 |
+ warnings.warn("holoviews and ipywidgets are not installed; plotting " |
|
26 |
+ "is disabled.", RuntimeWarning) |
|
25 | 27 |
|
26 |
- loop.call_soon(kernel_handler) |
|
27 |
- # loop is already running (e.g. tornado 5), nothing left to do |
|
28 |
- if loop.is_running(): |
|
29 |
- return |
|
30 |
- while True: |
|
31 |
- error = None |
|
32 |
- try: |
|
33 |
- loop.run_forever() |
|
34 |
- except KeyboardInterrupt: |
|
35 |
- continue |
|
36 |
- except Exception as e: |
|
37 |
- error = e |
|
38 |
- loop.run_until_complete(loop.shutdown_asyncgens()) |
|
39 |
- loop.close() |
|
40 |
- if error is not None: |
|
41 |
- raise error |
|
42 |
- break |
|
28 |
+ global _async_enabled |
|
29 |
+ get_ipython().magic('gui asyncio') |
|
30 |
+ _async_enabled = True |
|
43 | 31 |
|
44 | 32 |
|
45 |
-def notebook_extension(): |
|
46 |
- get_ipython().magic('gui asyncio') |
|
33 |
+def in_ipynb(): |
|
47 | 34 |
try: |
48 |
- import holoviews as hv |
|
49 |
- return hv.notebook_extension('bokeh') |
|
50 |
- except ModuleNotFoundError: |
|
51 |
- warnings.warn("The holoviews package is not installed so plotting" |
|
52 |
- "will not work.", RuntimeWarning) |
|
35 |
+ # If we are running in IPython, then `get_ipython()` is always a global |
|
36 |
+ return get_ipython().__class__.__name__ == 'ZMQInteractiveShell' |
|
37 |
+ except NameError: |
|
38 |
+ return False |
|
53 | 39 |
|
54 | 40 |
|
55 |
-# Plotting |
|
41 |
+# Fancy displays in the Jupyter notebook |
|
56 | 42 |
|
57 | 43 |
active_plotting_tasks = dict() |
58 | 44 |
|
... | ... |
@@ -78,11 +64,11 @@ def live_plot(runner, *, plotter=None, update_interval=2, name=None): |
78 | 64 |
dm : holoviews.DynamicMap |
79 | 65 |
The plot that automatically updates every update_interval. |
80 | 66 |
""" |
81 |
- try: |
|
82 |
- import holoviews as hv |
|
83 |
- except ModuleNotFoundError: |
|
84 |
- raise RuntimeError('Plotting requires the holoviews Python package' |
|
85 |
- ' which is not installed.') |
|
67 |
+ if not _plotting_enabled: |
|
68 |
+ raise RuntimeError("Live plotting is not enabled; did you run " |
|
69 |
+ "'adaptive.notebook_extension()'?") |
|
70 |
+ |
|
71 |
+ import holoviews as hv |
|
86 | 72 |
import ipywidgets |
87 | 73 |
from IPython.display import display |
88 | 74 |
|
... | ... |
@@ -136,6 +122,10 @@ def live_info(runner, *, update_interval=0.5): |
136 | 122 |
Returns an interactive ipywidget that can be |
137 | 123 |
visualized in a Jupyter notebook. |
138 | 124 |
""" |
125 |
+ if not _plotting_enabled: |
|
126 |
+ raise RuntimeError("Live plotting is not enabled; did you run " |
|
127 |
+ "'adaptive.notebook_extension()'?") |
|
128 |
+ |
|
139 | 129 |
import ipywidgets |
140 | 130 |
from IPython.display import display |
141 | 131 |
|
Previously this attribute was meant to be an implementation
detail, but it is useful information and should be made public.
Given that it is public information, it should have an informative
name.
... | ... |
@@ -164,8 +164,15 @@ def live_info(runner, *, update_interval=0.5): |
164 | 164 |
|
165 | 165 |
|
166 | 166 |
def _info_html(runner): |
167 |
+ status = runner.status() |
|
168 |
+ |
|
169 |
+ color = {'cancelled': 'orange', |
|
170 |
+ 'failed': 'red', |
|
171 |
+ 'running': 'blue', |
|
172 |
+ 'finished': 'green'}[status] |
|
173 |
+ |
|
167 | 174 |
info = [ |
168 |
- ('status', runner.status()), |
|
175 |
+ ('status', f'<font color="{color}">{status}</font>'), |
|
169 | 176 |
('elapsed time', datetime.timedelta(seconds=runner.elapsed_time())), |
170 | 177 |
] |
171 | 178 |
|
... | ... |
@@ -136,13 +136,13 @@ def live_info(runner, *, update_interval=0.5): |
136 | 136 |
Returns an interactive ipywidget that can be |
137 | 137 |
visualized in a Jupyter notebook. |
138 | 138 |
""" |
139 |
- import ipywidgets as widgets |
|
139 |
+ import ipywidgets |
|
140 | 140 |
from IPython.display import display |
141 | 141 |
|
142 |
- status = widgets.HTML(value=_info_html(runner)) |
|
142 |
+ status = ipywidgets.HTML(value=_info_html(runner)) |
|
143 | 143 |
|
144 |
- cancel = widgets.Button(description='cancel runner', |
|
145 |
- layout=widgets.Layout(width='100px')) |
|
144 |
+ cancel = ipywidgets.Button(description='cancel runner', |
|
145 |
+ layout=ipywidgets.Layout(width='100px')) |
|
146 | 146 |
cancel.on_click(lambda _: runner.cancel()) |
147 | 147 |
|
148 | 148 |
async def update(): |
... | ... |
@@ -153,10 +153,10 @@ def live_info(runner, *, update_interval=0.5): |
153 | 153 |
|
154 | 154 |
runner.ioloop.create_task(update()) |
155 | 155 |
|
156 |
- hbox = widgets.HBox( |
|
156 |
+ hbox = ipywidgets.HBox( |
|
157 | 157 |
(status, cancel), |
158 | 158 |
description='Runner stats', |
159 |
- layout=widgets.Layout(border='solid 1px', |
|
159 |
+ layout=ipywidgets.Layout(border='solid 1px', |
|
160 | 160 |
width='200px', |
161 | 161 |
align_items='center'), |
162 | 162 |
) |
... | ... |
@@ -1,5 +1,6 @@ |
1 | 1 |
# -*- coding: utf-8 -*- |
2 | 2 |
import asyncio |
3 |
+import datetime |
|
3 | 4 |
from pkg_resources import parse_version |
4 | 5 |
import warnings |
5 | 6 |
|
... | ... |
@@ -127,3 +128,57 @@ def live_plot(runner, *, plotter=None, update_interval=2, name=None): |
127 | 128 |
display(cancel_button) |
128 | 129 |
|
129 | 130 |
return dm |
131 |
+ |
|
132 |
+ |
|
133 |
+def live_info(runner, *, update_interval=0.5): |
|
134 |
+ """Display live information about the runner. |
|
135 |
+ |
|
136 |
+ Returns an interactive ipywidget that can be |
|
137 |
+ visualized in a Jupyter notebook. |
|
138 |
+ """ |
|
139 |
+ import ipywidgets as widgets |
|
140 |
+ from IPython.display import display |
|
141 |
+ |
|
142 |
+ status = widgets.HTML(value=_info_html(runner)) |
|
143 |
+ |
|
144 |
+ cancel = widgets.Button(description='cancel runner', |
|
145 |
+ layout=widgets.Layout(width='100px')) |
|
146 |
+ cancel.on_click(lambda _: runner.cancel()) |
|
147 |
+ |
|
148 |
+ async def update(): |
|
149 |
+ while not runner.task.done(): |
|
150 |
+ await asyncio.sleep(update_interval) |
|
151 |
+ status.value = _info_html(runner) |
|
152 |
+ status.value = _info_html(runner) |
|
153 |
+ |
|
154 |
+ runner.ioloop.create_task(update()) |
|
155 |
+ |
|
156 |
+ hbox = widgets.HBox( |
|
157 |
+ (status, cancel), |
|
158 |
+ description='Runner stats', |
|
159 |
+ layout=widgets.Layout(border='solid 1px', |
|
160 |
+ width='200px', |
|
161 |
+ align_items='center'), |
|
162 |
+ ) |
|
163 |
+ return display(hbox) |
|
164 |
+ |
|
165 |
+ |
|
166 |
+def _info_html(runner): |
|
167 |
+ info = [ |
|
168 |
+ ('status', runner.status()), |
|
169 |
+ ('elapsed time', datetime.timedelta(seconds=runner.elapsed_time())), |
|
170 |
+ ] |
|
171 |
+ |
|
172 |
+ try: |
|
173 |
+ info.append(('# of points', runner.learner.n)) |
|
174 |
+ except Exception: |
|
175 |
+ pass |
|
176 |
+ |
|
177 |
+ template = '<dt>{}</dt><dd>{}</dd>' |
|
178 |
+ table = '\n'.join(template.format(k, v) for k, v in info) |
|
179 |
+ |
|
180 |
+ return f''' |
|
181 |
+ <dl> |
|
182 |
+ {table} |
|
183 |
+ </dl> |
|
184 |
+ ''' |
... | ... |
@@ -82,6 +82,8 @@ def live_plot(runner, *, plotter=None, update_interval=2, name=None): |
82 | 82 |
except ModuleNotFoundError: |
83 | 83 |
raise RuntimeError('Plotting requires the holoviews Python package' |
84 | 84 |
' which is not installed.') |
85 |
+ import ipywidgets |
|
86 |
+ from IPython.display import display |
|
85 | 87 |
|
86 | 88 |
def plot_generator(): |
87 | 89 |
while True: |
... | ... |
@@ -113,4 +115,15 @@ def live_plot(runner, *, plotter=None, update_interval=2, name=None): |
113 | 115 |
|
114 | 116 |
active_plotting_tasks[name] = asyncio.get_event_loop().create_task(updater()) |
115 | 117 |
|
118 |
+ def cancel(_): |
|
119 |
+ try: |
|
120 |
+ active_plotting_tasks[name].cancel() |
|
121 |
+ except KeyError: |
|
122 |
+ pass |
|
123 |
+ |
|
124 |
+ cancel_button = ipywidgets.Button(description='cancel live-plot', |
|
125 |
+ layout=ipywidgets.Layout(width='150px')) |
|
126 |
+ cancel_button.on_click(cancel) |
|
127 |
+ display(cancel_button) |
|
128 |
+ |
|
116 | 129 |
return dm |
... | ... |
@@ -57,6 +57,26 @@ active_plotting_tasks = dict() |
57 | 57 |
|
58 | 58 |
|
59 | 59 |
def live_plot(runner, *, plotter=None, update_interval=2, name=None): |
60 |
+ """Live plotting of the learner's data. |
|
61 |
+ |
|
62 |
+ Parameters |
|
63 |
+ ---------- |
|
64 |
+ runner : Runner |
|
65 |
+ plotter : function |
|
66 |
+ A function that takes the learner as a argument and returns a |
|
67 |
+ holoviews object. By default learner.plot() will be called. |
|
68 |
+ update_interval : int |
|
69 |
+ Number of second between the updates of the plot. |
|
70 |
+ name : hasable |
|
71 |
+ Name for the `live_plot` task in `adaptive.active_plotting_tasks`. |
|
72 |
+ By default the name is `None` and if another task with the same name |
|
73 |
+ already exists that other live_plot is canceled. |
|
74 |
+ |
|
75 |
+ Returns |
|
76 |
+ ------- |
|
77 |
+ dm : holoviews.DynamicMap |
|
78 |
+ The plot that automatically updates every update_interval. |
|
79 |
+ """ |
|
60 | 80 |
try: |
61 | 81 |
import holoviews as hv |
62 | 82 |
except ModuleNotFoundError: |
... | ... |
@@ -53,10 +53,10 @@ def notebook_extension(): |
53 | 53 |
|
54 | 54 |
# Plotting |
55 | 55 |
|
56 |
-active_plotting_task = None |
|
56 |
+active_plotting_tasks = dict() |
|
57 | 57 |
|
58 | 58 |
|
59 |
-def live_plot(runner, *, plotter=None, update_interval=2): |
|
59 |
+def live_plot(runner, *, plotter=None, update_interval=2, name=None): |
|
60 | 60 |
try: |
61 | 61 |
import holoviews as hv |
62 | 62 |
except ModuleNotFoundError: |
... | ... |
@@ -84,12 +84,13 @@ def live_plot(runner, *, plotter=None, update_interval=2): |
84 | 84 |
await asyncio.sleep(update_interval) |
85 | 85 |
dm.event() # fire off one last update before we die |
86 | 86 |
finally: |
87 |
- global active_plotting_task |
|
88 |
- if active_plotting_task is asyncio.Task.current_task(): |
|
89 |
- active_plotting_task = None |
|
87 |
+ if active_plotting_tasks[name] is asyncio.Task.current_task(): |
|
88 |
+ active_plotting_tasks.pop(name, None) |
|
90 | 89 |
|
91 |
- if active_plotting_task: |
|
92 |
- active_plotting_task.cancel() |
|
93 |
- active_plotting_task = asyncio.get_event_loop().create_task(updater()) |
|
90 |
+ global active_plotting_tasks |
|
91 |
+ if name in active_plotting_tasks: |
|
92 |
+ active_plotting_tasks[name].cancel() |
|
93 |
+ |
|
94 |
+ active_plotting_tasks[name] = asyncio.get_event_loop().create_task(updater()) |
|
94 | 95 |
|
95 | 96 |
return dm |
Typically people will only want one live plot at a time, and this
avoids keeping several (typically expensive) background tasks
around. Also, set the active plot reference to None when the
live plot is cancelled.
... | ... |
@@ -53,10 +53,10 @@ def notebook_extension(): |
53 | 53 |
|
54 | 54 |
# Plotting |
55 | 55 |
|
56 |
-active_plotting_tasks = dict() |
|
56 |
+active_plotting_task = None |
|
57 | 57 |
|
58 | 58 |
|
59 |
-def live_plot(runner, *, plotter=None, update_interval=2, name=None): |
|
59 |
+def live_plot(runner, *, plotter=None, update_interval=2): |
|
60 | 60 |
try: |
61 | 61 |
import holoviews as hv |
62 | 62 |
except ModuleNotFoundError: |
... | ... |
@@ -73,9 +73,6 @@ def live_plot(runner, *, plotter=None, update_interval=2, name=None): |
73 | 73 |
dm = hv.DynamicMap(plot_generator(), |
74 | 74 |
streams=[hv.streams.Stream.define('Next')()]) |
75 | 75 |
|
76 |
- # Generate task name if not provided |
|
77 |
- if not name: |
|
78 |
- name = '_' |
|
79 | 76 |
|
80 | 77 |
# Could have used dm.periodic in the following, but this would either spin |
81 | 78 |
# off a thread (and learner is not threadsafe) or block the kernel. |
... | ... |
@@ -87,13 +84,12 @@ def live_plot(runner, *, plotter=None, update_interval=2, name=None): |
87 | 84 |
await asyncio.sleep(update_interval) |
88 | 85 |
dm.event() # fire off one last update before we die |
89 | 86 |
finally: |
90 |
- active_plotting_tasks.pop(name, None) |
|
87 |
+ global active_plotting_task |
|
88 |
+ if active_plotting_task is asyncio.Task.current_task(): |
|
89 |
+ active_plotting_task = None |
|
91 | 90 |
|
92 |
- task = asyncio.get_event_loop().create_task(updater()) |
|
93 |
- |
|
94 |
- if name in active_plotting_tasks: |
|
95 |
- active_plotting_tasks[name].cancel() |
|
96 |
- |
|
97 |
- active_plotting_tasks[name] = task |
|
91 |
+ if active_plotting_task: |
|
92 |
+ active_plotting_task.cancel() |
|
93 |
+ active_plotting_task = asyncio.get_event_loop().create_task(updater()) |
|
98 | 94 |
|
99 | 95 |
return dm |
... | ... |
@@ -55,10 +55,6 @@ def notebook_extension(): |
55 | 55 |
|
56 | 56 |
active_plotting_tasks = dict() |
57 | 57 |
|
58 |
-# Incremented by 'live_plot' on every successful plot creation; |
|
59 |
-# used to name plots that are not given an explicit name. |
|
60 |
-_last_plot_id = 0 |
|
61 |
- |
|
62 | 58 |
|
63 | 59 |
def live_plot(runner, *, plotter=None, update_interval=2, name=None): |
64 | 60 |
try: |
... | ... |
@@ -78,10 +74,8 @@ def live_plot(runner, *, plotter=None, update_interval=2, name=None): |
78 | 74 |
streams=[hv.streams.Stream.define('Next')()]) |
79 | 75 |
|
80 | 76 |
# Generate task name if not provided |
81 |
- global _last_plot_id |
|
82 | 77 |
if not name: |
83 |
- name = f'plot_{_last_plot_id}' |
|
84 |
- _last_plot_id += 1 |
|
78 |
+ name = '_' |
|
85 | 79 |
|
86 | 80 |
# Could have used dm.periodic in the following, but this would either spin |
87 | 81 |
# off a thread (and learner is not threadsafe) or block the kernel. |
... | ... |
@@ -97,6 +91,9 @@ def live_plot(runner, *, plotter=None, update_interval=2, name=None): |
97 | 91 |
|
98 | 92 |
task = asyncio.get_event_loop().create_task(updater()) |
99 | 93 |
|
94 |
+ if name in active_plotting_tasks: |
|
95 |
+ active_plotting_tasks[name].cancel() |
|
96 |
+ |
|
100 | 97 |
active_plotting_tasks[name] = task |
101 | 98 |
|
102 | 99 |
return dm |
remove references to live plotting tasks from the Runner and
instead maintain a mapping from plot name to task. This avoids
polluting the runner, but also allows users to maintain references
to plotting tasks, so that they can cancel them at will.
Closes #38.
... | ... |
@@ -53,7 +53,14 @@ def notebook_extension(): |
53 | 53 |
|
54 | 54 |
# Plotting |
55 | 55 |
|
56 |
-def live_plot(runner, *, plotter=None, update_interval=2): |
|
56 |
+active_plotting_tasks = dict() |
|
57 |
+ |
|
58 |
+# Incremented by 'live_plot' on every successful plot creation; |
|
59 |
+# used to name plots that are not given an explicit name. |
|
60 |
+_last_plot_id = 0 |
|
61 |
+ |
|
62 |
+ |
|
63 |
+def live_plot(runner, *, plotter=None, update_interval=2, name=None): |
|
57 | 64 |
try: |
58 | 65 |
import holoviews as hv |
59 | 66 |
except ModuleNotFoundError: |
... | ... |
@@ -70,19 +77,26 @@ def live_plot(runner, *, plotter=None, update_interval=2): |
70 | 77 |
dm = hv.DynamicMap(plot_generator(), |
71 | 78 |
streams=[hv.streams.Stream.define('Next')()]) |
72 | 79 |
|
80 |
+ # Generate task name if not provided |
|
81 |
+ global _last_plot_id |
|
82 |
+ if not name: |
|
83 |
+ name = f'plot_{_last_plot_id}' |
|
84 |
+ _last_plot_id += 1 |
|
85 |
+ |
|
73 | 86 |
# Could have used dm.periodic in the following, but this would either spin |
74 | 87 |
# off a thread (and learner is not threadsafe) or block the kernel. |
75 | 88 |
|
76 | 89 |
async def updater(): |
77 |
- while not runner.task.done(): |
|
78 |
- dm.event() |
|
79 |
- await asyncio.sleep(update_interval) |
|
80 |
- dm.event() # fire off one last update before we die |
|
90 |
+ try: |
|
91 |
+ while not runner.task.done(): |
|
92 |
+ dm.event() |
|
93 |
+ await asyncio.sleep(update_interval) |
|
94 |
+ dm.event() # fire off one last update before we die |
|
95 |
+ finally: |
|
96 |
+ active_plotting_tasks.pop(name, None) |
|
81 | 97 |
|
82 | 98 |
task = asyncio.get_event_loop().create_task(updater()) |
83 | 99 |
|
84 |
- if not hasattr(runner, 'live_plotters'): |
|
85 |
- runner.live_plotters = [] |
|
100 |
+ active_plotting_tasks[name] = task |
|
86 | 101 |
|
87 |
- runner.live_plotters.append(task) |
|
88 | 102 |
return dm |
... | ... |
@@ -1,50 +1,64 @@ |
1 | 1 |
# -*- coding: utf-8 -*- |
2 | 2 |
import asyncio |
3 |
+from pkg_resources import parse_version |
|
4 |
+import warnings |
|
3 | 5 |
|
4 | 6 |
from IPython import get_ipython |
7 |
+import ipykernel |
|
5 | 8 |
from ipykernel.eventloops import register_integration |
6 | 9 |
|
7 | 10 |
|
8 |
-# IPython event loop integraion |
|
9 |
- |
|
10 |
-@register_integration('asyncio') |
|
11 |
-def _loop_asyncio(kernel): |
|
12 |
- '''Start a kernel with asyncio event loop support.''' |
|
13 |
- loop = asyncio.get_event_loop() |
|
14 |
- |
|
15 |
- def kernel_handler(): |
|
16 |
- loop.call_soon(kernel.do_one_iteration) |
|
17 |
- loop.call_later(kernel._poll_interval, kernel_handler) |
|
18 |
- |
|
19 |
- loop.call_soon(kernel_handler) |
|
20 |
- # loop is already running (e.g. tornado 5), nothing left to do |
|
21 |
- if loop.is_running(): |
|
22 |
- return |
|
23 |
- while True: |
|
24 |
- error = None |
|
25 |
- try: |
|
26 |
- loop.run_forever() |
|
27 |
- except KeyboardInterrupt: |
|
28 |
- continue |
|
29 |
- except Exception as e: |
|
30 |
- error = e |
|
31 |
- loop.run_until_complete(loop.shutdown_asyncgens()) |
|
32 |
- loop.close() |
|
33 |
- if error is not None: |
|
34 |
- raise error |
|
35 |
- break |
|
11 |
+# IPython event loop integration |
|
12 |
+ |
|
13 |
+if parse_version(ipykernel.__version__) < parse_version('4.7.0'): |
|
14 |
+ # XXX: remove this function when we depend on ipykernel>=4.7.0 |
|
15 |
+ @register_integration('asyncio') |
|
16 |
+ def _loop_asyncio(kernel): |
|
17 |
+ """Start a kernel with asyncio event loop support. |
|
18 |
+ Taken from https://github.com/ipython/ipykernel/blob/fa814da201bdebd5b16110597604f7dabafec58d/ipykernel/eventloops.py#L294""" |
|
19 |
+ loop = asyncio.get_event_loop() |
|
20 |
+ |
|
21 |
+ def kernel_handler(): |
|
22 |
+ loop.call_soon(kernel.do_one_iteration) |
|
23 |
+ loop.call_later(kernel._poll_interval, kernel_handler) |
|
24 |
+ |
|
25 |
+ loop.call_soon(kernel_handler) |
|
26 |
+ # loop is already running (e.g. tornado 5), nothing left to do |
|
27 |
+ if loop.is_running(): |
|
28 |
+ return |
|
29 |
+ while True: |
|
30 |
+ error = None |
|
31 |
+ try: |
|
32 |
+ loop.run_forever() |
|
33 |
+ except KeyboardInterrupt: |
|
34 |
+ continue |
|
35 |
+ except Exception as e: |
|
36 |
+ error = e |
|
37 |
+ loop.run_until_complete(loop.shutdown_asyncgens()) |
|
38 |
+ loop.close() |
|
39 |
+ if error is not None: |
|
40 |
+ raise error |
|
41 |
+ break |
|
36 | 42 |
|
37 | 43 |
|
38 | 44 |
def notebook_extension(): |
39 |
- import holoviews |
|
40 | 45 |
get_ipython().magic('gui asyncio') |
41 |
- return holoviews.notebook_extension('bokeh') |
|
46 |
+ try: |
|
47 |
+ import holoviews as hv |
|
48 |
+ return hv.notebook_extension('bokeh') |
|
49 |
+ except ModuleNotFoundError: |
|
50 |
+ warnings.warn("The holoviews package is not installed so plotting" |
|
51 |
+ "will not work.", RuntimeWarning) |
|
42 | 52 |
|
43 | 53 |
|
44 | 54 |
# Plotting |
45 | 55 |
|
46 | 56 |
def live_plot(runner, *, plotter=None, update_interval=2): |
47 |
- import holoviews as hv |
|
57 |
+ try: |
|
58 |
+ import holoviews as hv |
|
59 |
+ except ModuleNotFoundError: |
|
60 |
+ raise RuntimeError('Plotting requires the holoviews Python package' |
|
61 |
+ ' which is not installed.') |
|
48 | 62 |
|
49 | 63 |
def plot_generator(): |
50 | 64 |
while True: |
... | ... |
@@ -65,7 +65,6 @@ def live_plot(runner, *, plotter=None, update_interval=2): |
65 | 65 |
await asyncio.sleep(update_interval) |
66 | 66 |
dm.event() # fire off one last update before we die |
67 | 67 |
|
68 |
- # Fire and forget -- the task will die anyway once the runner has finished. |
|
69 | 68 |
task = asyncio.get_event_loop().create_task(updater()) |
70 | 69 |
|
71 | 70 |
if not hasattr(runner, 'live_plotters'): |
... | ... |
@@ -66,6 +66,10 @@ def live_plot(runner, *, plotter=None, update_interval=2): |
66 | 66 |
dm.event() # fire off one last update before we die |
67 | 67 |
|
68 | 68 |
# Fire and forget -- the task will die anyway once the runner has finished. |
69 |
- asyncio.get_event_loop().create_task(updater()) |
|
69 |
+ task = asyncio.get_event_loop().create_task(updater()) |
|
70 | 70 |
|
71 |
+ if not hasattr(runner, 'live_plotters'): |
|
72 |
+ runner.live_plotters = [] |
|
73 |
+ |
|
74 |
+ runner.live_plotters.append(task) |
|
71 | 75 |
return dm |
... | ... |
@@ -36,14 +36,15 @@ def _loop_asyncio(kernel): |
36 | 36 |
|
37 | 37 |
|
38 | 38 |
def notebook_extension(): |
39 |
- import holoviews as hv |
|
39 |
+ import holoviews |
|
40 | 40 |
get_ipython().magic('gui asyncio') |
41 |
- return hv.notebook_extension('bokeh') |
|
41 |
+ return holoviews.notebook_extension('bokeh') |
|
42 | 42 |
|
43 | 43 |
|
44 | 44 |
# Plotting |
45 | 45 |
|
46 | 46 |
def live_plot(runner, *, plotter=None, update_interval=2): |
47 |
+ import holoviews as hv |
|
47 | 48 |
|
48 | 49 |
def plot_generator(): |
49 | 50 |
while True: |
... | ... |
@@ -3,7 +3,6 @@ import asyncio |
3 | 3 |
|
4 | 4 |
from IPython import get_ipython |
5 | 5 |
from ipykernel.eventloops import register_integration |
6 |
-import holoviews as hv |
|
7 | 6 |
|
8 | 7 |
|
9 | 8 |
# IPython event loop integraion |
... | ... |
@@ -37,6 +36,7 @@ def _loop_asyncio(kernel): |
37 | 36 |
|
38 | 37 |
|
39 | 38 |
def notebook_extension(): |
39 |
+ import holoviews as hv |
|
40 | 40 |
get_ipython().magic('gui asyncio') |
41 | 41 |
return hv.notebook_extension('bokeh') |
42 | 42 |
|
... | ... |
@@ -18,12 +18,22 @@ def _loop_asyncio(kernel): |
18 | 18 |
loop.call_later(kernel._poll_interval, kernel_handler) |
19 | 19 |
|
20 | 20 |
loop.call_soon(kernel_handler) |
21 |
- try: |
|
22 |
- if not loop.is_running(): |
|
21 |
+ # loop is already running (e.g. tornado 5), nothing left to do |
|
22 |
+ if loop.is_running(): |
|
23 |
+ return |
|
24 |
+ while True: |
|
25 |
+ error = None |
|
26 |
+ try: |
|
23 | 27 |
loop.run_forever() |
24 |
- finally: |
|
28 |
+ except KeyboardInterrupt: |
|
29 |
+ continue |
|
30 |
+ except Exception as e: |
|
31 |
+ error = e |
|
25 | 32 |
loop.run_until_complete(loop.shutdown_asyncgens()) |
26 | 33 |
loop.close() |
34 |
+ if error is not None: |
|
35 |
+ raise error |
|
36 |
+ break |
|
27 | 37 |
|
28 | 38 |
|
29 | 39 |
def notebook_extension(): |
... | ... |
@@ -52,6 +52,7 @@ def live_plot(runner, *, plotter=None, update_interval=1): |
52 | 52 |
while not runner.task.done(): |
53 | 53 |
dm.event() |
54 | 54 |
await asyncio.sleep(update_interval) |
55 |
+ dm.event() # fire off one last update before we die |
|
55 | 56 |
|
56 | 57 |
# Fire and forget -- the task will die anyway once the runner has finished. |
57 | 58 |
asyncio.get_event_loop().create_task(updater()) |
1 | 1 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,59 @@ |
1 |
+# -*- coding: utf-8 -*- |
|
2 |
+import asyncio |
|
3 |
+ |
|
4 |
+from IPython import get_ipython |
|
5 |
+from ipykernel.eventloops import register_integration |
|
6 |
+import holoviews as hv |
|
7 |
+ |
|
8 |
+ |
|
9 |
+# IPython event loop integraion |
|
10 |
+ |
|
11 |
+@register_integration('asyncio') |
|
12 |
+def _loop_asyncio(kernel): |
|
13 |
+ '''Start a kernel with asyncio event loop support.''' |
|
14 |
+ loop = asyncio.get_event_loop() |
|
15 |
+ |
|
16 |
+ def kernel_handler(): |
|
17 |
+ loop.call_soon(kernel.do_one_iteration) |
|
18 |
+ loop.call_later(kernel._poll_interval, kernel_handler) |
|
19 |
+ |
|
20 |
+ loop.call_soon(kernel_handler) |
|
21 |
+ try: |
|
22 |
+ if not loop.is_running(): |
|
23 |
+ loop.run_forever() |
|
24 |
+ finally: |
|
25 |
+ loop.run_until_complete(loop.shutdown_asyncgens()) |
|
26 |
+ loop.close() |
|
27 |
+ |
|
28 |
+ |
|
29 |
+def notebook_extension(): |
|
30 |
+ get_ipython().magic('gui asyncio') |
|
31 |
+ return hv.notebook_extension('bokeh') |
|
32 |
+ |
|
33 |
+ |
|
34 |
+# Plotting |
|
35 |
+ |
|
36 |
+def live_plot(runner, *, plotter=None, update_interval=1): |
|
37 |
+ |
|
38 |
+ def plot_generator(): |
|
39 |
+ while True: |
|
40 |
+ if not plotter: |
|
41 |
+ yield runner.learner.plot() |
|
42 |
+ else: |
|
43 |
+ yield plotter(runner.learner) |
|
44 |
+ |
|
45 |
+ dm = hv.DynamicMap(plot_generator(), |
|
46 |
+ streams=[hv.streams.Stream.define('Next')()]) |
|
47 |
+ |
|
48 |
+ # Could have used dm.periodic in the following, but this would either spin |
|
49 |
+ # off a thread (and learner is not threadsafe) or block the kernel. |
|
50 |
+ |
|
51 |
+ async def updater(): |
|
52 |
+ while not runner.task.done(): |
|
53 |
+ dm.event() |
|
54 |
+ await asyncio.sleep(update_interval) |
|
55 |
+ |
|
56 |
+ # Fire and forget -- the task will die anyway once the runner has finished. |
|
57 |
+ asyncio.get_event_loop().create_task(updater()) |
|
58 |
+ |
|
59 |
+ return dm |