mirror of
https://github.com/m-labs/artiq.git
synced 2024-12-18 07:56:30 +08:00
Experiment base class, replace __artiq_unit__ with docstring
This commit is contained in:
parent
407477bc5a
commit
f2e3dfb848
@ -1,4 +1,5 @@
|
||||
from artiq.language.core import *
|
||||
from artiq.language.experiment import Experiment
|
||||
from artiq.language.db import *
|
||||
from artiq.language.units import check_unit
|
||||
from artiq.language.units import ps, ns, us, ms, s
|
||||
|
@ -40,12 +40,13 @@ def get_argparser():
|
||||
parser_add.add_argument(
|
||||
"-t", "--timeout", default=None, type=float,
|
||||
help="specify a timeout for the experiment to complete")
|
||||
parser_add.add_argument("-u", "--unit", default=None,
|
||||
help="unit to run")
|
||||
parser_add.add_argument("-e", "--experiment", default=None,
|
||||
help="experiment to run")
|
||||
parser_add.add_argument("--rtr-group", default=None, type=str,
|
||||
help="real-time result group "
|
||||
"(defaults to filename)")
|
||||
parser_add.add_argument("file", help="file containing the unit to run")
|
||||
parser_add.add_argument("file",
|
||||
help="file containing the experiment to run")
|
||||
parser_add.add_argument("arguments", nargs="*",
|
||||
help="run arguments")
|
||||
|
||||
@ -103,7 +104,7 @@ def _action_submit(remote, args):
|
||||
|
||||
run_params = {
|
||||
"file": args.file,
|
||||
"unit": args.unit,
|
||||
"experiment": args.experiment,
|
||||
"timeout": args.timeout,
|
||||
"arguments": arguments,
|
||||
"rtr_group": args.rtr_group if args.rtr_group is not None \
|
||||
@ -147,10 +148,11 @@ def _action_del_parameter(remote, args):
|
||||
def _show_queue(queue):
|
||||
clear_screen()
|
||||
if queue:
|
||||
table = PrettyTable(["RID", "File", "Unit", "Timeout", "Arguments"])
|
||||
table = PrettyTable(["RID", "File", "Experiment", "Timeout",
|
||||
"Arguments"])
|
||||
for rid, run_params in queue:
|
||||
row = [rid, run_params["file"]]
|
||||
for x in run_params["unit"], run_params["timeout"]:
|
||||
for x in run_params["experiment"], run_params["timeout"]:
|
||||
row.append("" if x is None else x)
|
||||
row.append(format_arguments(run_params["arguments"]))
|
||||
table.add_row(row)
|
||||
@ -162,13 +164,13 @@ def _show_queue(queue):
|
||||
def _show_timed(timed):
|
||||
clear_screen()
|
||||
if timed:
|
||||
table = PrettyTable(["Next run", "TRID", "File", "Unit",
|
||||
table = PrettyTable(["Next run", "TRID", "File", "Experiment",
|
||||
"Timeout", "Arguments"])
|
||||
sp = sorted(timed.items(), key=lambda x: (x[1][0], x[0]))
|
||||
for trid, (next_run, run_params) in sp:
|
||||
row = [time.strftime("%m/%d %H:%M:%S", time.localtime(next_run)),
|
||||
trid, run_params["file"]]
|
||||
for x in run_params["unit"], run_params["timeout"]:
|
||||
for x in run_params["experiment"], run_params["timeout"]:
|
||||
row.append("" if x is None else x)
|
||||
row.append(format_arguments(run_params["arguments"]))
|
||||
table.add_row(row)
|
||||
|
@ -3,13 +3,13 @@
|
||||
import argparse
|
||||
import sys
|
||||
import time
|
||||
from inspect import isclass
|
||||
from operator import itemgetter
|
||||
from itertools import chain
|
||||
|
||||
import h5py
|
||||
|
||||
from artiq.language.db import *
|
||||
from artiq.language.experiment import is_experiment
|
||||
from artiq.protocols import pyon
|
||||
from artiq.protocols.file_db import FlatFileDB
|
||||
from artiq.master.worker_db import DBHub, ResultDB
|
||||
@ -68,15 +68,15 @@ def get_argparser():
|
||||
parser.add_argument("-p", "--pdb", default="pdb.pyon",
|
||||
help="parameter database file")
|
||||
|
||||
parser.add_argument("-e", "--elf", default=False, action="store_true",
|
||||
parser.add_argument("-E", "--elf", default=False, action="store_true",
|
||||
help="run ELF binary")
|
||||
parser.add_argument("-u", "--unit", default=None,
|
||||
help="unit to run")
|
||||
parser.add_argument("-e", "--experiment", default=None,
|
||||
help="experiment to run")
|
||||
parser.add_argument("-o", "--hdf5", default=None,
|
||||
help="write results to specified HDF5 file"
|
||||
" (default: print them)")
|
||||
parser.add_argument("file",
|
||||
help="file containing the unit to run")
|
||||
help="file containing the experiment to run")
|
||||
parser.add_argument("arguments", nargs="*",
|
||||
help="run arguments")
|
||||
|
||||
@ -105,27 +105,31 @@ def main():
|
||||
if args.arguments:
|
||||
print("Run arguments are not supported in ELF mode")
|
||||
sys.exit(1)
|
||||
unit_inst = ELFRunner(dps)
|
||||
unit_inst.run(args.file)
|
||||
exp_inst = ELFRunner(dps)
|
||||
exp_inst.run(args.file)
|
||||
else:
|
||||
module = file_import(args.file)
|
||||
if args.unit is None:
|
||||
units = [(k, v) for k, v in module.__dict__.items()
|
||||
if isclass(v) and hasattr(v, "__artiq_unit__")]
|
||||
l = len(units)
|
||||
if args.experiment is None:
|
||||
exps = [(k, v) for k, v in module.__dict__.items()
|
||||
if is_experiment(v)]
|
||||
l = len(exps)
|
||||
if l == 0:
|
||||
print("No units found in module")
|
||||
print("No experiments found in module")
|
||||
sys.exit(1)
|
||||
elif l > 1:
|
||||
print("More than one unit found in module:")
|
||||
for k, v in sorted(units, key=itemgetter(0)):
|
||||
print(" {} ({})".format(k, v.__artiq_unit__))
|
||||
print("Use -u to specify which unit to use.")
|
||||
print("More than one experiment found in module:")
|
||||
for k, v in sorted(experiments, key=itemgetter(0)):
|
||||
if v.__doc__ is None:
|
||||
print(" {}".format(k))
|
||||
else:
|
||||
print(" {} ({})".format(
|
||||
k, v.__doc__.splitlines()[0].strip()))
|
||||
print("Use -u to specify which experiment to use.")
|
||||
sys.exit(1)
|
||||
else:
|
||||
unit = units[0][1]
|
||||
exp = exps[0][1]
|
||||
else:
|
||||
unit = getattr(module, args.unit)
|
||||
exp = getattr(module, args.experiment)
|
||||
|
||||
try:
|
||||
arguments = _parse_arguments(args.arguments)
|
||||
@ -135,17 +139,16 @@ def main():
|
||||
|
||||
run_params = {
|
||||
"file": args.file,
|
||||
"unit": args.unit,
|
||||
"experiment": args.experiment,
|
||||
"timeout": None,
|
||||
"arguments": arguments
|
||||
}
|
||||
unit_inst = unit(dbh,
|
||||
scheduler=DummyScheduler(),
|
||||
run_params=run_params,
|
||||
**run_params["arguments"])
|
||||
unit_inst.run()
|
||||
if hasattr(unit_inst, "analyze"):
|
||||
unit_inst.analyze()
|
||||
exp_inst = exp(dbh,
|
||||
scheduler=DummyScheduler(),
|
||||
run_params=run_params,
|
||||
**run_params["arguments"])
|
||||
exp_inst.run()
|
||||
exp_inst.analyze()
|
||||
|
||||
if args.hdf5 is not None:
|
||||
f = h5py.File(args.hdf5, "w")
|
||||
|
@ -146,7 +146,7 @@ class ExplorerWindow(Window):
|
||||
arguments = self.controls.get_arguments()
|
||||
run_params = {
|
||||
"file": data["file"],
|
||||
"unit": data["unit"],
|
||||
"experiment": data["experiment"],
|
||||
"timeout": None,
|
||||
"arguments": arguments,
|
||||
"rtr_group": data["file"]
|
||||
|
@ -12,7 +12,7 @@ class _QueueStoreSyncer(ListSyncer):
|
||||
def convert(self, x):
|
||||
rid, run_params = x
|
||||
row = [rid, run_params["file"]]
|
||||
for e in run_params["unit"], run_params["timeout"]:
|
||||
for e in run_params["experiment"], run_params["timeout"]:
|
||||
row.append("" if e is None else str(e))
|
||||
row.append(format_arguments(run_params["arguments"]))
|
||||
return row
|
||||
@ -27,7 +27,7 @@ class _TimedStoreSyncer(DictSyncer):
|
||||
next_run, run_params = x
|
||||
row = [time.strftime("%m/%d %H:%M:%S", time.localtime(next_run)),
|
||||
trid, run_params["file"]]
|
||||
for e in run_params["unit"], run_params["timeout"]:
|
||||
for e in run_params["experiment"], run_params["timeout"]:
|
||||
row.append("" if e is None else str(e))
|
||||
row.append(format_arguments(run_params["arguments"]))
|
||||
return row
|
||||
@ -57,7 +57,7 @@ class SchedulerWindow(Window):
|
||||
|
||||
self.queue_store = Gtk.ListStore(int, str, str, str, str)
|
||||
self.queue_tree = Gtk.TreeView(self.queue_store)
|
||||
for i, title in enumerate(["RID", "File", "Unit", "Timeout", "Arguments"]):
|
||||
for i, title in enumerate(["RID", "File", "Experiment", "Timeout", "Arguments"]):
|
||||
renderer = Gtk.CellRendererText()
|
||||
column = Gtk.TreeViewColumn(title, renderer, text=i)
|
||||
self.queue_tree.append_column(column)
|
||||
@ -81,7 +81,7 @@ class SchedulerWindow(Window):
|
||||
|
||||
self.timed_store = Gtk.ListStore(str, int, str, str, str, str)
|
||||
self.timed_tree = Gtk.TreeView(self.timed_store)
|
||||
for i, title in enumerate(["Next run", "TRID", "File", "Unit",
|
||||
for i, title in enumerate(["Next run", "TRID", "File", "Experiment",
|
||||
"Timeout", "Arguments"]):
|
||||
renderer = Gtk.CellRendererText()
|
||||
column = Gtk.TreeViewColumn(title, renderer, text=i)
|
||||
|
40
artiq/language/experiment.py
Normal file
40
artiq/language/experiment.py
Normal file
@ -0,0 +1,40 @@
|
||||
from inspect import isclass
|
||||
|
||||
|
||||
class Experiment:
|
||||
"""Base class for experiments.
|
||||
|
||||
Deriving from this class enables automatic experiment discovery in
|
||||
Python modules.
|
||||
"""
|
||||
def run(self):
|
||||
"""The main entry point of the experiment.
|
||||
|
||||
This method must be overloaded by the user to implement the main
|
||||
control flow of the experiment.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def analyze(self):
|
||||
"""Entry point for analyzing the results of the experiment.
|
||||
|
||||
This method may be overloaded by the user to implement the analysis
|
||||
phase of the experiment, for example fitting curves.
|
||||
|
||||
Splitting this phase from ``run`` enables tweaking the analysis
|
||||
algorithm on pre-existing data, and CPU-bound analyses to be run
|
||||
overlapped with the next experiment in a pipelined manner.
|
||||
|
||||
This method must not interact with the hardware.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
def has_analyze(experiment):
|
||||
"""Checks if an experiment instance overloaded its ``analyze`` method."""
|
||||
return experiment.analyze.__func__ is not Experiment.analyze
|
||||
|
||||
|
||||
def is_experiment(o):
|
||||
"""Checks if a Python object is an instantiable experiment."""
|
||||
return isclass(o) and issubclass(o, Experiment) and o is not Experiment
|
@ -1,8 +1,8 @@
|
||||
import os
|
||||
from inspect import isclass
|
||||
|
||||
from artiq.protocols.sync_struct import Notifier
|
||||
from artiq.tools import file_import
|
||||
from artiq.language.experiment import is_experiment
|
||||
|
||||
|
||||
def scan_experiments():
|
||||
@ -14,13 +14,19 @@ def scan_experiments():
|
||||
except:
|
||||
continue
|
||||
for k, v in m.__dict__.items():
|
||||
if isclass(v) and hasattr(v, "__artiq_unit__"):
|
||||
if is_experiment(v):
|
||||
if v.__doc__ is None:
|
||||
name = k
|
||||
else:
|
||||
name = v.__doc__.splitlines()[0].strip()
|
||||
if name[-1] == ".":
|
||||
name = name[:-1]
|
||||
entry = {
|
||||
"file": os.path.join("repository", f),
|
||||
"unit": k,
|
||||
"experiment": k,
|
||||
"gui_file": getattr(v, "__artiq_gui_file__", None)
|
||||
}
|
||||
r[v.__artiq_unit__] = entry
|
||||
r[name] = entry
|
||||
return r
|
||||
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
import sys
|
||||
import time
|
||||
from inspect import isclass
|
||||
import traceback
|
||||
|
||||
from artiq.protocols import pyon
|
||||
@ -65,23 +64,24 @@ class Scheduler:
|
||||
cancel_timed = make_parent_action("scheduler_cancel_timed", "trid")
|
||||
|
||||
|
||||
def get_unit(file, unit):
|
||||
def get_exp(file, exp):
|
||||
module = file_import(file)
|
||||
if unit is None:
|
||||
units = [v for k, v in module.__dict__.items()
|
||||
if isclass(v) and hasattr(v, "__artiq_unit__")]
|
||||
if len(units) != 1:
|
||||
raise ValueError("Found {} units in module".format(len(units)))
|
||||
return units[0]
|
||||
if exp is None:
|
||||
exps = [v for k, v in module.__dict__.items()
|
||||
if is_experiment(v)]
|
||||
if len(exps) != 1:
|
||||
raise ValueError("Found {} experiments in module"
|
||||
.format(len(exps)))
|
||||
return exps[0]
|
||||
else:
|
||||
return getattr(module, unit)
|
||||
return getattr(module, exp)
|
||||
|
||||
|
||||
def run(rid, run_params):
|
||||
start_time = time.localtime()
|
||||
unit = get_unit(run_params["file"], run_params["unit"])
|
||||
exp = get_exp(run_params["file"], run_params["experiment"])
|
||||
|
||||
realtime_results = unit.realtime_results()
|
||||
realtime_results = exp.realtime_results()
|
||||
init_rt_results(realtime_results)
|
||||
|
||||
realtime_results_set = set()
|
||||
@ -97,13 +97,12 @@ def run(rid, run_params):
|
||||
dbh = DBHub(ParentDDB, ParentPDB, rdb)
|
||||
try:
|
||||
try:
|
||||
unit_inst = unit(dbh,
|
||||
scheduler=Scheduler,
|
||||
run_params=run_params,
|
||||
**run_params["arguments"])
|
||||
unit_inst.run()
|
||||
if hasattr(unit_inst, "analyze"):
|
||||
unit_inst.analyze()
|
||||
exp_inst = exp(dbh,
|
||||
scheduler=Scheduler,
|
||||
run_params=run_params,
|
||||
**run_params["arguments"])
|
||||
exp_inst.run()
|
||||
exp_inst.analyze()
|
||||
except Exception:
|
||||
put_object({"action": "report_completed",
|
||||
"status": "failed",
|
||||
@ -114,7 +113,7 @@ def run(rid, run_params):
|
||||
finally:
|
||||
dbh.close()
|
||||
|
||||
f = get_hdf5_output(start_time, rid, unit.__name__)
|
||||
f = get_hdf5_output(start_time, rid, exp.__name__)
|
||||
try:
|
||||
rdb.write_hdf5(f)
|
||||
finally:
|
||||
|
@ -2,15 +2,16 @@ from artiq import *
|
||||
|
||||
import pulse_rate, rtio_skew, rpc_timing
|
||||
|
||||
_units = [pulse_rate.PulseRate, rtio_skew.RTIOSkew, rpc_timing.RPCTiming]
|
||||
|
||||
class AllBenchmarks(AutoDB):
|
||||
__artiq_unit__ = "All benchmarks"
|
||||
_exps = [pulse_rate.PulseRate, rtio_skew.RTIOSkew, rpc_timing.RPCTiming]
|
||||
|
||||
class AllBenchmarks(Experiment, AutoDB):
|
||||
"""All benchmarks"""
|
||||
|
||||
def build(self):
|
||||
self.se = []
|
||||
for unit in _units:
|
||||
self.se.append(unit(self.dbh))
|
||||
for exp in _exps:
|
||||
self.se.append(exp(self.dbh))
|
||||
|
||||
def run(self):
|
||||
for se in self.se:
|
||||
|
@ -2,8 +2,8 @@ from artiq import *
|
||||
from artiq.coredevice.runtime_exceptions import RTIOUnderflow
|
||||
|
||||
|
||||
class PulseRate(AutoDB):
|
||||
__artiq_unit__ = "Pulse rate"
|
||||
class PulseRate(Experiment, AutoDB):
|
||||
"""Sustained pulse rate"""
|
||||
|
||||
class DBKeys:
|
||||
core = Device()
|
||||
|
@ -3,8 +3,8 @@ from math import sqrt
|
||||
from artiq import *
|
||||
|
||||
|
||||
class RPCTiming(AutoDB):
|
||||
__artiq_unit__ = "RPC timing"
|
||||
class RPCTiming(Experiment, AutoDB):
|
||||
"""RPC timing"""
|
||||
|
||||
class DBKeys:
|
||||
core = Device()
|
||||
|
@ -5,8 +5,8 @@ class PulseNotReceived(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class RTIOSkew(AutoDB):
|
||||
__artiq_unit__ = "RTIO skew"
|
||||
class RTIOSkew(Experiment, AutoDB):
|
||||
"""RTIO skew"""
|
||||
|
||||
class DBKeys:
|
||||
core = Device()
|
||||
|
@ -9,9 +9,7 @@ As a very first step, we will turn on a LED on the core device. Create a file ``
|
||||
from artiq import *
|
||||
|
||||
|
||||
class LED(AutoDB):
|
||||
__artiq_unit__ = "ARTIQ tutorial"
|
||||
|
||||
class LED(Experiment, AutoDB):
|
||||
class DBKeys:
|
||||
core = Device()
|
||||
led = Device()
|
||||
@ -23,8 +21,6 @@ As a very first step, we will turn on a LED on the core device. Create a file ``
|
||||
|
||||
The central part of our code is our ``LED`` class, that derives from :class:`artiq.language.db.AutoDB`. ``AutoDB`` is part of the mechanism that attaches device drivers and retrieves parameters according to a database. Our ``DBKeys`` class lists the devices (and parameters) that ``LED`` needs in order to operate, and the names of the attributes (e.g. ``led``) are used to search the database. ``AutoDB`` replaces them with the actual device drivers (and parameter values). Finally, the ``@kernel`` decorator tells the system that the ``run`` method must be executed on the core device (instead of the host).
|
||||
|
||||
The ``__artiq_unit__`` attribute tells the ARTIQ tools that our class is a "unit" (an entry point for an experiment) and gives it a name. The name can be any string, and its purpose is to name the experiment in user interfaces.
|
||||
|
||||
Copy the files ``ddb.pyon`` and ``pdb.pyon`` (containing the device and parameter databases) from the ``examples`` folder of ARTIQ into the same directory as ``led.py`` (alternatively, you can use the ``-d`` and ``-p`` options of ``artiq_run.py``). You can open the database files using a text editor - their contents are in a human-readable format.
|
||||
|
||||
Run your code using ``artiq_run.py``, which is part of the ARTIQ front-end tools: ::
|
||||
@ -43,9 +39,7 @@ Modify the code as follows: ::
|
||||
def input_led_state():
|
||||
return int(input("Enter desired LED state: "))
|
||||
|
||||
class LED(AutoDB):
|
||||
__artiq_unit__ = "ARTIQ tutorial"
|
||||
|
||||
class LED(Experiment, AutoDB):
|
||||
class DBKeys:
|
||||
core = Device()
|
||||
led = Device()
|
||||
@ -89,9 +83,7 @@ Create a new file ``rtio.py`` containing the following: ::
|
||||
|
||||
from artiq import *
|
||||
|
||||
class Tutorial(AutoDB):
|
||||
__artiq_unit__ = "ARTIQ tutorial"
|
||||
|
||||
class Tutorial(Experiment, AutoDB):
|
||||
class DBKeys:
|
||||
core = Device()
|
||||
ttl0 = Device()
|
||||
@ -114,9 +106,7 @@ Try reducing the period of the generated waveform until the CPU cannot keep up w
|
||||
def print_underflow():
|
||||
print("RTIO underflow occured")
|
||||
|
||||
class Tutorial(AutoDB):
|
||||
__artiq_unit__ = "ARTIQ tutorial"
|
||||
|
||||
class Tutorial(Experiment, AutoDB):
|
||||
class DBKeys:
|
||||
core = Device()
|
||||
led = Device()
|
||||
|
@ -1 +1 @@
|
||||
{"flopping_freq": 1500.0294421161527}
|
||||
{"flopping_freq": 1500.0164816344934}
|
||||
|
@ -1,8 +1,8 @@
|
||||
from artiq import *
|
||||
|
||||
|
||||
class DDSTest(AutoDB):
|
||||
__artiq_unit__ = "DDS test"
|
||||
class DDSTest(Experiment, AutoDB):
|
||||
"""DDS test"""
|
||||
|
||||
class DBKeys:
|
||||
core = Device()
|
||||
|
@ -23,8 +23,8 @@ def model_numpy(xdata, F0):
|
||||
return r
|
||||
|
||||
|
||||
class FloppingF(AutoDB):
|
||||
__artiq_unit__ = "Flopping F simulation"
|
||||
class FloppingF(Experiment, AutoDB):
|
||||
"""Flopping F simulation"""
|
||||
__artiq_gui_file__ = "flopping_f_simulation_gui.py"
|
||||
|
||||
class DBKeys:
|
||||
|
@ -3,8 +3,8 @@ import sys
|
||||
from artiq import *
|
||||
|
||||
|
||||
class Mandelbrot(AutoDB):
|
||||
__artiq_unit__ = "Mandelbrot set demo"
|
||||
class Mandelbrot(Experiment, AutoDB):
|
||||
"""Mandelbrot set demo"""
|
||||
|
||||
class DBKeys:
|
||||
core = Device()
|
||||
|
@ -1,8 +1,8 @@
|
||||
from artiq import *
|
||||
|
||||
|
||||
class PhotonHistogram(AutoDB):
|
||||
__artiq_unit__ = "Photon histogram"
|
||||
class PhotonHistogram(Experiment, AutoDB):
|
||||
"""Photon histogram"""
|
||||
|
||||
class DBKeys:
|
||||
core = Device()
|
||||
|
@ -10,8 +10,8 @@ transport_data = dict(
|
||||
# 4 devices, 3 board each, 3 dacs each
|
||||
)
|
||||
|
||||
class Transport(AutoDB):
|
||||
__artiq_unit__ = "Transport"
|
||||
class Transport(Experiment, AutoDB):
|
||||
"""Transport"""
|
||||
|
||||
class DBKeys:
|
||||
core = Device()
|
||||
|
@ -1,8 +1,8 @@
|
||||
from artiq import *
|
||||
|
||||
|
||||
class AluminumSpectroscopy(AutoDB):
|
||||
__artiq_unit__ = "Aluminum spectroscopy (simulation)"
|
||||
class AluminumSpectroscopy(Experiment, AutoDB):
|
||||
"""Aluminum spectroscopy (simulation)"""
|
||||
|
||||
class DBKeys:
|
||||
core = Device()
|
||||
|
@ -1,8 +1,8 @@
|
||||
from artiq import *
|
||||
|
||||
|
||||
class SimpleSimulation(AutoDB):
|
||||
__artiq_unit__ = "Simple simulation"
|
||||
class SimpleSimulation(Experiment, AutoDB):
|
||||
"""Simple simulation"""
|
||||
|
||||
class DBKeys:
|
||||
core = Device()
|
||||
|
Loading…
Reference in New Issue
Block a user