#!/usr/bin/env python # coding: utf-8 # # Hello Bluesky: Reading detectors and scanning # # In this notebook you will: # # * Connect to some simulated hardware. # * Acquire some data via two common experimental procedures ("plans"), ``count`` and ``scan``. # * Write a custom plan. # # Recommend Prerequisites: # # * [Hello Python and Jupyter](./Hello%20Python%20and%20Jupyter.ipynb) # ## Configuration # Below, we will connect to EPICS IOC(s) controlling simulated hardware in lieu of actual motors and detectors. An EPICS IOC is control system software that allows communication with a wide variety of hardware using a common interface. The IOCs should already be running in the background. Run this command to verify that they are running: it should produce output with RUNNING on each line. In the event of a problem, edit this command to replace `status` with `restart all` and run again. # In[ ]: get_ipython().system('supervisor/start_supervisor.sh status') # In[ ]: from bluesky_tutorial_utils.beamline_configuration import * # Check that we can communicate with the hardware. If this doesn't raise an error, it worked. # In[ ]: det.wait_for_connection() # ## Data Acquisition # # ### Executing a `count` plan with various parameters # In the example below, the Bluesky run engine is the interpreter of experiment plans and `count` is an experiment plan used here to acquire one reading from a point detector. # In[ ]: from bluesky.plans import count RE(count([det])) # The return value is a list of the run IDs that uniquely identify this data set. The "scan num" is easier to remember but is not good for long-term reference because it may not be unique. # # Let's looks at the documentation for `count` to see what our other options are. # In[ ]: help(count) # or, equiavently, type count? or ?count # Executing the next cell will display an empty widget. In the sections below, the scans that we run will add figures to this widget. # # If you are reading this in JupyterLab, right-click somewhere in the output area below and choose "Create New View for Output". This will display a up-to-date copy of the figures off to the side of this notebook, and avoid the need for frequent scrolling between this widget and the code that follows. # In[ ]: auto_plot_view # In[ ]: # five consecutive readings RE(count([det], num=5)) # In[ ]: # five sequential readings separated by a 1-second delay RE(count([det], num=5, delay=1)) # ### Scan # # Scan ``motor`` from -10 to 10, stopping at 15 equally-spaced points along the way and reading ``det``. # In[ ]: RE(scan([det], motor, -10, 10, 15)) # ### Simulators # # Bluesky includes utilities to inspecting plans before they are run. You can imagine various reasons you might want to do this. Example: # In[ ]: from bluesky.simulators import summarize_plan summarize_plan(scan([det], motor, -1, 1, 3)) # ### Custom plan # # Define a custom "plan", using the Python syntax ``yield from`` to dispatch out to built-in plans. # In[ ]: # The plan_stubs module contains smaller plans. # They can be used alone or as buildling blocks for larger plans. from bluesky.plan_stubs import mv def sweep_exposure_time(times): "Multiple scans: one per exposure time setting." for t in times: yield from mv(det.exp, t) yield from scan([det], motor, -10, 10, 5) # Before we run, let's make our simulated motor move faster, just to save time in this example. # In[ ]: motor.delay = 0 # In[ ]: RE(sweep_exposure_time([0.01, 0.1, 1])) # ## Exercises # Q1: Above we ran a `count` with multiple readings separated by a fixed delay. The ``delay`` parameter also accepts a list of values. Try a `count` with a variable delay. # # In[ ]: # Try your solution here. Fill in the blank: # RE(count(____))) # Execute the following cell to reveal a solution: # In[ ]: get_ipython().run_line_magic('load', 'solutions/count_variable_delay.py') # Q2: Write a custom plan that scans the same region twice, first with coarse steps and then with fine steps. # In[ ]: # Try your solution here. Fill in the blank: # def coarse_and_fine(detectors, motor, start, stop): # yield from scan(___) # yield from scan(___) # # RE(coarse_and_fine([det], motor, -10, 10)) # In[ ]: get_ipython().run_line_magic('load', 'solutions/scan_coarse_and_fine.py') # Q3. All of the usages of scan we have seen so far scan from negative to positive. Scan from positive to negative. # In[ ]: # Try your solution here. # In[ ]: get_ipython().run_line_magic('load', 'solutions/scan_positive_to_negative.py') # Q4: The ``scan`` plan samples equally-spaced points. To sample *arbitrary* points, you can use ``list_scan``. Import it from the same module that we imported ``scan`` from, then use ``list_scan?`` to view its documentation and figure out how to use it. Scan the positions ``[1, 1, 2, 3, 5, 8]``. # In[ ]: # Try your solution here. # In[ ]: get_ipython().run_line_magic('load', 'solutions/scan_fibonacci.py') # Q5: What's wrong with this? (What does it do?) # In[ ]: # Broken example def sweep_exposure_time(times): "Multiple scans: one per exposure time setting." for t in times: mv(det.exp, t) scan([det], motor, -10, 10, 15) # In[ ]: get_ipython().run_line_magic('load', 'solutions/broken_sweep_exposure_time_explanation.txt')