Concatenating plans¶
Problem¶
Write a custom plan to run a multi-step procedure and/or collect data for more than one run.
Approach¶
Bluesky provides several ways to combine plans. The choice depends on your use case and, to some extent, your personal taste.
Example Solutions¶
We will use some standard plans (scan, count), some stub plans (abs_set, sleep), and some plan utilities (pchain, and more later). See the plans documentation in bluesky for more.
from bluesky.plans import pchain, scan, count, abs_set, sleep
from bluesky.examples import det, motor
Simple chaining¶
The simplest approach is pchain
(for “plan chain”). Examples:
# scan; sleep for 5 seconds; scan again with denser steps
pchain(scan([det], motor, 1, 5, 5),
sleep(5),
scan([det], motor, 1, 5, 10))
# scan; pause and wait for the user to resume; scan again
pchain(scan([det], motor, 1, 5, 5),
pause(),
scan([det], motor, 1, 5, 5))
# scan a motor; then move a different motor; then count
pchain(scan([det], motor, 1, 5, 5),
abs_set(motor2, 5),
count([det]))
Controlling plans with arbitrary logic¶
Alternatively, plans can be combined by yielding from each plan in turn:
def scan_sleep_scan():
yield from scan([det], motor, 1, 5, 5)
yield from sleep(5)
yield from scan([det], motor, 1, 5, 10)
This is (approximately) what pchain
does. But this longer form gives us the
opportunity to intercept the results from intermediate steps and use them
to make plans responsive or adaptive. For example, we can query the user for
input:
from plans import input # note: this shadows the built-in function of the same name
def interactive_plan():
"Ask the user for a position. Move the motor there and count a detector."
pos = yield from input('where to?')
pos = float(pos) # convert string input to number, e.g., '5' -> 5
yield from abs_set(motor, pos)
yield from count([det])
We can also print to report progress, and we can include loops:
def loopy_plan():
for i in range(5):
print('iteration number', i)
yield from count([det])
# Note this is an illustrative example. In practice, for this specific
# case just use: count([det], num=5)
Avoiding ‘yield from’ with a decorator¶
If the yield from
seems forbidding, write a simple function that returns
a list of plans, and top it with a decorator that takes care of the rest.
This:
from bluesky.plans import planify
@planify
def scan_sleep_scan():
return [scan([det], motor, 1, 5, 5),
sleep(5),
scan([det], motor, 1, 5, 10)]
is precisely equivalent to:
def scan_sleep_scan():
yield from scan([det], motor, 1, 5, 5)
yield from sleep(5)
yield from scan([det], motor, 1, 5, 10)
You may use whichever pattern you find more readable.