Plesty Documentation

Plan & Step Operations

Design the atomic measurement schedule and implement typed step operations.

Steps are the unit of atomicity

A Step is the smallest unit of work that either completes — its result is persisted and journaled — or is re-run on resume:

from plesty.lib.experiment import Plan, Step

Step(
    id="scan[x=3,y=5]",        # stable, deterministic, unique within the plan
    op="measure_point",        # name of the experiment method to call
    params={"x": 3, "y": 5},   # keyword arguments passed to the method
)

Choose the step granularity by asking: if the run dies here, what is safe to redo? In plesty-pl-image-scan, one step is one marker cell — a partially captured cell is retaken as a whole so its images stay spatially aligned, while completed cells are never re-imaged.

Step ids must not depend on run time or randomness: resume matches completed steps by id.

The plan is frozen

build_plan() returns a Plan — the ordered steps plus the measurement config:

def build_plan(self) -> Plan:
    steps = [
        Step(id=f"scan[x={x}]", op="measure_point", params={"x": x})
        for x in range(self.points)
    ]
    return Plan(steps, config={"points": self.points, "noise": self.noise})

The plan is written to plan.json at run start and never mutated. Its content hash lets a resumed run verify it is continuing the same schedule — which is why build_plan() must be deterministic for a given configuration, and why the tuning config belongs inside the plan.

Writing step operations

A step operation is a public method — sync or async — that performs one measurement and returns the result:

async def measure_point(self, x: int) -> PlestyArray:
    """Measure one scan position."""
    self.devices.stage.write("POSITION", x)
    power = self.devices.powermeter.query("POWER")
    return PlestyArray([float(power)], name="signal", unit="watt",
                       description=f"intensity at x={x}")

The return value is persisted automatically as raw blob + JSON metadata (see Runs, data & resume) — a PlestyArray, encoded bytes (e.g. an image), or any JSON-serializable value.

Design principles

Return structured, typed data. Prefer PlestyArray (carries name, unit, description) or a typed structure over raw strings — downstream analyzers and mypy both need known types. Gate 4 requires explicit return type annotations on all public methods.

Set the state you need — don't assume it. A step may execute right after a resume, hours after the previous step. Always write the device state the step depends on before reading.

Make settle times explicit parameters. Physical instruments settle after setpoint changes; hardcoded sleeps can't be adjusted per hardware.

Keep steps independent. A step should not rely on results held in memory from a previous step — after a crash, that memory is gone. Anything a later step needs must come from the plan parameters or from persisted data.