refactor device/parameter management, immediate parameter updates, start introducing results

This commit is contained in:
Sebastien Bourdeauducq 2015-01-12 18:51:23 +08:00
parent c938e3f4f0
commit 891c0d12f2
31 changed files with 497 additions and 508 deletions

View File

@ -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

View File

@ -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,7 +14,8 @@ class _RuntimeEnvironment(LinkInterface):
return str(self.llvm_module)
class Comm(AutoContext):
class Comm(AutoDB):
class DBKeys:
implicit_core = False
def get_runtime_env(self):

View File

@ -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,7 +60,8 @@ def _read_exactly(f, n):
return r
class Comm(AutoContext):
class Comm(AutoDB):
class DBKeys:
serial_dev = Parameter("/dev/ttyUSB1")
baud_rate = Parameter(115200)
implicit_core = False

View File

@ -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,8 +44,9 @@ def _no_debug_unparse(label, node):
pass
class Core(AutoContext):
comm = Device("comm")
class Core(AutoDB):
class DBKeys:
comm = Device()
external_clock = Parameter(None)
implicit_core = False

View File

@ -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.
"""
class DBKeys:
dds_sysclk = Parameter(1*GHz)
reg_channel = Parameter()
rtio_switch = Parameter()
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):

View File

@ -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):

View File

@ -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

View File

@ -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

View File

@ -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

145
artiq/language/db.py Normal file
View File

@ -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

106
artiq/management/db.py Normal file
View File

@ -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()

View File

@ -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))

View File

@ -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()

View File

@ -1,12 +1,13 @@
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):
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):

View File

@ -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):

View File

@ -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):

View File

@ -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.

View File

@ -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}

View File

@ -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")
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 = Parameter(10)
photon_limit_high = Parameter(15)
photon_limit_low = Argument(10)
photon_limit_high = Argument(15)
@kernel
def run(self):

View File

@ -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)

View File

@ -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"}
},
}

View File

@ -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):

View File

@ -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])

View File

@ -1,10 +1,11 @@
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)

View File

@ -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):

View File

@ -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):

View File

@ -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()

View File

@ -10,11 +10,12 @@ 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)

View File

@ -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)

View File

@ -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(

View File

@ -6,13 +6,14 @@ 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")
class ELFRunner(AutoDB):
class DBKeys:
comm = Device()
implicit_core = False
def run(self, filename):
@ -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()