The Command Line

Starting an experiment always involves the same questions: which settings file, where the runs go, a new run or the continuation of a stopped one, a trial without instruments. ExperimentCLI (from plesty.lib.experiment) answers them the same way for every experiment, so each experiment only supplies what is its own: which devices it uses, and how it is built.

A minimal command line

# power_scan.py
import sys

from plesty.lib.device.composite_device import CompositeDevice
from plesty.lib.experiment import ExperimentCLI, Manifest

cli = ExperimentCLI(Manifest(module="power_scan", title="Power scan"))


@cli.rig
def rig(args, config, skip):
    return CompositeDevice(["pm"])


@cli.build
def build(args, config, run_root, devices):
    return PowerScan(config, devices=devices, run_root=run_root)


if __name__ == "__main__":
    sys.exit(cli.main())

PowerScan is the experiment class from Write the scan. The two functions are required:

  • @cli.rig builds the devices the experiment uses (PLESTY calls them its rig). It receives the parsed command-line arguments (args), the settings (config), and skip, the names of optional devices the operator chose to go without (see Checking the Devices Before a Run). It is called again each time the operator retries after a device failed to answer. Return None for an experiment that uses no devices.

  • @cli.build builds the experiment. It receives args, config, the folder to save runs in (run_root) and the devices; devices is None in a dry run, where nothing is connected.

Options

Option

Does

-c FILE, --config FILE

read the settings from FILE instead of the default, config/default.yaml (see The run configuration: config/*.yaml)

--run-root FOLDER

save the run in FOLDER instead of the default

-r RUN_ID, --resume RUN_ID

continue a stopped run (see Stopping and Continuing a Run)

--run-id NAME

name the new run NAME instead of <experiment>_<date>-<time>

--dry-run

build and print the plan, connect nothing

--manifest

print what the experiment declares, as JSON (see What the experiment declares)

--devices

print the devices of setup.yaml a run of these settings uses, as JSON — {"devices": ["hwp", "spec"]}, or {"devices": null} for all — and connect nothing

A settings file that does not exist counts as empty settings, so an experiment whose defaults are all in its code runs without one.

When a setup.yaml exists (see The Setup File), the command line reads it before anything connects. If a variable it names has no value, the run stops there and says which variables to set in which file. Only the benches of the devices the run uses count: see @cli.needs below.

Exit codes

Code

Means

0

the run completed, or the plan or manifest was printed

1

a device did not answer and the operator chose to stop (see Checking the Devices Before a Run), or the run was aborted

2

a variable named by setup.yaml is not set

130

the run was interrupted with Ctrl-C or canceled; it can be continued with --resume

Optional functions

Register these the same way, as decorators, when you need them:

Decorator

Called

Use it to

@cli.configure

with args and the loaded settings, before the plan is built

change the settings from command-line options; return the settings to use

@cli.check

with args and the settings, before any device connects

refuse incomplete settings: raise an exception with the reason

@cli.default_config

with args, when no -c was given

choose another settings file from other options, such as a --demo flag; return None for the default

@cli.finished

with args, the settings and the run folder, when the run completed

print a summary of what was measured; return the text

@cli.needs

with the settings, before anything connects

name the devices of setup.yaml these settings use, or return None for all of them. PLESTY Bench then starts only those, and the run needs only their benches set up

@cli.needs
def needs(config):
    # Measuring in place uses one bench; navigating to dots uses the camera bench too.
    return ["hwp", "qwp", "spec", "pm"] if not config.get("dots") else None

cli.optional("pm", …) names devices the run can do without: if only these fail to answer, the operator may continue without them.

Your own options

cli.add_argument adds an option, with the same arguments as argparse.ArgumentParser.add_argument. sets= names the setting the option replaces, so that programs reading the manifest know what it does:

cli.add_argument("--points", type=int, sets="points", help="Number of scan points.")


@cli.configure
def configure(args, config):
    if args.points is not None:
        config["points"] = args.points
    return config

What the experiment declares

Manifest describes the experiment to programs that start it for you, such as a console with a form for the settings. --manifest prints it without connecting anything or reading any settings, so such a program can ask an experiment what it takes before anything is set up.

Field

Holds

module

the name the experiment is started by, as in python -m <module>

title

a human-readable name

schema

the settings the experiment takes, each with its type, unit and description, in the same form as a device’s parameters

environment

the environment variables it reads; env_vars("PM_ADDRESS", …) builds them

options

the extra command-line options; filled in by cli.add_argument

config

the default settings file, config/default.yaml

run_root

the default folder for runs, runs

viz

the module of the experiment’s viewer, if it has one (see 3. Watch the data)