Plesty Documentation

Measurement Functions

Implement typed measurement sequences that orchestrate multiple devices and return structured results.

A measurement function in an experiment module takes physical parameters, drives devices in a sequence, and returns a structured result dict or dataclass.

Complete example: wavelength sweep

from __future__ import annotations

import time
from dataclasses import dataclass, field

from plesty.lib.device.composite_device import CompositeDevice
from plesty.lib.service import build_client


@dataclass
class WavelengthSweepResult:
    wavelengths: list[int] = field(default_factory=list)
    powers: list[float] = field(default_factory=list)
    unit: str = "watt"


def wavelength_sweep(
    instruments: CompositeDevice,
    start_nm: int,
    stop_nm: int,
    step_nm: int = 1,
    settle_ms: float = 200.0,
) -> WavelengthSweepResult:
    """Sweep laser wavelength and measure power at each step.

    Args:
        instruments: CompositeDevice with 'laser' and 'powermeter' attributes.
        start_nm: Starting wavelength in nm.
        stop_nm: Ending wavelength (inclusive) in nm.
        step_nm: Wavelength step size in nm.
        settle_ms: Settling time after each wavelength change in milliseconds.

    Returns:
        WavelengthSweepResult with wavelengths and corresponding power readings.
    """
    result = WavelengthSweepResult()
    wavelength = start_nm

    while wavelength <= stop_nm:
        instruments.laser.write("WAVELENGTH", wavelength)
        time.sleep(settle_ms / 1000.0)

        power = instruments.powermeter.query("POWER")
        result.wavelengths.append(wavelength)
        result.powers.append(float(power))

        wavelength += step_nm

    return result

Usage

from plesty.lib.service import build_client
from plesty.lib.device.composite_device import CompositeDevice
from plesty.scan_exp.measurements import wavelength_sweep

laser = build_client("tcp://laser-host:5555")
powermeter = build_client("tcp://pm-host:5556")

with CompositeDevice({"laser": laser, "powermeter": powermeter}) as instruments:
    data = wavelength_sweep(
        instruments,
        start_nm=1020,
        stop_nm=1080,
        step_nm=2,
        settle_ms=300.0,
    )

print(f"Measured {len(data.wavelengths)} points")
print(f"Peak power: {max(data.powers):.3e} W at {data.wavelengths[data.powers.index(max(data.powers))]} nm")

Design principles

Return structured data, not raw strings

Use dataclasses or typed dicts for return values. This gives mypy something to check and lets downstream analyzers work with known types.

Keep measurement functions pure with respect to device state

Don't rely on the device being in a particular state at the start of a measurement — always set the state you need explicitly before reading.

Include settle time parameters

Physical instruments have settling times after setpoint changes. Make settle times explicit parameters (not hardcoded) so the caller can adjust for different hardware.

Type-annotate everything

Gate 4 (Data Layer Compliance) requires explicit return type annotations on all public methods. Gate 2 (Code Hygiene) checks mypy in strict mode.

# Wrong — missing return type
def wavelength_sweep(instruments, start_nm, stop_nm):
    ...

# Correct
def wavelength_sweep(
    instruments: CompositeDevice,
    start_nm: int,
    stop_nm: int,
    step_nm: int = 1,
    settle_ms: float = 200.0,
) -> WavelengthSweepResult:
    ...