From 2a843ea436c4c7f34d936a0e4794e308dc9329bf Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Tue, 2 Dec 2014 17:19:05 +0800 Subject: [PATCH] language: replace AutoContext 'parameter' string with abstract attributes This allows specifying default values for parameters, and other data. --- artiq/__init__.py | 1 + artiq/coredevice/dds.py | 5 +- artiq/coredevice/gpio.py | 3 +- artiq/coredevice/rtio.py | 5 +- artiq/devices/pdq2/__init__.py | 5 +- artiq/language/context.py | 138 +++++++++++++++++++++++++ artiq/language/core.py | 99 ------------------ artiq/sim/devices.py | 9 +- doc/manual/core_language_reference.rst | 6 ++ doc/manual/getting_started.rst | 11 +- examples/al_spectroscopy.py | 11 +- examples/dds_test.py | 6 +- examples/photon_histogram.py | 6 +- examples/pulse_performance.py | 2 +- examples/rtio_skew.py | 3 +- examples/simple_simulation.py | 5 +- examples/transport.py | 12 ++- test/full_stack.py | 24 +++-- 18 files changed, 217 insertions(+), 134 deletions(-) create mode 100644 artiq/language/context.py diff --git a/artiq/__init__.py b/artiq/__init__.py index 9f7fc4cfa..24fdfcad1 100644 --- a/artiq/__init__.py +++ b/artiq/__init__.py @@ -1,3 +1,4 @@ from artiq.language.core import * +from artiq.language.context import * from artiq.language.units import ps, ns, us, ms, s from artiq.language.units import Hz, kHz, MHz, GHz diff --git a/artiq/coredevice/dds.py b/artiq/coredevice/dds.py index 691500337..7b1a1574a 100644 --- a/artiq/coredevice/dds.py +++ b/artiq/coredevice/dds.py @@ -1,4 +1,5 @@ from artiq.language.core import * +from artiq.language.context import * from artiq.language.units import * from artiq.coredevice import rtio @@ -23,7 +24,9 @@ class DDS(AutoContext): the DDS device. """ - parameters = "dds_sysclk reg_channel rtio_switch" + dds_sysclk = Parameter() + reg_channel = Parameter() + rtio_switch = Parameter() def build(self): self.previous_on = False diff --git a/artiq/coredevice/gpio.py b/artiq/coredevice/gpio.py index cf5456691..c0371ce3a 100644 --- a/artiq/coredevice/gpio.py +++ b/artiq/coredevice/gpio.py @@ -1,8 +1,9 @@ from artiq.language.core import * +from artiq.language.context import * class GPIOOut(AutoContext): - parameters = "channel" + channel = Parameter() @kernel def on(self): diff --git a/artiq/coredevice/rtio.py b/artiq/coredevice/rtio.py index 279fff996..694391e42 100644 --- a/artiq/coredevice/rtio.py +++ b/artiq/coredevice/rtio.py @@ -1,4 +1,5 @@ from artiq.language.core import * +from artiq.language.context import * class LLRTIOOut(AutoContext): @@ -11,7 +12,7 @@ class LLRTIOOut(AutoContext): ``RTIOOut`` instead. """ - parameters = "channel" + channel = Parameter() def build(self): self._set_oe() @@ -50,7 +51,7 @@ class LLRTIOOut(AutoContext): class _RTIOBase(AutoContext): - parameters = "channel" + channel = Parameter() 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 b4a7dc4ba..20d8e139c 100644 --- a/artiq/devices/pdq2/__init__.py +++ b/artiq/devices/pdq2/__init__.py @@ -1,4 +1,5 @@ from artiq.language.core import * +from artiq.language.context import * from artiq.language.units import * from artiq.coredevice import rtio @@ -111,7 +112,9 @@ class _Frame: class CompoundPDQ2(AutoContext): - parameters = "ids rtio_trigger rtio_frame" + ids = Parameter() + rtio_trigger = Parameter() + rtio_frame = Parameter() def build(self): self.trigger = rtio.LLRTIOOut(self, channel=self.rtio_trigger) diff --git a/artiq/language/context.py b/artiq/language/context.py new file mode 100644 index 000000000..2efddf92a --- /dev/null +++ b/artiq/language/context.py @@ -0,0 +1,138 @@ +""" +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 for ``AutoContext`` to process. + + :param default: Default value of the parameter to be used if not found + in database. + + """ + 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(): + setattr(self, k, v) + + for k in dir(self): + v = getattr(self, k) + if isinstance(v, _AttributeKind): + value = self.mvs.get_missing_value(k, v) + setattr(self, k, value) + + self.build() + + def get_missing_value(self, name, kind): + """Attempts to retrieve ``parameter`` 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) + + 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/core.py b/artiq/language/core.py index 9c98450cf..b60ef83e5 100644 --- a/artiq/language/core.py +++ b/artiq/language/core.py @@ -86,105 +86,6 @@ def array(element, count): return [_copy(element) for i in range(count)] -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 its ``parameters`` attribute and, if - they are not already existing, requests them from ``mvs`` (Missing Value - Supplier). - - 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 parameters: A string containing the parameters that the object must - have. It must be a space-separated list of valid Python identifiers. - Default: empty. - :var implicit_core: Automatically adds ``core`` to the parameter list. - Default: True. - - Example: - - >>> class SubExperiment(AutoContext): - ... parameters = "foo bar" - ... - ... def run(): - ... do_something(self.foo, self.bar) - ... - >>> class MainExperiment(AutoContext): - ... parameters = "bar1 bar2 offset" - ... - ... 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.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) - - """ - parameters = "" - implicit_core = True - - def __init__(self, mvs=None, **kwargs): - self.mvs = mvs - for k, v in kwargs.items(): - setattr(self, k, v) - - parameters = self.parameters.split() - if self.implicit_core: - parameters.append("core") - for parameter in parameters: - try: - value = getattr(self, parameter) - except AttributeError: - value = self.mvs.get_missing_value(parameter) - setattr(self, parameter, value) - - self.build() - - def get_missing_value(self, parameter): - """Attempts to retrieve ``parameter`` 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, parameter) - except AttributeError: - return self.mvs.get_missing_value(parameter) - - 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 - - _KernelFunctionInfo = _namedtuple("_KernelFunctionInfo", "core_name k_function") diff --git a/artiq/sim/devices.py b/artiq/sim/devices.py index ec958455c..aab83c1cf 100644 --- a/artiq/sim/devices.py +++ b/artiq/sim/devices.py @@ -1,6 +1,7 @@ from random import Random -from artiq.language.core import AutoContext, delay +from artiq.language.core import delay +from artiq.language.context import AutoContext, Parameter from artiq.language import units from artiq.sim import time @@ -11,7 +12,7 @@ class Core: class Input(AutoContext): - parameters = "name" + name = Parameter() implicit_core = False def build(self): @@ -30,7 +31,7 @@ class Input(AutoContext): class WaveOutput(AutoContext): - parameters = "name" + name = Parameter() implicit_core = False def pulse(self, frequency, duration): @@ -39,7 +40,7 @@ class WaveOutput(AutoContext): class VoltageOutput(AutoContext): - parameters = "name" + name = Parameter() implicit_core = False def set(self, value): diff --git a/doc/manual/core_language_reference.rst b/doc/manual/core_language_reference.rst index ff2a49c9b..1105845a3 100644 --- a/doc/manual/core_language_reference.rst +++ b/doc/manual/core_language_reference.rst @@ -7,6 +7,12 @@ Core language reference .. automodule:: artiq.language.core :members: +:mod:`artiq.language.context` module +--------------------------------- + +.. automodule:: artiq.language.context + :members: + :mod:`artiq.language.units` module ---------------------------------- diff --git a/doc/manual/getting_started.rst b/doc/manual/getting_started.rst index 4503a11a8..aad7ec9c5 100644 --- a/doc/manual/getting_started.rst +++ b/doc/manual/getting_started.rst @@ -10,7 +10,7 @@ As a very first step, we will turn on a LED on the core device. Create a file `` from artiq.coredevice import comm_serial, core, gpio class LED(AutoContext): - parameters = "led" + led = Device("gpio_out") @kernel def run(self): @@ -23,7 +23,7 @@ As a very first step, we will turn on a LED on the core device. Create a file `` exp = LED(core=core_driver, led=led_driver) exp.run() -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. We are not using the database yet; instead, we import and create the device drivers and establish communication with the core device manually. The ``parameters`` string gives the list of devices (and parameters) that our class needs in order to operate. ``AutoContext`` sets them as object attributes, so our ``led`` parameter becomes accessible as ``self.led``. 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.core.AutoContext`. ``AutoContext`` is part of the mechanism that attaches device drivers and retrieves parameters according to a database. We are not using the database yet; instead, we import and create the device drivers and establish communication with the core device manually. Abstract attributes such as ``Device("gpio_out")`` list the devices (and parameters) that our class needs in order to operate. ``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). Run this example with: :: @@ -42,7 +42,7 @@ Modify the code as follows: :: return int(input("Enter desired LED state: ")) class LED(AutoContext): - parameters = "led" + led = Device("gpio_out") @kernel def run(self): @@ -85,7 +85,7 @@ Create a new file ``rtio.py`` containing the following: :: from artiq.coredevice import comm_serial, core, rtio class Tutorial(AutoContext): - parameters = "o" + o = Device("ttl_out") @kernel def run(self): @@ -112,7 +112,8 @@ Try reducing the period of the generated waveform until the CPU cannot keep up w print("RTIO underflow occured") class Tutorial(AutoContext): - parameters = "led o" + led = Device("gpio_out") + o = Device("ttl_out") @kernel def run(self): diff --git a/examples/al_spectroscopy.py b/examples/al_spectroscopy.py index 1a94150e9..000799dff 100644 --- a/examples/al_spectroscopy.py +++ b/examples/al_spectroscopy.py @@ -2,8 +2,15 @@ from artiq import * class AluminumSpectroscopy(AutoContext): - parameters = "mains_sync laser_cooling spectroscopy spectroscopy_b state_detection pmt \ - spectroscopy_freq photon_limit_low photon_limit_high" + 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() + photon_limit_low = Parameter() + photon_limit_high = Parameter() @kernel def run(self): diff --git a/examples/dds_test.py b/examples/dds_test.py index 5921d4ef0..f79e6a5ad 100644 --- a/examples/dds_test.py +++ b/examples/dds_test.py @@ -3,7 +3,11 @@ from artiq.coredevice import comm_serial, core, dds, gpio class DDSTest(AutoContext): - parameters = "a b c d led" + a = Device("dds") + b = Device("dds") + c = Device("dds") + d = Device("dds") + led = Device("gpio_out") @kernel def run(self): diff --git a/examples/photon_histogram.py b/examples/photon_histogram.py index 03d433ac3..7b1005343 100644 --- a/examples/photon_histogram.py +++ b/examples/photon_histogram.py @@ -3,7 +3,11 @@ from artiq.coredevice import comm_serial, core, dds, rtio class PhotonHistogram(AutoContext): - parameters = "bd bdd pmt repeats nbins" + bd = Device("dds") + bdd = Device("dds") + pmt = Device("ttl_in") + repeats = Parameter() + nbins = Parameter() def report(self, i, n): print(i, n) diff --git a/examples/pulse_performance.py b/examples/pulse_performance.py index a08cc5e62..14e9e68de 100644 --- a/examples/pulse_performance.py +++ b/examples/pulse_performance.py @@ -8,7 +8,7 @@ def print_min_period(p): class PulsePerformance(AutoContext): - parameters = "o" + o = Device("ttl_out") @kernel def run(self): diff --git a/examples/rtio_skew.py b/examples/rtio_skew.py index cb0024f0b..cef7ff724 100644 --- a/examples/rtio_skew.py +++ b/examples/rtio_skew.py @@ -11,7 +11,8 @@ def print_failed(): class RTIOSkew(AutoContext): - parameters = "i o" + i = Device("ttl_in") + o = Device("ttl_out") @kernel def run(self): diff --git a/examples/simple_simulation.py b/examples/simple_simulation.py index 3f28e9363..a7725005b 100644 --- a/examples/simple_simulation.py +++ b/examples/simple_simulation.py @@ -2,7 +2,10 @@ from artiq import * class SimpleSimulation(AutoContext): - parameters = "a b c d" + a = Device("dds") + b = Device("dds") + c = Device("dds") + d = Device("dds") @kernel def run(self): diff --git a/examples/transport.py b/examples/transport.py index a7193d73e..63a9bf777 100644 --- a/examples/transport.py +++ b/examples/transport.py @@ -6,10 +6,14 @@ from artiq.devices import pdq2 class Transport(AutoContext): - parameters = ( - "bd pmt repeats nbins " - "electrodes transport_data wait_at_stop speed" - ) + bd = Device("dds") + pmt = Device("ttl_in") + repeats = Parameter() + nbins = Parameter() + electrodes = Device("pdq") + transport_data = Parameter() + wait_at_stop = Parameter() + speed = Parameter() def prepare(self, stop): t = self.transport_data["t"][:stop]*self.speed diff --git a/test/full_stack.py b/test/full_stack.py index 4ac65118c..9c3e0a682 100644 --- a/test/full_stack.py +++ b/test/full_stack.py @@ -26,11 +26,12 @@ def _run_on_host(k_class, **parameters): class _Primes(AutoContext): - parameters = "output_list max" + output_list = Parameter() + maximum = Parameter() @kernel def run(self): - for x in range(1, self.max): + for x in range(1, self.maximum): d = 2 prime = True while d*d <= x: @@ -74,7 +75,8 @@ class _Misc(AutoContext): class _PulseLogger(AutoContext): - parameters = "output_list name" + output_list = Parameter() + name = Parameter() def _append(self, t, l, f): if not hasattr(self, "first_timestamp"): @@ -95,7 +97,7 @@ class _PulseLogger(AutoContext): class _Pulses(AutoContext): - parameters = "output_list" + output_list = Parameter() def build(self): for name in "a", "b", "c", "d": @@ -119,7 +121,7 @@ class _MyException(Exception): class _Exceptions(AutoContext): - parameters = "trace" + trace = Parameter() @kernel def run(self): @@ -164,8 +166,8 @@ class _Exceptions(AutoContext): class ExecutionCase(unittest.TestCase): def test_primes(self): l_device, l_host = [], [] - _run_on_device(_Primes, max=100, output_list=l_device) - _run_on_host(_Primes, max=100, output_list=l_host) + _run_on_device(_Primes, maximum=100, output_list=l_device) + _run_on_host(_Primes, maximum=100, output_list=l_host) self.assertEqual(l_device, l_host) def test_misc(self): @@ -208,7 +210,9 @@ class ExecutionCase(unittest.TestCase): class _RTIOLoopback(AutoContext): - parameters = "i o npulses" + i = Device("ttl_in") + o = Device("ttl_out") + npulses = Parameter() def report(self, n): self.result = n @@ -225,7 +229,7 @@ class _RTIOLoopback(AutoContext): class _RTIOUnderflow(AutoContext): - parameters = "o" + o = Device("ttl_out") @kernel def run(self): @@ -235,7 +239,7 @@ class _RTIOUnderflow(AutoContext): class _RTIOSequenceError(AutoContext): - parameters = "o" + o = Device("ttl_out") @kernel def run(self):