231 lines
7.9 KiB
Python
231 lines
7.9 KiB
Python
"""
|
|
Dimensional Values
|
|
==================
|
|
|
|
Dimensional values are values of a python type ``int`` or ``float`` (or ``complex``).
|
|
The dimension is always of type ``units.dimensional_values.Dimension``.
|
|
|
|
The dimension can be assigned using the ``@`` operator.
|
|
"""
|
|
|
|
from .errors import *
|
|
|
|
class DimensionalValue(object):
|
|
__slots__ = "value", "dimension"
|
|
def __init__(self, value, dimension):
|
|
self.value = value
|
|
self.dimension = dimension
|
|
|
|
@staticmethod
|
|
def make(value, dimension):
|
|
if(dimension.is_scalar()):
|
|
return value * dimension.scale.to_scalar()
|
|
return DimensionalValue(value, dimension)
|
|
|
|
def _typecheck_add(self, other):
|
|
if(isinstance(other, (float, int, complex))):
|
|
raise DimensionalError("cannot add dimensional value {} to value without dimension {}".format(repr(self),
|
|
repr(other)))
|
|
if(isinstance(other, DimensionalValue)):
|
|
if(not self.dimension.can_rescale_to(other.dimension)):
|
|
raise DimensionalError("incompatible dimension: {} and {}".format(repr(self.dimension),
|
|
repr(other.dimension)))
|
|
else:
|
|
return
|
|
raise ValueError("cannot add DimensionalValue and {}".format(type(other)))
|
|
|
|
def _typecheck_mul(self, other):
|
|
if(not isinstance(other, (float, int, complex, DimensionalValue))):
|
|
raise ValueError("cannot mul DimensionalValue and {}".format(type(other)))
|
|
|
|
def __add__(self, other):
|
|
self._typecheck_add(other)
|
|
return DimensionalValue.make(self.value + other.value, self.dimension)
|
|
def __mul__(self, other):
|
|
self._typecheck_mul(other)
|
|
if(isinstance(other, DimensionalValue)):
|
|
value, dimension = self.dimension.auto_rescale_and_mul(self.value, other.value, other.dimension)
|
|
return DimensionalValue.make(value, dimension)
|
|
|
|
return DimensionalValue.make(self.value * other, self.dimension)
|
|
|
|
def __pow__(self, other):
|
|
if(not isinstance(other, (float, int, complex))):
|
|
raise ValueError("cannot raise DimensionalValue to non-scalar power")
|
|
|
|
if(isinstance(other, int)):
|
|
# scalings can be raisen to integers
|
|
return DimensionalValue.make(self.value ** other, self.dimension ** other)
|
|
if(isinstance(other, (float, complex))):
|
|
# scalings cannot be raisen to non-integers
|
|
value, dimension = self.dimension.rescale_to_base(self.value)
|
|
return DimensionalValue.make(value ** other, dimension ** other)
|
|
|
|
def __radd__(self, other):
|
|
self._typecheck_add(other)
|
|
return DimensionalValue.make(other.value + self.value , self.dimension)
|
|
def __rmul__(self, other):
|
|
self._typecheck_mul(other)
|
|
if(isinstance(other, DimensionalValue)):
|
|
value, dimension = self.dimension.auto_rescale_and_mul(self.value, other.value, other.dimension)
|
|
return DimensionalValue.make(value, dimension)
|
|
|
|
return DimensionalValue.make(self.value * other, self.dimension)
|
|
|
|
def __truediv__(self, other):
|
|
self._typecheck_mul(other)
|
|
if(isinstance(other, (float, int, complex))):
|
|
return DimensionalValue.make(self.value / other, self.dimension)
|
|
value, dimension = self.dimension.auto_rescale_and_mul(self.value, 1 / other.value, other.dimension ** -1)
|
|
return DimensionalValue.make(value, dimension)
|
|
def __rtruediv__(self, other):
|
|
self._typecheck_mul(other)
|
|
if(isinstance(other, (float, int, complex))):
|
|
return DimensionalValue.make(other / self.value, self.dimension ** -1)
|
|
value, dimension = other.dimension.auto_rescale_and_mul(other.value, 1 / self.value, self.dimension ** -1)
|
|
return DimensionalValue.make(value, dimension)
|
|
|
|
|
|
def __sub__(self, other):
|
|
self._typecheck_add(other)
|
|
return DimensionalValue.make(self.value - other.value, self.dimension)
|
|
def __sub__(self, other):
|
|
self._typecheck_add(other)
|
|
return DimensionalValue.make(other.value - self.value , self.dimension)
|
|
|
|
def __repr__(self):
|
|
return "{} * {}".format(repr(self.value), repr(self.dimension))
|
|
|
|
|
|
class Dimension(object):
|
|
__slots__ = "scale", "unit"
|
|
"""
|
|
class for representing dimensions
|
|
"""
|
|
def __init__(self, scale, unit):
|
|
self.scale = scale
|
|
self.unit = unit
|
|
|
|
def can_rescale_to(self, other):
|
|
"""
|
|
check if the dimensions are compatible
|
|
"""
|
|
if(not self.unit == other.unit):
|
|
return False
|
|
return True
|
|
|
|
def rescale(self, other):
|
|
"""
|
|
Rescale the ``DimensionalValue`` ``other`` to this
|
|
dimension.
|
|
"""
|
|
rescale = other.dimension.scale / self.scale
|
|
return DimensionalValue.make(other.value * rescale, other.dimension)
|
|
|
|
def auto_rescale_and_mul(self, v1, v2, other):
|
|
dummy = Dimension(Scaling(1, 1), self.unit)
|
|
v2 = dummy.rescale(DimensionalValue.make(v2, other)).value
|
|
v1 = dummy.rescale(DimensionalValue.make(v1, self)).value
|
|
|
|
return v1 * v2, Dimension(Scaling(1, 1), self.unit * other.unit)
|
|
|
|
def __mul__(self, other):
|
|
if(not isinstance(other, Dimension)):
|
|
raise TypeError("Cannot __mul__ Dimension and {}".format(type(other)))
|
|
return Dimension(self.scale * other.scale, self.unit * other.unit)
|
|
def __matmul__(self, other):
|
|
# FIXME:
|
|
# maybe turn this off.
|
|
# I don't like it :-(
|
|
if(isinstance(other, DimensionalValue)):
|
|
if(not self.can_rescale_to(other.dimension)):
|
|
raise DimensionalError("Cannot rescale {} to {}".format(repr(other), repr(self)))
|
|
return self.rescale(other).value
|
|
if(isinstance(other, (float, int, complex))):
|
|
return DimensionalValue.make(other, self)
|
|
raise ValueError("Cannot __matmul__ Dimensional and {}".format(type(other)))
|
|
def __rmatmul__(self, other):
|
|
if(isinstance(other, DimensionalValue)):
|
|
if(not self.can_rescale_to(other.dimension)):
|
|
raise DimensionalError("Cannot rescale {} to {}".format(repr(other), repr(self)))
|
|
return self.rescale(other).value
|
|
if(isinstance(other, (float, int, complex))):
|
|
return DimensionalValue.make(other, self)
|
|
raise ValueError("Cannot __matmul__ Dimension and {}".format(type(other)))
|
|
|
|
def __pow__(self, other):
|
|
if(not isinstance(other, (float, int, complex))):
|
|
raise ValueError("cannot raise Dimensional to non-scalar power")
|
|
return Dimension(self.scale ** other, self.unit ** other)
|
|
|
|
def __repr__(self):
|
|
return "{}@({})".format(repr(self.scale), repr(self.unit))
|
|
|
|
def __rrshift__(self, other):
|
|
print(__file__, ":", type(self), ":", "__rrshift__({}, {})".format(self, other))
|
|
if(not isinstance(other, DimensionalValue)):
|
|
raise TypeError("cannot shift non-DimensionalValue to Dimension")
|
|
|
|
other = self.rescale(other)
|
|
print(other.dimension.unit, self.unit)
|
|
unit = other.dimension.unit >> self.unit
|
|
return DimensionalValue.make(other.value, Dimension(other.dimension.scale, unit))
|
|
|
|
def is_scalar(self):
|
|
return self.unit.is_scalar()
|
|
# def __eq__(self, other):
|
|
# if(not isinstance(other, Dimension)):
|
|
# raise TypeError("cannot compare Dimension and {}".format(type(other)))
|
|
#
|
|
|
|
|
|
class Scaling(object):
|
|
__slots__ = "value", "exponent"
|
|
def __init__(self, value, exponent):
|
|
self.value = value
|
|
self.exponent = exponent
|
|
|
|
def __repr__(self):
|
|
if(self.value == 1 or self.exponent == 0):
|
|
return "1"
|
|
return "{}**{}".format(self.value, self.exponent)
|
|
|
|
|
|
def __pow__(self, other):
|
|
# this is actually kind a dirty:
|
|
# Scalings must not be raisen to non-integers,
|
|
# so just rescale the total dimensional value to the
|
|
# base dimension (scale = 1), so now we can ignore powers.
|
|
if(self.value == 1):
|
|
return Scaling(1, 1)
|
|
return Scaling(self.value , self.exponent * other)
|
|
|
|
def __truediv__(self, other):
|
|
"""
|
|
Return the scalar value that is required
|
|
to rescale the other dimension to this dimension.
|
|
"""
|
|
|
|
if(not isinstance(other, Scaling)):
|
|
raise TypeError("Cannot truedivide Scaling by {}".format(type(other)))
|
|
|
|
# Try to do this as elegant as possible:
|
|
# use as much integer math as possible.
|
|
if(other.value == self.value):
|
|
return self.value ** (self.exponent - other.exponent)
|
|
|
|
return self.value ** self.exponent / other.value ** other.exponent
|
|
|
|
def __mul__(self, other):
|
|
if(not isinstance(other, Scaling)):
|
|
raise TypeError("Cannot __mul__ Scaling and {}".format(type(other)))
|
|
if(self.value == other.value):
|
|
return Scaling(self.value, self.exponent + other.exponent)
|
|
return Scaling(self.value ** self.exponent + other.value ** other.exponent, 1)
|
|
|
|
def to_scalar(self):
|
|
return self.value ** self.exponent
|
|
|
|
|
|
|