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)
+-----------+------------+------------+------------+------------+
|   seq_num |       time |       det1 |       det2 |       det3 |
+-----------+------------+------------+------------+------------+
|         1 | 21:16:11.3 |            |            |            |
|         2 | 21:16:11.3 |            |            |            |
|         3 | 21:16:11.3 |            |            |            |
|         4 | 21:16:11.4 |            |            |            |
|         5 | 21:16:11.4 |            |            |            |
|         6 | 21:16:11.4 |            |            |            |
|         7 | 21:16:11.4 |            |            |            |
|         8 | 21:16:11.4 |            |            |            |
|         9 | 21:16:11.4 |            |            |            |
|        10 | 21:16:11.4 |            |            |            |
+-----------+------------+------------+------------+------------+
Scan scan ['969057'] (scan num: 4)
Out[12]: ['969057b8-2213-4366-9054-fd73f3c0679f']

Any of the plan’s parameters can be updated individually.

In [13]: plan.num = 4  # change number of data points from 10 to 4

In [14]: RE(plan)
+-----------+------------+------------+------------+------------+
|   seq_num |       time |       det1 |       det2 |       det3 |
+-----------+------------+------------+------------+------------+
|         1 | 21:16:11.6 |            |            |            |
|         2 | 21:16:11.6 |            |            |            |
|         3 | 21:16:11.6 |            |            |            |
|         4 | 21:16:11.6 |            |            |            |
+-----------+------------+------------+------------+------------+
Scan scan ['ef7341'] (scan num: 5)
Out[14]: ['ef734129-f982-41fc-a7e7-d8abd6cd66a7']

In [15]: plan.detectors.append(det3)  # add another detector

In [16]: RE(plan)
+-----------+------------+------------+------------+------------+
|   seq_num |       time |       det1 |       det2 |       det3 |
+-----------+------------+------------+------------+------------+
|         1 | 21:16:11.8 |            |            |      1.213 |
|         2 | 21:16:11.8 |            |            |      1.213 |
|         3 | 21:16:11.8 |            |            |      1.213 |
|         4 | 21:16:11.8 |            |            |      1.213 |
+-----------+------------+------------+------------+------------+
Scan scan ['93e72a'] (scan num: 6)
Out[16]: ['93e72a17-6848-4456-8d87-e9b4d8db7f8b']

The set method is a convenient way to update multiple parameters at once.

In [17]: plan.set(start=20, stop=25)
Count Take one or more readings from detectors.
Scan Scan over one variable in equally spaced steps.
RelativeScan Scan over one variable in equally spaced steps relative to current positon.
ListScan Scan over one variable in steps.
RelativeListScan Scan over one variable in steps relative to current position.
LogScan Scan over one variable in log-spaced steps.
RelativeLogScan Scan over one variable in log-spaced steps relative to current position.
InnerProductScan Scan over one multi-motor trajectory.
OuterProductScan Scan over a mesh; each motor is on an independent trajectory.
RelativeInnerProductScan Scan over one multi-motor trajectory relative to current position.
RelativeOuterProductScan Scan over a mesh relative to current position.
ScanND Scan over an arbitrary N-dimensional trajectory.
SpiralScan Spiral scan, centered around (x_start, y_start)
SpiralFermatScan Absolute fermat spiral scan, centered around (x_start, y_start)
RelativeSpiralScan Relative spiral scan
RelativeSpiralFermatScan Relative fermat spiral scan
AdaptiveScan Scan over one variable with adaptively tuned step size.
RelativeAdaptiveScan Relative scan over one variable with adaptively tuned step size.
Tweak Move and motor and read a detector with an interactive prompt.