simplify unit system and use floats by default

This commit is contained in:
Sebastien Bourdeauducq 2015-06-26 16:20:13 +02:00
parent e6a4c2fb36
commit c71fe29792
17 changed files with 43 additions and 576 deletions

View File

@ -1,6 +1,4 @@
from artiq.language.core import * from artiq.language.core import *
from artiq.language.experiment import Experiment from artiq.language.experiment import Experiment
from artiq.language.db import * from artiq.language.db import *
from artiq.language.units import check_unit from artiq.language.units import *
from artiq.language.units import ps, ns, us, ms, s
from artiq.language.units import Hz, kHz, MHz, GHz

View File

@ -5,7 +5,6 @@ from artiq.language.db import *
from artiq.language.units import ns from artiq.language.units import ns
from artiq.transforms.inline import inline from artiq.transforms.inline import inline
from artiq.transforms.lower_units import lower_units
from artiq.transforms.quantize_time import quantize_time from artiq.transforms.quantize_time import quantize_time
from artiq.transforms.remove_inter_assigns import remove_inter_assigns from artiq.transforms.remove_inter_assigns import remove_inter_assigns
from artiq.transforms.fold_constants import fold_constants from artiq.transforms.fold_constants import fold_constants
@ -61,13 +60,10 @@ class Core(AutoDB):
def transform_stack(self, func_def, rpc_map, exception_map, def transform_stack(self, func_def, rpc_map, exception_map,
debug_unparse=_no_debug_unparse): debug_unparse=_no_debug_unparse):
lower_units(func_def, rpc_map)
debug_unparse("lower_units", func_def)
remove_inter_assigns(func_def) remove_inter_assigns(func_def)
debug_unparse("remove_inter_assigns_1", func_def) debug_unparse("remove_inter_assigns_1", func_def)
quantize_time(func_def, self.ref_period.amount) quantize_time(func_def, self.ref_period)
debug_unparse("quantize_time", func_def) debug_unparse("quantize_time", func_def)
fold_constants(func_def) fold_constants(func_def)

View File

@ -2,7 +2,7 @@ import logging
import ctypes import ctypes
import struct import struct
from artiq.language.units import dB, check_unit, Quantity from artiq.language.units import dB
logger = logging.getLogger("lda") logger = logging.getLogger("lda")
@ -47,14 +47,7 @@ class Ldasim:
""" """
step = self.get_att_step_size() step = self.get_att_step_size()
att = round(attenuation/step)*step
if isinstance(attenuation, Quantity):
check_unit(attenuation, "dB")
att = attenuation
else:
att = attenuation*dB
att = round(att/step)*step
if att > self.get_att_max(): if att > self.get_att_max():
raise ValueError("Cannot set attenuation {} > {}" raise ValueError("Cannot set attenuation {} > {}"
@ -62,7 +55,7 @@ class Ldasim:
elif att < 0*dB: elif att < 0*dB:
raise ValueError("Cannot set attenuation {} < 0".format(att)) raise ValueError("Cannot set attenuation {} < 0".format(att))
else: else:
att = round(att.amount*4)/4. * dB att = round(att*4)/4. * dB
self._attenuation = att self._attenuation = att
def ping(self): def ping(self):
@ -218,14 +211,7 @@ class Lda:
""" """
step = self.get_att_step_size() step = self.get_att_step_size()
att = round(attenuation/step)*step
if isinstance(attenuation, Quantity):
check_unit(attenuation, "dB")
att = attenuation
else:
att = attenuation*dB
att = round(att/step)*step
if att > self.get_att_max(): if att > self.get_att_max():
raise ValueError("Cannot set attenuation {} > {}" raise ValueError("Cannot set attenuation {} > {}"
@ -233,7 +219,7 @@ class Lda:
elif att < 0*dB: elif att < 0*dB:
raise ValueError("Cannot set attenuation {} < 0".format(att)) raise ValueError("Cannot set attenuation {} < 0".format(att))
else: else:
self.set(0x8d, bytes([int(round(att.amount*4))])) self.set(0x8d, bytes([int(round(att*4))]))
def ping(self): def ping(self):
try: try:

View File

@ -4,7 +4,7 @@ import struct as st
import serial import serial
from artiq.language.units import V, strip_unit from artiq.language.units import V
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -302,7 +302,7 @@ class Tcube:
class Tpz(Tcube): class Tpz(Tcube):
def __init__(self, serial_dev): def __init__(self, serial_dev):
Tcube.__init__(self, serial_dev) Tcube.__init__(self, serial_dev)
self.voltage_limit = self.get_tpz_io_settings()[0].amount self.voltage_limit = self.get_tpz_io_settings()[0]
def handle_message(self, msg): def handle_message(self, msg):
msg_id = msg.id msg_id = msg.id
@ -372,8 +372,6 @@ class Tpz(Tcube):
between the three values 75 V, 100 V and 150 V. between the three values 75 V, 100 V and 150 V.
""" """
voltage = strip_unit(voltage, "V")
if voltage < 0 or voltage > self.voltage_limit: if voltage < 0 or voltage > self.voltage_limit:
raise ValueError("Voltage must be in range [0;{}]" raise ValueError("Voltage must be in range [0;{}]"
.format(self.voltage_limit)) .format(self.voltage_limit))
@ -390,7 +388,7 @@ class Tpz(Tcube):
get_msg = self.send_request(MGMSG.PZ_REQ_OUTPUTVOLTS, get_msg = self.send_request(MGMSG.PZ_REQ_OUTPUTVOLTS,
[MGMSG.PZ_GET_OUTPUTVOLTS], 1) [MGMSG.PZ_GET_OUTPUTVOLTS], 1)
return st.unpack("<H", get_msg.data[2:])[0]*self.voltage_limit*V/32767 return st.unpack("<H", get_msg.data[2:])[0]*self.voltage_limit/32767
def set_output_position(self, position_sw): def set_output_position(self, position_sw):
"""Set output position of the piezo actuator. """Set output position of the piezo actuator.
@ -532,7 +530,6 @@ class Tpz(Tcube):
<artiq.devices.thorlabs.driver.Tpz.set_tpz_io_settings>` method. <artiq.devices.thorlabs.driver.Tpz.set_tpz_io_settings>` method.
""" """
output = strip_unit(output, "V")
volt = round(output*32767/self.voltage_limit) volt = round(output*32767/self.voltage_limit)
payload = st.pack("<HHH", 1, lut_index, volt) payload = st.pack("<HHH", 1, lut_index, volt)
self.send(Message(MGMSG.PZ_SET_OUTPUTLUT, data=payload)) self.send(Message(MGMSG.PZ_SET_OUTPUTLUT, data=payload))
@ -548,7 +545,7 @@ class Tpz(Tcube):
get_msg = self.send_request(MGMSG.PZ_REQ_OUTPUTLUT, get_msg = self.send_request(MGMSG.PZ_REQ_OUTPUTLUT,
[MGMSG.PZ_GET_OUTPUTLUT], 1) [MGMSG.PZ_GET_OUTPUTLUT], 1)
(index, output) = st.unpack("<Hh", get_msg.data[2:]) (index, output) = st.unpack("<Hh", get_msg.data[2:])
return index, output*self.voltage_limit*V/32767 return index, output*self.voltage_limit/32767
def set_output_lut_parameters(self, mode, cycle_length, num_cycles, def set_output_lut_parameters(self, mode, cycle_length, num_cycles,
delay_time, precycle_rest, postcycle_rest): delay_time, precycle_rest, postcycle_rest):
@ -684,9 +681,6 @@ class Tpz(Tcube):
100 V limit. 100 V limit.
150 V limit. 150 V limit.
You can either provide this parameter as an integer or as a
:class:`artiq.language.units` Volt quantity (e.g. 75*V).
:param hub_analog_input: When the T-Cube piezo driver unit is used in :param hub_analog_input: When the T-Cube piezo driver unit is used in
conjunction with the T-Cube Strain Gauge Reader (TSG001) on the conjunction with the T-Cube Strain Gauge Reader (TSG001) on the
T-Cube Controller Hub (TCH001), a feedback signal can be passed T-Cube Controller Hub (TCH001), a feedback signal can be passed
@ -706,7 +700,7 @@ class Tpz(Tcube):
connectors. connectors.
""" """
self.voltage_limit = strip_unit(voltage_limit, "V") self.voltage_limit = voltage_limit
if self.voltage_limit == 75: if self.voltage_limit == 75:
voltage_limit = 1 voltage_limit = 1
@ -727,21 +721,21 @@ class Tpz(Tcube):
Hub analog input. Refer to :py:meth:`set_tpz_io_settings() Hub analog input. Refer to :py:meth:`set_tpz_io_settings()
<artiq.devices.thorlabs.driver.Tpz.set_tpz_io_settings>` for the <artiq.devices.thorlabs.driver.Tpz.set_tpz_io_settings>` for the
meaning of those parameters. meaning of those parameters.
:rtype: a 2 elements tuple (Quantity, int) :rtype: a 2 elements tuple (int, int)
""" """
get_msg = self.send_request(MGMSG.PZ_REQ_TPZ_IOSETTINGS, get_msg = self.send_request(MGMSG.PZ_REQ_TPZ_IOSETTINGS,
[MGMSG.PZ_GET_TPZ_IOSETTINGS], 1) [MGMSG.PZ_GET_TPZ_IOSETTINGS], 1)
voltage_limit, hub_analog_input = st.unpack("<HH", get_msg.data[2:6]) voltage_limit, hub_analog_input = st.unpack("<HH", get_msg.data[2:6])
if voltage_limit == 1: if voltage_limit == 1:
voltage_limit = 75*V voltage_limit = 75
elif voltage_limit == 2: elif voltage_limit == 2:
voltage_limit = 100*V voltage_limit = 100
elif voltage_limit == 3: elif voltage_limit == 3:
voltage_limit = 150*V voltage_limit = 150
else: else:
raise ValueError("Voltage limit should be in range [1; 3]") raise ValueError("Voltage limit should be in range [1; 3]")
self.voltage_limit = voltage_limit.amount self.voltage_limit = voltage_limit
return voltage_limit, hub_analog_input return voltage_limit, hub_analog_input
@ -1398,14 +1392,13 @@ class TpzSim:
return self.intensity return self.intensity
def set_tpz_io_settings(self, voltage_limit, hub_analog_input): def set_tpz_io_settings(self, voltage_limit, hub_analog_input):
self.voltage_limit = strip_unit(voltage_limit, "V") if voltage_limit not in [75, 100, 150]:
if self.voltage_limit not in [75, 100, 150]:
raise ValueError("voltage_limit must be 75 V, 100 V or 150 V") raise ValueError("voltage_limit must be 75 V, 100 V or 150 V")
self.voltage_limit = voltage_limit
self.hub_analog_input = hub_analog_input self.hub_analog_input = hub_analog_input
def get_tpz_io_settings(self): def get_tpz_io_settings(self):
return self.voltage_limit*V, self.hub_analog_input return self.voltage_limit, self.hub_analog_input
class TdcSim: class TdcSim:

View File

@ -9,7 +9,6 @@ from pyqtgraph import dockarea
from artiq.tools import TaskObject from artiq.tools import TaskObject
from artiq.protocols.sync_struct import Subscriber from artiq.protocols.sync_struct import Subscriber
from artiq.language.units import strip_unit
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -170,7 +169,7 @@ class _DeviceManager:
if (v["module"] == "artiq.coredevice.dds" if (v["module"] == "artiq.coredevice.dds"
and v["class"] == "DDS"): and v["class"] == "DDS"):
channel = v["arguments"]["channel"] channel = v["arguments"]["channel"]
sysclk = strip_unit(v["arguments"]["sysclk"], "Hz") sysclk = v["arguments"]["sysclk"]
self.dds_widgets[channel] = _DDSWidget( self.dds_widgets[channel] = _DDSWidget(
self.send_to_device, channel, sysclk, k) self.send_to_device, channel, sysclk, k)
self.dds_cb() self.dds_cb()

View File

@ -231,7 +231,7 @@ def time_to_cycles(time, core=None):
""" """
if core is None: if core is None:
raise ValueError("Core device must be specified for time conversion") raise ValueError("Core device must be specified for time conversion")
return round64(time.amount//core.ref_period) return round64(time//core.ref_period)
def cycles_to_time(cycles, core=None): def cycles_to_time(cycles, core=None):

View File

@ -1,266 +1,17 @@
"""
Definition and management of physical units.
"""
from fractions import Fraction as _Fraction
class DimensionError(Exception):
"""Raised when attempting an operation with incompatible units.
When targeting the core device, all units are statically managed at
compilation time. Thus, when raised by functions in this module, this
exception cannot be caught in the kernel as it is raised by the compiler
instead.
"""
pass
def mul_dimension(l, r):
"""Returns the unit obtained by multiplying unit ``l`` with unit ``r``.
Raises ``DimensionError`` if the resulting unit is not implemented.
"""
if l is None:
return r
if r is None:
return l
if {l, r} == {"Hz", "s"}:
return None
raise DimensionError
def _rmul_dimension(l, r):
return mul_dimension(r, l)
def div_dimension(l, r):
"""Returns the unit obtained by dividing unit ``l`` with unit ``r``.
Raises ``DimensionError`` if the resulting unit is not implemented.
"""
if l == r:
return None
if r is None:
return l
if l is None:
if r == "s":
return "Hz"
if r == "Hz":
return "s"
raise DimensionError
def _rdiv_dimension(l, r):
return div_dimension(r, l)
def addsub_dimension(x, y):
"""Returns the unit obtained by adding or subtracting unit ``l`` with
unit ``r``.
Raises ``DimensionError`` if ``l`` and ``r`` are different.
"""
if x == y:
return x
else:
raise DimensionError
_prefixes_str = "pnum_kMG" _prefixes_str = "pnum_kMG"
_smallest_prefix = _Fraction(1, 10**12) _smallest_prefix_exp = -12
def _format(amount, unit):
if amount is NotImplemented:
return NotImplemented
if unit is None:
return amount
else:
return Quantity(amount, unit)
class Quantity:
"""Represents an amount in a given fundamental unit (identified by a
string).
The amount can be of any Python numerical type (integer, float,
Fraction, ...).
Arithmetic operations and comparisons are directly delegated to the
underlying numerical types.
"""
def __init__(self, amount, unit):
self.amount = amount
self.unit = unit
def __repr__(self):
r_amount = self.amount
if isinstance(r_amount, int) or isinstance(r_amount, _Fraction):
r_prefix = 0
r_amount = r_amount/_smallest_prefix
if r_amount:
numerator = r_amount.numerator
while numerator % 1000 == 0 and r_prefix < len(_prefixes_str):
numerator /= 1000
r_amount /= 1000
r_prefix += 1
prefix_str = _prefixes_str[r_prefix]
if prefix_str == "_":
prefix_str = ""
return str(r_amount) + " " + prefix_str + self.unit
else:
return str(r_amount) + " " + self.unit
def __float__(self):
return float(self.amount)
# mul/div
def _binop(self, other, opf_name, dim_function):
opf = getattr(self.amount, opf_name)
if isinstance(other, Quantity):
amount = opf(other.amount)
unit = dim_function(self.unit, other.unit)
else:
amount = opf(other)
unit = dim_function(self.unit, None)
return _format(amount, unit)
def __mul__(self, other):
return self._binop(other, "__mul__", mul_dimension)
def __rmul__(self, other):
return self._binop(other, "__rmul__", _rmul_dimension)
def __truediv__(self, other):
return self._binop(other, "__truediv__", div_dimension)
def __rtruediv__(self, other):
return self._binop(other, "__rtruediv__", _rdiv_dimension)
def __floordiv__(self, other):
return self._binop(other, "__floordiv__", div_dimension)
def __rfloordiv__(self, other):
return self._binop(other, "__rfloordiv__", _rdiv_dimension)
# unary ops
def __neg__(self):
return Quantity(self.amount.__neg__(), self.unit)
def __pos__(self):
return Quantity(self.amount.__pos__(), self.unit)
def __abs__(self):
return Quantity(abs(self.amount), self.unit)
# add/sub
def __add__(self, other):
return self._binop(other, "__add__", addsub_dimension)
def __radd__(self, other):
return self._binop(other, "__radd__", addsub_dimension)
def __sub__(self, other):
return self._binop(other, "__sub__", addsub_dimension)
def __rsub__(self, other):
return self._binop(other, "__rsub__", addsub_dimension)
def __mod__(self, other):
return self._binop(other, "__mod__", addsub_dimension)
def __rmod__(self, other):
return self._binop(other, "__rmod__", addsub_dimension)
# comparisons
def _cmp(self, other, opf_name):
if not isinstance(other, Quantity) or other.unit != self.unit:
raise DimensionError
return getattr(self.amount, opf_name)(other.amount)
def __lt__(self, other):
return self._cmp(other, "__lt__")
def __le__(self, other):
return self._cmp(other, "__le__")
def __eq__(self, other):
return self._cmp(other, "__eq__")
def __ne__(self, other):
return self._cmp(other, "__ne__")
def __gt__(self, other):
return self._cmp(other, "__gt__")
def __ge__(self, other):
return self._cmp(other, "__ge__")
def _register_unit(unit, prefixes): def _register_unit(unit, prefixes):
amount = _smallest_prefix exponent = _smallest_prefix_exp
for prefix in _prefixes_str: for prefix in _prefixes_str:
if prefix in prefixes: if prefix in prefixes:
quantity = Quantity(amount, unit)
full_name = prefix + unit if prefix != "_" else unit full_name = prefix + unit if prefix != "_" else unit
globals()[full_name] = quantity globals()[full_name] = 10.**exponent
amount *= 1000 exponent += 3
_register_unit("s", "pnum_") _register_unit("s", "pnum_")
_register_unit("Hz", "_kMG") _register_unit("Hz", "_kMG")
_register_unit("dB", "_") _register_unit("dB", "_")
_register_unit("V", "um_k") _register_unit("V", "um_k")
def check_unit(value, unit):
"""Checks that the value has the specified unit. Unit specification is
a string representing the unit without any prefix (e.g. ``s``, ``Hz``).
Checking for a dimensionless value (not a ``Quantity`` instance) is done
by setting ``unit`` to ``None``.
If the units do not match, ``DimensionError`` is raised.
This function can be used in kernels and is executed at compilation time.
There is already unit checking built into the arithmetic, so you typically
need to use this function only when using the ``amount`` property of
``Quantity``.
"""
if unit is None:
if isinstance(value, Quantity):
raise DimensionError
else:
if not isinstance(value, Quantity) or value.unit != unit:
raise DimensionError
def strip_unit(value, unit):
"""Check that the value has the specified unit and returns its amount.
Raises ``DimensionError`` if the units does not match.
If the passed value is not a ``Quantity``, it is assumed to be in the
specified unit and is returned unchanged.
If ``unit`` is ``None``, passing a ``Quantity`` as value raises
``DimensionError``.
"""
if unit is None:
if isinstance(value, Quantity):
raise DimensionError
else:
return value
else:
if isinstance(value, Quantity):
if value.unit != unit:
raise DimensionError
else:
return value.amount
else:
return value

View File

@ -6,7 +6,6 @@ import traceback
import time import time
from artiq.protocols import pyon from artiq.protocols import pyon
from artiq.language.units import strip_unit
from artiq.tools import (asyncio_process_wait_timeout, asyncio_process_wait, from artiq.tools import (asyncio_process_wait_timeout, asyncio_process_wait,
asyncio_wait_or_cancel) asyncio_wait_or_cancel)
@ -50,7 +49,7 @@ class Worker:
avail = set(range(n_user_watchdogs + 1)) \ avail = set(range(n_user_watchdogs + 1)) \
- set(self.watchdogs.keys()) - set(self.watchdogs.keys())
wid = next(iter(avail)) wid = next(iter(avail))
self.watchdogs[wid] = time.monotonic() + strip_unit(t, "s") self.watchdogs[wid] = time.monotonic() + t
return wid return wid
def delete_watchdog(self, wid): def delete_watchdog(self, wid):

View File

@ -14,7 +14,6 @@ The main rationale for this new custom serializer (instead of using JSON) is
that JSON does not support Numpy and more generally cannot be extended with that JSON does not support Numpy and more generally cannot be extended with
other data types while keeping a concise syntax. Here we can use the Python other data types while keeping a concise syntax. Here we can use the Python
function call syntax to mark special data types. function call syntax to mark special data types.
""" """
@ -25,8 +24,6 @@ import tempfile
import numpy import numpy
from artiq.language.units import Quantity
_encode_map = { _encode_map = {
type(None): "none", type(None): "none",
@ -39,7 +36,6 @@ _encode_map = {
list: "list", list: "list",
dict: "dict", dict: "dict",
Fraction: "fraction", Fraction: "fraction",
Quantity: "quantity",
numpy.ndarray: "nparray" numpy.ndarray: "nparray"
} }
@ -110,10 +106,6 @@ class _Encoder:
return "Fraction({}, {})".format(encode(x.numerator), return "Fraction({}, {})".format(encode(x.numerator),
encode(x.denominator)) encode(x.denominator))
def encode_quantity(self, x):
return "Quantity({}, {})".format(encode(x.amount),
encode(x.unit))
def encode_nparray(self, x): def encode_nparray(self, x):
r = "nparray(" r = "nparray("
r += encode(x.shape) + ", " r += encode(x.shape) + ", "
@ -147,7 +139,6 @@ _eval_dict = {
"true": True, "true": True,
"Fraction": Fraction, "Fraction": Fraction,
"Quantity": Quantity,
"nparray": _nparray "nparray": _nparray
} }

View File

@ -4,7 +4,6 @@ import os
from fractions import Fraction from fractions import Fraction
from artiq import * from artiq import *
from artiq.language.units import DimensionError
from artiq.coredevice import comm_tcp, core, runtime_exceptions, ttl from artiq.coredevice import comm_tcp, core, runtime_exceptions, ttl
from artiq.sim import devices as sim_devices from artiq.sim import devices as sim_devices
@ -51,7 +50,6 @@ class _Primes(AutoDB):
class _Misc(AutoDB): class _Misc(AutoDB):
def build(self): def build(self):
self.input = 84 self.input = 84
self.inhomogeneous_units = []
self.al = [1, 2, 3, 4, 5] self.al = [1, 2, 3, 4, 5]
self.list_copy_in = [2*Hz, 10*MHz] self.list_copy_in = [2*Hz, 10*MHz]
@ -59,29 +57,10 @@ class _Misc(AutoDB):
def run(self): def run(self):
self.half_input = self.input//2 self.half_input = self.input//2
self.decimal_fraction = Fraction("1.2") self.decimal_fraction = Fraction("1.2")
self.inhomogeneous_units.append(1000*Hz)
self.inhomogeneous_units.append(10*s)
self.acc = 0 self.acc = 0
for i in range(len(self.al)): for i in range(len(self.al)):
self.acc += self.al[i] self.acc += self.al[i]
self.list_copy_out = self.list_copy_in self.list_copy_out = self.list_copy_in
self.unit_comp = [1*MHz for _ in range(3)]
@kernel
def dimension_error1(self):
print(1*Hz + 1*s)
@kernel
def dimension_error2(self):
print(1*Hz < 1*s)
@kernel
def dimension_error3(self):
check_unit(1*Hz, "s")
@kernel
def dimension_error4(self):
delay(10*Hz)
class _PulseLogger(AutoDB): class _PulseLogger(AutoDB):
@ -103,9 +82,9 @@ class _PulseLogger(AutoDB):
@kernel @kernel
def pulse(self, f, duration): def pulse(self, f, duration):
self.on(int(now().amount*1000000000), f) self.on(int(now()*1000000000), f)
delay(duration) delay(duration)
self.off(int(now().amount*1000000000)) self.off(int(now()*1000000000))
class _Pulses(AutoDB): class _Pulses(AutoDB):
@ -229,18 +208,8 @@ class ExecutionCase(unittest.TestCase):
uut.run() uut.run()
self.assertEqual(uut.half_input, 42) self.assertEqual(uut.half_input, 42)
self.assertEqual(uut.decimal_fraction, Fraction("1.2")) self.assertEqual(uut.decimal_fraction, Fraction("1.2"))
self.assertEqual(uut.inhomogeneous_units, [1000*Hz, 10*s])
self.assertEqual(uut.acc, sum(uut.al)) self.assertEqual(uut.acc, sum(uut.al))
self.assertEqual(uut.list_copy_in, uut.list_copy_out) self.assertEqual(uut.list_copy_in, uut.list_copy_out)
self.assertEqual(uut.unit_comp, [1*MHz for _ in range(3)])
with self.assertRaises(DimensionError):
uut.dimension_error1()
with self.assertRaises(DimensionError):
uut.dimension_error2()
with self.assertRaises(DimensionError):
uut.dimension_error3()
with self.assertRaises(DimensionError):
uut.dimension_error4()
finally: finally:
comm.close() comm.close()

View File

@ -10,9 +10,9 @@ lda_serial = os.getenv("ARTIQ_LDA_SERIAL")
class GenericLdaTest: class GenericLdaTest:
def test_attenuation(self): def test_attenuation(self):
step = self.cont.get_att_step_size().amount step = self.cont.get_att_step_size()
max = self.cont.get_att_max().amount attmax = self.cont.get_att_max()
test_vector = [i*step*dB for i in range(0, int(max*int(1/step)+1))] test_vector = [i*step*dB for i in range(0, int(attmax*int(1/step)+1))]
for i in test_vector: for i in test_vector:
with self.subTest(i=i): with self.subTest(i=i):
self.cont.set_attenuation(i) self.cont.set_attenuation(i)

View File

@ -88,7 +88,7 @@ class GenericTpzTest:
def test_ouput_volts(self): def test_ouput_volts(self):
for voltage in 5*V, 10*V, 15*V, \ for voltage in 5*V, 10*V, 15*V, \
round(self.cont.get_tpz_io_settings()[0].amount)*V: round(self.cont.get_tpz_io_settings()[0])*V:
with self.subTest(voltage=voltage): with self.subTest(voltage=voltage):
test_vector = voltage test_vector = voltage
self.cont.set_output_volts(test_vector) self.cont.set_output_volts(test_vector)

View File

@ -6,21 +6,13 @@ from artiq.coredevice import comm_dummy, core
from artiq.transforms.unparse import unparse from artiq.transforms.unparse import unparse
# Original code before inline:
#
# n = time_to_cycles(1.2345*ns)
# ftw = self.dds.frequency_to_ftw(345*MHz)
# f = self.dds.ftw_to_frequency(ftw)
# phi = 1000*cycles_to_time(n)*f
# do_someting(int(phi))
#
optimize_in = """ optimize_in = """
def run(): def run():
dds_sysclk = Quantity(Fraction(1000000000, 1), 'Hz') dds_sysclk = Fraction(1000000000, 1)
n = time_to_cycles((1.2345 * Quantity(Fraction(1, 1000000000), 's'))) n = time_to_cycles((1.2345 * Fraction(1, 1000000000)))
with sequential: with sequential:
frequency = (345 * Quantity(Fraction(1000000, 1), 'Hz')) frequency = 345 * Fraction(1000000, 1)
frequency_to_ftw_return = int((((2 ** 32) * frequency) / dds_sysclk)) frequency_to_ftw_return = int((((2 ** 32) * frequency) / dds_sysclk))
ftw = frequency_to_ftw_return ftw = frequency_to_ftw_return
with sequential: with sequential:

View File

@ -1,190 +0,0 @@
import ast
from collections import defaultdict
from copy import copy
from artiq.language import units
from artiq.transforms.tools import embeddable_func_names
def _add_units(f, unit_list):
def wrapper(*args):
new_args = []
for arg, unit in zip(args, unit_list):
if unit is None:
new_args.append(arg)
else:
if isinstance(arg, list):
new_args.append([units.Quantity(x, unit) for x in arg])
else:
new_args.append(units.Quantity(arg, unit))
return f(*new_args)
return wrapper
class _UnitsLowerer(ast.NodeTransformer):
def __init__(self, rpc_map):
self.rpc_map = rpc_map
# (original rpc number, (unit list)) -> new rpc number
self.rpc_remap = defaultdict(lambda: len(self.rpc_remap))
self.variable_units = dict()
def visit_Name(self, node):
try:
unit = self.variable_units[node.id]
except KeyError:
pass
else:
if unit is not None:
node.unit = unit
return node
def visit_BoolOp(self, node):
self.generic_visit(node)
us = [getattr(value, "unit", None) for value in node.values]
if not all(u == us[0] for u in us[1:]):
raise units.DimensionError
return node
def visit_Compare(self, node):
self.generic_visit(node)
u0 = getattr(node.left, "unit", None)
us = [getattr(comparator, "unit", None)
for comparator in node.comparators]
if not all(u == u0 for u in us):
raise units.DimensionError
return node
def visit_UnaryOp(self, node):
self.generic_visit(node)
if hasattr(node.operand, "unit"):
node.unit = node.operand.unit
return node
def visit_BinOp(self, node):
self.generic_visit(node)
op = type(node.op)
left_unit = getattr(node.left, "unit", None)
right_unit = getattr(node.right, "unit", None)
if op in (ast.Add, ast.Sub, ast.Mod):
unit = units.addsub_dimension(left_unit, right_unit)
elif op == ast.Mult:
unit = units.mul_dimension(left_unit, right_unit)
elif op in (ast.Div, ast.FloorDiv):
unit = units.div_dimension(left_unit, right_unit)
else:
if left_unit is not None or right_unit is not None:
raise units.DimensionError
unit = None
if unit is not None:
node.unit = unit
return node
def visit_Attribute(self, node):
self.generic_visit(node)
if node.attr == "amount" and hasattr(node.value, "unit"):
del node.value.unit
return node.value
else:
return node
def visit_List(self, node):
self.generic_visit(node)
if node.elts:
us = [getattr(elt, "unit", None) for elt in node.elts]
if not all(u == us[0] for u in us[1:]):
raise units.DimensionError
node.unit = us[0]
return node
def visit_ListComp(self, node):
self.generic_visit(node)
if hasattr(node.elt, "unit"):
node.unit = node.elt.unit
return node
def visit_Call(self, node):
self.generic_visit(node)
if node.func.id == "Quantity":
amount, unit = node.args
amount.unit = unit.s
return amount
elif node.func.id in ("now", "cycles_to_time"):
node.unit = "s"
elif node.func.id == "syscall":
# only RPCs can have units
if node.args[0].s == "rpc":
unit_list = tuple(getattr(arg, "unit", None)
for arg in node.args[2:])
rpc_n = node.args[1].n
node.args[1].n = self.rpc_remap[(rpc_n, (unit_list))]
else:
if any(hasattr(arg, "unit") for arg in node.args):
raise units.DimensionError
elif node.func.id in ("delay", "at", "time_to_cycles", "watchdog"):
if getattr(node.args[0], "unit", None) != "s":
raise units.DimensionError
elif node.func.id == "check_unit":
self.generic_visit(node)
elif node.func.id in embeddable_func_names:
# must be last (some embeddable funcs may have units)
if any(hasattr(arg, "unit") for arg in node.args):
raise units.DimensionError
return node
def visit_Expr(self, node):
self.generic_visit(node)
if (isinstance(node.value, ast.Call)
and node.value.func.id == "check_unit"):
call = node.value
if (isinstance(call.args[1], ast.NameConstant)
and call.args[1].value is None):
if hasattr(call.value.args[0], "unit"):
raise units.DimensionError
elif isinstance(call.args[1], ast.Str):
if getattr(call.args[0], "unit", None) != call.args[1].s:
raise units.DimensionError
else:
raise NotImplementedError
return None
else:
return node
def _update_target(self, target, unit):
if isinstance(target, ast.Name):
if target.id in self.variable_units:
if self.variable_units[target.id] != unit:
raise TypeError(
"Inconsistent units for variable '{}': '{}' and '{}'"
.format(target.id,
self.variable_units[target.id],
unit))
else:
self.variable_units[target.id] = unit
def visit_Assign(self, node):
node.value = self.visit(node.value)
unit = getattr(node.value, "unit", None)
for target in node.targets:
self._update_target(target, unit)
return node
def visit_AugAssign(self, node):
value = self.visit_BinOp(ast.BinOp(
op=node.op, left=node.target, right=node.value))
unit = getattr(value, "unit", None)
self._update_target(node.target, unit)
return node
# Only dimensionless iterators are supported
def visit_For(self, node):
self.generic_visit(node)
self._update_target(node.target, None)
return node
def lower_units(func_def, rpc_map):
ul = _UnitsLowerer(rpc_map)
ul.visit(func_def)
original_map = copy(rpc_map)
for (original_rpcn, unit_list), new_rpcn in ul.rpc_remap.items():
rpc_map[new_rpcn] = _add_units(original_map[original_rpcn], unit_list)

View File

@ -11,7 +11,7 @@ embeddable_funcs = (
core_language.syscall, core_language.watchdog, core_language.syscall, core_language.watchdog,
range, bool, int, float, round, len, range, bool, int, float, round, len,
core_language.int64, core_language.round64, core_language.int64, core_language.round64,
Fraction, units.Quantity, units.check_unit, core_language.EncodedException Fraction, core_language.EncodedException
) )
embeddable_func_names = {func.__name__ for func in embeddable_funcs} embeddable_func_names = {func.__name__ for func in embeddable_funcs}
@ -61,11 +61,6 @@ def value_to_ast(value):
for kg in core_language.kernel_globals: for kg in core_language.kernel_globals:
if value is getattr(core_language, kg): if value is getattr(core_language, kg):
return ast.Name(kg, ast.Load()) return ast.Name(kg, ast.Load())
if isinstance(value, units.Quantity):
return ast.Call(
func=ast.Name("Quantity", ast.Load()),
args=[value_to_ast(value.amount), ast.Str(value.unit)],
keywords=[], starargs=None, kwargs=None)
raise NotASTRepresentable(str(value)) raise NotASTRepresentable(str(value))
@ -88,14 +83,6 @@ def eval_constant(node):
numerator = eval_constant(node.args[0]) numerator = eval_constant(node.args[0])
denominator = eval_constant(node.args[1]) denominator = eval_constant(node.args[1])
return Fraction(numerator, denominator) return Fraction(numerator, denominator)
elif funcname == "Quantity":
amount, unit = node.args
amount = eval_constant(amount)
try:
unit = getattr(units, unit.id)
except:
raise NotConstant
return units.Quantity(amount, unit)
else: else:
raise NotConstant raise NotConstant
else: else:
@ -105,8 +92,7 @@ def eval_constant(node):
_replaceable_funcs = { _replaceable_funcs = {
"bool", "int", "float", "round", "bool", "int", "float", "round",
"int64", "round64", "Fraction", "int64", "round64", "Fraction",
"time_to_cycles", "cycles_to_time", "time_to_cycles", "cycles_to_time"
"Quantity"
} }

View File

@ -22,7 +22,7 @@ class RPCTiming(Experiment, AutoDB):
t1 = self.core.get_rtio_time() t1 = self.core.get_rtio_time()
self.nop(10) self.nop(10)
t2 = self.core.get_rtio_time() t2 = self.core.get_rtio_time()
self.ts[i] = float(t2.amount - t1.amount) self.ts[i] = t2 - t1
def run(self): def run(self):
self.bench() self.bench()

View File

@ -60,22 +60,19 @@
"type": "local", "type": "local",
"module": "artiq.coredevice.dds", "module": "artiq.coredevice.dds",
"class": "DDS", "class": "DDS",
"arguments": {"sysclk": Quantity(Fraction(1000000000, 1), "Hz"), "arguments": {"sysclk": 1e9, "channel": 0}
"channel": 0}
}, },
"dds1": { "dds1": {
"type": "local", "type": "local",
"module": "artiq.coredevice.dds", "module": "artiq.coredevice.dds",
"class": "DDS", "class": "DDS",
"arguments": {"sysclk": Quantity(Fraction(1000000000, 1), "Hz"), "arguments": {"sysclk": 1e9, "channel": 1}
"channel": 1}
}, },
"dds2": { "dds2": {
"type": "local", "type": "local",
"module": "artiq.coredevice.dds", "module": "artiq.coredevice.dds",
"class": "DDS", "class": "DDS",
"arguments": {"sysclk": Quantity(Fraction(1000000000, 1), "Hz"), "arguments": {"sysclk": 1e9, "channel": 2}
"channel": 2}
}, },
"qc_q1_0": { "qc_q1_0": {