Forked from anonymous/Experiments as Iterators.ipynb
Created
August 31, 2016 21:13
-
-
Save danielballan/6913e1969be38e308b2649fabf1750b0 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"cells": [ | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "slide" | |
} | |
}, | |
"source": [ | |
"# Experiments as Iterators: asyncio in Science\n", | |
"\n", | |
"<ul>\n", | |
"<li>Daniel Allan</li>\n", | |
"<li>Thomas Caswell</li>\n", | |
"<li>Kenneth Lauer</li>\n", | |
"</ul>\n", | |
"\n", | |
"<p>Brookhaven National Lab</p>\n", | |
"<p>This talk: http://tiny.cc/dba-scipy2016</p>\n", | |
"<p>Source: https://github.com/NSLS-II</p>\n", | |
"<p>Project Documentation: https://NSLS-II.github.io</p>\n", | |
"</center>" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"source": [ | |
"## Origin of this Project\n", | |
"\n", | |
"National Synchrontron Light Source II at Brookhaven National Lab\n", | |
"\n", | |
"![NSLS-II](https://www.bnl.gov/ps/images/NSLS2-arial-1080px.jpg)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"source": [ | |
"## NSLS-II\n", | |
"\n", | |
"* 12 semi-independent research groups, scaling up to 60 in ~5 years\n", | |
"* Scaling up to 19 Pb/year in \"expensive pixels\"\n", | |
"* No sacred data formats...\n", | |
"* ... but one validatable, extensible (NoSQL) schema for all:\n", | |
" * metadata\n", | |
" * data or *references* to data" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"source": [ | |
"## Data Acquisition Software Design Goals\n", | |
"\n", | |
"* Integrate with the **scipy stack**.\n", | |
"* Support **streaming** data analysis.\n", | |
"* Capture metadata to record\n", | |
" * a detailed **snapshot** of the hardware (all experiment state);\n", | |
" * and the scientist's **intention**, the meaning of the measurements.\n", | |
"* Make datasets **searchable** with rich queries on metadata and data.\n", | |
"* As much as possible, minimize inventing a domain-specific language." | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"source": [ | |
"Traditional data acquisition script:\n", | |
"\n", | |
"```python\n", | |
"for i in range(5):\n", | |
" try:\n", | |
" put('MOTOR_ID', i)\n", | |
" value = get('DETECTOR_ID')\n", | |
" # bespoke I/O code\n", | |
" except:\n", | |
" # bespoke cleanup to ensure hardware safety\n", | |
"```\n", | |
"\n", | |
"Metadata is stuffed into filenames or custom headers." | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"source": [ | |
"### ophyd (device abstraction layer)\n", | |
"\n", | |
"```python\n", | |
"In [3]: import ophyd\n", | |
"\n", | |
"In [4]: motor = ophyd.EpicsMotor('MOTOR_ID', name='motor')\n", | |
"\n", | |
"In [5]: motor.read()\n", | |
"Out[5]: {'motor':\n", | |
" {'value': 5.0,\n", | |
" 'timestamp': 1468325228.751564}}\n", | |
" \n", | |
"In [6]: motor.set(6.0)\n", | |
"```\n", | |
"\n", | |
"Devices are expected to support a common interface: `read`, `set`, `stop`, ....\n", | |
"\n", | |
"Our devices talk to EPICS, but yours could talk to LabView, RasberryPi, raw serial, etc." | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"source": [ | |
"Devices provide human-friendly names (useful during analysis) and a hierarchical structure.\n", | |
"\n", | |
"```python\n", | |
"class MultiAxisMirror(ophyd.Device):\n", | |
" x = ophyd.Component(ophyd.EpicsMotor, ':X')\n", | |
" y = ophyd.Component(ophyd.EpicsMotor, ':Y')\n", | |
" pitch = ophyd.Component(ophyd.EpicsMotor, ':P')\n", | |
"\n", | |
"\n", | |
"mirror = MultiAxisMirror('SOME_ID', name='mirror')\n", | |
"\n", | |
"In [1]: mirror.read()\n", | |
"Out[1]: {'mirror_x': {'value': 1.0, ...},\n", | |
" ...: 'mirror_y': {'value': 1.5, ...},\n", | |
" ...: 'mirror_pitch': {'value': 0.3, ...}}\n", | |
"```\n" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"source": [ | |
"### bluesky (experiment specification/execution)\n", | |
"\n", | |
"New-style data acquisition program (more declarative, less imperative):\n", | |
"\n", | |
"```python\n", | |
"from bluesky.plans import (open_run, close_run,\n", | |
" abs_set,\n", | |
" trigger_and_read)\n", | |
" \n", | |
"def plan():\n", | |
" \"scan 'motor' from 1 to 5 while reading 'det'\"\n", | |
" yield from open_run(some_metadata_dict)\n", | |
" for i in range(5):\n", | |
" yield from abs_set(motor, i)\n", | |
" yield from trigger_and_read([det])\n", | |
" yield from close_run()\n", | |
"\n", | |
"RE(plan()) # execute (I/O and cleanup for free!)\n", | |
"```" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"source": [ | |
"### A Two-Slide Crash Course in ``yield`` and ``yield from``" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"outputs": [], | |
"source": [ | |
"# Python 2.5+ (PEP 342)\n", | |
"\n", | |
"def g():\n", | |
" # g is a 'generator'\n", | |
" yield 1\n", | |
" yield 2\n", | |
" \n", | |
"a = g() # a is a 'generator instance'" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"outputs": [], | |
"source": [ | |
"next(a)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"outputs": [], | |
"source": [ | |
"next(a)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"outputs": [], | |
"source": [ | |
"list(g())" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"outputs": [], | |
"source": [ | |
"# Python 3.3+ (PEP 380)\n", | |
"\n", | |
"def h():\n", | |
" yield 0\n", | |
" yield from g()\n", | |
" yield 4\n", | |
"\n", | |
"list(h())" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"source": [ | |
"Go watch James Powell's *Generators Will Free Your Mind* on YouTube!" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"source": [ | |
"## The rest of this talk:\n", | |
"\n", | |
"(1) How the **RunEngine** `RE` executes the instructions in the `plan`\n", | |
"\n", | |
"(2) Neat implications of expressing a **science experiment as a generator**\n", | |
"\n", | |
"(3) Reliably **capturing state and semantics** is a critical input to reproducible workflows in experimental science" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "slide" | |
} | |
}, | |
"source": [ | |
"## The RunEngine from Scratch\n", | |
"\n", | |
"In Which We Built Progressively More Complex Implementations" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"source": [ | |
"### A 'plan' is an iterable of 'messages'\n", | |
"\n", | |
"A `Msg` is a `namedtuple` with four fields:\n", | |
"\n", | |
"* a **command**, given as a string, e.g., ``'set'`` or ``'sleep'``\n", | |
"* a target **obj**, e.g., ``motor`` (if applicable)\n", | |
"* positional **args**\n", | |
"* **kwargs**" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"outputs": [], | |
"source": [ | |
"from bluesky import Msg\n", | |
"\n", | |
"Msg('sleep', None, 1)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"source": [ | |
"### Version 0: the simplest possible RunEngine" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"outputs": [], | |
"source": [ | |
"import time\n", | |
"from bluesky import Msg\n", | |
"\n", | |
"function_map = {'sleep': lambda msg: time.sleep(*msg.args),\n", | |
" 'print': lambda msg: print(*msg.args)}\n", | |
"\n", | |
"def RE_v0(plan):\n", | |
" for msg in plan:\n", | |
" print('PROCESSING: %r' % (msg,))\n", | |
" func = function_map[msg.command]\n", | |
" func(msg)\n", | |
" \n", | |
"sleepy_plan = [Msg('sleep', None, 1),\n", | |
" Msg('print', None, 'HELLO WORLD')]\n", | |
"\n", | |
"RE_v0(sleepy_plan)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"source": [ | |
"### Version 1: a RunEngine that supports adaptive plan logic" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"outputs": [], | |
"source": [ | |
"from bluesky.utils import ensure_generator\n", | |
"\n", | |
"def RE_v1(plan):\n", | |
" plan = ensure_generator(plan)\n", | |
" last_result = None\n", | |
"\n", | |
" while True:\n", | |
" try:\n", | |
" msg = plan.send(last_result)\n", | |
" except StopIteration:\n", | |
" break\n", | |
" print('PROCESSING: %r' % (msg,))\n", | |
" func = function_map[msg.command]\n", | |
" last_result = func(msg)\n", | |
"\n", | |
"function_map['sum'] = lambda msg: sum(msg.args)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"outputs": [], | |
"source": [ | |
"def adding_plan():\n", | |
" \"Ask the RunEngine to add to numbers. Print the result.\"\n", | |
" yield Msg('sleep', None, 1)\n", | |
" result = yield Msg('sum', None, 3, 4)\n", | |
" print('RECEIVED:', result)\n", | |
" \n", | |
"RE_v1(adding_plan())" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"outputs": [], | |
"source": [ | |
"def adaptive_adding_plan():\n", | |
" \"Keep adding 3 until the result is greater than 8.\"\n", | |
" result = 1\n", | |
" while True:\n", | |
" yield Msg('sleep', None, 1)\n", | |
" result = yield Msg('sum', None, result, 3)\n", | |
" print('RECEIVED:', result)\n", | |
" if result > 8:\n", | |
" print('we are done')\n", | |
" break\n", | |
" \n", | |
"RE_v1(adaptive_adding_plan())" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"source": [ | |
"### Version 2: refactor as a callable class\n", | |
"\n", | |
"A class, unlike a simple function, gives us access to the internal state." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"outputs": [], | |
"source": [ | |
"class RunEngine_v2:\n", | |
" def __call__(self, plan):\n", | |
" self._run(plan)\n", | |
" \n", | |
" def _run(self, plan):\n", | |
" plan = ensure_generator(plan)\n", | |
" last_result = None\n", | |
"\n", | |
" while True:\n", | |
" try:\n", | |
" msg = plan.send(last_result)\n", | |
" except StopIteration:\n", | |
" break\n", | |
" print('PROCESSING: %r' % (msg,))\n", | |
" func = function_map[msg.command]\n", | |
" last_result = func(msg)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"outputs": [], | |
"source": [ | |
"# It still works.\n", | |
"RE_v2 = RunEngine_v2()\n", | |
"RE_v2(adaptive_adding_plan())" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"source": [ | |
"### Version 3: toward a RunEngine that supports interruptions / resuming\n", | |
"\n", | |
"* The RunEngine still manages the main loop, processing the plan\n", | |
"* asyncio provides an outer event loop that manages multiple frames of execution" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"outputs": [], | |
"source": [ | |
"import asyncio\n", | |
"\n", | |
"loop = asyncio.get_event_loop()\n", | |
"\n", | |
"# Reimplement all command functions as coroutines.\n", | |
"\n", | |
"@asyncio.coroutine\n", | |
"def _sum(msg):\n", | |
" return sum(msg.args)\n", | |
"\n", | |
"@asyncio.coroutine\n", | |
"def _sleep(msg):\n", | |
" yield from asyncio.sleep(*msg.args, loop=loop)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": { | |
"collapsed": true, | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"outputs": [], | |
"source": [ | |
"class RunEngine_v3:\n", | |
" def __init__(self):\n", | |
" loop = asyncio.new_event_loop()\n", | |
" self.coroutine_map = {'sleep': _sleep,\n", | |
" 'sum': _sum}\n", | |
" \n", | |
" def __call__(self, plan):\n", | |
" self._task = loop.create_task(self._run(plan))\n", | |
" loop.run_until_complete(self._task)\n", | |
" \n", | |
" if self._task.done() and not self._task.cancelled():\n", | |
" exc = self._task.exception()\n", | |
" if exc is not None:\n", | |
" raise exc\n", | |
" \n", | |
" @asyncio.coroutine\n", | |
" def _run(self, plan):\n", | |
" plan = ensure_generator(plan)\n", | |
" last_result = None\n", | |
"\n", | |
" while True:\n", | |
" try:\n", | |
" msg = plan.send(last_result)\n", | |
" except StopIteration:\n", | |
" break\n", | |
" print('PROCESSING: %r' % (msg,))\n", | |
" coroutine = self.coroutine_map[msg.command]\n", | |
" last_result = yield from coroutine(msg)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"outputs": [], | |
"source": [ | |
"# And it still works\n", | |
"RE_v3 = RunEngine_v3()\n", | |
"RE_v3(adaptive_adding_plan())" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "slide" | |
} | |
}, | |
"source": [ | |
"## Execution can be interrupted and cleanly resumed\n", | |
"\n", | |
"* Interrupt interactively with Ctrl+C\n", | |
"* Interrupt programmatically from the plan or in response to some external condition." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"outputs": [], | |
"source": [ | |
"from bluesky import RunEngine # finally, the real thing\n", | |
"\n", | |
"# Make a RunEngine.\n", | |
"RE = RunEngine({})\n", | |
"\n", | |
"# Teach it our toy command, 'sum'.\n", | |
"@asyncio.coroutine\n", | |
"def _sum(msg):\n", | |
" return sum(msg.args)\n", | |
"\n", | |
"RE.register_command('sum', _sum)\n", | |
" \n", | |
"# Make it verbose, as our demo versions were.\n", | |
"RE.msg_hook = lambda msg: print(\"PROCESSING:\", msg)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": { | |
"collapsed": false, | |
"scrolled": true, | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"outputs": [], | |
"source": [ | |
"# Demo capturing Ctrl+C\n", | |
"\n", | |
"RE(adaptive_adding_plan())" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"outputs": [], | |
"source": [ | |
"# RunEngine \"rewinds\" its cache of messages and\n", | |
"# starts again from the beginning.\n", | |
"RE.resume()" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": { | |
"collapsed": true, | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"outputs": [], | |
"source": [ | |
"from itertools import count\n", | |
"\n", | |
"def pausing_plan():\n", | |
" \"Pause after the second iteration and let the user resume or abort.\"\n", | |
" ret = 1\n", | |
" for i in count():\n", | |
" yield Msg('sleep', None, 1)\n", | |
" if i == 1:\n", | |
" yield Msg('pause')\n", | |
" ret = yield Msg('sum', None, ret, 3)\n", | |
" print('RECEIVED:', ret)\n", | |
" if ret > 8:\n", | |
" print('we are done')\n", | |
" break" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": { | |
"collapsed": false, | |
"scrolled": false, | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"outputs": [], | |
"source": [ | |
"RE(pausing_plan())" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"outputs": [], | |
"source": [ | |
"# RunEngine resumes from where it left off.\n", | |
"RE.resume()" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": { | |
"collapsed": true, | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"outputs": [], | |
"source": [ | |
"from itertools import count\n", | |
"\n", | |
"def pausing_plan_with_checkpoints():\n", | |
" \"Pause after the second iteration and let the user resume or abort.\"\n", | |
" ret = 1\n", | |
" for i in count():\n", | |
" yield Msg('checkpoint') # NEW\n", | |
" yield Msg('sleep', None, 1)\n", | |
" if i == 1:\n", | |
" yield Msg('pause')\n", | |
" ret = yield Msg('sum', None, ret, 3)\n", | |
" print('RECEIVED:', ret)\n", | |
" if ret > 8:\n", | |
" print('we are done')\n", | |
" break" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": { | |
"collapsed": false, | |
"scrolled": true, | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"outputs": [], | |
"source": [ | |
"RE(pausing_plan_with_checkpoints())" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"outputs": [], | |
"source": [ | |
"RE.resume()" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"outputs": [], | |
"source": [ | |
"RE(pausing_plan_with_checkpoints())\n", | |
"RE.abort()" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "slide" | |
} | |
}, | |
"source": [ | |
"## Plans can handle exceptions" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": { | |
"collapsed": false, | |
"scrolled": false, | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"outputs": [], | |
"source": [ | |
"def problematic_plan():\n", | |
" yield Msg('sleep', None, 1)\n", | |
" print(\"ERROR!\")\n", | |
" raise Exception(\"Problematic!\")\n", | |
" \n", | |
"RE(problematic_plan())" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"outputs": [], | |
"source": [ | |
"from bluesky.plans import finalize_wrapper\n", | |
"\n", | |
"def cleanup():\n", | |
" # e.g., move motors back to safe positions.\n", | |
" yield Msg('sleep', None, 2)\n", | |
" print(\"Everything has been made safe.\")\n", | |
" \n", | |
"def make_safe(plan):\n", | |
" yield from finalize_wrapper(plan, cleanup())\n", | |
"\n", | |
"# cleanup() will be yielded from before\n", | |
"# exception is re-raised.\n", | |
"RE(make_safe(problematic_plan()))" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"outputs": [], | |
"source": [ | |
"from bluesky.plans import finalize_wrapper\n", | |
" \n", | |
"def raises_in_RE():\n", | |
" \"will cause an error inside _sleep() coroutine\"\n", | |
" yield Msg('sleep', None, 'a')\n", | |
"\n", | |
"RE(raises_in_RE())" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"outputs": [], | |
"source": [ | |
"# cleanup() will be yielded from before\n", | |
"# exception is re-raised\n", | |
"RE(make_safe(raises_in_RE()))" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"outputs": [], | |
"source": [ | |
"def reckless_plan():\n", | |
" try:\n", | |
" yield from raises_in_RE()\n", | |
" except Exception as exc:\n", | |
" print(\"Ignoring:\", exc)\n", | |
" yield Msg('sleep', None, 1)\n", | |
" \n", | |
"RE(reckless_plan())" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "slide" | |
} | |
}, | |
"source": [ | |
"## The RunEngine collates all metadata and data into validated dicts\n", | |
"\n", | |
"* Dicts contain metadata and data.\n", | |
"* They are validated against a JSON schema.\n", | |
"* Dicts are dispatched to a list of \"subscribers\".\n", | |
" * Subscribers can be blocking or non-blocking.\n", | |
" * Subscribers can be lossy.\n", | |
"\n", | |
"Examples:\n", | |
"* Insert dicts into to NoSQL database.\n", | |
"* Print, plot, or produce log entries.\n", | |
"* Write CSV files or HDF5 files." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"outputs": [], | |
"source": [ | |
"RE.msg_hook = None # turn off verbosity\n", | |
"\n", | |
"# Use synthetic hardware objects.\n", | |
"from bluesky.examples import det, motor\n", | |
"motor._fake_sleep = 0.25 # sim moving time\n", | |
"\n", | |
"from bluesky.plans import (open_run, close_run,\n", | |
" abs_set,\n", | |
" trigger_and_read)\n", | |
"\n", | |
"def plan():\n", | |
" \"scan 'motor' from 1 to 5 while reading 'detector'\"\n", | |
" yield from open_run({'some_metadata': 'hello world'})\n", | |
" for i in range(5):\n", | |
" yield from abs_set(motor, i)\n", | |
" yield from trigger_and_read([det, motor])\n", | |
" yield from close_run()" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"outputs": [], | |
"source": [ | |
"RE(plan())" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"outputs": [], | |
"source": [ | |
"RE(plan(), print)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"outputs": [], | |
"source": [ | |
"from bluesky.callbacks import LiveTable\n", | |
"\n", | |
"RE(plan(), LiveTable([motor, det]))" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"outputs": [], | |
"source": [ | |
"%matplotlib notebook\n", | |
"import matplotlib.pyplot as plt" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": { | |
"collapsed": false, | |
"scrolled": false, | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"outputs": [], | |
"source": [ | |
"from bluesky.callbacks import LivePlot\n", | |
"\n", | |
"RE(plan(), LivePlot('det', 'motor'))" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "slide" | |
} | |
}, | |
"source": [ | |
"## More Use Cases: Adaptive Steps and Nested Generators\n", | |
"\n", | |
"Behold the Power of the Fully *Operational* RunEngine" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"outputs": [], | |
"source": [ | |
"plt.figure();" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"outputs": [], | |
"source": [ | |
"from bluesky.plans import adaptive_scan\n", | |
"motor._fake_sleep = 0\n", | |
"\n", | |
"RE(adaptive_scan([det], 'det', motor, -10, 10, 0.1, 5, 0.1, True),\n", | |
" LivePlot('det', 'motor', marker='o'))" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"outputs": [], | |
"source": [ | |
"RE(plan(), LiveTable([motor, det]))" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"outputs": [], | |
"source": [ | |
"from bluesky.plans import relative_set_wrapper\n", | |
"\n", | |
"# We left motor at position 4.0 above.\n", | |
"\n", | |
"RE(relative_set_wrapper(plan()), LiveTable([motor, det]))" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": { | |
"collapsed": false, | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"outputs": [], | |
"source": [ | |
"from bluesky.plans import msg_mutator\n", | |
"\n", | |
"def triple_sleep(msg):\n", | |
" \"Alter any sleep messages to triple the sleep time.\"\n", | |
" if msg.command == 'sleep':\n", | |
" t, = msg.args\n", | |
" new_msg = msg._replace(args=(3 * t,))\n", | |
" return new_msg\n", | |
" else:\n", | |
" return msg\n", | |
"\n", | |
"RE.msg_hook = print\n", | |
"RE(msg_mutator(adding_plan(), triple_sleep))" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "slide" | |
} | |
}, | |
"source": [ | |
"## Additional Info\n", | |
"\n", | |
"<p>This talk: http://tiny.cc/dba-scipy2016</p>\n", | |
"<p>Source: https://github.com/NSLS-II</p>\n", | |
"<p>Project Documentation: https://NSLS-II.github.io</p>\n", | |
"<p>Document Model: https://NSLS-II.github.io/architecture-overview.html</p>" | |
] | |
} | |
], | |
"metadata": { | |
"anaconda-cloud": {}, | |
"celltoolbar": "Slideshow", | |
"kernelspec": { | |
"display_name": "Python [bnl]", | |
"language": "python", | |
"name": "Python [bnl]" | |
}, | |
"language_info": { | |
"codemirror_mode": { | |
"name": "ipython", | |
"version": 3 | |
}, | |
"file_extension": ".py", | |
"mimetype": "text/x-python", | |
"name": "python", | |
"nbconvert_exporter": "python", | |
"pygments_lexer": "ipython3", | |
"version": "3.5.2" | |
}, | |
"nbpresent": { | |
"slides": { | |
"dbc322bf-e91e-46c0-bfbc-0e7360629b7b": { | |
"id": "dbc322bf-e91e-46c0-bfbc-0e7360629b7b", | |
"prev": "ffd53d27-ea32-4370-bd07-35b600c3ab58", | |
"regions": { | |
"d3eb6fc4-5017-4091-8686-1348d63cfd3a": { | |
"attrs": { | |
"height": 1, | |
"width": 1, | |
"x": 0, | |
"y": 0 | |
}, | |
"id": "d3eb6fc4-5017-4091-8686-1348d63cfd3a" | |
} | |
} | |
}, | |
"ffd53d27-ea32-4370-bd07-35b600c3ab58": { | |
"id": "ffd53d27-ea32-4370-bd07-35b600c3ab58", | |
"layout": "grid", | |
"prev": null, | |
"regions": { | |
"558cca0b-969a-417b-bbc3-2caf93e21cc5": { | |
"attrs": { | |
"height": 0.8333333333333334, | |
"pad": 0.01, | |
"width": 0.8333333333333334, | |
"x": 0.08333333333333333, | |
"y": 0.08333333333333333 | |
}, | |
"id": "558cca0b-969a-417b-bbc3-2caf93e21cc5" | |
}, | |
"c0806422-85fb-4629-bd67-86d1fd3809e8": { | |
"attrs": { | |
"height": 1, | |
"pad": 0.01, | |
"width": 1, | |
"x": 0, | |
"y": 0 | |
}, | |
"id": "c0806422-85fb-4629-bd67-86d1fd3809e8" | |
} | |
} | |
} | |
}, | |
"themes": {} | |
} | |
}, | |
"nbformat": 4, | |
"nbformat_minor": 0 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment