mirror of https://github.com/m-labs/artiq.git
environment: refactor
This commit is contained in:
parent
12a8c76df9
commit
caf774579a
|
@ -36,6 +36,8 @@ unreleased [2.x]
|
|||
(i.e. grouping by day and then by hour, instead of by day and then by minute)
|
||||
* GUI tools save their state file in the user's home directory instead of the
|
||||
current directory.
|
||||
* The ``parent`` keyword argument of ``HasEnvironment`` (and ``EnvExperiment``)
|
||||
has been replaced. Pass the parent as first argument instead.
|
||||
|
||||
|
||||
unreleased [1.0rc3]
|
||||
|
|
|
@ -54,8 +54,8 @@ class ArgumentsDemo(EnvExperiment):
|
|||
self.setattr_argument("enum", EnumerationValue(
|
||||
["foo", "bar", "quux"], "foo"), "Group")
|
||||
|
||||
self.sc1 = SubComponent1(parent=self)
|
||||
self.sc2 = SubComponent2(parent=self)
|
||||
self.sc1 = SubComponent1(self)
|
||||
self.sc2 = SubComponent2(self)
|
||||
|
||||
def run(self):
|
||||
logging.error("logging test: error")
|
||||
|
|
|
@ -17,8 +17,8 @@ class Transport(EnvExperiment):
|
|||
|
||||
self.setattr_argument("wait_at_stop", NumberValue(100*us))
|
||||
self.setattr_argument("speed", NumberValue(1/(10*us)))
|
||||
self.repeats = int(self.get_argument("repeats", NumberValue(100)))
|
||||
self.bins = int(self.get_argument("bins", NumberValue(100)))
|
||||
self.setattr_argument("repeats", NumberValue(100, step=1, ndecimals=0))
|
||||
self.setattr_argument("bins", NumberValue(100, step=1, ndecimals=0))
|
||||
|
||||
t = np.linspace(0, 10, 101) # waveform time
|
||||
u = 1 - np.cos(np.pi*t/t[-1])
|
||||
|
|
|
@ -7,9 +7,6 @@ from artiq.experiment import *
|
|||
|
||||
class Histograms(EnvExperiment):
|
||||
"""Histograms demo"""
|
||||
def build(self):
|
||||
pass
|
||||
|
||||
def run(self):
|
||||
nbins = 50
|
||||
npoints = 20
|
||||
|
|
|
@ -102,7 +102,7 @@ class SpeedBenchmark(EnvExperiment):
|
|||
self.scheduler.priority, None, False)
|
||||
|
||||
def run_without_scheduler(self, pause):
|
||||
payload = globals()["_Payload" + self.payload](*self.managers())
|
||||
payload = globals()["_Payload" + self.payload](self)
|
||||
|
||||
start_time = time.monotonic()
|
||||
for i in range(int(self.nruns)):
|
||||
|
|
|
@ -12,7 +12,7 @@ import h5py
|
|||
|
||||
from llvmlite_artiq import binding as llvm
|
||||
|
||||
from artiq.language.environment import EnvExperiment
|
||||
from artiq.language.environment import EnvExperiment, ProcessArgumentManager
|
||||
from artiq.master.databases import DeviceDB, DatasetDB
|
||||
from artiq.master.worker_db import DeviceManager, DatasetManager
|
||||
from artiq.coredevice.core import CompileError, host_only
|
||||
|
@ -167,7 +167,8 @@ def _build_experiment(device_mgr, dataset_mgr, args):
|
|||
"arguments": arguments
|
||||
}
|
||||
device_mgr.virtual_devices["scheduler"].expid = expid
|
||||
return exp(device_mgr, dataset_mgr, **arguments)
|
||||
argument_mgr = ProcessArgumentManager(arguments)
|
||||
return exp((device_mgr, dataset_mgr, argument_mgr))
|
||||
|
||||
|
||||
def run(with_file=False):
|
||||
|
|
|
@ -7,8 +7,7 @@ from artiq.protocols import pyon
|
|||
__all__ = ["NoDefault",
|
||||
"PYONValue", "BooleanValue", "EnumerationValue",
|
||||
"NumberValue", "StringValue",
|
||||
"HasEnvironment",
|
||||
"Experiment", "EnvExperiment", "is_experiment"]
|
||||
"HasEnvironment", "Experiment", "EnvExperiment"]
|
||||
|
||||
|
||||
class NoDefault:
|
||||
|
@ -145,51 +144,63 @@ class StringValue(_SimpleArgProcessor):
|
|||
pass
|
||||
|
||||
|
||||
class HasEnvironment:
|
||||
"""Provides methods to manage the environment of an experiment (devices,
|
||||
parameters, results, arguments)."""
|
||||
def __init__(self, device_mgr=None, dataset_mgr=None, *, parent=None,
|
||||
default_arg_none=False, **kwargs):
|
||||
class TraceArgumentManager:
|
||||
def __init__(self):
|
||||
self.requested_args = OrderedDict()
|
||||
|
||||
self.__device_mgr = device_mgr
|
||||
self.__dataset_mgr = dataset_mgr
|
||||
self.__parent = parent
|
||||
self.__default_arg_none = default_arg_none
|
||||
def get(self, key, processor, group):
|
||||
self.requested_args[key] = processor, group
|
||||
return None
|
||||
|
||||
|
||||
class ProcessArgumentManager:
|
||||
def __init__(self, unprocessed_arguments):
|
||||
self.unprocessed_arguments = unprocessed_arguments
|
||||
|
||||
def get(self, key, processor, group):
|
||||
if key in self.unprocessed_arguments:
|
||||
r = processor.process(self.unprocessed_arguments[key])
|
||||
else:
|
||||
r = processor.default()
|
||||
return r
|
||||
|
||||
|
||||
class HasEnvironment:
|
||||
"""Provides methods to manage the environment of an experiment (arguments,
|
||||
devices, datasets)."""
|
||||
def __init__(self, managers_or_parent, *args, **kwargs):
|
||||
if isinstance(managers_or_parent, tuple):
|
||||
self.__device_mgr = managers_or_parent[0]
|
||||
self.__dataset_mgr = managers_or_parent[1]
|
||||
self.__argument_mgr = managers_or_parent[2]
|
||||
else:
|
||||
self.__device_mgr = managers_or_parent.__device_mgr
|
||||
self.__dataset_mgr = managers_or_parent.__dataset_mgr
|
||||
self.__argument_mgr = managers_or_parent.__argument_mgr
|
||||
|
||||
self.__kwargs = kwargs
|
||||
self.__in_build = True
|
||||
self.build()
|
||||
self.build(*args, **kwargs)
|
||||
self.__in_build = False
|
||||
for key in self.__kwargs.keys():
|
||||
if key not in self.requested_args:
|
||||
raise TypeError("Got unexpected argument: " + key)
|
||||
del self.__kwargs
|
||||
|
||||
def build(self):
|
||||
"""Must be implemented by the user to request arguments.
|
||||
"""Should be implemented by the user to request arguments.
|
||||
|
||||
Other initialization steps such as requesting devices and parameters
|
||||
or initializing real-time results may also be performed here.
|
||||
Other initialization steps such as requesting devices may also be
|
||||
performed here.
|
||||
|
||||
When the repository is scanned, any requested devices and parameters
|
||||
are set to ``None``."""
|
||||
raise NotImplementedError
|
||||
When the repository is scanned, any requested devices and arguments
|
||||
are set to ``None``.
|
||||
|
||||
def managers(self):
|
||||
"""Returns the device manager and the dataset manager, in this order.
|
||||
Leftover positional and keyword arguments from the constructor are
|
||||
forwarded to this method. This is intended for experiments that are
|
||||
only meant to be executed programmatically (not from the GUI)."""
|
||||
pass
|
||||
|
||||
This is the same order that the constructor takes them, allowing
|
||||
sub-objects to be created with this idiom to pass the environment
|
||||
around: ::
|
||||
|
||||
sub_object = SomeLibrary(*self.managers())
|
||||
"""
|
||||
return self.__device_mgr, self.__dataset_mgr
|
||||
|
||||
def get_argument(self, key, processor=None, group=None):
|
||||
def get_argument(self, key, processor, group=None):
|
||||
"""Retrieves and returns the value of an argument.
|
||||
|
||||
This function should only be called from ``build``.
|
||||
|
||||
:param key: Name of the argument.
|
||||
:param processor: A description of how to process the argument, such
|
||||
as instances of ``BooleanValue`` and ``NumberValue``.
|
||||
|
@ -199,22 +210,7 @@ class HasEnvironment:
|
|||
if not self.__in_build:
|
||||
raise TypeError("get_argument() should only "
|
||||
"be called from build()")
|
||||
if self.__parent is not None and key not in self.__kwargs:
|
||||
return self.__parent.get_argument(key, processor, group)
|
||||
if processor is None:
|
||||
processor = PYONValue()
|
||||
self.requested_args[key] = processor, group
|
||||
try:
|
||||
argval = self.__kwargs[key]
|
||||
except KeyError:
|
||||
try:
|
||||
return processor.default()
|
||||
except DefaultMissing:
|
||||
if self.__default_arg_none:
|
||||
return None
|
||||
else:
|
||||
raise
|
||||
return processor.process(argval)
|
||||
return self.__argument_mgr.get(key, processor, group)
|
||||
|
||||
def setattr_argument(self, key, processor=None, group=None):
|
||||
"""Sets an argument as attribute. The names of the argument and of the
|
||||
|
@ -223,16 +219,10 @@ class HasEnvironment:
|
|||
|
||||
def get_device_db(self):
|
||||
"""Returns the full contents of the device database."""
|
||||
if self.__parent is not None:
|
||||
return self.__parent.get_device_db()
|
||||
return self.__device_mgr.get_device_db()
|
||||
|
||||
def get_device(self, key):
|
||||
"""Creates and returns a device driver."""
|
||||
if self.__parent is not None:
|
||||
return self.__parent.get_device(key)
|
||||
if self.__device_mgr is None:
|
||||
raise ValueError("Device manager not present")
|
||||
return self.__device_mgr.get(key)
|
||||
|
||||
def setattr_device(self, key):
|
||||
|
@ -254,11 +244,6 @@ class HasEnvironment:
|
|||
:param save: the data is saved into the local storage of the current
|
||||
run (archived as a HDF5 file).
|
||||
"""
|
||||
if self.__parent is not None:
|
||||
self.__parent.set_dataset(key, value, broadcast, persist, save)
|
||||
return
|
||||
if self.__dataset_mgr is None:
|
||||
raise ValueError("Dataset manager not present")
|
||||
self.__dataset_mgr.set(key, value, broadcast, persist, save)
|
||||
|
||||
def mutate_dataset(self, key, index, value):
|
||||
|
@ -267,10 +252,6 @@ class HasEnvironment:
|
|||
|
||||
If the dataset was created in broadcast mode, the modification is
|
||||
immediately transmitted."""
|
||||
if self.__parent is not None:
|
||||
self.__parent.mutate_dataset(key, index, value)
|
||||
if self.__dataset_mgr is None:
|
||||
raise ValueError("Dataset manager not present")
|
||||
self.__dataset_mgr.mutate(key, index, value)
|
||||
|
||||
def get_dataset(self, key, default=NoDefault):
|
||||
|
@ -283,10 +264,6 @@ class HasEnvironment:
|
|||
If the dataset does not exist, returns the default value. If no default
|
||||
is provided, raises ``KeyError``.
|
||||
"""
|
||||
if self.__parent is not None:
|
||||
return self.__parent.get_dataset(key, default)
|
||||
if self.__dataset_mgr is None:
|
||||
raise ValueError("Dataset manager not present")
|
||||
try:
|
||||
return self.__dataset_mgr.get(key)
|
||||
except KeyError:
|
||||
|
@ -302,7 +279,7 @@ class HasEnvironment:
|
|||
|
||||
|
||||
class Experiment:
|
||||
"""Base class for experiments.
|
||||
"""Base class for top-level experiments.
|
||||
|
||||
Deriving from this class enables automatic experiment discovery in
|
||||
Python modules.
|
||||
|
@ -348,15 +325,15 @@ class Experiment:
|
|||
|
||||
|
||||
class EnvExperiment(Experiment, HasEnvironment):
|
||||
"""Base class for experiments that use the ``HasEnvironment`` environment
|
||||
manager.
|
||||
"""Base class for top-level experiments that use the ``HasEnvironment``
|
||||
environment manager.
|
||||
|
||||
Most experiment should derive from this class."""
|
||||
pass
|
||||
|
||||
|
||||
def is_experiment(o):
|
||||
"""Checks if a Python object is an instantiable user experiment."""
|
||||
"""Checks if a Python object is a top-level experiment class."""
|
||||
return (isclass(o)
|
||||
and issubclass(o, Experiment)
|
||||
and o is not Experiment
|
||||
|
|
|
@ -12,7 +12,8 @@ from artiq.protocols import pipe_ipc, pyon
|
|||
from artiq.protocols.packed_exceptions import raise_packed_exc
|
||||
from artiq.tools import multiline_log_config, file_import
|
||||
from artiq.master.worker_db import DeviceManager, DatasetManager
|
||||
from artiq.language.environment import is_experiment
|
||||
from artiq.language.environment import (is_experiment, TraceArgumentManager,
|
||||
ProcessArgumentManager)
|
||||
from artiq.language.core import set_watchdog_factory, TerminationRequested
|
||||
from artiq.coredevice.core import CompileError, host_only, _render_diagnostic
|
||||
from artiq import __version__ as artiq_version
|
||||
|
@ -138,11 +139,11 @@ def examine(device_mgr, dataset_mgr, file):
|
|||
name = exp_class.__doc__.splitlines()[0].strip()
|
||||
if name[-1] == ".":
|
||||
name = name[:-1]
|
||||
exp_inst = exp_class(device_mgr, dataset_mgr,
|
||||
default_arg_none=True)
|
||||
argument_mgr = TraceArgumentManager()
|
||||
exp_inst = exp_class((device_mgr, dataset_mgr, argument_mgr))
|
||||
arginfo = OrderedDict(
|
||||
(k, (proc.describe(), group))
|
||||
for k, (proc, group) in exp_inst.requested_args.items())
|
||||
for k, (proc, group) in argument_mgr.requested_args.items())
|
||||
register_experiment(class_name, name, arginfo)
|
||||
|
||||
|
||||
|
@ -213,8 +214,8 @@ def main():
|
|||
time.strftime("%H", start_time))
|
||||
os.makedirs(dirname, exist_ok=True)
|
||||
os.chdir(dirname)
|
||||
exp_inst = exp(
|
||||
device_mgr, dataset_mgr, **expid["arguments"])
|
||||
argument_mgr = ProcessArgumentManager(expid["arguments"])
|
||||
exp_inst = exp((device_mgr, dataset_mgr, argument_mgr))
|
||||
put_object({"action": "completed"})
|
||||
elif action == "prepare":
|
||||
exp_inst.prepare()
|
||||
|
|
|
@ -6,19 +6,21 @@ from artiq.sim import devices as sim_devices
|
|||
from artiq.test.hardware_testbench import ExperimentCase
|
||||
|
||||
|
||||
def _run_on_host(k_class, **arguments):
|
||||
dmgr = dict()
|
||||
dmgr["core"] = sim_devices.Core(dmgr)
|
||||
k_inst = k_class(dmgr, **arguments)
|
||||
def _run_on_host(k_class, *args, **kwargs):
|
||||
device_mgr = dict()
|
||||
device_mgr["core"] = sim_devices.Core(device_mgr)
|
||||
|
||||
k_inst = k_class((device_mgr, None, None),
|
||||
*args, **kwargs)
|
||||
k_inst.run()
|
||||
return k_inst
|
||||
|
||||
|
||||
class _Primes(EnvExperiment):
|
||||
def build(self):
|
||||
def build(self, output_list, maximum):
|
||||
self.setattr_device("core")
|
||||
self.setattr_argument("output_list")
|
||||
self.setattr_argument("maximum")
|
||||
self.output_list = output_list
|
||||
self.maximum = maximum
|
||||
|
||||
def _add_output(self, x):
|
||||
self.output_list.append(x)
|
||||
|
@ -72,10 +74,10 @@ class _Misc(EnvExperiment):
|
|||
|
||||
|
||||
class _PulseLogger(EnvExperiment):
|
||||
def build(self):
|
||||
def build(self, parent_test, name):
|
||||
self.setattr_device("core")
|
||||
self.setattr_argument("parent_test")
|
||||
self.setattr_argument("name")
|
||||
self.parent_test = parent_test
|
||||
self.name = name
|
||||
|
||||
def _append(self, t, l, f):
|
||||
if not hasattr(self.parent_test, "first_timestamp"):
|
||||
|
@ -98,12 +100,12 @@ class _PulseLogger(EnvExperiment):
|
|||
|
||||
|
||||
class _Pulses(EnvExperiment):
|
||||
def build(self):
|
||||
def build(self, output_list):
|
||||
self.setattr_device("core")
|
||||
self.setattr_argument("output_list")
|
||||
self.output_list = output_list
|
||||
|
||||
for name in "a", "b", "c", "d":
|
||||
pl = _PulseLogger(*self.managers(),
|
||||
pl = _PulseLogger(self,
|
||||
parent_test=self,
|
||||
name=name)
|
||||
setattr(self, name, pl)
|
||||
|
@ -125,9 +127,9 @@ class _MyException(Exception):
|
|||
|
||||
|
||||
class _Exceptions(EnvExperiment):
|
||||
def build(self):
|
||||
def build(self, trace):
|
||||
self.setattr_device("core")
|
||||
self.setattr_argument("trace")
|
||||
self.trace = trace
|
||||
|
||||
def _trace(self, i):
|
||||
self.trace.append(i)
|
||||
|
@ -172,9 +174,9 @@ class _Exceptions(EnvExperiment):
|
|||
|
||||
|
||||
class _RPCExceptions(EnvExperiment):
|
||||
def build(self):
|
||||
def build(self, catch):
|
||||
self.setattr_device("core")
|
||||
self.setattr_argument("catch", PYONValue(False))
|
||||
self.catch = catch
|
||||
|
||||
self.success = False
|
||||
|
||||
|
|
|
@ -147,11 +147,11 @@ class Watchdog(EnvExperiment):
|
|||
|
||||
|
||||
class LoopbackCount(EnvExperiment):
|
||||
def build(self):
|
||||
def build(self, npulses):
|
||||
self.setattr_device("core")
|
||||
self.setattr_device("loop_in")
|
||||
self.setattr_device("loop_out")
|
||||
self.setattr_argument("npulses")
|
||||
self.npulses = npulses
|
||||
|
||||
def set_count(self, count):
|
||||
self.set_dataset("count", count)
|
||||
|
@ -320,9 +320,9 @@ class CoredeviceTest(ExperimentCase):
|
|||
|
||||
|
||||
class RPCTiming(EnvExperiment):
|
||||
def build(self):
|
||||
def build(self, repeats=100):
|
||||
self.setattr_device("core")
|
||||
self.setattr_argument("repeats", PYONValue(100))
|
||||
self.repeats = repeats
|
||||
|
||||
def nop(self):
|
||||
pass
|
||||
|
|
|
@ -108,9 +108,11 @@ class ExperimentCase(unittest.TestCase):
|
|||
def tearDown(self):
|
||||
self.device_mgr.close_devices()
|
||||
|
||||
def create(self, cls, **kwargs):
|
||||
def create(self, cls, *args, **kwargs):
|
||||
try:
|
||||
exp = cls(self.device_mgr, self.dataset_mgr, **kwargs)
|
||||
exp = cls(
|
||||
(self.device_mgr, self.dataset_mgr, None),
|
||||
*args, **kwargs)
|
||||
exp.prepare()
|
||||
return exp
|
||||
except KeyError as e:
|
||||
|
@ -118,15 +120,15 @@ class ExperimentCase(unittest.TestCase):
|
|||
raise unittest.SkipTest(
|
||||
"device_db entry `{}` not found".format(*e.args))
|
||||
|
||||
def execute(self, cls, **kwargs):
|
||||
def execute(self, cls, *args, **kwargs):
|
||||
expid = {
|
||||
"file": sys.modules[cls.__module__].__file__,
|
||||
"class_name": cls.__name__,
|
||||
"arguments": kwargs
|
||||
"arguments": dict()
|
||||
}
|
||||
self.device_mgr.virtual_devices["scheduler"].expid = expid
|
||||
try:
|
||||
exp = self.create(cls, **kwargs)
|
||||
exp = self.create(cls, *args, **kwargs)
|
||||
exp.run()
|
||||
exp.analyze()
|
||||
return exp
|
||||
|
|
Loading…
Reference in New Issue