language: replace AutoContext 'parameter' string with abstract attributes

This allows specifying default values for parameters, and other data.
This commit is contained in:
Sebastien Bourdeauducq 2014-12-02 17:19:05 +08:00
parent 83d3b97b23
commit 2a843ea436
18 changed files with 217 additions and 134 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

138
artiq/language/context.py Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -8,7 +8,7 @@ def print_min_period(p):
class PulsePerformance(AutoContext):
parameters = "o"
o = Device("ttl_out")
@kernel
def run(self):

View File

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

View File

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

View File

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

View File

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