forked from M-Labs/artiq
1
0
Fork 0

Experiment base class, replace __artiq_unit__ with docstring

This commit is contained in:
Sebastien Bourdeauducq 2015-03-08 15:43:04 +01:00
parent 407477bc5a
commit f2e3dfb848
21 changed files with 144 additions and 102 deletions

View File

@ -1,4 +1,5 @@
from artiq.language.core import * from artiq.language.core import *
from artiq.language.experiment import Experiment
from artiq.language.db import * from artiq.language.db import *
from artiq.language.units import check_unit from artiq.language.units import check_unit
from artiq.language.units import ps, ns, us, ms, s from artiq.language.units import ps, ns, us, ms, s

View File

@ -40,12 +40,13 @@ def get_argparser():
parser_add.add_argument( parser_add.add_argument(
"-t", "--timeout", default=None, type=float, "-t", "--timeout", default=None, type=float,
help="specify a timeout for the experiment to complete") help="specify a timeout for the experiment to complete")
parser_add.add_argument("-u", "--unit", default=None, parser_add.add_argument("-e", "--experiment", default=None,
help="unit to run") help="experiment to run")
parser_add.add_argument("--rtr-group", default=None, type=str, parser_add.add_argument("--rtr-group", default=None, type=str,
help="real-time result group " help="real-time result group "
"(defaults to filename)") "(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="*", parser_add.add_argument("arguments", nargs="*",
help="run arguments") help="run arguments")
@ -103,7 +104,7 @@ def _action_submit(remote, args):
run_params = { run_params = {
"file": args.file, "file": args.file,
"unit": args.unit, "experiment": args.experiment,
"timeout": args.timeout, "timeout": args.timeout,
"arguments": arguments, "arguments": arguments,
"rtr_group": args.rtr_group if args.rtr_group is not None \ "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): def _show_queue(queue):
clear_screen() clear_screen()
if queue: if queue:
table = PrettyTable(["RID", "File", "Unit", "Timeout", "Arguments"]) table = PrettyTable(["RID", "File", "Experiment", "Timeout",
"Arguments"])
for rid, run_params in queue: for rid, run_params in queue:
row = [rid, run_params["file"]] 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("" if x is None else x)
row.append(format_arguments(run_params["arguments"])) row.append(format_arguments(run_params["arguments"]))
table.add_row(row) table.add_row(row)
@ -162,13 +164,13 @@ def _show_queue(queue):
def _show_timed(timed): def _show_timed(timed):
clear_screen() clear_screen()
if timed: if timed:
table = PrettyTable(["Next run", "TRID", "File", "Unit", table = PrettyTable(["Next run", "TRID", "File", "Experiment",
"Timeout", "Arguments"]) "Timeout", "Arguments"])
sp = sorted(timed.items(), key=lambda x: (x[1][0], x[0])) sp = sorted(timed.items(), key=lambda x: (x[1][0], x[0]))
for trid, (next_run, run_params) in sp: for trid, (next_run, run_params) in sp:
row = [time.strftime("%m/%d %H:%M:%S", time.localtime(next_run)), row = [time.strftime("%m/%d %H:%M:%S", time.localtime(next_run)),
trid, run_params["file"]] 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("" if x is None else x)
row.append(format_arguments(run_params["arguments"])) row.append(format_arguments(run_params["arguments"]))
table.add_row(row) table.add_row(row)

View File

@ -3,13 +3,13 @@
import argparse import argparse
import sys import sys
import time import time
from inspect import isclass
from operator import itemgetter from operator import itemgetter
from itertools import chain from itertools import chain
import h5py import h5py
from artiq.language.db import * from artiq.language.db import *
from artiq.language.experiment import is_experiment
from artiq.protocols import pyon from artiq.protocols import pyon
from artiq.protocols.file_db import FlatFileDB from artiq.protocols.file_db import FlatFileDB
from artiq.master.worker_db import DBHub, ResultDB from artiq.master.worker_db import DBHub, ResultDB
@ -68,15 +68,15 @@ def get_argparser():
parser.add_argument("-p", "--pdb", default="pdb.pyon", parser.add_argument("-p", "--pdb", default="pdb.pyon",
help="parameter database file") 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") help="run ELF binary")
parser.add_argument("-u", "--unit", default=None, parser.add_argument("-e", "--experiment", default=None,
help="unit to run") help="experiment to run")
parser.add_argument("-o", "--hdf5", default=None, parser.add_argument("-o", "--hdf5", default=None,
help="write results to specified HDF5 file" help="write results to specified HDF5 file"
" (default: print them)") " (default: print them)")
parser.add_argument("file", parser.add_argument("file",
help="file containing the unit to run") help="file containing the experiment to run")
parser.add_argument("arguments", nargs="*", parser.add_argument("arguments", nargs="*",
help="run arguments") help="run arguments")
@ -105,27 +105,31 @@ def main():
if args.arguments: if args.arguments:
print("Run arguments are not supported in ELF mode") print("Run arguments are not supported in ELF mode")
sys.exit(1) sys.exit(1)
unit_inst = ELFRunner(dps) exp_inst = ELFRunner(dps)
unit_inst.run(args.file) exp_inst.run(args.file)
else: else:
module = file_import(args.file) module = file_import(args.file)
if args.unit is None: if args.experiment is None:
units = [(k, v) for k, v in module.__dict__.items() exps = [(k, v) for k, v in module.__dict__.items()
if isclass(v) and hasattr(v, "__artiq_unit__")] if is_experiment(v)]
l = len(units) l = len(exps)
if l == 0: if l == 0:
print("No units found in module") print("No experiments found in module")
sys.exit(1) sys.exit(1)
elif l > 1: elif l > 1:
print("More than one unit found in module:") print("More than one experiment found in module:")
for k, v in sorted(units, key=itemgetter(0)): for k, v in sorted(experiments, key=itemgetter(0)):
print(" {} ({})".format(k, v.__artiq_unit__)) if v.__doc__ is None:
print("Use -u to specify which unit to use.") print(" {}".format(k))
else:
print(" {} ({})".format(
k, v.__doc__.splitlines()[0].strip()))
print("Use -u to specify which experiment to use.")
sys.exit(1) sys.exit(1)
else: else:
unit = units[0][1] exp = exps[0][1]
else: else:
unit = getattr(module, args.unit) exp = getattr(module, args.experiment)
try: try:
arguments = _parse_arguments(args.arguments) arguments = _parse_arguments(args.arguments)
@ -135,17 +139,16 @@ def main():
run_params = { run_params = {
"file": args.file, "file": args.file,
"unit": args.unit, "experiment": args.experiment,
"timeout": None, "timeout": None,
"arguments": arguments "arguments": arguments
} }
unit_inst = unit(dbh, exp_inst = exp(dbh,
scheduler=DummyScheduler(), scheduler=DummyScheduler(),
run_params=run_params, run_params=run_params,
**run_params["arguments"]) **run_params["arguments"])
unit_inst.run() exp_inst.run()
if hasattr(unit_inst, "analyze"): exp_inst.analyze()
unit_inst.analyze()
if args.hdf5 is not None: if args.hdf5 is not None:
f = h5py.File(args.hdf5, "w") f = h5py.File(args.hdf5, "w")

View File

@ -146,7 +146,7 @@ class ExplorerWindow(Window):
arguments = self.controls.get_arguments() arguments = self.controls.get_arguments()
run_params = { run_params = {
"file": data["file"], "file": data["file"],
"unit": data["unit"], "experiment": data["experiment"],
"timeout": None, "timeout": None,
"arguments": arguments, "arguments": arguments,
"rtr_group": data["file"] "rtr_group": data["file"]

View File

@ -12,7 +12,7 @@ class _QueueStoreSyncer(ListSyncer):
def convert(self, x): def convert(self, x):
rid, run_params = x rid, run_params = x
row = [rid, run_params["file"]] 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("" if e is None else str(e))
row.append(format_arguments(run_params["arguments"])) row.append(format_arguments(run_params["arguments"]))
return row return row
@ -27,7 +27,7 @@ class _TimedStoreSyncer(DictSyncer):
next_run, run_params = x next_run, run_params = x
row = [time.strftime("%m/%d %H:%M:%S", time.localtime(next_run)), row = [time.strftime("%m/%d %H:%M:%S", time.localtime(next_run)),
trid, run_params["file"]] 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("" if e is None else str(e))
row.append(format_arguments(run_params["arguments"])) row.append(format_arguments(run_params["arguments"]))
return row return row
@ -57,7 +57,7 @@ class SchedulerWindow(Window):
self.queue_store = Gtk.ListStore(int, str, str, str, str) self.queue_store = Gtk.ListStore(int, str, str, str, str)
self.queue_tree = Gtk.TreeView(self.queue_store) 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() renderer = Gtk.CellRendererText()
column = Gtk.TreeViewColumn(title, renderer, text=i) column = Gtk.TreeViewColumn(title, renderer, text=i)
self.queue_tree.append_column(column) 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_store = Gtk.ListStore(str, int, str, str, str, str)
self.timed_tree = Gtk.TreeView(self.timed_store) 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"]): "Timeout", "Arguments"]):
renderer = Gtk.CellRendererText() renderer = Gtk.CellRendererText()
column = Gtk.TreeViewColumn(title, renderer, text=i) column = Gtk.TreeViewColumn(title, renderer, text=i)

View 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

View File

@ -1,8 +1,8 @@
import os import os
from inspect import isclass
from artiq.protocols.sync_struct import Notifier from artiq.protocols.sync_struct import Notifier
from artiq.tools import file_import from artiq.tools import file_import
from artiq.language.experiment import is_experiment
def scan_experiments(): def scan_experiments():
@ -14,13 +14,19 @@ def scan_experiments():
except: except:
continue continue
for k, v in m.__dict__.items(): 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 = { entry = {
"file": os.path.join("repository", f), "file": os.path.join("repository", f),
"unit": k, "experiment": k,
"gui_file": getattr(v, "__artiq_gui_file__", None) "gui_file": getattr(v, "__artiq_gui_file__", None)
} }
r[v.__artiq_unit__] = entry r[name] = entry
return r return r

View File

@ -1,6 +1,5 @@
import sys import sys
import time import time
from inspect import isclass
import traceback import traceback
from artiq.protocols import pyon from artiq.protocols import pyon
@ -65,23 +64,24 @@ class Scheduler:
cancel_timed = make_parent_action("scheduler_cancel_timed", "trid") cancel_timed = make_parent_action("scheduler_cancel_timed", "trid")
def get_unit(file, unit): def get_exp(file, exp):
module = file_import(file) module = file_import(file)
if unit is None: if exp is None:
units = [v for k, v in module.__dict__.items() exps = [v for k, v in module.__dict__.items()
if isclass(v) and hasattr(v, "__artiq_unit__")] if is_experiment(v)]
if len(units) != 1: if len(exps) != 1:
raise ValueError("Found {} units in module".format(len(units))) raise ValueError("Found {} experiments in module"
return units[0] .format(len(exps)))
return exps[0]
else: else:
return getattr(module, unit) return getattr(module, exp)
def run(rid, run_params): def run(rid, run_params):
start_time = time.localtime() 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) init_rt_results(realtime_results)
realtime_results_set = set() realtime_results_set = set()
@ -97,13 +97,12 @@ def run(rid, run_params):
dbh = DBHub(ParentDDB, ParentPDB, rdb) dbh = DBHub(ParentDDB, ParentPDB, rdb)
try: try:
try: try:
unit_inst = unit(dbh, exp_inst = exp(dbh,
scheduler=Scheduler, scheduler=Scheduler,
run_params=run_params, run_params=run_params,
**run_params["arguments"]) **run_params["arguments"])
unit_inst.run() exp_inst.run()
if hasattr(unit_inst, "analyze"): exp_inst.analyze()
unit_inst.analyze()
except Exception: except Exception:
put_object({"action": "report_completed", put_object({"action": "report_completed",
"status": "failed", "status": "failed",
@ -114,7 +113,7 @@ def run(rid, run_params):
finally: finally:
dbh.close() dbh.close()
f = get_hdf5_output(start_time, rid, unit.__name__) f = get_hdf5_output(start_time, rid, exp.__name__)
try: try:
rdb.write_hdf5(f) rdb.write_hdf5(f)
finally: finally:

View File

@ -2,15 +2,16 @@ from artiq import *
import pulse_rate, rtio_skew, rpc_timing import pulse_rate, rtio_skew, rpc_timing
_units = [pulse_rate.PulseRate, rtio_skew.RTIOSkew, rpc_timing.RPCTiming]
class AllBenchmarks(AutoDB): _exps = [pulse_rate.PulseRate, rtio_skew.RTIOSkew, rpc_timing.RPCTiming]
__artiq_unit__ = "All benchmarks"
class AllBenchmarks(Experiment, AutoDB):
"""All benchmarks"""
def build(self): def build(self):
self.se = [] self.se = []
for unit in _units: for exp in _exps:
self.se.append(unit(self.dbh)) self.se.append(exp(self.dbh))
def run(self): def run(self):
for se in self.se: for se in self.se:

View File

@ -2,8 +2,8 @@ from artiq import *
from artiq.coredevice.runtime_exceptions import RTIOUnderflow from artiq.coredevice.runtime_exceptions import RTIOUnderflow
class PulseRate(AutoDB): class PulseRate(Experiment, AutoDB):
__artiq_unit__ = "Pulse rate" """Sustained pulse rate"""
class DBKeys: class DBKeys:
core = Device() core = Device()

View File

@ -3,8 +3,8 @@ from math import sqrt
from artiq import * from artiq import *
class RPCTiming(AutoDB): class RPCTiming(Experiment, AutoDB):
__artiq_unit__ = "RPC timing" """RPC timing"""
class DBKeys: class DBKeys:
core = Device() core = Device()

View File

@ -5,8 +5,8 @@ class PulseNotReceived(Exception):
pass pass
class RTIOSkew(AutoDB): class RTIOSkew(Experiment, AutoDB):
__artiq_unit__ = "RTIO skew" """RTIO skew"""
class DBKeys: class DBKeys:
core = Device() core = Device()

View File

@ -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 * from artiq import *
class LED(AutoDB): class LED(Experiment, AutoDB):
__artiq_unit__ = "ARTIQ tutorial"
class DBKeys: class DBKeys:
core = Device() core = Device()
led = 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 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. 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: :: 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(): def input_led_state():
return int(input("Enter desired LED state: ")) return int(input("Enter desired LED state: "))
class LED(AutoDB): class LED(Experiment, AutoDB):
__artiq_unit__ = "ARTIQ tutorial"
class DBKeys: class DBKeys:
core = Device() core = Device()
led = Device() led = Device()
@ -89,9 +83,7 @@ Create a new file ``rtio.py`` containing the following: ::
from artiq import * from artiq import *
class Tutorial(AutoDB): class Tutorial(Experiment, AutoDB):
__artiq_unit__ = "ARTIQ tutorial"
class DBKeys: class DBKeys:
core = Device() core = Device()
ttl0 = 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(): def print_underflow():
print("RTIO underflow occured") print("RTIO underflow occured")
class Tutorial(AutoDB): class Tutorial(Experiment, AutoDB):
__artiq_unit__ = "ARTIQ tutorial"
class DBKeys: class DBKeys:
core = Device() core = Device()
led = Device() led = Device()

View File

@ -1 +1 @@
{"flopping_freq": 1500.0294421161527} {"flopping_freq": 1500.0164816344934}

View File

@ -1,8 +1,8 @@
from artiq import * from artiq import *
class DDSTest(AutoDB): class DDSTest(Experiment, AutoDB):
__artiq_unit__ = "DDS test" """DDS test"""
class DBKeys: class DBKeys:
core = Device() core = Device()

View File

@ -23,8 +23,8 @@ def model_numpy(xdata, F0):
return r return r
class FloppingF(AutoDB): class FloppingF(Experiment, AutoDB):
__artiq_unit__ = "Flopping F simulation" """Flopping F simulation"""
__artiq_gui_file__ = "flopping_f_simulation_gui.py" __artiq_gui_file__ = "flopping_f_simulation_gui.py"
class DBKeys: class DBKeys:

View File

@ -3,8 +3,8 @@ import sys
from artiq import * from artiq import *
class Mandelbrot(AutoDB): class Mandelbrot(Experiment, AutoDB):
__artiq_unit__ = "Mandelbrot set demo" """Mandelbrot set demo"""
class DBKeys: class DBKeys:
core = Device() core = Device()

View File

@ -1,8 +1,8 @@
from artiq import * from artiq import *
class PhotonHistogram(AutoDB): class PhotonHistogram(Experiment, AutoDB):
__artiq_unit__ = "Photon histogram" """Photon histogram"""
class DBKeys: class DBKeys:
core = Device() core = Device()

View File

@ -10,8 +10,8 @@ transport_data = dict(
# 4 devices, 3 board each, 3 dacs each # 4 devices, 3 board each, 3 dacs each
) )
class Transport(AutoDB): class Transport(Experiment, AutoDB):
__artiq_unit__ = "Transport" """Transport"""
class DBKeys: class DBKeys:
core = Device() core = Device()

View File

@ -1,8 +1,8 @@
from artiq import * from artiq import *
class AluminumSpectroscopy(AutoDB): class AluminumSpectroscopy(Experiment, AutoDB):
__artiq_unit__ = "Aluminum spectroscopy (simulation)" """Aluminum spectroscopy (simulation)"""
class DBKeys: class DBKeys:
core = Device() core = Device()

View File

@ -1,8 +1,8 @@
from artiq import * from artiq import *
class SimpleSimulation(AutoDB): class SimpleSimulation(Experiment, AutoDB):
__artiq_unit__ = "Simple simulation" """Simple simulation"""
class DBKeys: class DBKeys:
core = Device() core = Device()