From 891c0d12f21c39eb062bffad86976c6e3fbc232f Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Mon, 12 Jan 2015 18:51:23 +0800 Subject: [PATCH] refactor device/parameter management, immediate parameter updates, start introducing results --- artiq/__init__.py | 2 +- artiq/coredevice/comm_dummy.py | 7 +- artiq/coredevice/comm_serial.py | 11 +- artiq/coredevice/core.py | 11 +- artiq/coredevice/dds.py | 13 +-- artiq/coredevice/gpio.py | 7 +- artiq/coredevice/rtio.py | 12 ++- artiq/devices/pdq2/__init__.py | 19 ++-- artiq/language/context.py | 179 -------------------------------- artiq/language/db.py | 145 ++++++++++++++++++++++++++ artiq/management/db.py | 106 +++++++++++++++++++ artiq/management/dpdb.py | 133 ------------------------ artiq/management/worker_impl.py | 31 +++--- artiq/sim/devices.py | 22 ++-- artiq/test/full_stack.py | 51 +++++---- doc/manual/getting_started.rst | 24 +++-- doc/manual/writing_a_driver.rst | 2 +- doc/slides/artiq_overview.tex | 6 +- examples/al_spectroscopy.py | 21 ++-- examples/ddb.pyon | 24 ++--- examples/ddb_sim.pyon | 14 +-- examples/dds_test.py | 11 +- examples/mandelbrot.py | 2 +- examples/photon_histogram.py | 13 +-- examples/pulse_performance.py | 5 +- examples/rtio_skew.py | 7 +- examples/simple_simulation.py | 24 ++--- examples/transport.py | 19 ++-- frontend/artiq_client.py | 20 ++-- frontend/artiq_master.py | 22 ++-- frontend/artiq_run.py | 42 +++++--- 31 files changed, 497 insertions(+), 508 deletions(-) delete mode 100644 artiq/language/context.py create mode 100644 artiq/language/db.py create mode 100644 artiq/management/db.py delete mode 100644 artiq/management/dpdb.py diff --git a/artiq/__init__.py b/artiq/__init__.py index bba7663cb..29f6ef2bd 100644 --- a/artiq/__init__.py +++ b/artiq/__init__.py @@ -1,5 +1,5 @@ from artiq.language.core import * -from artiq.language.context import * +from artiq.language.db import * from artiq.language.units import check_unit from artiq.language.units import ps, ns, us, ms, s from artiq.language.units import Hz, kHz, MHz, GHz diff --git a/artiq/coredevice/comm_dummy.py b/artiq/coredevice/comm_dummy.py index a23210dbe..842e00f9c 100644 --- a/artiq/coredevice/comm_dummy.py +++ b/artiq/coredevice/comm_dummy.py @@ -1,6 +1,6 @@ from operator import itemgetter -from artiq.language.context import AutoContext +from artiq.language.db import AutoDB from artiq.language.units import ms, ns from artiq.coredevice.runtime import LinkInterface @@ -14,8 +14,9 @@ class _RuntimeEnvironment(LinkInterface): return str(self.llvm_module) -class Comm(AutoContext): - implicit_core = False +class Comm(AutoDB): + class DBKeys: + implicit_core = False def get_runtime_env(self): return _RuntimeEnvironment(1*ns) diff --git a/artiq/coredevice/comm_serial.py b/artiq/coredevice/comm_serial.py index ce4ec135a..73d784372 100644 --- a/artiq/coredevice/comm_serial.py +++ b/artiq/coredevice/comm_serial.py @@ -7,7 +7,7 @@ import logging from artiq.language import core as core_language from artiq.language import units -from artiq.language.context import * +from artiq.language.db import * from artiq.coredevice.runtime import Environment from artiq.coredevice import runtime_exceptions from artiq.coredevice.rpc_wrapper import RPCWrapper @@ -60,10 +60,11 @@ def _read_exactly(f, n): return r -class Comm(AutoContext): - serial_dev = Parameter("/dev/ttyUSB1") - baud_rate = Parameter(115200) - implicit_core = False +class Comm(AutoDB): + class DBKeys: + serial_dev = Parameter("/dev/ttyUSB1") + baud_rate = Parameter(115200) + implicit_core = False def build(self): self.port = serial.Serial(self.serial_dev, baudrate=115200) diff --git a/artiq/coredevice/core.py b/artiq/coredevice/core.py index e4e227d45..f52bf2001 100644 --- a/artiq/coredevice/core.py +++ b/artiq/coredevice/core.py @@ -1,7 +1,7 @@ import os from artiq.language.core import * -from artiq.language.context import * +from artiq.language.db import * from artiq.transforms.inline import inline from artiq.transforms.lower_units import lower_units @@ -44,10 +44,11 @@ def _no_debug_unparse(label, node): pass -class Core(AutoContext): - comm = Device("comm") - external_clock = Parameter(None) - implicit_core = False +class Core(AutoDB): + class DBKeys: + comm = Device() + external_clock = Parameter(None) + implicit_core = False def build(self): self.runtime_env = self.comm.get_runtime_env() diff --git a/artiq/coredevice/dds.py b/artiq/coredevice/dds.py index 823c91f5f..2fba939e8 100644 --- a/artiq/coredevice/dds.py +++ b/artiq/coredevice/dds.py @@ -1,5 +1,5 @@ from artiq.language.core import * -from artiq.language.context import * +from artiq.language.db import * from artiq.language.units import * from artiq.coredevice import rtio @@ -10,7 +10,7 @@ PHASE_MODE_ABSOLUTE = 1 PHASE_MODE_TRACKING = 2 -class DDS(AutoContext): +class DDS(AutoDB): """Core device Direct Digital Synthesis (DDS) driver. Controls DDS devices managed directly by the core device's runtime. It also @@ -24,15 +24,16 @@ class DDS(AutoContext): the DDS device. """ - dds_sysclk = Parameter(1*GHz) - reg_channel = Parameter() - rtio_switch = Parameter() + class DBKeys: + dds_sysclk = Parameter(1*GHz) + reg_channel = Argument() + rtio_switch = Argument() def build(self): self.previous_on = False self.previous_frequency = 0*MHz self.set_phase_mode(PHASE_MODE_CONTINUOUS) - self.sw = rtio.RTIOOut(self, channel=self.rtio_switch) + self.sw = rtio.RTIOOut(core=self.core, channel=self.rtio_switch) @portable def frequency_to_ftw(self, frequency): diff --git a/artiq/coredevice/gpio.py b/artiq/coredevice/gpio.py index c0371ce3a..982ec1fc7 100644 --- a/artiq/coredevice/gpio.py +++ b/artiq/coredevice/gpio.py @@ -1,9 +1,10 @@ from artiq.language.core import * -from artiq.language.context import * +from artiq.language.db import * -class GPIOOut(AutoContext): - channel = Parameter() +class GPIOOut(AutoDB): + class DBKeys: + channel = Argument() @kernel def on(self): diff --git a/artiq/coredevice/rtio.py b/artiq/coredevice/rtio.py index 694391e42..bb548aad4 100644 --- a/artiq/coredevice/rtio.py +++ b/artiq/coredevice/rtio.py @@ -1,8 +1,8 @@ from artiq.language.core import * -from artiq.language.context import * +from artiq.language.db import * -class LLRTIOOut(AutoContext): +class LLRTIOOut(AutoDB): """Low-level RTIO output driver. Allows setting RTIO outputs at arbitrary times, without time unit @@ -12,7 +12,8 @@ class LLRTIOOut(AutoContext): ``RTIOOut`` instead. """ - channel = Parameter() + class DBKeys: + channel = Argument() def build(self): self._set_oe() @@ -50,8 +51,9 @@ class LLRTIOOut(AutoContext): self.set_value(t, 0) -class _RTIOBase(AutoContext): - channel = Parameter() +class _RTIOBase(AutoDB): + class DBKeys: + channel = Argument() def build(self): self.previous_timestamp = int64(0) # in RTIO cycles diff --git a/artiq/devices/pdq2/__init__.py b/artiq/devices/pdq2/__init__.py index 20d8e139c..5ad2180c0 100644 --- a/artiq/devices/pdq2/__init__.py +++ b/artiq/devices/pdq2/__init__.py @@ -1,5 +1,5 @@ from artiq.language.core import * -from artiq.language.context import * +from artiq.language.db import * from artiq.language.units import * from artiq.coredevice import rtio @@ -111,16 +111,17 @@ class _Frame: del self.fn -class CompoundPDQ2(AutoContext): - ids = Parameter() - rtio_trigger = Parameter() - rtio_frame = Parameter() +class CompoundPDQ2(AutoDB): + class DBKeys: + ids = Argument() + rtio_trigger = Argument() + rtio_frame = Argument() def build(self): - self.trigger = rtio.LLRTIOOut(self, channel=self.rtio_trigger) - self.frame0 = rtio.LLRTIOOut(self, channel=self.rtio_frame[0]) - self.frame1 = rtio.LLRTIOOut(self, channel=self.rtio_frame[1]) - self.frame2 = rtio.LLRTIOOut(self, channel=self.rtio_frame[2]) + self.trigger = rtio.LLRTIOOut(core=self.core, channel=self.rtio_trigger) + self.frame0 = rtio.LLRTIOOut(core=self.core, channel=self.rtio_frame[0]) + self.frame1 = rtio.LLRTIOOut(core=self.core, channel=self.rtio_frame[1]) + self.frame2 = rtio.LLRTIOOut(core=self.core, channel=self.rtio_frame[2]) self.frames = [] self.current_frame = -1 diff --git a/artiq/language/context.py b/artiq/language/context.py deleted file mode 100644 index 6674e3f2d..000000000 --- a/artiq/language/context.py +++ /dev/null @@ -1,179 +0,0 @@ -""" -Device and parameter attributes. - -""" - -class _AttributeKind: - pass - - -class Device(_AttributeKind): - """Represents a device for ``AutoContext`` to process. - - :param type_hint: An optional string giving a hint about the type of the - device. - - """ - def __init__(self, type_hint=None): - self.type_hint = type_hint - - -class NoDefault: - """Represents the absence of a default value for ``Parameter``. - - """ - pass - - -class Parameter(_AttributeKind): - """Represents a parameter (from the database) for ``AutoContext`` - to process. - - :param default: Default value of the parameter to be used if not found - in the database. - :param write_db: Writes any modification of the parameter back to the - database. - - """ - def __init__(self, default=NoDefault, write_db=False): - self.default = default - self.write_db = write_db - - -class Argument(_AttributeKind): - """Represents an argument (specifiable at instance creation) for - ``AutoContext`` 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 AutoContext: - """Base class to automate device and parameter discovery. - - Drivers and experiments should in most cases overload this class to - obtain the parameters and devices (including the core device) that they - need. - - This class sets all its ``__init__`` keyword arguments as attributes. It - then iterates over each element in the attribute dictionary of the class, - and when they are abtract attributes (e.g. ``Device``, ``Parameter``), - requests them from the ``mvs`` (Missing Value Supplier) object. - - A ``AutoContext`` instance can be used as MVS. If the requested parameter - is within its attributes, the value of that attribute is returned. - Otherwise, the request is forwarded to the parent MVS. - - All keyword arguments are set as object attributes. This enables setting - parameters of a lower-level ``AutoContext`` object using keyword arguments - without having those explicitly listed in the upper-level ``AutoContext`` - parameter list. - - At the top-level, it is possible to have a MVS that issues requests to a - database and hardware management system. - - :var implicit_core: Automatically adds a ``core`` device to the attributes. - Default: True. - - Example: - - >>> class SubExperiment(AutoContext): - ... foo = Parameter() - ... bar = Parameter() - ... - ... def run(self): - ... do_something(self.foo, self.bar) - ... - >>> class MainExperiment(AutoContext): - ... bar1 = Parameter() - ... bar2 = Parameter() - ... offset = Parameter() - ... - ... def build(self): - ... self.exp1 = SubExperiment(self, bar=self.bar1) - ... self.exp2 = SubExperiment(self, bar=self.bar2) - ... self.exp3 = SubExperiment(self, bar=self.bar2 + self.offset) - ... - ... def run(self): - ... self.exp1.run() - ... self.exp2.run() - ... self.exp3.run() - ... - >>> # does not require a database. - >>> a = MainExperiment(foo=1, bar1=2, bar2=3, offset=0) - >>> # "foo" and "offset" are automatically retrieved from the database. - >>> b = MainExperiment(db_mvs, bar1=2, bar2=3) - - """ - implicit_core = True - - def __init__(self, mvs=None, **kwargs): - if self.implicit_core: - if hasattr(self, "core"): - raise ValueError( - "Set implicit_core to False when" - " core is explicitly specified") - self.core = Device("core") - - self.mvs = mvs - for k, v in kwargs.items(): - if hasattr(self, k): - p = getattr(self, k) - if isinstance(p, Parameter) and p.write_db: - self.mvs.register_parameter_wb(self, k) - if (not hasattr(self, k) - or not isinstance(getattr(self, k), _AttributeKind)): - raise ValueError( - "Got unexpected keyword argument: '{}'".format(k)) - setattr(self, k, v) - - for k in dir(self): - v = getattr(self, k) - if isinstance(v, _AttributeKind): - if isinstance(v, Argument): - # never goes through MVS - if v.default is NoDefault: - raise AttributeError( - "No value specified for argument '{}'".format(k)) - value = v.default - else: - if self.mvs is None: - if (isinstance(v, Parameter) - and v.default is not NoDefault): - value = v.default - else: - raise AttributeError("Attribute '{}' not specified" - " and no MVS present".format(k)) - else: - value = self.mvs.get_missing_value(k, v, self) - if isinstance(v, Parameter) and v.write_db: - self.mvs.register_parameter_wb(self, k) - setattr(self, k, value) - - self.build() - - def get_missing_value(self, name, kind, requester): - """Attempts to retrieve ``name`` from the object's attributes. - If not present, forwards the request to the parent MVS. - - The presence of this method makes ``AutoContext`` act as a MVS. - - """ - try: - return getattr(self, name) - except AttributeError: - return self.mvs.get_missing_value(name, kind, requester) - - 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/db.py b/artiq/language/db.py new file mode 100644 index 000000000..6c7a0d154 --- /dev/null +++ b/artiq/language/db.py @@ -0,0 +1,145 @@ +""" +Connection to device, parameter and result database. + +""" + +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 + + def __init__(self, dbh=None, **kwargs): + self.dbh = dbh + + dbkeys = self.DBKeys() + if getattr(dbkeys, "implicit_core", True): + if hasattr(dbkeys, "core"): + raise ValueError( + "Set implicit_core to False when" + " core is explicitly specified") + dbkeys.core = Device() + + for k, v in kwargs.items(): + object.__setattr__(self, k, v) + + for k in dir(dbkeys): + if k not in self.__dict__: + ak = getattr(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() + + 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/management/db.py b/artiq/management/db.py new file mode 100644 index 000000000..ddd675fbc --- /dev/null +++ b/artiq/management/db.py @@ -0,0 +1,106 @@ +from collections import OrderedDict, defaultdict +import importlib +from time import time + +from artiq.language.db import * +from artiq.management import pyon +from artiq.management.sync_struct import Notifier + + +class FlatFileDB: + def __init__(self, filename): + self.filename = filename + self.data = Notifier(pyon.load_file(self.filename)) + self.hooks = [] + + def save(self): + pyon.store_file(self.filename, self.data.backing_struct) + + def request(self, name): + return self.data.backing_struct[name] + + def set(self, name, value): + self.data[name] = value + self.save() + timestamp = time() + for hook in self.hooks: + hook.set(timestamp, name, value) + + def delete(self, name): + del self.data[name] + self.save() + timestamp = time() + for hook in self.hooks: + hook.delete(timestamp, name) + + +class SimpleHistory: + def __init__(self, depth): + self.depth = depth + self.history = Notifier([]) + + def set(self, timestamp, name, value): + if len(self.history.backing_struct) >= self.depth: + del self.history[0] + self.history.append((timestamp, name, value)) + + def delete(self, timestamp, name): + if len(self.history.backing_struct) >= self.depth: + del self.history[0] + self.history.append((timestamp, name)) + + +class ResultDB: + def __init__(self): + self.data = defaultdict(list) + + def request(self, name): + return self.data[name] + + def set(self, name, value): + self.data[name] = value + + +def _create_device(desc, dbh): + module = importlib.import_module(desc["module"]) + device_class = getattr(module, desc["class"]) + return device_class(dbh, **desc["arguments"]) + + +class DBHub: + """Connects device, parameter and result databases to experiment. + Handle device driver creation and destruction. + + """ + def __init__(self, ddb, pdb, rdb): + self.ddb = ddb + self.active_devices = OrderedDict() + + self.get_parameter = pdb.request + self.set_parameter = pdb.set + self.get_result = rdb.request + self.set_result = rdb.set + + def get_device(self, name): + if name in self.active_devices: + return self.active_devices[name] + else: + desc = self.ddb.request(name) + while isinstance(desc, str): + # alias + desc = self.ddb.request(desc) + dev = _create_device(desc, self) + self.active_devices[name] = dev + return dev + + def close(self): + """Closes all active devices, in the opposite order as they were + requested. + + Do not use the same ``DBHub`` again after calling + this function. + + """ + for dev in reversed(list(self.active_devices.values())): + if hasattr(dev, "close"): + dev.close() diff --git a/artiq/management/dpdb.py b/artiq/management/dpdb.py deleted file mode 100644 index 9a18b262e..000000000 --- a/artiq/management/dpdb.py +++ /dev/null @@ -1,133 +0,0 @@ -from collections import OrderedDict -import importlib -from time import time - -from artiq.language.context import * -from artiq.management import pyon -from artiq.management.sync_struct import Notifier - - -def create_device(desc, mvs): - module = importlib.import_module(desc["module"]) - device_class = getattr(module, desc["class"]) - return device_class(mvs, **desc["parameters"]) - - -class DeviceParamSupplier: - """Supplies devices and parameters to AutoContext objects. - - """ - def __init__(self, req_device, req_parameter): - self.req_device = req_device - self.req_parameter = req_parameter - self.active_devices = OrderedDict() - # list of (requester, name) - self.parameter_wb = [] - - def get_missing_value(self, name, kind, requester): - if isinstance(kind, Device): - if name in self.active_devices: - return self.active_devices[name] - else: - try: - desc = self.req_device(name) - except KeyError: - raise KeyError( - "Unknown device '{}' of type '{}' requested by {}" - .format(name, kind.type_hint, requester)) - try: - while isinstance(desc, str): - # alias - desc = self.req_device(desc) - except KeyError: - raise KeyError( - "Unknown alias '{}' for device '{}' of type '{}'" - " requested by {}" - .format(desc, name, kind.type_hint, requester)) - dev = create_device(desc, self) - self.active_devices[name] = dev - return dev - elif isinstance(kind, Parameter): - try: - return self.req_parameter(name) - except KeyError: - if kind.default is not NoDefault: - return kind.default - else: - raise KeyError("Unknown parameter: " + name) - else: - raise NotImplementedError - - def register_parameter_wb(self, requester, name): - self.parameter_wb.append((requester, name)) - - def close(self): - """Closes all active devices, in the opposite order as they were - requested. - - Do not use the same ``DeviceParamSupplier`` again after calling - this function. - - """ - for dev in reversed(list(self.active_devices.values())): - if hasattr(dev, "close"): - dev.close() - - -class DeviceParamDB: - def __init__(self, ddb_file, pdb_file): - self.ddb_file = ddb_file - self.pdb_file = pdb_file - self.ddb = Notifier(pyon.load_file(self.ddb_file)) - self.pdb = Notifier(pyon.load_file(self.pdb_file)) - self.parameter_hooks = [] - - def save_ddb(self): - pyon.store_file(self.ddb_file, self.ddb.backing_struct) - - def save_pdb(self): - pyon.store_file(self.pdb_file, self.pdb.backing_struct) - - def req_device(self, name): - return self.ddb.backing_struct[name] - - def set_device(self, name, description): - self.ddb[name] = description - self.save_ddb() - - def del_device(self, name): - del self.ddb[name] - self.save_ddb() - - def req_parameter(self, name): - return self.pdb.backing_struct[name] - - def set_parameter(self, name, value): - self.pdb[name] = value - self.save_pdb() - timestamp = time() - for hook in self.parameter_hooks: - hook.set_parameter(timestamp, name, value) - - def del_parameter(self, name): - del self.pdb[name] - self.save_pdb() - timestamp = time() - for hook in self.parameter_hooks: - hook.del_parameter(timestamp, name) - - -class SimpleParameterHistory: - def __init__(self, depth): - self.depth = depth - self.history = Notifier([]) - - def set_parameter(self, timestamp, name, value): - if len(self.history.backing_struct) >= self.depth: - del self.history[0] - self.history.append((timestamp, name, value)) - - def del_parameter(self, timestamp, name): - if len(self.history.backing_struct) >= self.depth: - del self.history[0] - self.history.append((timestamp, name)) diff --git a/artiq/management/worker_impl.py b/artiq/management/worker_impl.py index 00227c698..aabee342a 100644 --- a/artiq/management/worker_impl.py +++ b/artiq/management/worker_impl.py @@ -4,24 +4,24 @@ import traceback from artiq.management import pyon from artiq.management.file_import import file_import -from artiq.language.context import AutoContext -from artiq.management.dpdb import DeviceParamSupplier +from artiq.language.db import AutoDB +from artiq.management.db import DBHub, ResultDB -def run(dps, file, unit, arguments): +def run(dbh, file, unit, arguments): module = file_import(file) if unit is None: units = [v for k, v in module.__dict__.items() if k[0] != "_" and isclass(v) - and issubclass(v, AutoContext) - and v is not AutoContext] + and issubclass(v, AutoDB) + and v is not AutoDB] if len(units) != 1: raise ValueError("Found {} units in module".format(len(units))) unit = units[0] else: unit = getattr(module, unit) - unit_inst = unit(dps, **arguments) + unit_inst = unit(dbh, **arguments) unit_inst.run() @@ -56,9 +56,13 @@ def make_parent_action(action, argnames, exception=ParentActionError): return parent_action -req_device = make_parent_action("req_device", "name", KeyError) -req_parameter = make_parent_action("req_parameter", "name", KeyError) -set_parameter = make_parent_action("set_parameter", "name value") +class ParentDDB: + request = make_parent_action("req_device", "name", KeyError) + + +class ParentPDB: + request = make_parent_action("req_parameter", "name", KeyError) + set = make_parent_action("set_parameter", "name value") def main(): @@ -68,12 +72,11 @@ def main(): obj = get_object() put_object("ack") - dps = DeviceParamSupplier(req_device, req_parameter) + rdb = ResultDB() + dbh = DBHub(ParentDDB, ParentPDB, rdb) try: try: - run(dps, **obj) - for requester, name in dps.parameter_wb: - set_parameter(name, getattr(requester, name)) + run(dbh, **obj) except Exception: put_object({"action": "report_completed", "status": "failed", @@ -82,7 +85,7 @@ def main(): put_object({"action": "report_completed", "status": "ok"}) finally: - dps.close() + dbh.close() if __name__ == "__main__": main() diff --git a/artiq/sim/devices.py b/artiq/sim/devices.py index 7ef00413e..7dc4ef4c0 100644 --- a/artiq/sim/devices.py +++ b/artiq/sim/devices.py @@ -1,13 +1,14 @@ from random import Random from artiq.language.core import delay, kernel -from artiq.language.context import AutoContext, Parameter +from artiq.language.db import AutoDB, Argument from artiq.language import units from artiq.sim import time -class Core(AutoContext): - implicit_core = False +class Core(AutoDB): + class DBKeys: + implicit_core = False _level = 0 @@ -20,8 +21,9 @@ class Core(AutoContext): return r -class Input(AutoContext): - name = Parameter() +class Input(AutoDB): + class DBKeys: + name = Argument() def build(self): self.prng = Random() @@ -40,8 +42,9 @@ class Input(AutoContext): return result -class WaveOutput(AutoContext): - name = Parameter() +class WaveOutput(AutoDB): + class DBKeys: + name = Argument() @kernel def pulse(self, frequency, duration): @@ -49,8 +52,9 @@ class WaveOutput(AutoContext): delay(duration) -class VoltageOutput(AutoContext): - name = Parameter() +class VoltageOutput(AutoDB): + class DBKeys: + name = Argument() @kernel def set(self, value): diff --git a/artiq/test/full_stack.py b/artiq/test/full_stack.py index 395be1e46..e1a5edc28 100644 --- a/artiq/test/full_stack.py +++ b/artiq/test/full_stack.py @@ -28,9 +28,10 @@ def _run_on_host(k_class, **parameters): k_inst.run() -class _Primes(AutoContext): - output_list = Parameter() - maximum = Parameter() +class _Primes(AutoDB): + class DBKeys: + output_list = Argument() + maximum = Argument() @kernel def run(self): @@ -46,7 +47,7 @@ class _Primes(AutoContext): self.output_list.append(x) -class _Misc(AutoContext): +class _Misc(AutoDB): def build(self): self.input = 84 self.inhomogeneous_units = [] @@ -82,9 +83,10 @@ class _Misc(AutoContext): delay(10*Hz) -class _PulseLogger(AutoContext): - output_list = Parameter() - name = Parameter() +class _PulseLogger(AutoDB): + class DBKeys: + output_list = Argument() + name = Argument() def _append(self, t, l, f): if not hasattr(self, "first_timestamp"): @@ -104,12 +106,15 @@ class _PulseLogger(AutoContext): self.off(int(now().amount*1000000000)) -class _Pulses(AutoContext): - output_list = Parameter() +class _Pulses(AutoDB): + class DBKeys: + output_list = Argument() def build(self): for name in "a", "b", "c", "d": - pl = _PulseLogger(self, name=name) + pl = _PulseLogger(core=self.core, + output_list=self.output_list, + name=name) setattr(self, name, pl) @kernel @@ -128,8 +133,9 @@ class _MyException(Exception): pass -class _Exceptions(AutoContext): - trace = Parameter() +class _Exceptions(AutoDB): + class DBKeys: + trace = Argument() @kernel def run(self): @@ -170,7 +176,7 @@ class _Exceptions(AutoContext): self.trace.append(104) -class _RPCExceptions(AutoContext): +class _RPCExceptions(AutoDB): def build(self): self.success = False @@ -250,10 +256,11 @@ class ExecutionCase(unittest.TestCase): comm.close() -class _RTIOLoopback(AutoContext): - i = Device("ttl_in") - o = Device("ttl_out") - npulses = Parameter() +class _RTIOLoopback(AutoDB): + class DBKeys: + i = Device() + o = Device() + npulses = Argument() def report(self, n): self.result = n @@ -269,8 +276,9 @@ class _RTIOLoopback(AutoContext): self.report(self.i.count()) -class _RTIOUnderflow(AutoContext): - o = Device("ttl_out") +class _RTIOUnderflow(AutoDB): + class DBKeys: + o = Device() @kernel def run(self): @@ -279,8 +287,9 @@ class _RTIOUnderflow(AutoContext): self.o.pulse(25*ns) -class _RTIOSequenceError(AutoContext): - o = Device("ttl_out") +class _RTIOSequenceError(AutoDB): + class DBKeys: + o = Device() @kernel def run(self): diff --git a/doc/manual/getting_started.rst b/doc/manual/getting_started.rst index c74e140fb..943b2f03c 100644 --- a/doc/manual/getting_started.rst +++ b/doc/manual/getting_started.rst @@ -9,15 +9,16 @@ As a very first step, we will turn on a LED on the core device. Create a file `` from artiq import * - class LED(AutoContext): - led = Device("gpio_out") + class LED(AutoDB): + class DBKeys: + led = Device() @kernel def run(self): self.led.on() -The central part of our code is our ``LED`` class, that derives from :class:`artiq.language.core.AutoContext`. ``AutoContext`` is part of the mechanism that attaches device drivers and retrieves parameters according to a database. Abstract attributes such as ``Device("gpio_out")`` list the devices (and parameters) that our class needs in order to operate, and the names of the attributes (e.g. ``led``) are used to search the database. ``AutoContext`` 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). 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. @@ -37,8 +38,9 @@ Modify the code as follows: :: def input_led_state(): return int(input("Enter desired LED state: ")) - class LED(AutoContext): - led = Device("gpio_out") + class LED(AutoDB): + class DBKeys: + led = Device() @kernel def run(self): @@ -79,8 +81,9 @@ Create a new file ``rtio.py`` containing the following: :: from artiq import * - class Tutorial(AutoContext): - ttl0 = Device("ttl_out") + class Tutorial(AutoDB): + class DBKeys: + ttl0 = Device() @kernel def run(self): @@ -100,9 +103,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(AutoContext): - led = Device("gpio_out") - ttl0 = Device("ttl_out") + class Tutorial(AutoDB): + class DBKeys: + led = Device() + ttl0 = Device() @kernel def run(self): diff --git a/doc/manual/writing_a_driver.rst b/doc/manual/writing_a_driver.rst index 3fa1e6c43..2a2a1a7ec 100644 --- a/doc/manual/writing_a_driver.rst +++ b/doc/manual/writing_a_driver.rst @@ -81,7 +81,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.core.AutoContext` mechanism and used normally as a device. +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. :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/slides/artiq_overview.tex b/doc/slides/artiq_overview.tex index 528b9d425..86706dc88 100644 --- a/doc/slides/artiq_overview.tex +++ b/doc/slides/artiq_overview.tex @@ -86,7 +86,7 @@ with parallel: \begin{frame}[fragile] \frametitle{\fontseries{l}\selectfont Object orientation and code reuse} \begin{verbatimtab} -class Main(AutoContext): +class Main(AutoDB): def build(self): self.ion1 = Ion(...) self.ion2 = Ion(...) @@ -130,9 +130,9 @@ class Main(AutoContext): \begin{frame}[fragile] \frametitle{\fontseries{l}\selectfont Channels and parameters} \begin{itemize} -\item A kernel is a method of a class that derives from the \verb!AutoContext! class +\item A kernel is a method of a class \item The entry point for an experiment is called \verb!run! --- may or may not be a kernel -\item The \verb!AutoContext! class manages channels and parameters, and sets them as attributes +\item The \verb!AutoDB! class manages channels and parameters \item If channels/parameters are passed as constructor arguments, those are used \item Otherwise, they are looked up in the device and parameter databases \end{itemize} diff --git a/examples/al_spectroscopy.py b/examples/al_spectroscopy.py index fadd7d262..a1f2768a6 100644 --- a/examples/al_spectroscopy.py +++ b/examples/al_spectroscopy.py @@ -1,16 +1,17 @@ from artiq import * -class AluminumSpectroscopy(AutoContext): - mains_sync = Device("ttl_in") - laser_cooling = Device("dds") - spectroscopy = Device("dds") - spectroscopy_b = Device("dac") - state_detection = Device("dds") - pmt = Device("ttl_in") - spectroscopy_freq = Parameter(432*MHz) - photon_limit_low = Parameter(10) - photon_limit_high = Parameter(15) +class AluminumSpectroscopy(AutoDB): + class DBKeys: + 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) @kernel def run(self): diff --git a/examples/ddb.pyon b/examples/ddb.pyon index 55e1197d3..dfde1c55c 100644 --- a/examples/ddb.pyon +++ b/examples/ddb.pyon @@ -2,67 +2,67 @@ "comm": { "module": "artiq.coredevice.comm_serial", "class": "Comm", - "parameters": {} + "arguments": {} }, "core": { "module": "artiq.coredevice.core", "class": "Core", - "parameters": {} + "arguments": {} }, "led": { "module": "artiq.coredevice.gpio", "class": "GPIOOut", - "parameters": {"channel": 0} + "arguments": {"channel": 0} }, "pmt0": { "module": "artiq.coredevice.rtio", "class": "RTIOIn", - "parameters": {"channel": 0} + "arguments": {"channel": 0} }, "pmt1": { "module": "artiq.coredevice.rtio", "class": "RTIOIn", - "parameters": {"channel": 1} + "arguments": {"channel": 1} }, "ttl0": { "module": "artiq.coredevice.rtio", "class": "RTIOOut", - "parameters": {"channel": 2} + "arguments": {"channel": 2} }, "ttl1": { "module": "artiq.coredevice.rtio", "class": "RTIOOut", - "parameters": {"channel": 3} + "arguments": {"channel": 3} }, "ttl2": { "module": "artiq.coredevice.rtio", "class": "RTIOOut", - "parameters": {"channel": 4} + "arguments": {"channel": 4} }, "dds0": { "module": "artiq.coredevice.dds", "class": "DDS", - "parameters": {"reg_channel": 0, "rtio_switch": 5} + "arguments": {"reg_channel": 0, "rtio_switch": 5} }, "dds1": { "module": "artiq.coredevice.dds", "class": "DDS", - "parameters": {"reg_channel": 1, "rtio_switch": 6} + "arguments": {"reg_channel": 1, "rtio_switch": 6} }, "dds2": { "module": "artiq.coredevice.dds", "class": "DDS", - "parameters": {"reg_channel": 2, "rtio_switch": 7} + "arguments": {"reg_channel": 2, "rtio_switch": 7} }, "electrodes": { "module": "artiq.devices.pdq2", "class": "CompoundPDQ2", - "parameters": { + "arguments": { "ids": ["qc_q1_0", "qc_q1_1", "qc_q1_2", "qc_q1_3"], "rtio_trigger": 7, "rtio_frame": (2, 3, 4) diff --git a/examples/ddb_sim.pyon b/examples/ddb_sim.pyon index a69f07b36..65046a116 100644 --- a/examples/ddb_sim.pyon +++ b/examples/ddb_sim.pyon @@ -2,36 +2,36 @@ "core": { "module": "artiq.sim.devices", "class": "Core", - "parameters": {} + "arguments": {} }, "mains_sync": { "module": "artiq.sim.devices", "class": "Input", - "parameters": {"name": "mains_sync"} + "arguments": {"name": "mains_sync"} }, "pmt": { "module": "artiq.sim.devices", "class": "Input", - "parameters": {"name": "pmt"} + "arguments": {"name": "pmt"} }, "laser_cooling": { "module": "artiq.sim.devices", "class": "WaveOutput", - "parameters": {"name": "laser_cooling"} + "arguments": {"name": "laser_cooling"} }, "spectroscopy": { "module": "artiq.sim.devices", "class": "WaveOutput", - "parameters": {"name": "spectroscopy"} + "arguments": {"name": "spectroscopy"} }, "spectroscopy_b": { "module": "artiq.sim.devices", "class": "VoltageOutput", - "parameters": {"name": "spectroscopy_b"} + "arguments": {"name": "spectroscopy_b"} }, "state_detection": { "module": "artiq.sim.devices", "class": "WaveOutput", - "parameters": {"name": "state_detection"} + "arguments": {"name": "state_detection"} }, } diff --git a/examples/dds_test.py b/examples/dds_test.py index a84692cc4..f680d78db 100644 --- a/examples/dds_test.py +++ b/examples/dds_test.py @@ -1,11 +1,12 @@ from artiq import * -class DDSTest(AutoContext): - dds0 = Device("dds") - dds1 = Device("dds") - dds2 = Device("dds") - led = Device("gpio_out") +class DDSTest(AutoDB): + class DBKeys: + dds0 = Device() + dds1 = Device() + dds2 = Device() + led = Device() @kernel def run(self): diff --git a/examples/mandelbrot.py b/examples/mandelbrot.py index 529b987c1..fcc14e0d8 100644 --- a/examples/mandelbrot.py +++ b/examples/mandelbrot.py @@ -3,7 +3,7 @@ import sys from artiq import * -class Mandelbrot(AutoContext): +class Mandelbrot(AutoDB): def col(self, i): sys.stdout.write(" .,-:;i+hHM$*#@ "[i]) diff --git a/examples/photon_histogram.py b/examples/photon_histogram.py index 95b59769e..60672a4ab 100644 --- a/examples/photon_histogram.py +++ b/examples/photon_histogram.py @@ -1,13 +1,14 @@ from artiq import * -class PhotonHistogram(AutoContext): - bd = Device("dds") - bdd = Device("dds") - pmt = Device("ttl_in") +class PhotonHistogram(AutoDB): + class DBKeys: + bd = Device() + bdd = Device() + pmt = Device() - nbins = Argument(100) - repeats = Argument(100) + nbins = Argument(100) + repeats = Argument(100) @kernel def cool_detect(self): diff --git a/examples/pulse_performance.py b/examples/pulse_performance.py index ef4cc64c5..08c982d89 100644 --- a/examples/pulse_performance.py +++ b/examples/pulse_performance.py @@ -6,8 +6,9 @@ def print_min_period(p): print("Minimum square wave output period: {} ns".format(p)) -class PulsePerformance(AutoContext): - ttl0 = Device("ttl_out") +class PulsePerformance(AutoDB): + class DBKeys: + ttl0 = Device() @kernel def run(self): diff --git a/examples/rtio_skew.py b/examples/rtio_skew.py index 7de60c19c..bf8d95e2b 100644 --- a/examples/rtio_skew.py +++ b/examples/rtio_skew.py @@ -9,9 +9,10 @@ def print_failed(): print("Pulse was not received back") -class RTIOSkew(AutoContext): - pmt0 = Device("ttl_in") - ttl0 = Device("ttl_out") +class RTIOSkew(AutoDB): + class DBKeys: + pmt0 = Device() + ttl0 = Device() @kernel def run(self): diff --git a/examples/simple_simulation.py b/examples/simple_simulation.py index a7725005b..3938d2549 100644 --- a/examples/simple_simulation.py +++ b/examples/simple_simulation.py @@ -1,11 +1,12 @@ from artiq import * -class SimpleSimulation(AutoContext): - a = Device("dds") - b = Device("dds") - c = Device("dds") - d = Device("dds") +class SimpleSimulation(AutoDB): + class DBKeys: + a = Device() + b = Device() + c = Device() + d = Device() @kernel def run(self): @@ -20,17 +21,16 @@ class SimpleSimulation(AutoContext): def main(): from artiq.sim import devices as sd - from artiq.sim import time + core = sd.Core() exp = SimpleSimulation( - core=sd.Core(), - a=sd.WaveOutput(name="a"), - b=sd.WaveOutput(name="b"), - c=sd.WaveOutput(name="c"), - d=sd.WaveOutput(name="d"), + 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"), ) exp.run() - print(time.manager.format_timeline()) if __name__ == "__main__": main() diff --git a/examples/transport.py b/examples/transport.py index 5b19de851..f2e6be55f 100644 --- a/examples/transport.py +++ b/examples/transport.py @@ -10,17 +10,18 @@ transport_data = dict( # 4 devices, 3 board each, 3 dacs each ) -class Transport(AutoContext): - bd = Device("dds") - bdd = Device("dds") - pmt = Device("ttl_in") - electrodes = Device("pdq") +class Transport(AutoDB): + class DBKeys: + bd = Device() + bdd = Device() + pmt = Device() + electrodes = Device() - wait_at_stop = Parameter(100*us) - speed = Parameter(1.5) + wait_at_stop = Parameter(100*us) + speed = Parameter(1.5) - repeats = Argument(100) - nbins = Argument(100) + repeats = Argument(100) + nbins = Argument(100) def prepare(self, stop): t = transport_data["t"][:stop]*self.speed diff --git a/frontend/artiq_client.py b/frontend/artiq_client.py index 9e391a8ac..3d8aaeef8 100755 --- a/frontend/artiq_client.py +++ b/frontend/artiq_client.py @@ -113,19 +113,19 @@ def _action_cancel(remote, args): def _action_set_device(remote, args): - remote.set_device(args.name, pyon.decode(args.description)) + remote.set(args.name, pyon.decode(args.description)) def _action_del_device(remote, args): - remote.del_device(args.name) + remote.delete(args.name) def _action_set_parameter(remote, args): - remote.set_parameter(args.name, pyon.decode(args.value)) + remote.set(args.name, pyon.decode(args.value)) def _action_del_parameter(remote, args): - remote.del_parameter(args.name) + remote.delete(args.name) def _show_queue(queue): @@ -233,10 +233,14 @@ def main(): sys.exit(1) else: port = 8888 if args.port is None else args.port - if action in ("submit", "cancel"): - target_name = "master_schedule" - else: - target_name = "master_dpdb" + target_name = { + "submit": "master_schedule", + "cancel": "master_schedule", + "set_device": "master_ddb", + "del_device": "master_ddb", + "set_parameter": "master_pdb", + "del_parameter": "master_pdb", + }[action] remote = Client(args.server, port, target_name) try: globals()["_action_" + action](remote, args) diff --git a/frontend/artiq_master.py b/frontend/artiq_master.py index 11a1041b2..8a4a239ae 100755 --- a/frontend/artiq_master.py +++ b/frontend/artiq_master.py @@ -6,7 +6,7 @@ import atexit from artiq.management.pc_rpc import Server from artiq.management.sync_struct import Publisher -from artiq.management.dpdb import DeviceParamDB, SimpleParameterHistory +from artiq.management.db import FlatFileDB, SimpleHistory from artiq.management.scheduler import Scheduler @@ -27,24 +27,26 @@ def _get_args(): def main(): args = _get_args() - dpdb = DeviceParamDB("ddb.pyon", "pdb.pyon") - simplephist = SimpleParameterHistory(30) - dpdb.parameter_hooks.append(simplephist) + ddb = FlatFileDB("ddb.pyon") + pdb = FlatFileDB("pdb.pyon") + simplephist = SimpleHistory(30) + pdb.hooks.append(simplephist) loop = asyncio.get_event_loop() atexit.register(lambda: loop.close()) scheduler = Scheduler({ - "req_device": dpdb.req_device, - "req_parameter": dpdb.req_parameter, - "set_parameter": dpdb.set_parameter + "req_device": ddb.request, + "req_parameter": pdb.request, + "set_parameter": pdb.set }) loop.run_until_complete(scheduler.start()) atexit.register(lambda: loop.run_until_complete(scheduler.stop())) server_control = Server({ "master_schedule": scheduler, - "master_dpdb": dpdb + "master_ddb": ddb, + "master_pdb": pdb }) loop.run_until_complete(server_control.start( args.bind, args.port_control)) @@ -53,8 +55,8 @@ def main(): server_notify = Publisher({ "queue": scheduler.queue, "periodic": scheduler.periodic, - "devices": dpdb.ddb, - "parameters": dpdb.pdb, + "devices": ddb.data, + "parameters": pdb.data, "parameters_simplehist": simplephist.history }) loop.run_until_complete(server_notify.start( diff --git a/frontend/artiq_run.py b/frontend/artiq_run.py index 1d6222467..1d968a84b 100755 --- a/frontend/artiq_run.py +++ b/frontend/artiq_run.py @@ -6,14 +6,15 @@ from inspect import isclass from operator import itemgetter from artiq.management.file_import import file_import -from artiq.language.context import * +from artiq.language.db import * from artiq.management import pyon -from artiq.management.dpdb import DeviceParamDB, DeviceParamSupplier +from artiq.management.db import * -class ELFRunner(AutoContext): - comm = Device("comm") - implicit_core = False +class ELFRunner(AutoDB): + class DBKeys: + comm = Device() + implicit_core = False def run(self, filename): with open(filename, "rb") as f: @@ -23,8 +24,14 @@ class ELFRunner(AutoContext): comm.serve(dict(), dict()) +class SimpleParamLogger: + def set(self, timestamp, name, value): + print("Parameter change: {} -> {}".format(name, value)) + + def _get_args(): - parser = argparse.ArgumentParser(description="Local running tool") + parser = argparse.ArgumentParser( + description="Local experiment running tool") parser.add_argument("-d", "--ddb", default="ddb.pyon", help="device database file") @@ -54,8 +61,11 @@ def _parse_arguments(arguments): def main(): args = _get_args() - dpdb = DeviceParamDB(args.ddb, args.pdb) - dps = DeviceParamSupplier(dpdb.req_device, dpdb.req_parameter) + ddb = FlatFileDB(args.ddb) + pdb = FlatFileDB(args.pdb) + pdb.hooks.append(SimpleParamLogger()) + rdb = ResultDB() + dbh = DBHub(ddb, pdb, rdb) try: if args.elf: if args.arguments: @@ -69,8 +79,8 @@ def main(): units = [(k, v) for k, v in module.__dict__.items() if k[0] != "_" and isclass(v) - and issubclass(v, AutoContext) - and v is not AutoContext] + and issubclass(v, AutoDB) + and v is not AutoDB] l = len(units) if l == 0: print("No units found in module") @@ -92,15 +102,15 @@ def main(): print("Failed to parse run arguments") sys.exit(1) - unit_inst = unit(dps, **arguments) + unit_inst = unit(dbh, **arguments) unit_inst.run() - if dps.parameter_wb: - print("Modified parameters:") - for requester, name in dps.parameter_wb: - print("{}: {}".format(name, getattr(requester, name))) + if rdb.data: + print("Results:") + for k, v in rdb.data.items(): + print("{}: {}".format(k, v)) finally: - dps.close() + dbh.close() if __name__ == "__main__": main()