How to optimize with ophyd and ophyd-async devices
==================================================
This guide will walk you through the process of setting up Blop to optimize with `ophyd `_ and `ophyd-async `_ devices.
Ophyd devices
-------------
Ophyd's :class:`ophyd.Signal` class implements both the :class:`blop.protocols.Sensor` and :class:`blop.protocols.Actuator` protocols, so they can be used directly with Blop. You can also use the :class:`ophyd.SignalRO` class which only implements the :class:`blop.protocols.Sensor` protocol if you want to capture this data at each step of the experiment.
The ``name`` attribute of the signal will be used as the name of the :class:`blop.ax.DOF` on the backend.
.. testcode::
from blop.ax import Agent, RangeDOF, Objective
from ophyd import Signal, SignalRO
some_control_signal = Signal(name="some_control_signal")
some_readable_signal = SignalRO(name="some_readable_signal")
dof = RangeDOF(actuator=some_control_signal, bounds=(0, 1000), parameter_type="float")
agent = Agent(
sensors=[some_readable_signal],
dofs=[dof],
objectives=[Objective(name="result", minimize=False)],
evaluation_function=lambda uid, suggestions: [{"result": 0.1, "_id": suggestion["_id"]} for suggestion in suggestions],
)
Ophyd-async devices
-------------------
Ophyd-async's :class:`ophyd_async.core.SignalW` class implements the :class:`blop.protocols.Actuator` protocol, so they can also be used directly with Blop. The :class:`ophyd_async.core.SignalR` class implements the :class:`blop.protocols.Sensor` protocol. And the :class:`ophyd_async.core.SignalRW` class implements both.
Below we create *soft* signals that return instances of the above classes.
Once again, the ``name`` attribute of the signal will be used as the name of the :class:`blop.ax.DOF` on the backend.
.. testcode::
from blop.ax import Agent, RangeDOF, Objective
from ophyd_async.core import soft_signal_rw, soft_signal_r_and_setter
some_control_signal = soft_signal_rw(float, name="some_control_signal")
some_readable_signal = soft_signal_r_and_setter(float, name="some_readable_signal")
dof = RangeDOF(actuator=some_control_signal, bounds=(0, 1000), parameter_type="float")
agent = Agent(
sensors=[some_readable_signal],
dofs=[dof],
objectives=[Objective(name="result", minimize=False)],
evaluation_function=lambda uid, suggestions: [{"result": 0.1, "_id": suggestion["_id"]} for suggestion in suggestions],
)
Using your devices in custom acquisition plans
----------------------------------------------
If you use a custom acquisition plan by implementing the :class:`blop.protocols.AcquisitionPlan` protocol, you can use the ``actuators`` and/or ``sensors`` arguments to access the ophyd or ophyd-async devices you configured as DOFs.
.. testcode::
import bluesky.plan_stubs as bps
from bluesky.utils import MsgGenerator
from bluesky.run_engine import RunEngine
from ophyd_async.core import soft_signal_rw
from blop.ax import Agent, RangeDOF, Objective
from blop.protocols import AcquisitionPlan, Actuator, Sensor
def custom_acquire(suggestions: list[dict], actuators: list[Actuator], sensors: list[Sensor]) -> MsgGenerator[str]:
assert actuators[0].name == "signal1"
assert sensors[0].name == "signal2"
yield from bps.null()
return "test-uid-123"
RE = RunEngine({})
signal1 = soft_signal_rw(float, name="signal1")
signal2 = soft_signal_rw(float, name="signal2")
dof = RangeDOF(actuator=signal1, bounds=(0, 1000), parameter_type="float")
agent = Agent(
sensors=[signal2],
dofs=[dof],
acquisition_plan=custom_acquire,
objectives=[Objective(name="result", minimize=False)],
evaluation_function=lambda uid, suggestions: [{"result": 0.1, "_id": suggestion["_id"]} for suggestion in suggestions],
)
RE(agent.optimize())
.. testoutput::
:hide:
...