Skip to content

Write a plan

Unlike simpler scripts that are left to users for ad-hoc use, plans in a beamline context must adhere to specific conventions due to their complexity. To write a plan, you will have to use the bluesky native syntax (python). Read the corresponding section in the how-to guides.

Where to save the file with the plan

The file with the plan must be save in ~/bluesky/beamlinetools/beamlinetools/plans/myplan.py. Replace myplan.py with a name that makes sense. Name the plan something that makes sense. In this and the following section we will refer to it as align_second_crystal_roll.

How to include the plan in the bluesky installation

In the file ~/bluesky/beamlinetools/beamlinetools/beamline_config/plans.py import your plan:

from beamlinetools.plans.myplan import my_wonderful_plan

Then in the file ~/bluesky/beamlinetools/beamlinetools/beamline_config/plans.py look for the section # Create aliases for standard plans and create an alias adding _plan at the end of your plan name:

# Create aliases for standard plans
...
...
flyscan_plan = flyscan
align_second_crystal_roll_plan = align_second_crystal_roll

Restart Bluesky.

Example

Below is an example of a plan that aligns the second crystal of a DCM to maximize detector current.

Do the necessary imports

The imports include functions for relative scanning and utility stubs from Bluesky, along with specific beamline configurations for devices and beamline control.

from bluesky.plans import relative_scan as dscan
import bluesky.plan_stubs as bps

from beamlinetools.beamline_config.base import bec
from beamlinetools.beamline_config.beamline import dcm, kth01

Plan definition

Defines a function align_second_crystal_roll that scans the roll of the second crystal of a DCM, analyzing the results to adjust the crystal's position. The function uses parameters to control the scan range, number of steps, and an optional offset for fine adjustments.

def align_second_crystal_roll(cr2roll_range_u_deg:float=100, steps:int=20, offset:float=0, md=None):
    """
    This plan will scan the dcm 2nd crystal ropi position and then move to the center of the peak.


    Parameters
    ------------
    cr2roll_range_u_deg : float, default 1
        degrees either side of the current position to scan
    steps : int, default 20
        number of steps to take
    offset : float, default 0
        offset to add to the maximum peak position to move to
    md : dict, optional

    -------
    example usage:
        RE(align_second_crystal(steps=50, offset=0.5)  # scan 50 steps, sets the final position to half of the fwhm

    """

Metadata Setup

Sets up a metadata dictionary _md that includes the plan name, involved detectors, and the type of scan. This metadata is important for record-keeping and data analysis.

_md = {'plan_name': 'align_second_crystal',
        'detectors': [kth01.name],
        'scan_type': 'alignment',
        'hints': {}
    }
_md.update(md or {})    

Performing the Scan

Uses a generator-based approach with yield from to perform a relative scan. The yield from syntax is crucial as it allows the plan to be paused and resumed, enabling integration into the larger Bluesky event-driven framework.

yield from dscan([kth01], dcm.cr2roll, -cr2roll_range_u_deg/1000, cr2roll_range_u_deg/1000, steps, md=_md)

Analyzing Results and Adjusting Position

After the scan, the function analyzes the peak data stored in bec.peaks and adjusts the roll position of the crystal based on the detected peak and an optional offset applied to the full width at half maximum (FWHM).

peaks_dict = bec.peaks
max_pos = peaks_dict['max']['kth01'][0]
if peaks_dict['fwhm']['kth01']:
    fwhm = peaks_dict['fwhm']['kth01'][0]
    max_pos += fwhm*offset


yield from bps.mv(dcm.cr2roll, max_pos)

Complete Plan

Here is the complete plan combining all parts, providing a structured approach to align the second crystal:

from bluesky.plans import relative_scan as dscan
import bluesky.plan_stubs as bps


# from beamline
from beamlinetools.beamline_config.base import bec
from beamlinetools.beamline_config.beamline import dcm, kth01


def align_second_crystal_roll(cr2roll_range_u_deg:float=100, steps:int=20, offset:float=0, md=None):
    """
    This plan will scan the dcm 2nd crystal ropi position and then move to the center of the peak.


    Parameters
    ------------
    cr2roll_range_u_deg : float, default 1
        degrees either side of the current position to scan
    steps : int, default 20
        number of steps to take
    offset : float, default 0
        offset to add to the maximum peak position to move to
    md : dict, optional

    -------
    example usage:
        RE(align_second_crystal(steps=50, offset=0.5)  # scan 50 steps, sets the final position to half of the fwhm

    """
    _md = {'plan_name': 'align_second_crystal',
           'detectors': [kth01.name],
           'scan_type': 'alignment',
           'hints': {}
        }
    _md.update(md or {})    
    yield from dscan([kth01], dcm.cr2roll, -cr2roll_range_u_deg/1000, cr2roll_range_u_deg/1000, steps, md=_md)
    peaks_dict = bec.peaks
    max_pos = peaks_dict['max']['kth01'][0]
    if peaks_dict['fwhm']['kth01']:
        fwhm = peaks_dict['fwhm']['kth01'][0]
        max_pos += fwhm*offset


    yield from bps.mv(dcm.cr2roll, max_pos)