2014-11-23 08:56:51 +08:00
|
|
|
"""
|
|
|
|
Definition and management of physical units.
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
2014-09-29 23:41:37 +08:00
|
|
|
from fractions import Fraction as _Fraction
|
2014-05-17 20:08:50 +08:00
|
|
|
|
2014-09-05 12:03:22 +08:00
|
|
|
|
2014-11-23 08:56:51 +08:00
|
|
|
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
|
2014-05-17 20:08:50 +08:00
|
|
|
|
2014-09-29 23:41:37 +08:00
|
|
|
|
2014-10-05 23:15:15 +08:00
|
|
|
def mul_dimension(l, r):
|
2014-11-23 08:56:51 +08:00
|
|
|
"""Returns the unit obtained by multiplying unit ``l`` with unit ``r``.
|
|
|
|
|
|
|
|
Raises ``DimensionError`` if the resulting unit is not implemented.
|
|
|
|
|
|
|
|
"""
|
2014-10-05 23:15:15 +08:00
|
|
|
if l is None:
|
|
|
|
return r
|
|
|
|
if r is None:
|
|
|
|
return l
|
|
|
|
if {l, r} == {"Hz", "s"}:
|
|
|
|
return None
|
2014-11-23 08:56:51 +08:00
|
|
|
raise DimensionError
|
2014-09-29 23:41:37 +08:00
|
|
|
|
2014-10-05 23:15:15 +08:00
|
|
|
|
|
|
|
def _rmul_dimension(l, r):
|
|
|
|
return mul_dimension(r, l)
|
|
|
|
|
|
|
|
|
|
|
|
def div_dimension(l, r):
|
2014-11-23 08:56:51 +08:00
|
|
|
"""Returns the unit obtained by dividing unit ``l`` with unit ``r``.
|
|
|
|
|
|
|
|
Raises ``DimensionError`` if the resulting unit is not implemented.
|
|
|
|
|
|
|
|
"""
|
2014-10-05 23:15:15 +08:00
|
|
|
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"
|
2014-11-23 08:56:51 +08:00
|
|
|
raise DimensionError
|
2014-10-05 23:15:15 +08:00
|
|
|
|
|
|
|
|
|
|
|
def _rdiv_dimension(l, r):
|
|
|
|
return div_dimension(r, l)
|
|
|
|
|
|
|
|
|
|
|
|
def addsub_dimension(x, y):
|
2014-11-23 08:56:51 +08:00
|
|
|
"""Returns the unit obtained by adding or subtracting unit ``l`` with
|
|
|
|
unit ``r``.
|
|
|
|
|
|
|
|
Raises ``DimensionError`` if ``l`` and ``r`` are different.
|
|
|
|
|
|
|
|
"""
|
2014-10-05 23:15:15 +08:00
|
|
|
if x == y:
|
|
|
|
return x
|
|
|
|
else:
|
2014-11-23 08:56:51 +08:00
|
|
|
raise DimensionError
|
|
|
|
|
|
|
|
|
|
|
|
_prefixes_str = "pnum_kMG"
|
|
|
|
_smallest_prefix = _Fraction(1, 10**12)
|
2014-10-05 23:15:15 +08:00
|
|
|
|
|
|
|
|
|
|
|
def _format(amount, unit):
|
|
|
|
if amount is NotImplemented:
|
|
|
|
return NotImplemented
|
|
|
|
if unit is None:
|
|
|
|
return amount
|
|
|
|
else:
|
|
|
|
return Quantity(amount, unit)
|
2014-09-05 12:03:22 +08:00
|
|
|
|
2014-05-17 20:08:50 +08:00
|
|
|
|
|
|
|
class Quantity:
|
2014-10-05 21:01:08 +08:00
|
|
|
"""Represents an amount in a given fundamental unit (identified by a
|
|
|
|
string).
|
2014-09-29 23:41:37 +08:00
|
|
|
|
|
|
|
The amount can be of any Python numerical type (integer, float,
|
|
|
|
Fraction, ...).
|
|
|
|
Arithmetic operations and comparisons are directly delegated to the
|
|
|
|
underlying numerical types.
|
|
|
|
|
|
|
|
"""
|
2014-09-05 12:03:22 +08:00
|
|
|
def __init__(self, amount, unit):
|
|
|
|
self.amount = amount
|
|
|
|
self.unit = unit
|
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
r_amount = self.amount
|
2014-09-29 23:41:37 +08:00
|
|
|
if isinstance(r_amount, int) or isinstance(r_amount, _Fraction):
|
2014-09-05 12:03:22 +08:00
|
|
|
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 = ""
|
2014-10-05 21:35:24 +08:00
|
|
|
return str(r_amount) + " " + prefix_str + self.unit
|
2014-09-05 12:03:22 +08:00
|
|
|
else:
|
2014-10-05 21:35:24 +08:00
|
|
|
return str(r_amount) + " " + self.unit
|
2014-09-05 12:03:22 +08:00
|
|
|
|
2015-02-16 22:48:05 +08:00
|
|
|
def __float__(self):
|
|
|
|
return float(self.amount)
|
|
|
|
|
2014-09-05 12:03:22 +08:00
|
|
|
# mul/div
|
2014-10-05 23:15:15 +08:00
|
|
|
def _binop(self, other, opf_name, dim_function):
|
|
|
|
opf = getattr(self.amount, opf_name)
|
2014-09-05 12:03:22 +08:00
|
|
|
if isinstance(other, Quantity):
|
2014-10-05 23:15:15 +08:00
|
|
|
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)
|
2014-09-05 12:03:22 +08:00
|
|
|
|
|
|
|
def __rmul__(self, other):
|
2014-10-05 23:15:15 +08:00
|
|
|
return self._binop(other, "__rmul__", _rmul_dimension)
|
2014-09-05 12:03:22 +08:00
|
|
|
|
|
|
|
def __truediv__(self, other):
|
2014-10-05 23:15:15 +08:00
|
|
|
return self._binop(other, "__truediv__", div_dimension)
|
|
|
|
|
|
|
|
def __rtruediv__(self, other):
|
|
|
|
return self._binop(other, "__rtruediv__", _rdiv_dimension)
|
2014-09-05 12:03:22 +08:00
|
|
|
|
|
|
|
def __floordiv__(self, other):
|
2014-10-05 23:15:15 +08:00
|
|
|
return self._binop(other, "__floordiv__", div_dimension)
|
|
|
|
|
|
|
|
def __rfloordiv__(self, other):
|
|
|
|
return self._binop(other, "__rfloordiv__", _rdiv_dimension)
|
2014-09-05 12:03:22 +08:00
|
|
|
|
|
|
|
# unary ops
|
|
|
|
def __neg__(self):
|
2014-10-05 23:15:15 +08:00
|
|
|
return Quantity(self.amount.__neg__(), self.unit)
|
2014-09-05 12:03:22 +08:00
|
|
|
|
|
|
|
def __pos__(self):
|
2014-10-05 23:15:15 +08:00
|
|
|
return Quantity(self.amount.__pos__(), self.unit)
|
2014-09-05 12:03:22 +08:00
|
|
|
|
2015-02-21 00:37:30 +08:00
|
|
|
def __abs__(self):
|
|
|
|
return Quantity(abs(self.amount), self.unit)
|
|
|
|
|
2014-09-05 12:03:22 +08:00
|
|
|
# add/sub
|
|
|
|
def __add__(self, other):
|
2014-10-05 23:15:15 +08:00
|
|
|
return self._binop(other, "__add__", addsub_dimension)
|
2014-09-05 12:03:22 +08:00
|
|
|
|
|
|
|
def __radd__(self, other):
|
2014-10-05 23:15:15 +08:00
|
|
|
return self._binop(other, "__radd__", addsub_dimension)
|
2014-09-05 12:03:22 +08:00
|
|
|
|
|
|
|
def __sub__(self, other):
|
2014-10-05 23:15:15 +08:00
|
|
|
return self._binop(other, "__sub__", addsub_dimension)
|
2014-09-05 12:03:22 +08:00
|
|
|
|
|
|
|
def __rsub__(self, other):
|
2014-10-05 23:15:15 +08:00
|
|
|
return self._binop(other, "__rsub__", addsub_dimension)
|
2014-09-05 12:03:22 +08:00
|
|
|
|
2014-10-06 23:26:35 +08:00
|
|
|
def __mod__(self, other):
|
|
|
|
return self._binop(other, "__mod__", addsub_dimension)
|
|
|
|
|
|
|
|
def __rmod__(self, other):
|
|
|
|
return self._binop(other, "__rmod__", addsub_dimension)
|
|
|
|
|
2014-09-05 12:03:22 +08:00
|
|
|
# comparisons
|
2014-10-05 23:15:15 +08:00
|
|
|
def _cmp(self, other, opf_name):
|
2014-11-23 08:56:51 +08:00
|
|
|
if not isinstance(other, Quantity) or other.unit != self.unit:
|
|
|
|
raise DimensionError
|
|
|
|
return getattr(self.amount, opf_name)(other.amount)
|
2014-10-05 23:15:15 +08:00
|
|
|
|
2014-09-05 12:03:22 +08:00
|
|
|
def __lt__(self, other):
|
2014-10-05 23:15:15 +08:00
|
|
|
return self._cmp(other, "__lt__")
|
2014-09-05 12:03:22 +08:00
|
|
|
|
|
|
|
def __le__(self, other):
|
2014-10-05 23:15:15 +08:00
|
|
|
return self._cmp(other, "__le__")
|
2014-09-05 12:03:22 +08:00
|
|
|
|
|
|
|
def __eq__(self, other):
|
2014-10-05 23:15:15 +08:00
|
|
|
return self._cmp(other, "__eq__")
|
2014-09-05 12:03:22 +08:00
|
|
|
|
|
|
|
def __ne__(self, other):
|
2014-10-05 23:15:15 +08:00
|
|
|
return self._cmp(other, "__ne__")
|
2014-09-05 12:03:22 +08:00
|
|
|
|
|
|
|
def __gt__(self, other):
|
2014-10-05 23:15:15 +08:00
|
|
|
return self._cmp(other, "__gt__")
|
2014-09-05 12:03:22 +08:00
|
|
|
|
|
|
|
def __ge__(self, other):
|
2014-10-05 23:15:15 +08:00
|
|
|
return self._cmp(other, "__ge__")
|
2014-09-05 12:03:22 +08:00
|
|
|
|
2014-05-17 20:08:50 +08:00
|
|
|
|
2014-10-05 21:01:08 +08:00
|
|
|
def _register_unit(unit, prefixes):
|
2014-09-05 12:03:22 +08:00
|
|
|
amount = _smallest_prefix
|
|
|
|
for prefix in _prefixes_str:
|
|
|
|
if prefix in prefixes:
|
|
|
|
quantity = Quantity(amount, unit)
|
2014-10-05 21:01:08 +08:00
|
|
|
full_name = prefix + unit if prefix != "_" else unit
|
2014-09-05 12:03:22 +08:00
|
|
|
globals()[full_name] = quantity
|
|
|
|
amount *= 1000
|
2014-05-17 20:08:50 +08:00
|
|
|
|
2014-08-13 17:52:01 +08:00
|
|
|
_register_unit("s", "pnum_")
|
|
|
|
_register_unit("Hz", "_kMG")
|
2015-01-09 22:09:17 +08:00
|
|
|
_register_unit("dB", "_")
|
2014-11-23 08:56:51 +08:00
|
|
|
|
|
|
|
|
|
|
|
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
|