mirror of
https://github.com/m-labs/artiq.git
synced 2024-12-25 19:28:26 +08:00
language: replace AutoContext 'parameter' string with abstract attributes
This allows specifying default values for parameters, and other data.
This commit is contained in:
parent
83d3b97b23
commit
2a843ea436
@ -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
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
@ -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
138
artiq/language/context.py
Normal 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
|
@ -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")
|
||||
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
----------------------------------
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
@ -8,7 +8,7 @@ def print_min_period(p):
|
||||
|
||||
|
||||
class PulsePerformance(AutoContext):
|
||||
parameters = "o"
|
||||
o = Device("ttl_out")
|
||||
|
||||
@kernel
|
||||
def run(self):
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
Loading…
Reference in New Issue
Block a user