Skip to content

Commit 2914e3e

Browse files
authored
Merge pull request #60 from ISISComputingGroup/tutorial
Add tutorial
2 parents 0d42959 + 5c384b5 commit 2914e3e

File tree

2 files changed

+213
-50
lines changed

2 files changed

+213
-50
lines changed

doc/index.rst

Lines changed: 7 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -22,58 +22,15 @@ abstraction library, which allows bluesky to communicate with an underlying cont
2222
- Bluesky or scanning-related utilities which are useful across multiple beamlines.
2323

2424

25-
Overview
26-
========
25+
Getting started
26+
===============
2727

28-
.. note::
29-
30-
bluesky is a very flexible data acquisition framework. The following example illustrates a minimal scan,
31-
not the full extent of bluesky's functionality.
32-
33-
Using ``ibex_bluesky_core``, one can define some simple instrument-specific devices::
34-
35-
from ibex_bluesky_core.devices.block import block_r, block_mot
36-
37-
mot = block_mot("mot") # An IBEX block pointing at a motor
38-
det = block_r(float, "p5") # A readback block
39-
40-
And define a simple step-scan which uses those devices::
41-
42-
import bluesky.plans as bp
43-
from ophyd_async.plan_stubs import ensure_connected
44-
45-
def my_plan(start: float, stop: float, num: int):
46-
yield from ensure_connected(det, mot, force_reconnect=True)
47-
yield from bp.scan([det], mot, start, stop, num)
48-
49-
After which, a simple scan can be run by a user::
50-
51-
from ibex_bluesky_core.run_engine import get_run_engine
52-
53-
# A bluesky RunEngine instance, already available if using IBEX GUI
54-
RE = get_run_engine()
55-
56-
# Scan "mot" from 0 to 10 in 5 steps, reading "my_detector" at each step.
57-
RE(my_plan(0, 10, 5))
58-
59-
That plan may then also use:
60-
61-
- Other `experimental plans <https://blueskyproject.io/bluesky/main/plans.html#summary>`_ or
62-
`plan stubs <https://blueskyproject.io/bluesky/main/plans.html#stub-plans>`_, to build up more complex
63-
plans.
64-
- `Callbacks <https://blueskyproject.io/bluesky/main/callbacks.html>`_ provided by either
65-
``ibex_bluesky_core`` or ``bluesky``, which do something with the results of the scan: live
66-
fitting, live plotting, file-writing, ...
67-
- `Simulation facilities <https://blueskyproject.io/bluesky/main/simulation.html>`_ provided by
68-
bluesky, to check for problems before the plan is run
69-
- And a range of other functionality!
70-
71-
See the `manual system tests <https://github.com/ISISComputingGroup/ibex_bluesky_core/tree/main/manual_system_tests>`_ for
72-
some full runnable examples of complex plans, using a wider range of bluesky functionality.
28+
.. toctree::
29+
:maxdepth: 2
30+
:caption: Tutorial
31+
:glob:
7332

74-
The reference documentation below lists the functionality that has been implemented in ``ibex_bluesky_core``,
75-
however the vast majority of `bluesky <https://blueskyproject.io/bluesky/main/index.html>`_ functionality remains
76-
available, and the advanced user is also encouraged to read that documentation.
33+
tutorial/overview.md
7734

7835
Reference documentation
7936
=======================

doc/tutorial/overview.md

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
# Getting started
2+
3+
`ibex_bluesky_core` is a library which bridges the
4+
[IBEX control system](https://github.com/ISISComputingGroup/ibex_user_manual/wiki/What-Is-IBEX)
5+
and the [bluesky data acquisition framework](https://blueskyproject.io/).
6+
7+
Bluesky is a highly flexible data acquisition system, which has previously been used at
8+
large-scale research facilities such as [NSLS-II](https://www.bnl.gov/nsls2/) and
9+
[Diamond](https://www.diamond.ac.uk/Home.html), among others.
10+
11+
While the bluesky framework itself is generic enough to cope with many forms of data acquisition,
12+
one of the core use cases is "scanning" - that is, measuring how some experimental parameter(s)
13+
vary with respect to other parameter(s). Bluesky has extensive mechanisms and helpers for typical
14+
scanning workflows.
15+
16+
The most important concepts in bluesky are:
17+
- **Plans** tell bluesky what to do next, in the form of **Messages**
18+
- **Devices** encapsulate the details of how some specific device is controlled
19+
- The **RunEngine** executes plans (possibly interacting with devices)
20+
- **Callbacks** do something with data emitted by the scan
21+
22+
## Plans
23+
24+
A plan is an _iterable_ of _messages_. A very simple plan, which doesn't do anything, is:
25+
26+
```python
27+
from bluesky.utils import Msg
28+
29+
my_plan = [Msg("null")]
30+
```
31+
32+
Where `Msg("null")` is an instruction to bluesky (which in this case, does nothing).
33+
34+
While it's possible to write bluesky plans as any iterable, in practice plans are usually written
35+
using python [generators](https://peps.python.org/pep-0255/), using python's
36+
[`yield from`](https://peps.python.org/pep-0380/) syntax to delegate to other plans as necessary:
37+
38+
```python
39+
import bluesky.plan_stubs as bps
40+
41+
def plan():
42+
yield from bps.null()
43+
```
44+
45+
## Devices
46+
47+
`ibex_bluesky_core` provides built-in support for a number of ISIS-specific devices. For example,
48+
blocks are available as devices:
49+
50+
```python
51+
from ibex_bluesky_core.devices.block import block_r, block_mot
52+
53+
mot = block_mot("mot") # An IBEX block pointing at a motor
54+
det = block_r(float, "p5") # A readback block with float datatype
55+
```
56+
57+
Block objects provide several mechanisms for configuring write behaviour - see
58+
{py:obj}`ibex_bluesky_core.devices.block.BlockWriteConfig` for detailed options.
59+
60+
Likewise, the DAE is available as a bluesky device: see [the DAE Documentation](../devices/dae.md)
61+
for full examples including example configurations.
62+
63+
## Setting and reading values
64+
65+
Bluesky provides plan stubs for setting & reading values from bluesky devices: `bps.mv()` and
66+
`bps.rd()` respectively.
67+
68+
```python
69+
from ibex_bluesky_core.devices.block import BlockMot
70+
import bluesky.plan_stubs as bps
71+
72+
def multiply_motor_pos_by_2(mot: BlockMot):
73+
current_value = yield from bps.rd(mot)
74+
yield from bps.mv(mot, current_value * 2.0)
75+
```
76+
77+
```{danger}
78+
Notice that we are using `bps.rd()` and `bps.mv()` here, rather than `g.cget()` or `g.cset()`.
79+
Bare `genie` or `inst` commands **must not** be used in bluesky plans - instead, prefer to use the
80+
bluesky-native functionality - i.e. plans using `yield from`.
81+
82+
Carefully review [calling external code](../plan_stubs/external_code.md) if you do need to call
83+
external code in a plan.
84+
```
85+
86+
For more details about plan stubs (plan fragments like `mv` and `read`), see
87+
[bluesky plan stubs documentation](https://blueskyproject.io/bluesky/main/plans.html#stub-plans)
88+
89+
## Scanning
90+
91+
Having created some simple devices, those devices can be used in standard bluesky plans:
92+
93+
```python
94+
from ophyd_async.plan_stubs import ensure_connected
95+
import bluesky.plans as bp
96+
from ibex_bluesky_core.devices.block import block_r, block_mot
97+
98+
def my_plan(det_block_name: str, mot_block_name: str, start: float, stop: float, num: int):
99+
mot = block_mot(mot_block_name)
100+
det = block_r(float, det_block_name)
101+
102+
# Devices connect up-front - this means that plans are generally "fail-fast", and
103+
# will detect problems such as typos in block names before the whole plan runs.
104+
yield from ensure_connected(det, mot, force_reconnect=True)
105+
106+
# Delegate to bluesky's scan plan.
107+
yield from bp.scan([det], mot, start, stop, num)
108+
```
109+
110+
For details about plans which are available directly from `bluesky` - like `bp.scan` above - see
111+
[bluesky's plan documentation](https://blueskyproject.io/bluesky/main/plans.html#pre-assembled-plans).
112+
113+
## The `RunEngine`
114+
115+
The `RunEngine` is the central "conductor" in bluesky - it is responsible for reading a plan and
116+
performing the associated actions on the hardware. To get a run engine instance, use:
117+
118+
```python
119+
from ibex_bluesky_core.run_engine import get_run_engine
120+
RE = get_run_engine()
121+
```
122+
123+
```{tip}
124+
In the IBEX GUI, manually getting a runengine is unnecessary - it is done automatically.
125+
```
126+
127+
Then execute a plan using the RunEngine:
128+
129+
```
130+
RE(my_plan("det", "mot", 0, 10, 5))
131+
```
132+
133+
Noth that typing `my_plan("det", "mot", 0, 10, 5)` does not do anything by itself.
134+
That is because `my_plan` is a python generator - which does nothing until iterated.
135+
To actually execute the plan, it must be passed to the `RunEngine`, which is conventionally
136+
called `RE`.
137+
138+
For more detail about the RunEngine, see:
139+
- [bluesky RunEngine docs](https://blueskyproject.io/bluesky/main/tutorial.html#the-runengine)
140+
- [bluesky RunEngine API docs](https://blueskyproject.io/bluesky/main/run_engine_api.html)
141+
142+
## Callbacks
143+
144+
Callbacks are bluesky's mechanism for listening to data from a scan. Some examples of common callbacks
145+
are:
146+
- [File writing](../callbacks/file_writing.md)
147+
- [Plotting](../callbacks/plotting.md)
148+
- [Fitting](../fitting/fitting.md)
149+
- [Live Tables](https://blueskyproject.io/bluesky/main/callbacks.html#livetable)
150+
151+
It is possible to use callbacks manually, when executing a plan:
152+
153+
```python
154+
from bluesky.callbacks import LiveTable
155+
156+
RE(my_plan("det", "mot", 0, 10, 5), LiveTable(["mot", "det"]))
157+
```
158+
159+
However, to save typing out callbacks repeatedly, user-specified plans can add callbacks via
160+
[`subs_decorator`](https://blueskyproject.io/bluesky/main/callbacks.html#through-a-plan):
161+
162+
```python
163+
from ibex_bluesky_core.devices.block import block_r, block_mot
164+
from ophyd_async.plan_stubs import ensure_connected
165+
from bluesky.preprocessors import subs_decorator
166+
from bluesky.callbacks import LiveTable
167+
import bluesky.plans as bp
168+
169+
def my_plan(det_block_name: str, mot_block_name: str, start: float, stop: float, num: int):
170+
mot = block_mot(mot_block_name)
171+
det = block_r(float, det_block_name)
172+
173+
@subs_decorator([
174+
LiveTable([mot.name, det.name]),
175+
])
176+
def _inner():
177+
yield from ensure_connected(det, mot, force_reconnect=True)
178+
yield from bp.scan([det], mot, start, stop, num)
179+
yield from _inner()
180+
```
181+
182+
The above will show a `LiveTable` by default, any time `my_plan` is executed. The same mechanism can
183+
be used for example to always configure a particular scan with plots and a fit with a specific type.
184+
185+
For more information on callbacks, see
186+
[bluesky callbacks documentation](https://blueskyproject.io/bluesky/main/callbacks.html).
187+
188+
## See also
189+
190+
**Plans & plan-stubs**
191+
- Bluesky [experiment plans](https://blueskyproject.io/bluesky/main/plans.html#summary)
192+
- Bluesky [plan stubs](https://blueskyproject.io/bluesky/main/plans.html#stub-plans)
193+
- {py:obj}`ibex_bluesky_core.plan_stubs`
194+
195+
**Callbacks**
196+
- [Bluesky callbacks](https://blueskyproject.io/bluesky/main/callbacks.html)
197+
- {py:obj}`ibex_bluesky_core.callbacks`
198+
- [Fitting callbacks](../fitting/fitting.md)
199+
200+
**Full Examples**
201+
- [Manual system tests](https://github.com/ISISComputingGroup/ibex_bluesky_core/tree/main/manual_system_tests) (full,
202+
runnable example plans)
203+
204+
**External documentation**
205+
- [bluesky](https://blueskyproject.io/bluesky)
206+
- [ophyd-async](https://blueskyproject.io/ophyd-async)

0 commit comments

Comments
 (0)