Writing an Experiment
An experiment is a class based on Experiment (from plesty.lib.experiment). You
write two kinds of method: build_plan(), which lists the steps, and one method per
kind of step, which does the work. The parts introduces the
words used here.
The plan and the step methods
import asyncio
from plesty.lib.data import PlestyArray
from plesty.lib.experiment import Experiment, Plan, Step
class LineScan(Experiment):
def build_plan(self) -> Plan:
steps = [Step(id=f"scan[x={x}]", op="scan_point", params={"x": x}) for x in range(5)]
return Plan(steps, config={"points": 5})
def scan_point(self, x: int) -> PlestyArray:
# drive the devices through self.devices, and return the measurement
return PlestyArray([float(x)], name="signal", unit="a.u.")
run_id = asyncio.run(LineScan(run_root="runs").run())
build_plan()returns aPlan: the steps in order, andconfig, the settings the plan was built from. Both are saved with the run.Each
Stepnames the method to call (op) and the keyword arguments to call it with (params).A step method can be an ordinary function or an
asyncone; the framework awaits it either way. What it returns is saved as the step’s record.run()runs the plan and returns the run’s id. It isasync, so start it withasyncio.run(...), or use The Command Line, which does that for you.
Three rules keep a run safe to continue after it stops:
build_plan()returns the same plan for the same settings. When a run continues, the plan is built again and compared, by fingerprint, with the one saved; a different plan is refused (PlanMismatchError).A step’s id comes from its values, such as
scan[x=3,y=5], never from the time or a random number, because finished steps are recognised by their id.Every
opnames a public method of the class, and not one of the methods the framework itself calls (build_plan,run,setup,teardown). All steps are checked before the first one runs; a wrong one raisesInvalidOperationErrorlisting every offending step, before any device is touched.
Devices
The experiment reaches its devices through self.devices, a
CompositeDevice passed to the constructor:
experiment = LineScan(devices=CompositeDevice(["stage", "camera"]), run_root="runs")
Inside a step method, self.devices.call("stage", "write", "position", 3.0) calls a
method of the device named stage; for a device on another computer, a call that
times out is retried after reconnecting.
Setting up and tearing down
Two more methods run around the steps:
setup()runs before the first step. By default it connects every device.teardown()runs after the last step. By default it disconnects every device. It always runs, whether the run finished, failed or was stopped, so the devices are released however it ends.
Both are async. To add your own work, override them and call the default:
async def setup(self) -> None:
await super().setup()
self.devices.call("stage", "home")
When a step fails
A step that raises an exception is tried again, max_retries attempts in all (3 by
default), waiting retry_sleep seconds (3 by default) in between. If every attempt
fails, the run stops and is marked as aborted in the journal. Both are arguments of
the constructor:
LineScan(devices=devices, run_root="runs", max_retries=5, retry_sleep=10.0)
Reporting progress within a step
A step that takes minutes, such as a long exposure or a slow magnet ramp, looks the
same from outside as one that hangs. Call self.progress() inside it to say how far
it has got:
def acquire(self, frames: int) -> dict:
for i in range(frames):
self.devices.call("camera", "expose")
self.progress(i + 1, frames, "exposing")
return {"frames": frames}
This writes progress.json in the run folder, where a viewer or a console can show
it. It holds only the present moment and is not part of the run’s results.
Other constructor arguments
Argument |
Default |
Meaning |
|---|---|---|
|
|
the |
|
the shared disk, or |
the folder that runs are saved in; a relative path is taken inside the shared disk when there is one |
|
the class name, in lower case |
the name that starts each run’s id |
|
|
|
|
from |
the shared disk; see Data on a Shared Disk |