From 32d141f5ac8e76ae58062b9a2f06b8093b70728f Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Mon, 13 Jul 2015 22:08:20 +0200 Subject: [PATCH] refactor ddb/pdb/rdb --- artiq/coredevice/comm_dummy.py | 6 +- artiq/coredevice/comm_serial.py | 9 +- artiq/coredevice/comm_tcp.py | 9 +- artiq/coredevice/core.py | 12 +- artiq/coredevice/dds.py | 21 +- artiq/coredevice/ttl.py | 28 ++- artiq/devices/pdq2/mediator.py | 22 +-- artiq/devices/pxi6733/mediator.py | 25 +-- artiq/frontend/artiq_compile.py | 9 +- artiq/frontend/artiq_gui.py | 2 + artiq/frontend/artiq_master.py | 15 +- artiq/frontend/artiq_run.py | 43 ++--- artiq/language/__init__.py | 9 +- artiq/language/db.py | 133 ------------- artiq/language/environment.py | 182 ++++++++++++++++++ artiq/language/experiment.py | 54 ------ artiq/master/repository.py | 5 +- artiq/master/results.py | 103 ---------- artiq/master/worker_db.py | 164 +++++++++------- artiq/master/worker_impl.py | 32 ++- artiq/protocols/file_db.py | 2 +- artiq/sim/devices.py | 30 ++- artiq/test/coredevice.py | 153 +++++++-------- artiq/test/coredevice_vs_host.py | 62 +++--- artiq/test/hardware_testbench.py | 18 +- artiq/test/scheduler.py | 10 +- artiq/test/transforms.py | 4 +- artiq/test/worker.py | 12 +- artiq/tools.py | 2 +- doc/manual/conf.py | 2 +- doc/manual/core_language_reference.rst | 6 +- doc/manual/developing_a_ndsp.rst | 2 +- doc/manual/faq.rst | 19 +- doc/manual/getting_started.rst | 42 ++-- examples/master/repository/dds_test.py | 22 +-- .../repository/flopping_f_simulation.py | 28 ++- examples/master/repository/handover.py | 8 +- examples/master/repository/mandelbrot.py | 6 +- .../master/repository/photon_histogram.py | 38 ++-- examples/master/repository/transport.py | 29 ++- examples/sim/al_spectroscopy.py | 24 +-- examples/sim/simple_simulation.py | 27 ++- 42 files changed, 650 insertions(+), 779 deletions(-) delete mode 100644 artiq/language/db.py create mode 100644 artiq/language/environment.py delete mode 100644 artiq/language/experiment.py delete mode 100644 artiq/master/results.py diff --git a/artiq/coredevice/comm_dummy.py b/artiq/coredevice/comm_dummy.py index 807f83ca5..d2803ce7a 100644 --- a/artiq/coredevice/comm_dummy.py +++ b/artiq/coredevice/comm_dummy.py @@ -1,6 +1,5 @@ from operator import itemgetter -from artiq.language.db import AutoDB from artiq.language.units import ms from artiq.coredevice.runtime import LinkInterface @@ -13,7 +12,10 @@ class _RuntimeEnvironment(LinkInterface): return str(self.llvm_module) -class Comm(AutoDB): +class Comm: + def __init__(self, dmgr): + pass + def get_runtime_env(self): return _RuntimeEnvironment() diff --git a/artiq/coredevice/comm_serial.py b/artiq/coredevice/comm_serial.py index 25c926a25..70218da14 100644 --- a/artiq/coredevice/comm_serial.py +++ b/artiq/coredevice/comm_serial.py @@ -3,16 +3,15 @@ import serial import struct from artiq.coredevice.comm_generic import CommGeneric -from artiq.language.db import * logger = logging.getLogger(__name__) -class Comm(CommGeneric, AutoDB): - class DBKeys: - serial_dev = Argument() - baud_rate = Argument(115200) +class Comm(CommGeneric): + def __init__(self, dmgr, serial_dev, baud_rate=115200): + self.serial_dev = serial_dev + self.baud_rate = baud_rate def open(self): if hasattr(self, "port"): diff --git a/artiq/coredevice/comm_tcp.py b/artiq/coredevice/comm_tcp.py index 4cc254a10..eda672750 100644 --- a/artiq/coredevice/comm_tcp.py +++ b/artiq/coredevice/comm_tcp.py @@ -2,16 +2,15 @@ import logging import socket from artiq.coredevice.comm_generic import CommGeneric -from artiq.language.db import * logger = logging.getLogger(__name__) -class Comm(CommGeneric, AutoDB): - class DBKeys: - host = Argument() - port = Argument(1381) +class Comm(CommGeneric): + def __init__(self, dmgr, host, port=1381): + self.host = host + self.port = port def open(self): if hasattr(self, "socket"): diff --git a/artiq/coredevice/core.py b/artiq/coredevice/core.py index 61e7ed353..b41561161 100644 --- a/artiq/coredevice/core.py +++ b/artiq/coredevice/core.py @@ -1,7 +1,6 @@ import os from artiq.language.core import * -from artiq.language.db import * from artiq.language.units import ns from artiq.transforms.inline import inline @@ -46,13 +45,12 @@ def _no_debug_unparse(label, node): pass -class Core(AutoDB): - class DBKeys: - comm = Device() - ref_period = Argument(8*ns) - external_clock = Argument(False) +class Core: + def __init__(self, dmgr, ref_period=8*ns, external_clock=False): + self.comm = dmgr.get("comm") + self.ref_period = ref_period + self.external_clock = external_clock - def build(self): self.first_run = True self.core = self self.comm.core = self diff --git a/artiq/coredevice/dds.py b/artiq/coredevice/dds.py index 323209479..9ba4584d8 100644 --- a/artiq/coredevice/dds.py +++ b/artiq/coredevice/dds.py @@ -1,5 +1,4 @@ from artiq.language.core import * -from artiq.language.db import * from artiq.language.units import * @@ -23,14 +22,12 @@ class _BatchContextManager: self.dds_bus.batch_exit() -class DDSBus(AutoDB): +class DDSBus: """Core device Direct Digital Synthesis (DDS) bus batching driver. Manages batching of DDS commands on a DDS shared bus.""" - class DBKeys: - core = Device() - - def build(self): + def __init__(self, dmgr): + self.core = dmgr.get("core") self.batch = _BatchContextManager(self) @kernel @@ -46,7 +43,7 @@ class DDSBus(AutoDB): syscall("dds_batch_exit") -class _DDSGeneric(AutoDB): +class _DDSGeneric: """Core device Direct Digital Synthesis (DDS) driver. Controls one DDS channel managed directly by the core device's runtime. @@ -57,12 +54,10 @@ class _DDSGeneric(AutoDB): :param sysclk: DDS system frequency. :param channel: channel number of the DDS device to control. """ - class DBKeys: - core = Device() - sysclk = Argument() - channel = Argument() - - def build(self): + def __init__(self, dmgr, sysclk, channel): + self.core = dmgr.get("core") + self.sysclk = sysclk + self.channel = channel self.phase_mode = PHASE_MODE_CONTINUOUS @portable diff --git a/artiq/coredevice/ttl.py b/artiq/coredevice/ttl.py index d65aa62ff..b9d680f8f 100644 --- a/artiq/coredevice/ttl.py +++ b/artiq/coredevice/ttl.py @@ -1,8 +1,7 @@ from artiq.language.core import * -from artiq.language.db import * -class TTLOut(AutoDB): +class TTLOut: """RTIO TTL output driver. This should be used with output-only channels. @@ -10,12 +9,10 @@ class TTLOut(AutoDB): :param core: core device :param channel: channel number """ - class DBKeys: - core = Device() - channel = Argument() + def __init__(self, dmgr, channel): + self.core = dmgr.get("core") + self.channel = channel - - def build(self): # in RTIO cycles self.o_previous_timestamp = int64(0) @@ -58,7 +55,7 @@ class TTLOut(AutoDB): self.off() -class TTLInOut(AutoDB): +class TTLInOut: """RTIO TTL input/output driver. In output mode, provides functions to set the logic level on the signal. @@ -76,11 +73,10 @@ class TTLInOut(AutoDB): :param core: core device :param channel: channel number """ - class DBKeys: - core = Device() - channel = Argument() + def __init__(self, dmgr, channel): + self.core = dmgr.get("core") + self.channel = channel - def build(self): # in RTIO cycles self.o_previous_timestamp = int64(0) self.i_previous_timestamp = int64(0) @@ -208,7 +204,7 @@ class TTLInOut(AutoDB): return syscall("ttl_get", self.channel, self.i_previous_timestamp) -class TTLClockGen(AutoDB): +class TTLClockGen: """RTIO TTL clock generator driver. This should be used with TTL channels that have a clock generator @@ -217,9 +213,9 @@ class TTLClockGen(AutoDB): :param core: core device :param channel: channel number """ - class DBKeys: - core = Device() - channel = Argument() + def __init__(self, dmgr, channel): + self.core = dmgr.get("core") + self.channel = channel def build(self): # in RTIO cycles diff --git a/artiq/devices/pdq2/mediator.py b/artiq/devices/pdq2/mediator.py index 39b6a89e6..27265bf9e 100644 --- a/artiq/devices/pdq2/mediator.py +++ b/artiq/devices/pdq2/mediator.py @@ -1,5 +1,4 @@ from artiq.language.core import * -from artiq.language.db import * from artiq.language.units import * @@ -154,19 +153,14 @@ class _Frame: self.pdq.next_segment = -1 -class CompoundPDQ2(AutoDB): - class DBKeys: - core = Device() - pdq2_devices = Argument() - trigger_device = Argument() - frame_devices = Argument() - - def build(self): - self.pdq2s = [self.dbh.get_device(d) for d in self.pdq2_devices] - self.trigger = self.dbh.get_device(self.trigger_device) - self.frame0 = self.dbh.get_device(self.frame_devices[0]) - self.frame1 = self.dbh.get_device(self.frame_devices[1]) - self.frame2 = self.dbh.get_device(self.frame_devices[2]) +class CompoundPDQ2: + def __init__(self, dmgr, pdq2_devices, trigger_device, frame_devices): + self.core = dmgr.get("core") + self.pdq2s = [dmgr.get(d) for d in self.pdq2_devices] + self.trigger = dmgr.get(trigger_device) + self.frame0 = dmgr.get(frame_devices[0]) + self.frame1 = dmgr.get(frame_devices[1]) + self.frame2 = dmgr.get(frame_devices[2]) self.frames = [] self.current_frame = -1 diff --git a/artiq/devices/pxi6733/mediator.py b/artiq/devices/pxi6733/mediator.py index 675a08847..047653b61 100644 --- a/artiq/devices/pxi6733/mediator.py +++ b/artiq/devices/pxi6733/mediator.py @@ -1,7 +1,6 @@ import numpy as np from artiq.language.core import * -from artiq.language.db import * from artiq.language.units import * from artiq.wavesynth.compute_samples import Synthesizer @@ -139,20 +138,16 @@ class _Frame: self.daqmx.next_segment = -1 -class CompoundDAQmx(AutoDB): - class DBKeys: - core = Device() - daqmx_device = Argument() - clock_device = Argument() - channel_count = Argument() - sample_rate = Argument() - sample_rate_in_mu = Argument(False) - - def build(self): - self.daqmx = self.dbh.get_device(self.daqmx_device) - self.clock = self.dbh.get_device(self.clock_device) - - if not self.sample_rate_in_mu: +class CompoundDAQmx: + def __init__(self, dmgr, daqmx_device, clock_device, channel_count, + sample_rate, sample_rate_in_mu=False): + self.core = dmgr.get("core") + self.daqmx = dmgr.get(daqmx_device) + self.clock = dmgr.get(clock_device) + self.channel_count = channel_count + if self.sample_rate_in_mu: + self.sample_rate = sample_rate + else: self.sample_rate = self.clock.frequency_to_ftw(sample_rate) self.frame = None diff --git a/artiq/frontend/artiq_compile.py b/artiq/frontend/artiq_compile.py index e4e369211..c47360c51 100755 --- a/artiq/frontend/artiq_compile.py +++ b/artiq/frontend/artiq_compile.py @@ -4,7 +4,7 @@ import logging import argparse from artiq.protocols.file_db import FlatFileDB -from artiq.master.worker_db import DBHub +from artiq.master.worker_db import DeviceManager from artiq.tools import * @@ -36,15 +36,14 @@ def main(): args = get_argparser().parse_args() init_logger(args) - ddb = FlatFileDB(args.ddb) + dmgr = DeviceManager(FlatFileDB(args.ddb)) pdb = FlatFileDB(args.pdb) - dbh = DBHub(ddb, pdb, rdb=None, read_only=True) try: module = file_import(args.file) exp = get_experiment(module, args.experiment) arguments = parse_arguments(args.arguments) - exp_inst = exp(dbh, **arguments) + exp_inst = exp(dmgr, pdb, **arguments) if (not hasattr(exp.run, "k_function_info") or not exp.run.k_function_info): @@ -56,7 +55,7 @@ def main(): [exp_inst], {}, with_attr_writeback=False) finally: - dbh.close_devices() + dmgr.close_devices() if rpc_map: raise ValueError("Experiment must not use RPC") diff --git a/artiq/frontend/artiq_gui.py b/artiq/frontend/artiq_gui.py index a6278a28c..839121458 100755 --- a/artiq/frontend/artiq_gui.py +++ b/artiq/frontend/artiq_gui.py @@ -18,9 +18,11 @@ from artiq.gui.parameters import ParametersDock from artiq.gui.schedule import ScheduleDock from artiq.gui.log import LogDock + data_dir = os.path.join(os.path.abspath(os.path.dirname(__file__)), "..", "gui") + def get_argparser(): parser = argparse.ArgumentParser(description="ARTIQ GUI client") parser.add_argument( diff --git a/artiq/frontend/artiq_master.py b/artiq/frontend/artiq_master.py index 1f95d26cc..37e3a6fde 100755 --- a/artiq/frontend/artiq_master.py +++ b/artiq/frontend/artiq_master.py @@ -6,10 +6,10 @@ import atexit import os from artiq.protocols.pc_rpc import Server -from artiq.protocols.sync_struct import Publisher +from artiq.protocols.sync_struct import Notifier, Publisher, process_mod from artiq.protocols.file_db import FlatFileDB from artiq.master.scheduler import Scheduler -from artiq.master.results import RTResults, get_last_rid +from artiq.master.worker_db import get_last_rid from artiq.master.repository import Repository from artiq.tools import verbosity_args, init_logger @@ -36,7 +36,7 @@ def main(): init_logger(args) ddb = FlatFileDB("ddb.pyon") pdb = FlatFileDB("pdb.pyon") - rtr = RTResults() + rtr = Notifier(dict()) repository = Repository() if os.name == "nt": @@ -47,11 +47,10 @@ def main(): atexit.register(lambda: loop.close()) worker_handlers = { - "req_device": ddb.request, - "req_parameter": pdb.request, + "get_device": ddb.get, + "get_parameter": pdb.get, "set_parameter": pdb.set, - "init_rt_results": rtr.init, - "update_rt_results": rtr.update, + "update_rt_results": lambda mod: process_mod(rtr, mod), } scheduler = Scheduler(get_last_rid() + 1, worker_handlers) worker_handlers["scheduler_submit"] = scheduler.submit @@ -72,7 +71,7 @@ def main(): "schedule": scheduler.notifier, "devices": ddb.data, "parameters": pdb.data, - "rt_results": rtr.groups, + "rt_results": rtr, "explist": repository.explist }) loop.run_until_complete(server_notify.start( diff --git a/artiq/frontend/artiq_run.py b/artiq/frontend/artiq_run.py index 38fc7fe96..baace1d0e 100755 --- a/artiq/frontend/artiq_run.py +++ b/artiq/frontend/artiq_run.py @@ -9,20 +9,19 @@ import logging import h5py -from artiq.language.db import * -from artiq.language.experiment import Experiment +from artiq.language.environment import EnvExperiment from artiq.protocols.file_db import FlatFileDB -from artiq.master.worker_db import DBHub, ResultDB +from artiq.master.worker_db import DeviceManager, ResultDB from artiq.tools import * logger = logging.getLogger(__name__) -class ELFRunner(Experiment, AutoDB): - class DBKeys: - core = Device() - file = Argument() +class ELFRunner(EnvExperiment): + def build(self): + self.attr_device("core") + self.attr_argument("file") def run(self): with open(self.file, "rb") as f: @@ -37,12 +36,11 @@ class SimpleParamLogger: class DummyScheduler: - def __init__(self, expid): + def __init__(self): self.next_rid = 0 - self.next_trid = 0 self.pipeline_name = "main" self.priority = 0 - self.expid = expid + self.expid = None def submit(self, pipeline_name, expid, priority, due_date, flush): rid = self.next_rid @@ -78,7 +76,7 @@ def get_argparser(with_file=True): return parser -def _build_experiment(dbh, args): +def _build_experiment(dmgr, pdb, rdb, args): if hasattr(args, "file"): if args.file.endswith(".elf"): if args.arguments: @@ -86,7 +84,7 @@ def _build_experiment(dbh, args): if args.experiment: raise ValueError("experiment-by-name not supported " "for ELF kernels") - return ELFRunner(dbh, file=args.file) + return ELFRunner(dmgr, pdb, rdb, file=args.file) else: module = file_import(args.file) file = args.file @@ -100,34 +98,33 @@ def _build_experiment(dbh, args): "experiment": args.experiment, "arguments": arguments } - return exp(dbh, - scheduler=DummyScheduler(expid), - **arguments) + dmgr.virtual_devices["scheduler"].expid = expid + return exp(dmgr, pdb, rdb, **arguments) def run(with_file=False): args = get_argparser(with_file).parse_args() init_logger(args) - ddb = FlatFileDB(args.ddb) + dmgr = DeviceManager(FlatFileDB(args.ddb), + virtual_devices={"scheduler": DummyScheduler()}) pdb = FlatFileDB(args.pdb) pdb.hooks.append(SimpleParamLogger()) - rdb = ResultDB(lambda description: None, lambda mod: None) - dbh = DBHub(ddb, pdb, rdb) + rdb = ResultDB() try: - exp_inst = _build_experiment(dbh, args) - rdb.build() + exp_inst = _build_experiment(dmgr, pdb, rdb, args) + exp_inst.prepare() exp_inst.run() exp_inst.analyze() finally: - dbh.close_devices() + dmgr.close_devices() if args.hdf5 is not None: with h5py.File(args.hdf5, "w") as f: rdb.write_hdf5(f) - elif rdb.data.read or rdb.realtime_data.read: - r = chain(rdb.realtime_data.read.items(), rdb.data.read.items()) + elif rdb.rt.read or rdb.nrt: + r = chain(rdb.rt.read.items(), rdb.nrt.items()) for k, v in sorted(r, key=itemgetter(0)): print("{}: {}".format(k, v)) diff --git a/artiq/language/__init__.py b/artiq/language/__init__.py index ad51894b5..7dfe5dba0 100644 --- a/artiq/language/__init__.py +++ b/artiq/language/__init__.py @@ -1,11 +1,10 @@ -from artiq.language import core, experiment, db, units +from artiq.language import core, environment, units from artiq.language.core import * -from artiq.language.experiment import * -from artiq.language.db import * +from artiq.language.environment import * from artiq.language.units import * + __all__ = [] __all__.extend(core.__all__) -__all__.extend(experiment.__all__) -__all__.extend(db.__all__) +__all__.extend(environment.__all__) __all__.extend(units.__all__) diff --git a/artiq/language/db.py b/artiq/language/db.py deleted file mode 100644 index d5f055603..000000000 --- a/artiq/language/db.py +++ /dev/null @@ -1,133 +0,0 @@ -""" -Connection to device, parameter and result database. -""" - -__all__ = ["Device", "NoDefault", "Parameter", "Argument", "Result", "AutoDB"] - - -class _AttributeKind: - pass - - -class Device(_AttributeKind): - """Represents a device for ``AutoDB`` to process.""" - pass - - -class NoDefault: - """Represents the absence of a default value for ``Parameter`` - and ``Argument``. - """ - pass - - -class Parameter(_AttributeKind): - """Represents a parameter (from the database) for ``AutoDB`` - to process. - - :param default: Default value of the parameter to be used if not found - in the database. - """ - def __init__(self, default=NoDefault): - self.default = default - - -class Argument(_AttributeKind): - """Represents an argument (specifiable at instance creation) for - ``AutoDB`` to process. - - :param default: Default value of the argument to be used if not specified - at instance creation. - """ - def __init__(self, default=NoDefault): - self.default = default - - -class Result(_AttributeKind): - """Represents a result for ``AutoDB`` to process.""" - pass - - -class AutoDB: - """Base class to automate device, parameter and result database access. - - Drivers and experiments should in most cases overload this class to - obtain the parameters and devices (including the core device) that they - need, report results, and modify parameters. - - :param dbh: database hub to use. If ``None``, all devices and parameters - must be supplied as keyword arguments, and reporting results and - modifying parameters is not supported. - """ - class DBKeys: - pass - - realtime_results = dict() - - def __init__(self, dbh=None, **kwargs): - self.dbh = dbh - - for k, v in kwargs.items(): - object.__setattr__(self, k, v) - - for k in dir(self.DBKeys): - if k not in self.__dict__: - ak = getattr(self.DBKeys, k) - if isinstance(ak, Argument): - if ak.default is NoDefault: - raise AttributeError( - "No value specified for argument '{}'".format(k)) - object.__setattr__(self, k, ak.default) - elif isinstance(ak, Device): - try: - dev = self.dbh.get_device(k) - except KeyError: - raise KeyError("Device '{}' not found".format(k)) - object.__setattr__(self, k, dev) - self.build() - if self.dbh is not None and self.realtime_results: - self.dbh.add_rt_results(self.realtime_results) - - def __getattr__(self, name): - ak = getattr(self.DBKeys, name) - if isinstance(ak, Parameter): - try: - if self.dbh is None: - raise KeyError - return self.dbh.get_parameter(name) - except KeyError: - if ak.default is not NoDefault: - return ak.default - else: - raise AttributeError("Parameter '{}' not in database" - " and without default value" - .format(name)) - elif isinstance(ak, Result): - try: - return self.dbh.get_result(name) - except KeyError: - raise AttributeError("Result '{}' not found".format(name)) - else: - raise ValueError - - def __setattr__(self, name, value): - try: - ak = getattr(self.DBKeys, name) - except AttributeError: - object.__setattr__(self, name, value) - else: - if isinstance(ak, Parameter): - self.dbh.set_parameter(name, value) - elif isinstance(ak, Result): - self.dbh.set_result(name, value) - else: - raise ValueError - - def build(self): - """This is called by ``__init__`` after the parameter initialization - is done. - - The user may overload this method to complete the object's - initialization with all parameters available. - """ - pass diff --git a/artiq/language/environment.py b/artiq/language/environment.py new file mode 100644 index 000000000..4065759b2 --- /dev/null +++ b/artiq/language/environment.py @@ -0,0 +1,182 @@ +from inspect import isclass + + +__all__ = ["NoDefault", "FreeValue", "HasEnvironment", + "Experiment", "EnvExperiment", "is_experiment"] + + +class NoDefault: + """Represents the absence of a default value.""" + pass + + +class FreeValue: + def __init__(self, default=NoDefault): + if default is not NoDefault: + self.default_value = default + + def default(self): + return self.default_value + + def process(self, x): + return x + + def describe(self): + d = {"ty": "FreeValue"} + if hasattr(self, "default_value"): + d["default"] = self.default_value + return d + + +class HasEnvironment: + """Provides methods to manage the environment of an experiment (devices, + parameters, results, arguments).""" + def __init__(self, dmgr=None, pdb=None, rdb=None, *, + param_override=dict(), **kwargs): + self.requested_args = dict() + + self.__dmgr = dmgr + self.__pdb = pdb + self.__rdb = rdb + self.__param_override = param_override + + self.__kwargs = kwargs + self.__in_build = True + self.build() + 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): + raise NotImplementedError + + def dbs(self): + return self.__dmgr, self.__pdb, self.__rdb + + def get_argument(self, key, processor=None): + if not self.__in_build: + raise TypeError("get_argument() should only " + "be called from build()") + if processor is None: + processor = FreeValue() + self.requested_args[key] = processor + try: + argval = self.__kwargs[key] + except KeyError: + return processor.default() + return processor.process(argval) + + def attr_argument(self, key, processor=None): + setattr(self, key, self.get_argument(key, processor)) + + def get_device(self, key): + if self.__dmgr is None: + raise ValueError("Device manager not present") + return self.__dmgr.get(key) + + def attr_device(self, key): + setattr(self, key, self.get_device(key)) + + def get_parameter(self, key, default=NoDefault): + if self.__pdb is None: + raise ValueError("Parameter database not present") + if key in self.__param_override: + return self.__param_override[key] + try: + return self.__pdb.get(key) + except KeyError: + if default is not NoDefault: + return default + else: + raise + + def attr_parameter(self, key, default=NoDefault): + setattr(self, key, self.get_parameter(key, default)) + + def set_parameter(self, key, value): + if self.__pdb is None: + raise ValueError("Parameter database not present") + self.__pdb.set(key, value) + + def set_result(self, key, value, realtime=False): + if self.__rdb is None: + raise ValueError("Result database not present") + if realtime: + if key in self.__rdb.nrt: + raise ValueError("Result is already non-realtime") + self.__rdb.rt[key] = value + notifier = self.__rdb.rt[key] + notifier.kernel_attr_init = False + return notifier + else: + if key in self.__rdb.rt.read: + raise ValueError("Result is already realtime") + self.__rdb.nrt[key] = value + + def attr_rtresult(self, key, init_value): + setattr(self, key, set_result(key, init_value, True)) + + def get_result(self, key): + if self.__rdb is None: + raise ValueError("Result database not present") + return self.__rdb.get(key) + + +class Experiment: + """Base class for experiments. + + Deriving from this class enables automatic experiment discovery in + Python modules. + """ + def prepare(self): + """Entry point for pre-computing data necessary for running the + experiment. + + Doing such computations outside of ``run`` enables more efficient + scheduling of multiple experiments that need to access the shared + hardware during part of their execution. + + This method must not interact with the hardware. + """ + pass + + 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. + + This method may interact with the hardware. + + The experiment may call the scheduler's ``pause`` method while in + ``run``. + """ + 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 + + +class EnvExperiment(Experiment, HasEnvironment): + pass + + +def is_experiment(o): + """Checks if a Python object is an instantiable user experiment.""" + return (isclass(o) + and issubclass(o, Experiment) + and o is not Experiment + and o is not EnvExperiment) diff --git a/artiq/language/experiment.py b/artiq/language/experiment.py deleted file mode 100644 index b3394918c..000000000 --- a/artiq/language/experiment.py +++ /dev/null @@ -1,54 +0,0 @@ -from inspect import isclass - -__all__ = ["Experiment", "is_experiment"] - - -class Experiment: - """Base class for experiments. - - Deriving from this class enables automatic experiment discovery in - Python modules. - """ - def prepare(self): - """Entry point for pre-computing data necessary for running the - experiment. - - Doing such computations outside of ``run`` enables more efficient - scheduling of multiple experiments that need to access the shared - hardware during part of their execution. - - This method must not interact with the hardware. - """ - pass - - 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. - - This method may interact with the hardware. - - The experiment may call the scheduler's ``pause`` method while in - ``run``. - """ - 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 is_experiment(o): - """Checks if a Python object is an instantiable experiment.""" - return isclass(o) and issubclass(o, Experiment) and o is not Experiment diff --git a/artiq/master/repository.py b/artiq/master/repository.py index 90ef186e9..3ddea5110 100644 --- a/artiq/master/repository.py +++ b/artiq/master/repository.py @@ -2,7 +2,7 @@ import os from artiq.protocols.sync_struct import Notifier from artiq.tools import file_import -from artiq.language.experiment import is_experiment +from artiq.language.environment import is_experiment def scan_experiments(): @@ -23,8 +23,7 @@ def scan_experiments(): name = name[:-1] entry = { "file": os.path.join("repository", f), - "experiment": k, - "gui_file": getattr(v, "__artiq_gui_file__", None) + "experiment": k } r[name] = entry return r diff --git a/artiq/master/results.py b/artiq/master/results.py deleted file mode 100644 index 8dc8eaff6..000000000 --- a/artiq/master/results.py +++ /dev/null @@ -1,103 +0,0 @@ -import os -import time -import re - -import numpy -import h5py - -from artiq.protocols.sync_struct import Notifier, process_mod - - -def get_hdf5_output(start_time, rid, name): - dirname = os.path.join("results", - time.strftime("%Y-%m-%d", start_time), - time.strftime("%H-%M", start_time)) - filename = "{:09}-{}.h5".format(rid, name) - os.makedirs(dirname, exist_ok=True) - return h5py.File(os.path.join(dirname, filename), "w") - - -def get_last_rid(): - r = -1 - try: - day_folders = os.listdir("results") - except: - return r - day_folders = filter(lambda x: re.fullmatch('\d\d\d\d-\d\d-\d\d', x), - day_folders) - for df in day_folders: - day_path = os.path.join("results", df) - try: - minute_folders = os.listdir(day_path) - except: - continue - minute_folders = filter(lambda x: re.fullmatch('\d\d-\d\d', x), - minute_folders) - for mf in minute_folders: - minute_path = os.path.join(day_path, mf) - try: - h5files = os.listdir(minute_path) - except: - continue - for x in h5files: - m = re.fullmatch('(\d\d\d\d\d\d\d\d\d)-.*\.h5', x) - rid = int(m.group(1)) - if rid > r: - r = rid - return r - - -_type_to_hdf5 = { - int: h5py.h5t.STD_I64BE, - float: h5py.h5t.IEEE_F64BE -} - -def result_dict_to_hdf5(f, rd): - for name, data in rd.items(): - if isinstance(data, list): - el_ty = type(data[0]) - for d in data: - if type(d) != el_ty: - raise TypeError("All list elements must have the same" - " type for HDF5 output") - try: - el_ty_h5 = _type_to_hdf5[el_ty] - except KeyError: - raise TypeError("List element type {} is not supported for" - " HDF5 output".format(el_ty)) - dataset = f.create_dataset(name, (len(data), ), el_ty_h5) - dataset[:] = data - elif isinstance(data, numpy.ndarray): - f.create_dataset(name, data=data) - else: - ty = type(data) - try: - ty_h5 = _type_to_hdf5[ty] - except KeyError: - raise TypeError("Type {} is not supported for HDF5 output" - .format(ty)) - dataset = f.create_dataset(name, (), ty_h5) - dataset[()] = data - - -class RTResults: - def __init__(self): - self.groups = Notifier(dict()) - self.current_group = "default" - - def init(self, description): - data = dict() - for rtr in description.keys(): - if isinstance(rtr, tuple): - for e in rtr: - data[e] = [] - else: - data[rtr] = [] - self.groups[self.current_group] = { - "description": description, - "data": data - } - - def update(self, mod): - target = self.groups[self.current_group]["data"] - process_mod(target, mod) diff --git a/artiq/master/worker_db.py b/artiq/master/worker_db.py index 2ba8b84f2..bd6d008b3 100644 --- a/artiq/master/worker_db.py +++ b/artiq/master/worker_db.py @@ -1,76 +1,114 @@ from collections import OrderedDict import importlib import logging +import os +import time +import re + +import numpy +import h5py from artiq.protocols.sync_struct import Notifier from artiq.protocols.pc_rpc import Client, BestEffortClient -from artiq.master.results import result_dict_to_hdf5 logger = logging.getLogger(__name__) -class ResultDB: - def __init__(self, init_rt_results, update_rt_results): - self.init_rt_results = init_rt_results - self.update_rt_results = update_rt_results - self.rtr_description = dict() +def get_hdf5_output(start_time, rid, name): + dirname = os.path.join("results", + time.strftime("%Y-%m-%d", start_time), + time.strftime("%H-%M", start_time)) + filename = "{:09}-{}.h5".format(rid, name) + os.makedirs(dirname, exist_ok=True) + return h5py.File(os.path.join(dirname, filename), "w") - def add_rt_results(self, rtr_description): - intr = set(self.rtr_description.keys()).intersection( - set(rtr_description.keys())) - if intr: - raise ValueError("Duplicate realtime results: " + ", ".join(intr)) - self.rtr_description.update(rtr_description) - def build(self): - realtime_results_set = set() - for rtr in self.rtr_description.keys(): - if isinstance(rtr, tuple): - for e in rtr: - realtime_results_set.add(e) - else: - realtime_results_set.add(rtr) - - self.realtime_data = Notifier({x: [] for x in realtime_results_set}) - self.data = Notifier(dict()) - - self.init_rt_results(self.rtr_description) - self.realtime_data.publish = lambda notifier, data: \ - self.update_rt_results(data) - - def _request(self, name): - try: - return self.realtime_data[name] - except KeyError: - try: - return self.data[name] - except KeyError: - self.data[name] = [] - return self.data[name] - - def request(self, name): - r = self._request(name) - r.kernel_attr_init = False +def get_last_rid(): + r = -1 + try: + day_folders = os.listdir("results") + except: return r + day_folders = filter(lambda x: re.fullmatch('\d\d\d\d-\d\d-\d\d', x), + day_folders) + for df in day_folders: + day_path = os.path.join("results", df) + try: + minute_folders = os.listdir(day_path) + except: + continue + minute_folders = filter(lambda x: re.fullmatch('\d\d-\d\d', x), + minute_folders) + for mf in minute_folders: + minute_path = os.path.join(day_path, mf) + try: + h5files = os.listdir(minute_path) + except: + continue + for x in h5files: + m = re.fullmatch('(\d\d\d\d\d\d\d\d\d)-.*\.h5', x) + rid = int(m.group(1)) + if rid > r: + r = rid + return r - def set(self, name, value): - if name in self.realtime_data.read: - self.realtime_data[name] = value + +_type_to_hdf5 = { + int: h5py.h5t.STD_I64BE, + float: h5py.h5t.IEEE_F64BE +} + +def result_dict_to_hdf5(f, rd): + for name, data in rd.items(): + if isinstance(data, list): + el_ty = type(data[0]) + for d in data: + if type(d) != el_ty: + raise TypeError("All list elements must have the same" + " type for HDF5 output") + try: + el_ty_h5 = _type_to_hdf5[el_ty] + except KeyError: + raise TypeError("List element type {} is not supported for" + " HDF5 output".format(el_ty)) + dataset = f.create_dataset(name, (len(data), ), el_ty_h5) + dataset[:] = data + elif isinstance(data, numpy.ndarray): + f.create_dataset(name, data=data) else: - self.data[name] = value + ty = type(data) + try: + ty_h5 = _type_to_hdf5[ty] + except KeyError: + raise TypeError("Type {} is not supported for HDF5 output" + .format(ty)) + dataset = f.create_dataset(name, (), ty_h5) + dataset[()] = data + + +class ResultDB: + def __init__(self): + self.rt = Notifier(dict()) + self.nrt = dict() + + def get(self, key): + try: + return self.nrt[key] + except KeyError: + return self.rt[key].read def write_hdf5(self, f): - result_dict_to_hdf5(f, self.realtime_data.read) - result_dict_to_hdf5(f, self.data.read) + result_dict_to_hdf5(f, self.rt.read) + result_dict_to_hdf5(f, self.nrt) -def create_device(desc, dbh): +def create_device(desc, dmgr): ty = desc["type"] if ty == "local": module = importlib.import_module(desc["module"]) device_class = getattr(module, desc["class"]) - return device_class(dbh, **desc["arguments"]) + return device_class(dmgr, **desc["arguments"]) elif ty == "controller": if desc["best_effort"]: cl = BestEffortClient @@ -81,30 +119,26 @@ def create_device(desc, dbh): raise ValueError("Unsupported type in device DB: " + ty) -class DBHub: - """Connects device, parameter and result databases to experiment. - Handle device driver creation and destruction. - """ - def __init__(self, ddb, pdb, rdb, read_only=False): +class DeviceManager: + """Handles creation and destruction of local device drivers and controller + RPC clients.""" + def __init__(self, ddb, virtual_devices=dict()): self.ddb = ddb + self.virtual_devices = virtual_devices self.active_devices = OrderedDict() - self.get_parameter = pdb.request - - if not read_only: - self.set_parameter = pdb.set - self.add_rt_results = rdb.add_rt_results - self.get_result = rdb.request - self.set_result = rdb.set - - def get_device(self, name): + def get(self, name): + """Get the device driver or controller client corresponding to a + device database entry.""" + if name in self.virtual_devices: + return self.virtual_devices[name] if name in self.active_devices: return self.active_devices[name] else: - desc = self.ddb.request(name) + desc = self.ddb.get(name) while isinstance(desc, str): # alias - desc = self.ddb.request(desc) + desc = self.ddb.get(desc) dev = create_device(desc, self) self.active_devices[name] = dev return dev diff --git a/artiq/master/worker_impl.py b/artiq/master/worker_impl.py index c5307d51a..eb0fec592 100644 --- a/artiq/master/worker_impl.py +++ b/artiq/master/worker_impl.py @@ -3,9 +3,8 @@ import time from artiq.protocols import pyon from artiq.tools import file_import -from artiq.master.worker_db import DBHub, ResultDB -from artiq.master.results import get_hdf5_output -from artiq.language.experiment import is_experiment +from artiq.master.worker_db import DeviceManager, ResultDB, get_hdf5_output +from artiq.language.environment import is_experiment from artiq.language.core import set_watchdog_factory @@ -46,15 +45,14 @@ def make_parent_action(action, argnames, exception=ParentActionError): class ParentDDB: - request = make_parent_action("req_device", "name", KeyError) + get = make_parent_action("get_device", "name", KeyError) class ParentPDB: - request = make_parent_action("req_parameter", "name", KeyError) + get = make_parent_action("get_parameter", "name", KeyError) set = make_parent_action("set_parameter", "name value") -init_rt_results = make_parent_action("init_rt_results", "description") update_rt_results = make_parent_action("update_rt_results", "mod") @@ -82,7 +80,7 @@ class Scheduler: "pipeline_name expid priority due_date flush")) cancel = staticmethod(make_parent_action("scheduler_cancel", "rid")) - def __init__(self, pipeline_name, expid, priority): + def set_run_info(self, pipeline_name, expid, priority): self.pipeline_name = pipeline_name self.expid = expid self.priority = priority @@ -110,8 +108,10 @@ def main(): exp = None exp_inst = None - rdb = ResultDB(init_rt_results, update_rt_results) - dbh = DBHub(ParentDDB, ParentPDB, rdb) + dmgr = DeviceManager(ParentDDB, + virtual_devices={"scheduler": Scheduler()}) + rdb = ResultDB() + rdb.rt.publish = update_rt_results try: while True: @@ -120,16 +120,12 @@ def main(): if action == "build": start_time = time.localtime() rid = obj["rid"] - pipeline_name = obj["pipeline_name"] expid = obj["expid"] - priority = obj["priority"] exp = get_exp(expid["file"], expid["experiment"]) - exp_inst = exp(dbh, - scheduler=Scheduler(pipeline_name, - expid, - priority), - **expid["arguments"]) - rdb.build() + dmgr.virtual_devices["scheduler"].set_run_info( + obj["pipeline_name"], expid, obj["priority"]) + exp_inst = exp(dmgr, ParentPDB, rdb, + **expid["arguments"]) put_object({"action": "completed"}) elif action == "prepare": exp_inst.prepare() @@ -150,7 +146,7 @@ def main(): elif action == "terminate": break finally: - dbh.close_devices() + dmgr.close_devices() if __name__ == "__main__": main() diff --git a/artiq/protocols/file_db.py b/artiq/protocols/file_db.py index 6675dfcff..b7499587e 100644 --- a/artiq/protocols/file_db.py +++ b/artiq/protocols/file_db.py @@ -20,7 +20,7 @@ class FlatFileDB: def save(self): pyon.store_file(self.filename, self.data.read) - def request(self, name): + def get(self, name): return self.data.read[name] def set(self, name, value): diff --git a/artiq/sim/devices.py b/artiq/sim/devices.py index 25fc934f5..3f11c1c1e 100644 --- a/artiq/sim/devices.py +++ b/artiq/sim/devices.py @@ -1,13 +1,12 @@ from random import Random from artiq.language.core import delay, kernel -from artiq.language.db import * from artiq.language import units from artiq.sim import time -class Core(AutoDB): - def build(self): +class Core: + def __init__(self, dmgr): self.ref_period = 1 self._level = 0 @@ -20,12 +19,11 @@ class Core(AutoDB): return r -class Input(AutoDB): - class DBKeys: - core = Device() - name = Argument() +class Input: + def __init__(self, dmgr, name): + self.core = dmgr.get("core") + self.name = name - def build(self): self.prng = Random() @kernel @@ -42,10 +40,10 @@ class Input(AutoDB): return result -class WaveOutput(AutoDB): - class DBKeys: - core = Device() - name = Argument() +class WaveOutput: + def __init__(self, dmgr, name): + self.core = dmgr.get("core") + self.name = name @kernel def pulse(self, frequency, duration): @@ -53,10 +51,10 @@ class WaveOutput(AutoDB): delay(duration) -class VoltageOutput(AutoDB): - class DBKeys: - core = Device() - name = Argument() +class VoltageOutput: + def __init__(self, dmgr, name): + self.core = dmgr.get("core") + self.name = name @kernel def set(self, value): diff --git a/artiq/test/coredevice.py b/artiq/test/coredevice.py index c780d19c2..863d4b383 100644 --- a/artiq/test/coredevice.py +++ b/artiq/test/coredevice.py @@ -6,11 +6,10 @@ from artiq.coredevice.runtime_exceptions import RTIOUnderflow from artiq.coredevice import runtime_exceptions -class RTT(Experiment, AutoDB): - class DBKeys: - core = Device() - ttl_inout = Device() - rtt = Result() +class RTT(EnvExperiment): + def build(self): + self.attr_device("core") + self.attr_device("ttl_inout") @kernel def run(self): @@ -24,15 +23,15 @@ class RTT(Experiment, AutoDB): delay(1*us) t0 = now_mu() self.ttl_inout.pulse(1*us) - self.rtt = mu_to_seconds(self.ttl_inout.timestamp() - t0) + self.set_result("rtt", + mu_to_seconds(self.ttl_inout.timestamp() - t0)) -class Loopback(Experiment, AutoDB): - class DBKeys: - core = Device() - loop_in = Device() - loop_out = Device() - rtt = Result() +class Loopback(EnvExperiment): + def build(self): + self.attr_device("core") + self.attr_device("loop_in") + self.attr_device("loop_out") @kernel def run(self): @@ -44,15 +43,15 @@ class Loopback(Experiment, AutoDB): delay(1*us) t0 = now_mu() self.loop_out.pulse(1*us) - self.rtt = mu_to_seconds(self.loop_in.timestamp() - t0) + self.set_result("rtt", + mu_to_seconds(self.loop_in.timestamp() - t0)) -class ClockGeneratorLoopback(Experiment, AutoDB): - class DBKeys: - core = Device() - loop_clock_in = Device() - loop_clock_out = Device() - count = Result() +class ClockGeneratorLoopback(EnvExperiment): + def build(self): + self.attr_device("core") + self.attr_device("loop_clock_in") + self.attr_device("loop_clock_out") @kernel def run(self): @@ -64,14 +63,14 @@ class ClockGeneratorLoopback(Experiment, AutoDB): with sequential: delay(200*ns) self.loop_clock_out.set(1*MHz) - self.count = self.loop_clock_in.count() + self.set_result("count", + self.loop_clock_in.count()) -class PulseRate(Experiment, AutoDB): - class DBKeys: - core = Device() - loop_out = Device() - pulse_rate = Result() +class PulseRate(EnvExperiment): + def build(self): + self.attr_device("core") + self.attr_device("loop_out") @kernel def run(self): @@ -85,13 +84,14 @@ class PulseRate(Experiment, AutoDB): dt += 1 self.core.break_realtime() else: - self.pulse_rate = mu_to_seconds(2*dt) + self.set_result("pulse_rate", + mu_to_seconds(2*dt)) break -class Watchdog(Experiment, AutoDB): - class DBKeys: - core = Device() +class Watchdog(EnvExperiment): + def build(self): + self.attr_device("core") @kernel def run(self): @@ -100,14 +100,11 @@ class Watchdog(Experiment, AutoDB): pass -class LoopbackCount(Experiment, AutoDB): - class DBKeys: - core = Device() - ttl_inout = Device() - npulses = Argument() - - def report(self, n): - self.result = n +class LoopbackCount(EnvExperiment): + def build(self): + self.attr_device("core") + self.attr_device("ttl_inout") + self.attr_argument("npulses") @kernel def run(self): @@ -119,13 +116,14 @@ class LoopbackCount(Experiment, AutoDB): for i in range(self.npulses): delay(25*ns) self.ttl_inout.pulse(25*ns) - self.report(self.ttl_inout.count()) + self.set_result("count", + self.ttl_inout.count()) -class Underflow(Experiment, AutoDB): - class DBKeys: - core = Device() - ttl_out = Device() +class Underflow(EnvExperiment): + def build(self): + self.attr_device("core") + self.attr_device("ttl_out") @kernel def run(self): @@ -134,10 +132,10 @@ class Underflow(Experiment, AutoDB): self.ttl_out.pulse(25*ns) -class SequenceError(Experiment, AutoDB): - class DBKeys: - core = Device() - ttl_out = Device() +class SequenceError(EnvExperiment): + def build(self): + self.attr_device("core") + self.attr_device("ttl_out") @kernel def run(self): @@ -147,21 +145,18 @@ class SequenceError(Experiment, AutoDB): self.ttl_out.pulse(25*us) -class TimeKeepsRunning(Experiment, AutoDB): - class DBKeys: - core = Device() - time_at_start = Result() +class TimeKeepsRunning(EnvExperiment): + def build(self): + self.attr_device("core") @kernel def run(self): - self.time_at_start = now_mu() + self.set_result("time_at_start", now_mu()) -class Handover(Experiment, AutoDB): - class DBKeys: - core = Device() - t1 = Result() - t2 = Result() +class Handover(EnvExperiment): + def build(self): + self.attr_device("core") @kernel def get_now(self): @@ -169,34 +164,34 @@ class Handover(Experiment, AutoDB): def run(self): self.get_now() - self.t1 = self.time_at_start + self.set_result("t1", self.time_at_start) self.get_now() - self.t2 = self.time_at_start + self.set_result("t2", self.time_at_start) class CoredeviceTest(ExperimentCase): def test_rtt(self): self.execute(RTT) - rtt = self.dbh.get_result("rtt").read + rtt = self.rdb.get("rtt") print(rtt) self.assertGreater(rtt, 0*ns) self.assertLess(rtt, 100*ns) def test_loopback(self): self.execute(Loopback) - rtt = self.dbh.get_result("rtt").read + rtt = self.rdb.get("rtt") print(rtt) self.assertGreater(rtt, 0*ns) self.assertLess(rtt, 50*ns) def test_clock_generator_loopback(self): self.execute(ClockGeneratorLoopback) - count = self.dbh.get_result("count").read + count = self.rdb.get("count") self.assertEqual(count, 10) def test_pulse_rate(self): self.execute(PulseRate) - rate = self.dbh.get_result("pulse_rate").read + rate = self.rdb.get("pulse_rate") print(rate) self.assertGreater(rate, 100*ns) self.assertLess(rate, 2500*ns) @@ -204,7 +199,8 @@ class CoredeviceTest(ExperimentCase): def test_loopback_count(self): npulses = 2 r = self.execute(LoopbackCount, npulses=npulses) - self.assertEqual(r.result, npulses) + count = self.rdb.get("count") + self.assertEqual(count, npulses) def test_underflow(self): with self.assertRaises(runtime_exceptions.RTIOUnderflow): @@ -221,26 +217,23 @@ class CoredeviceTest(ExperimentCase): def test_time_keeps_running(self): self.execute(TimeKeepsRunning) - t1 = self.dbh.get_result("time_at_start").read + t1 = self.rdb.get("time_at_start") self.execute(TimeKeepsRunning) - t2 = self.dbh.get_result("time_at_start").read - dead_time = mu_to_seconds(t2 - t1, self.dbh.get_device("core")) + t2 = self.rdb.get("time_at_start") + dead_time = mu_to_seconds(t2 - t1, self.dmgr.get("core")) print(dead_time) self.assertGreater(dead_time, 1*ms) self.assertLess(dead_time, 300*ms) def test_handover(self): self.execute(Handover) - self.assertEqual(self.dbh.get_result("t1").read, - self.dbh.get_result("t2").read) + self.assertEqual(self.rdb.get("t1"), self.rdb.get("t2")) -class RPCTiming(Experiment, AutoDB): - class DBKeys: - core = Device() - repeats = Argument(100) - rpc_time_mean = Result() - rpc_time_stddev = Result() +class RPCTiming(EnvExperiment): + def build(self): + self.attr_device("core") + self.attr_argument("repeats", FreeValue(100)) def nop(self, x): pass @@ -257,14 +250,14 @@ class RPCTiming(Experiment, AutoDB): def run(self): self.bench() mean = sum(self.ts)/self.repeats - self.rpc_time_stddev = sqrt( - sum([(t - mean)**2 for t in self.ts])/self.repeats)*s - self.rpc_time_mean = mean*s + self.set_result("rpc_time_stddev", sqrt( + sum([(t - mean)**2 for t in self.ts])/self.repeats)) + self.set_result("rpc_time_mean", mean) class RPCTest(ExperimentCase): def test_rpc_timing(self): self.execute(RPCTiming) - self.assertGreater(self.dbh.get_result("rpc_time_mean").read, 100*ns) - self.assertLess(self.dbh.get_result("rpc_time_mean").read, 15*ms) - self.assertLess(self.dbh.get_result("rpc_time_stddev").read, 1*ms) + self.assertGreater(self.rdb.get("rpc_time_mean"), 100*ns) + self.assertLess(self.rdb.get("rpc_time_mean"), 15*ms) + self.assertLess(self.rdb.get("rpc_time_stddev"), 1*ms) diff --git a/artiq/test/coredevice_vs_host.py b/artiq/test/coredevice_vs_host.py index 623aff4e8..31ef27da4 100644 --- a/artiq/test/coredevice_vs_host.py +++ b/artiq/test/coredevice_vs_host.py @@ -6,18 +6,19 @@ from artiq.sim import devices as sim_devices from artiq.test.hardware_testbench import ExperimentCase -def _run_on_host(k_class, **parameters): - coredev = sim_devices.Core() - k_inst = k_class(core=coredev, **parameters) +def _run_on_host(k_class, **arguments): + dmgr = dict() + dmgr["core"] = sim_devices.Core(dmgr) + k_inst = k_class(dmgr, **arguments) k_inst.run() return k_inst -class _Primes(Experiment, AutoDB): - class DBKeys: - core = Device() - output_list = Argument() - maximum = Argument() +class _Primes(EnvExperiment): + def build(self): + self.attr_device("core") + self.attr_argument("output_list") + self.attr_argument("maximum") @kernel def run(self): @@ -33,11 +34,10 @@ class _Primes(Experiment, AutoDB): self.output_list.append(x) -class _Misc(Experiment, AutoDB): - class DBKeys: - core = Device() - +class _Misc(EnvExperiment): def build(self): + self.attr_device("core") + self.input = 84 self.al = [1, 2, 3, 4, 5] self.list_copy_in = [2*Hz, 10*MHz] @@ -52,11 +52,11 @@ class _Misc(Experiment, AutoDB): self.list_copy_out = self.list_copy_in -class _PulseLogger(Experiment, AutoDB): - class DBKeys: - core = Device() - output_list = Argument() - name = Argument() +class _PulseLogger(EnvExperiment): + def build(self): + self.attr_device("core") + self.attr_argument("output_list") + self.attr_argument("name") def _append(self, t, l, f): if not hasattr(self, "first_timestamp"): @@ -79,14 +79,13 @@ class _PulseLogger(Experiment, AutoDB): self.off(now_mu()) -class _Pulses(Experiment, AutoDB): - class DBKeys: - core = Device() - output_list = Argument() - +class _Pulses(EnvExperiment): def build(self): + self.attr_device("core") + self.attr_argument("output_list") + for name in "a", "b", "c", "d": - pl = _PulseLogger(core=self.core, + pl = _PulseLogger(*self.dbs(), output_list=self.output_list, name=name) setattr(self, name, pl) @@ -107,10 +106,10 @@ class _MyException(Exception): pass -class _Exceptions(Experiment, AutoDB): - class DBKeys: - core = Device() - trace = Argument() +class _Exceptions(EnvExperiment): + def build(self): + self.attr_device("core") + self.attr_argument("trace") @kernel def run(self): @@ -151,12 +150,11 @@ class _Exceptions(Experiment, AutoDB): self.trace.append(104) -class _RPCExceptions(Experiment, AutoDB): - class DBKeys: - core = Device() - catch = Argument(False) - +class _RPCExceptions(EnvExperiment): def build(self): + self.attr_device("core") + self.attr_argument("catch", FreeValue(False)) + self.success = False def exception_raiser(self): diff --git a/artiq/test/hardware_testbench.py b/artiq/test/hardware_testbench.py index 386e255cb..a082a676c 100644 --- a/artiq/test/hardware_testbench.py +++ b/artiq/test/hardware_testbench.py @@ -5,9 +5,8 @@ import logging from artiq.language import * from artiq.protocols.file_db import FlatFileDB -from artiq.master.worker_db import DBHub, ResultDB -from artiq.frontend.artiq_run import ( - DummyScheduler, DummyWatchdog, SimpleParamLogger) +from artiq.master.worker_db import DeviceManager, ResultDB +from artiq.frontend.artiq_run import DummyScheduler artiq_root = os.getenv("ARTIQ_ROOT") @@ -33,9 +32,10 @@ def get_from_ddb(*path, default="skip"): class ExperimentCase(unittest.TestCase): def setUp(self): self.ddb = FlatFileDB(os.path.join(artiq_root, "ddb.pyon")) + self.dmgr = DeviceManager(self.ddb, + virtual_devices={"scheduler": DummyScheduler()}) self.pdb = FlatFileDB(os.path.join(artiq_root, "pdb.pyon")) - self.rdb = ResultDB(lambda description: None, lambda mod: None) - self.dbh = DBHub(self.ddb, self.pdb, self.rdb) + self.rdb = ResultDB() def execute(self, cls, **kwargs): expid = { @@ -43,16 +43,16 @@ class ExperimentCase(unittest.TestCase): "experiment": cls.__name__, "arguments": kwargs } - sched = DummyScheduler(expid) + self.dmgr.virtual_devices["scheduler"].expid = expid try: try: - exp = cls(self.dbh, scheduler=sched, **kwargs) + exp = cls(self.dmgr, self.pdb, self.rdb, **kwargs) except KeyError as e: # skip if ddb does not match requirements raise unittest.SkipTest(*e.args) - self.rdb.build() + exp.prepare() exp.run() exp.analyze() return exp finally: - self.dbh.close_devices() + self.dmgr.close_devices() diff --git a/artiq/test/scheduler.py b/artiq/test/scheduler.py index 53dd7c6f3..7493e53d4 100644 --- a/artiq/test/scheduler.py +++ b/artiq/test/scheduler.py @@ -7,12 +7,18 @@ from artiq import * from artiq.master.scheduler import Scheduler -class EmptyExperiment(Experiment, AutoDB): +class EmptyExperiment(EnvExperiment): + def build(self): + pass + def run(self): pass -class BackgroundExperiment(Experiment, AutoDB): +class BackgroundExperiment(EnvExperiment): + def build(self): + self.attr_device("scheduler") + def run(self): while True: self.scheduler.pause() diff --git a/artiq/test/transforms.py b/artiq/test/transforms.py index 75af1b2dc..dffee41a2 100644 --- a/artiq/test/transforms.py +++ b/artiq/test/transforms.py @@ -36,7 +36,9 @@ def run(): class OptimizeCase(unittest.TestCase): def test_optimize(self): - coredev = core.Core(comm=comm_dummy.Comm(), ref_period=1*ns) + dmgr = dict() + dmgr["comm"] = comm_dummy.Comm(dmgr) + coredev = core.Core(dmgr, ref_period=1*ns) func_def = ast.parse(optimize_in).body[0] coredev.transform_stack(func_def, dict(), dict()) self.assertEqual(unparse(func_def), optimize_out) diff --git a/artiq/test/worker.py b/artiq/test/worker.py index ccd5339aa..8682ea041 100644 --- a/artiq/test/worker.py +++ b/artiq/test/worker.py @@ -7,20 +7,26 @@ from artiq import * from artiq.master.worker import * -class WatchdogNoTimeout(Experiment, AutoDB): +class WatchdogNoTimeout(EnvExperiment): + def build(self): + pass + def run(self): for i in range(10): with watchdog(0.5*s): sleep(0.1) -class WatchdogTimeout(Experiment, AutoDB): +class WatchdogTimeout(EnvExperiment): + def build(self): + pass + def run(self): with watchdog(0.1*s): sleep(100.0) -class WatchdogTimeoutInBuild(Experiment, AutoDB): +class WatchdogTimeoutInBuild(EnvExperiment): def build(self): with watchdog(0.1*s): sleep(100.0) diff --git a/artiq/tools.py b/artiq/tools.py index 1d1f9c553..2f7870eab 100644 --- a/artiq/tools.py +++ b/artiq/tools.py @@ -7,7 +7,7 @@ import asyncio import time import os.path -from artiq.language.experiment import is_experiment +from artiq.language.environment import is_experiment from artiq.protocols import pyon diff --git a/doc/manual/conf.py b/doc/manual/conf.py index d550ee3ff..2df182943 100644 --- a/doc/manual/conf.py +++ b/doc/manual/conf.py @@ -118,7 +118,7 @@ pygments_style = 'sphinx' # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'classic' +html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the diff --git a/doc/manual/core_language_reference.rst b/doc/manual/core_language_reference.rst index 35c285e01..9f0a21d06 100644 --- a/doc/manual/core_language_reference.rst +++ b/doc/manual/core_language_reference.rst @@ -9,10 +9,10 @@ The most commonly used features from those modules can be imported with ``from a .. automodule:: artiq.language.core :members: -:mod:`artiq.language.db` module -------------------------------- +:mod:`artiq.language.environment` module +---------------------------------------- -.. automodule:: artiq.language.db +.. automodule:: artiq.language.environment :members: :mod:`artiq.language.units` module diff --git a/doc/manual/developing_a_ndsp.rst b/doc/manual/developing_a_ndsp.rst index 0bc867892..d6320c242 100644 --- a/doc/manual/developing_a_ndsp.rst +++ b/doc/manual/developing_a_ndsp.rst @@ -103,7 +103,7 @@ Run it as before, while the controller is running. You should see the message ap $ ./hello_controller.py message: Hello World! -When using the driver in an experiment, for simple cases the ``Client`` instance can be returned by the :class:`artiq.language.db.AutoDB` mechanism and used normally as a device. +When using the driver in an experiment, the ``Client`` instance can be returned by the environment mechanism (via the ``get_device`` and ``attr_device`` methods of :class:`artiq.language.environment.HasEnvironment`) and used normally as a device. :warning: RPC servers operate on copies of objects provided by the client, and modifications to mutable types are not written back. For example, if the client passes a list as a parameter of an RPC method, and that method ``append()s`` an element to the list, the element is not appended to the client's list. diff --git a/doc/manual/faq.rst b/doc/manual/faq.rst index 2e95d2864..c30912291 100644 --- a/doc/manual/faq.rst +++ b/doc/manual/faq.rst @@ -7,30 +7,19 @@ How do I ... prevent my first RTIO command from causing an underflow? -------------------------------------------------------- -The RTIO timestamp counter starts counting at zero at the beginning of the first kernel run on the core device. The first RTIO event is programmed with a small timestamp above zero. If the kernel needs more time than this timestamp to produce the event, an underflow will occur. You can prevent it by calling ``break_realtime`` just before programming the first event. - -override the `sysclk` frequency of just one DDS? ------------------------------------------------- - -Override the parameter using an argument in the DDB. +The first RTIO event is programmed with a small timestamp above the value of the timecounter at the start of the experiment. If the kernel needs more time than this timestamp to produce the event, an underflow will occur. You can prevent it by calling ``break_realtime`` just before programming the first event, or by adding a sufficient delay. organize parameters in folders? ------------------------------- -Use GUI auto-completion and filtering. -Names need to be unique. +Folders are not supported yet, use GUI filtering for now. Names need to be unique. enforce functional dependencies between parameters? --------------------------------------------------- If you want to override a parameter ``b`` in the PDB to be ``b = 2*a``, use wrapper experiments, overriding parameters by passing them to the -experiment's constructor. - -get rid of ``DBKeys``? ----------------------- - -``DBKeys`` references keys in PDB, DDB and RDB. +experiment's constructor (``param_override`` argument). write a generator feeding a kernel feeding an analyze function? --------------------------------------------------------------- @@ -94,4 +83,4 @@ The preferred way to specify a serial device is to make use of the ``hwgrep://`` URL: it allows to select the serial device by its USB vendor ID, product ID and/or serial number. Those never change, unlike the device file name. -See the :ref:`TDC001 documentation ` for an example of ``hwgrep://`` usage. \ No newline at end of file +See the :ref:`TDC001 documentation ` for an example of ``hwgrep://`` usage. diff --git a/doc/manual/getting_started.rst b/doc/manual/getting_started.rst index da2952fd3..5a5fd1329 100644 --- a/doc/manual/getting_started.rst +++ b/doc/manual/getting_started.rst @@ -11,23 +11,23 @@ As a very first step, we will turn on a LED on the core device. Create a file `` from artiq import * - class LED(Experiment, AutoDB): - class DBKeys: - core = Device() - led = Device() + class LED(EnvExperiment): + def build(self): + self.attr_device("core") + self.attr_device("led") @kernel def run(self): self.led.on() -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.environment.EnvExperiment`. Among other features, ``EnvExperiment`` calls our ``build`` method and provides the ``attr_device`` method that interfaces to the device database to create the appropriate device drivers and make those drivers accessible as ``self.core`` and ``self.led``. The ``@kernel`` decorator tells the system that the ``run`` method must be executed on the core device (instead of the host). The decorator uses ``self.core`` internally, which is why we request the core device using ``attr_device`` like any other. 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``, which is part of the ARTIQ front-end tools: :: - $ artiq_run.py led.py + $ artiq_run led.py The LED of the device should turn on. Congratulations! You have a basic ARTIQ system up and running. @@ -41,10 +41,10 @@ Modify the code as follows: :: def input_led_state(): return int(input("Enter desired LED state: ")) - class LED(Experiment, AutoDB): - class DBKeys: - core = Device() - led = Device() + class LED(EnvExperiment): + def build(self): + self.attr_device("core") + self.attr_device("led") @kernel def run(self): @@ -58,9 +58,9 @@ Modify the code as follows: :: You can then turn the LED off and on by entering 0 or 1 at the prompt that appears: :: - $ artiq_run.py led.py + $ artiq_run led.py Enter desired LED state: 1 - $ artiq_run.py led.py + $ artiq_run led.py Enter desired LED state: 0 What happens is the ARTIQ compiler notices that the ``input_led_state`` function does not have a ``@kernel`` decorator and thus must be executed on the host. When the core device calls it, it sends a request to the host to execute it. The host displays the prompt, collects user input, and sends the result back to the core device, which sets the LED state accordingly. @@ -90,10 +90,10 @@ Create a new file ``rtio.py`` containing the following: :: from artiq import * - class Tutorial(Experiment, AutoDB): - class DBKeys: - core = Device() - ttl0 = Device() + class Tutorial(EnvExperiment): + def build(self): + self.attr_device("core") + self.attr_device("ttl0") @kernel def run(self): @@ -113,10 +113,10 @@ Try reducing the period of the generated waveform until the CPU cannot keep up w def print_underflow(): print("RTIO underflow occured") - class Tutorial(Experiment, AutoDB): - class DBKeys: - core = Device() - ttl0 = Device() + class Tutorial(EnvExperiment): + def build(self): + self.attr_device("core") + self.attr_device("ttl0") @kernel def run(self): diff --git a/examples/master/repository/dds_test.py b/examples/master/repository/dds_test.py index a36af8447..ff4458abc 100644 --- a/examples/master/repository/dds_test.py +++ b/examples/master/repository/dds_test.py @@ -1,19 +1,19 @@ from artiq import * -class DDSTest(Experiment, AutoDB): +class DDSTest(EnvExperiment): """DDS test""" - class DBKeys: - core = Device() - dds_bus = Device() - dds0 = Device() - dds1 = Device() - dds2 = Device() - ttl0 = Device() - ttl1 = Device() - ttl2 = Device() - led = Device() + def build(self): + self.attr_device("core") + self.attr_device("dds_bus") + self.attr_device("dds0") + self.attr_device("dds1") + self.attr_device("dds2") + self.attr_device("ttl0") + self.attr_device("ttl1") + self.attr_device("ttl2") + self.attr_device("led") @kernel def run(self): diff --git a/examples/master/repository/flopping_f_simulation.py b/examples/master/repository/flopping_f_simulation.py index 23cf42239..19a5127d4 100644 --- a/examples/master/repository/flopping_f_simulation.py +++ b/examples/master/repository/flopping_f_simulation.py @@ -23,25 +23,21 @@ def model_numpy(xdata, F0): return r -class FloppingF(Experiment, AutoDB): +class FloppingF(EnvExperiment): """Flopping F simulation""" - class DBKeys: - npoints = Argument(100) - min_freq = Argument(1000) - max_freq = Argument(2000) + def build(self): + self.attr_argument("npoints", FreeValue(100)) + self.attr_argument("min_freq", FreeValue(1000)) + self.attr_argument("max_freq", FreeValue(2000)) - F0 = Argument(1500) - noise_amplitude = Argument(0.1) + self.attr_argument("F0", FreeValue(1500)) + self.attr_argument("noise_amplitude", FreeValue(0.1)) - frequency = Result() - brightness = Result() + self.frequency = self.set_result("flopping_f_frequency", [], True) + self.brightness = self.set_result("flopping_f_brightness", [], True) - flopping_freq = Parameter() - - realtime_results = { - ("frequency", "brightness"): "xy" - } + self.attr_device("scheduler") def run(self): for i in range(self.npoints): @@ -56,7 +52,7 @@ class FloppingF(Experiment, AutoDB): def analyze(self): popt, pcov = curve_fit(model_numpy, self.frequency.read, self.brightness.read, - p0=[self.flopping_freq]) + p0=[self.get_parameter("flopping_freq")]) perr = np.sqrt(np.diag(pcov)) if perr < 0.1: - self.flopping_freq = float(popt) + self.set_parameter("flopping_freq", float(popt)) diff --git a/examples/master/repository/handover.py b/examples/master/repository/handover.py index 8974678a9..5870d27ba 100644 --- a/examples/master/repository/handover.py +++ b/examples/master/repository/handover.py @@ -1,10 +1,10 @@ from artiq import * -class Handover(Experiment, AutoDB): - class DBKeys: - core = Device() - led = Device() +class Handover(EnvExperiment): + def build(self): + self.attr_device("core") + self.attr_device("led") @kernel def blink_once(self): diff --git a/examples/master/repository/mandelbrot.py b/examples/master/repository/mandelbrot.py index 0a0c030a6..a14e58900 100644 --- a/examples/master/repository/mandelbrot.py +++ b/examples/master/repository/mandelbrot.py @@ -3,11 +3,11 @@ import sys from artiq import * -class Mandelbrot(Experiment, AutoDB): +class Mandelbrot(EnvExperiment): """Mandelbrot set demo""" - class DBKeys: - core = Device() + def build(self): + self.attr_device("core") def col(self, i): sys.stdout.write(" .,-:;i+hHM$*#@ "[i]) diff --git a/examples/master/repository/photon_histogram.py b/examples/master/repository/photon_histogram.py index aebe1beb4..bad44e1b7 100644 --- a/examples/master/repository/photon_histogram.py +++ b/examples/master/repository/photon_histogram.py @@ -1,29 +1,24 @@ from artiq import * -class PhotonHistogram(Experiment, AutoDB): +class PhotonHistogram(EnvExperiment): """Photon histogram""" - class DBKeys: - core = Device() - dds_bus = Device() - bd_dds = Device() - bd_sw = Device() - bdd_dds = Device() - bdd_sw = Device() - pmt = Device() + def build(self): + self.attr_device("core") + self.attr_device("dds_bus") + self.attr_device("bd_dds") + self.attr_device("bd_sw") + self.attr_device("bdd_dds") + self.attr_device("bdd_sw") + self.attr_device("pmt") - nbins = Argument(100) - repeats = Argument(100) + self.attr_argument("nbins", FreeValue(100)) + self.attr_argument("repeats", FreeValue(100)) - cool_f = Parameter(230*MHz) - detect_f = Parameter(220*MHz) - detect_t = Parameter(100*us) - - ion_present = Parameter(True) - - hist = Result() - total = Result() + self.attr_parameter("cool_f", 230*MHz) + self.attr_parameter("detect_f", 220*MHz) + self.attr_parameter("detect_t", 100*us) @kernel def program_cooling(self): @@ -65,9 +60,8 @@ class PhotonHistogram(Experiment, AutoDB): hist[n] += 1 total += n - self.hist = hist - self.total = total - self.ion_present = total > 5*self.repeats + self.set_result("cooling_photon_histogram", hist) + self.set_parameter("ion_present", total > 5*self.repeats) if __name__ == "__main__": diff --git a/examples/master/repository/transport.py b/examples/master/repository/transport.py index ce1b2ece4..ed44e0a26 100644 --- a/examples/master/repository/transport.py +++ b/examples/master/repository/transport.py @@ -10,23 +10,22 @@ transport_data = dict( # 4 devices, 3 board each, 3 dacs each ) -class Transport(Experiment, AutoDB): +class Transport(EnvExperiment): """Transport""" - class DBKeys: - core = Device() - bd = Device() - bdd = Device() - pmt = Device() - electrodes = Device() + def build(self): + self.attr_device("core") + self.attr_device("bd") + self.attr_device("bdd") + self.attr_device("pmt") + self.attr_device("electrodes") - wait_at_stop = Parameter(100*us) - speed = Parameter(1.5) + self.attr_argument("wait_at_stop", FreeValue(100*us)) + self.attr_argument("speed", FreeValue(1.5)) + self.attr_argument("repeats", FreeValue(100)) + self.attr_argument("nbins", FreeValue(100)) - repeats = Argument(100) - nbins = Argument(100) - - def prepare(self, stop): + def calc_waveforms(self, stop): t = transport_data["t"][:stop]*self.speed u = transport_data["u"][:stop] @@ -89,9 +88,9 @@ class Transport(Experiment, AutoDB): def scan(self, stops): for s in stops: self.histogram = [] - # non-kernel, calculate waveforms, build frames + # non-kernel, build frames # could also be rpc'ed from repeat() - self.prepare(s) + self.calc_waveforms(s) # kernel part self.repeat() # live update 2d plot with current self.histogram diff --git a/examples/sim/al_spectroscopy.py b/examples/sim/al_spectroscopy.py index f41f09886..7f52fc32c 100644 --- a/examples/sim/al_spectroscopy.py +++ b/examples/sim/al_spectroscopy.py @@ -1,20 +1,20 @@ from artiq import * -class AluminumSpectroscopy(Experiment, AutoDB): +class AluminumSpectroscopy(EnvExperiment): """Aluminum spectroscopy (simulation)""" - class DBKeys: - core = Device() - mains_sync = Device() - laser_cooling = Device() - spectroscopy = Device() - spectroscopy_b = Device() - state_detection = Device() - pmt = Device() - spectroscopy_freq = Parameter(432*MHz) - photon_limit_low = Argument(10) - photon_limit_high = Argument(15) + def build(self): + self.attr_device("core") + self.attr_device("mains_sync") + self.attr_device("laser_cooling") + self.attr_device("spectroscopy") + self.attr_device("spectroscopy_b") + self.attr_device("state_detection") + self.attr_device("pmt") + self.attr_parameter("spectroscopy_freq", 432*MHz) + self.attr_argument("photon_limit_low", FreeValue(10)) + self.attr_argument("photon_limit_high", FreeValue(15)) @kernel def run(self): diff --git a/examples/sim/simple_simulation.py b/examples/sim/simple_simulation.py index e0e9d2b0c..602fcbd54 100644 --- a/examples/sim/simple_simulation.py +++ b/examples/sim/simple_simulation.py @@ -1,15 +1,13 @@ from artiq import * -class SimpleSimulation(Experiment, AutoDB): +class SimpleSimulation(EnvExperiment): """Simple simulation""" - class DBKeys: - core = Device() - a = Device() - b = Device() - c = Device() - d = Device() + def build(self): + self.attr_device("core") + for wo in "abcd": + self.attr_device(wo) @kernel def run(self): @@ -23,16 +21,13 @@ class SimpleSimulation(Experiment, AutoDB): def main(): - from artiq.sim import devices as sd + from artiq.sim import devices - core = sd.Core() - exp = SimpleSimulation( - core=core, - a=sd.WaveOutput(core=core, name="a"), - b=sd.WaveOutput(core=core, name="b"), - c=sd.WaveOutput(core=core, name="c"), - d=sd.WaveOutput(core=core, name="d"), - ) + dmgr = dict() + dmgr["core"] = devices.Core(dmgr) + for wo in "abcd": + dmgr[wo] = devices.WaveOutput(dmgr, wo) + exp = SimpleSimulation(dmgr) exp.run() if __name__ == "__main__":