Plans¶
An experimental procedure is represented as a sequence of granular instructions. In bluesky, each instruction is called a message and a sequence of instructions is called a plan.
The plans are organized like a burger menu. A variety of fully-assembled plans are provided — just as you might order the All-American Burger or The Hog Wild Burger. But you can also build your own plan by mixing the ingredients to make something original.
(For developers: what we are calling a “plan” is not actually a special data
structure. It is typically a generator, but it can be any iterable or iterator
— a list, for example. The messages in a plan are Msg
objects, a
namedtuple.)
Standard Plans (ready to use)¶
The names below are links. Click for details, and see below for examples.
count |
Take one or more readings from detectors. |
scan |
Scan over one variable in equally spaced steps. |
relative_scan |
Scan over one variable in equally spaced steps relative to current positon. |
list_scan |
Scan over one variable in steps. |
relative_list_scan |
Scan over one variable in steps relative to current position. |
log_scan |
Scan over one variable in log-spaced steps. |
relative_log_scan |
Scan over one variable in log-spaced steps relative to current position. |
inner_product_scan |
Scan over one multi-motor trajectory. |
outer_product_scan |
Scan over a mesh; each motor is on an independent trajectory. |
relative_inner_product_scan |
Scan over one multi-motor trajectory relative to current position. |
relative_outer_product_scan |
Scan over a mesh relative to current position. |
scan_nd |
Scan over an arbitrary N-dimensional trajectory. |
spiral |
Spiral scan, centered around (x_start, y_start) |
spiral_fermat |
Absolute fermat spiral scan, centered around (x_start, y_start) |
relative_spiral |
Relative spiral scan |
relative_spiral_fermat |
Relative fermat spiral scan |
adaptive_scan |
Scan over one variable with adaptively tuned step size. |
relative_adaptive_scan |
Relative scan over one variable with adaptively tuned step size. |
tweak |
Move and motor and read a detector with an interactive prompt. |
fly |
Perform a fly scan with one or more ‘flyers’. |
Basic Usage¶
Before we begin, we’ll make a RunEngine to execute our plans.
This RunEngine is not set up to save any data. You may already have a
RunEngine instance, RE
, defined in an IPython profile. If RE
is already
defined, do not redefine it. Just skip to the next step.
In [1]: from bluesky import RunEngine
In [2]: RE = RunEngine({})
Execute the a count
plan, which reads one or more detectors.
In [3]: from bluesky.plans import count
In [4]: from bluesky.examples import det # a simulated detector
In [5]: RE(count([det]))
Out[5]: ['83f74546-6dd1-43fb-9e35-ed5b4c82e339']
It worked, but the data was not displayed. This time, send the output
LiveTable
.
In [6]: from bluesky.callbacks import LiveTable
In [7]: RE(count([det]), LiveTable([det]))
+-----------+------------+------------+
| seq_num | time | det |
+-----------+------------+------------+
| 1 | 21:16:10.8 | 0.000 |
+-----------+------------+------------+
generator count ['53e4fd'] (scan num: 2)
Out[7]: ['53e4fdd0-5234-4a35-b1dd-9ab19175cc24']
Stub Plans (ingredients for remixing)¶
trigger_and_read |
Trigger and read a list of detectors and bundle readings into one Event. |
abs_set |
Set a value. |
rel_set |
Set a value relative to current value. |
wait |
Wait for all statuses in a group to report being finished. |
sleep |
Tell the RunEngine to sleep, while asynchronously doing other processing. |
checkpoint |
If interrupted, rewind to this point. |
clear_checkpoint |
Designate that it is not safe to resume. |
pause |
Pause and wait for the user to resume. |
deferred_pause |
Pause at the next checkpoint. |
open_run |
Mark the beginning of a new ‘run’. |
close_run |
Mark the end of the current ‘run’. |
create |
Bundle future readings into a new Event document. |
save |
Close a bundle of readings and emit a completed Event document. |
trigger |
Trigger and acquisition. |
read |
Take a reading and add it to the current bundle of readings. |
monitor |
Asynchronously monitor for new values and emit Event documents. |
unmonitor |
Stop monitoring. |
kickoff |
Kickoff a fly-scanning device. |
collect |
Collect data cached by a fly-scanning device and emit documents. |
configure |
Change Device configuration and emit an updated Event Descriptor document. |
stage |
‘Stage’ a device (i.e., prepare it for use, ‘arm’ it). |
unstage |
‘Unstage’ a device (i.e., put it in standby, ‘disarm’ it). |
subscribe |
Subscribe the stream of emitted documents. |
unsubscribe |
Remove a subscription. |
wait_for |
Low-level: wait for a list of asyncio.Future objects to set (complete). |
null |
Yield a no-op Message. |
one_1d_step |
Inner loop of a 1D step scan |
one_nd_step |
Inner loop of an N-dimensional step scan |
Combining Plans¶
Plans are iterables (roughly speaking, lists) and the Python language has nice facilities for handling them. For example to join to plans together, use
from bluesky.examples import motor, det
from bluesky.plans import scan, sleep, pchain
master_plan = pchain(scan([det], motor, 1, 5, 3),
sleep(),
scan([det], motor, 5, 10, 2)
RE(master_plan)
This has advantages over executing them in sequence like so:
# Don't do this.
RE(plan1); RE(plan2)
If there are no interruptions or errors, these two methods are equivalent. But
in the event of a problem, the RunEngine can do more to recover if it
maintains control. In the second example, it loses control between completing
plan1
and beginning plan2
.
What if want to print
or do other activities between executing the plans?
There is another way to combine plans to accomodate this.
def make_master_plan():
plan1 = scan([det1, det2], motor, 1, 5, 10)
plan2 = relative_scan([det1], motor, 5, 10, 10)
yield from plan1
print('plan1 is finished -- moving onto plan2')
yield from plan2
RE(make_master_plan()) # note the ()
Arbitrary Python code can go inside master_plan
. It could employ if
blocks, for
loops – anything except a return
statement. If you want to
know more about what is happening here, structures like this in Python are
called generators.
Here are a couple more useful recipes:
"Run plan1, wait for user confirmation, then run plan2."
def make_master_plan():
plan1 = scan([det1, det2], motor, 1, 5, 10)
plan2 = relative_scan([det1], motor, 5, 10, 10)
yield from plan1
# pause and consult the user
if input('continue? (y/n)') != 'y':
raise StopIteration
yield from plan2
"Run a plan several times, changing the step size each time."
def make_master_plan():
for num in range(5, 10):
# Change the number of steps in the plan in each loop
plan1 = scan([det1, det2], motor, 1, 5, num)
yield from plan1
Plan Context Managers¶
These context managers provide a sunninct, readable syntax for inserting plans before and after other plans.
baseline_context |
Read every device once upon entering and exiting the context. |
monitor_context |
Asynchronously monitor signals, generating separate event streams. |
subs_context |
Subscribe callbacks to the document stream; then unsubscribe on exit. |
run_context |
Enclose in ‘open_run’ and ‘close_run’ messages. |
event_context |
Bundle readings into an ‘event’ (a datapoint). |
stage_context |
Stage devices upon entering context and unstage upon exiting. |
For example, the baseline_context
reads a list of detectors at the
beginning and end of an experiment.
from bluesky.plans import baseline_context, count
from bluesky.examples import det
plans = []
with baseline_context(plans, [det]):
plans.append(count([det]))
Use with the planify
decorator to join the list of plans into one plan.
from bluesky.plans import planify
@planify
def count_with_baseline_readings():
plans = []
with baseline_context(plans, [det]):
plans.append(count([det]))
return plans
Plan Preprocessors¶
These “preprocessors” take in a plan and modify its contents on the fly.
For example, relative_set
rewrites all positions to be relative to the
initial position.
finalize_wrapper |
try...finally helper |
subs_wrapper |
Subscribe callbacks to the document stream; finally, unsubscribe. |
inject_md_wrapper |
Inject additional metadata into a run. |
run_wrapper |
Enclose in ‘open_run’ and ‘close_run’ messages. |
monitor_during_wrapper |
Monitor (asynchronously read) devices during runs. |
fly_during_wrapper |
Kickoff and collect “flyer” (asynchronously collect) objects during runs. |
baseline_wrapper |
Preprocessor that records a baseline of all devices after open_run |
relative_set_wrapper |
Interpret ‘set’ messages on devices as relative to initial position. |
reset_positions_wrapper |
Return movable devices to their initial positions at the end. |
lazily_stage_wrapper |
This is a preprocessor that inserts ‘stage’ messages and appends ‘unstage’. |
These wrappers operate on a generator instance. There are corresponding
functions that operate on a generator function. They are named
*_decorator
, corresponding to each *_wrapper
above.
def relative_scan(detectors, motor, start, stop, num):
absolute = scan(detectors, motor, start, stop, num)
relative = relative_set(absolute, [motor])
yield from relative
Or, equivalently, using the planify
decorator:
@planify
def relative_scan(detectors, motor, start, stop, num):
absolute = scan(detectors, motor, start, stop, num)
relative = relative_set(absolute, [motor])
return [relative]
Plan Utilities¶
pchain (*args) |
Like itertools.chain but using yield from |
msg_mutator (plan, msg_proc) |
A simple preprocessor that mutates or deletes single messages in a plan |
plan_mutator (plan, msg_proc) |
Alter the contents of a plan on the fly by changing or inserting messages. |
single_gen (msg) |
Turn a single message into a plan |
planify (func) |
Turn a function that returns a list of generators into a coroutine. |
broadcast_msg (command, objs, *args, **kwargs) |
Generate many copies of a mesasge, applying it to a list of devices. |
repeater (n, gen_func, *args, **kwargs) |
Generate n chained copies of the messages from gen_func |
caching_repeater (n, plan) |
Generate n chained copies of the messages in a plan. |
make_decorator (wrapper) |
The functions named *_wrapper accept a generator instance and return a mutated generator instance. |
Object-Oriented Standard Plans¶
These provide a different way of using the standard plans. The plan becomes a reusable object, whose parameters can be adjusted interactively between uses.
In [8]: from bluesky.plans import Scan
In [9]: from bluesky.examples import motor, det, det3
In [10]: plan = Scan([det], motor, 1, 5, 10)
In [11]: RE(plan)
+-----------+------------+------------+------------+------------+
| seq_num | time | det1 | det2 | det3 |
+-----------+------------+------------+------------+------------+
| 1 | 21:16:11.1 | | | |
| 2 | 21:16:11.1 | | | |
| 3 | 21:16:11.1 | | | |
| 4 | 21:16:11.1 | | | |
| 5 | 21:16:11.1 | | | |
| 6 | 21:16:11.1 | | | |
| 7 | 21:16:11.1 | | | |
| 8 | 21:16:11.2 | | | |
| 9 | 21:16:11.2 | | | |
| 10 | 21:16:11.2 | | | |
+-----------+------------+------------+------------+------------+
Scan scan ['b3ff5a'] (scan num: 3)
Out[11]: ['b3ff5a96-0e27-4aea-b6f0-9018897911f2']
In [12]: RE(plan)