initial work is basically finished
This commit is contained in:
commit
46a30708e1
7
test.py
Normal file
7
test.py
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# coding: utf-8
|
||||||
|
from units.units import BaseUnit
|
||||||
|
m = BaseUnit("m", "meter", "length", True)
|
||||||
|
s = BaseUnit("s", "second", "time", True)
|
||||||
|
print(m * s)
|
||||||
|
print(m / s)
|
||||||
|
print((m / s)**-1)
|
36
units/SI.py
Normal file
36
units/SI.py
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
from .backend import base_unit, compound_unit
|
||||||
|
from .backend.dimensional_values import Dimension, Scaling
|
||||||
|
|
||||||
|
A = base_unit("A", "Ampere", "Current", True)
|
||||||
|
m = base_unit("m", "meter", "Length", True)
|
||||||
|
K = base_unit("K", "Kelvin", "Temperature", True)
|
||||||
|
s = base_unit("s", "second", "Time", True)
|
||||||
|
kg = base_unit("kg", "kilo grams", "Mass", True)
|
||||||
|
cd = base_unit("cd", "candela", "Luminous intensity", True)
|
||||||
|
mol = base_unit("mol", "mole", "Amount of substance", True)
|
||||||
|
|
||||||
|
cm = Dimension(Scaling(10, -2), m)
|
||||||
|
km = Dimension(Scaling(10, 3), m)
|
||||||
|
|
||||||
|
g = Dimension(Scaling(10, -3), kg)
|
||||||
|
|
||||||
|
|
||||||
|
Hz = compound_unit("Hz", "Hertz", "Frequency", [(s, -1)])
|
||||||
|
N = compound_unit("N", "Newton", "Force", [(m, 1), (kg, 1), (s, -2)])
|
||||||
|
Pa = compound_unit("Pa", "Pascal", "Pressure", [(N, 1), (m, -2)])
|
||||||
|
J = compound_unit("J", "Joule", "Energy", [(kg, 1), (m, 2), (s, -2)])
|
||||||
|
W = compound_unit("W", "Watt", "Power", [(J, 1), (s, -1)])
|
||||||
|
C = compound_unit("C", "Coulomb", "Electric charge", [(A, 1), (s, 1)])
|
||||||
|
V = compound_unit("V", "Volt", "Voltage", [(W, 1), (A, -1)])
|
||||||
|
F = compound_unit("F", "Farad", "Capacity", [(C, 1), (V, -1)])
|
||||||
|
ohm = compound_unit("omega", "Ohm", "Resistance is futile", [(V, 1), (A, -1)])
|
||||||
|
omega = ohm
|
||||||
|
S = compound_unit("S", "Siemens", "Conductance", [(ohm, -1)])
|
||||||
|
Wb = compound_unit("Wb", "Weber", "Magnetic flux", [(V, 1), (s, 1)])
|
||||||
|
T = compound_unit("T", "Tesla", "Magnetic flux density", [(Wb, 1), (m, -2)])
|
||||||
|
H = compound_unit("H", "Henry", "Inductance", [(Wb, 1), (A, -1)])
|
||||||
|
lx = compound_unit("lx", "Lux", "Illuminance", [(cd, 1), (m, -2)])
|
||||||
|
Bq = compound_unit("Bq", "Becquerel", "Radioactivity", [(s, -1)])
|
||||||
|
Gy = compound_unit("Gy", "Gray", "Absorbed dose (of ionizing radiation)", [(J, 1), (kg, -1)])
|
||||||
|
Sv = compound_unit("Sv", "Sievert", "Equivalent dose (of ionizing radiation)", [(J, 1), (kg, -1)])
|
||||||
|
kat = compound_unit("kat", "Katal", "Catalytic activity", [(mol, 1), (s, -1)])
|
0
units/__init__.py
Normal file
0
units/__init__.py
Normal file
24
units/backend/__init__.py
Normal file
24
units/backend/__init__.py
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
|
||||||
|
"""
|
||||||
|
backend package for units
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .units import BaseUnit, NamedCompoundUnit
|
||||||
|
|
||||||
|
REGISTRY = dict()
|
||||||
|
|
||||||
|
def base_unit(symbol, name, description, is_SI):
|
||||||
|
if(symbol in REGISTRY):
|
||||||
|
return REGISTRY[symbol]
|
||||||
|
unit = BaseUnit(symbol, name, description, is_SI)
|
||||||
|
REGISTRY[symbol] = unit
|
||||||
|
return unit
|
||||||
|
|
||||||
|
def compound_unit(symbol, name, description, bases):
|
||||||
|
if(symbol in REGISTRY):
|
||||||
|
return REGISTRY[symbol]
|
||||||
|
unit = NamedCompoundUnit(symbol, name, description, bases)
|
||||||
|
REGISTRY[symbol] = unit
|
||||||
|
return unit
|
||||||
|
|
||||||
|
|
230
units/backend/dimensional_values.py
Normal file
230
units/backend/dimensional_values.py
Normal file
|
@ -0,0 +1,230 @@
|
||||||
|
"""
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
|
6
units/backend/errors.py
Normal file
6
units/backend/errors.py
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
class DimensionalError(Exception):
|
||||||
|
pass
|
||||||
|
class RescalingError(Exception):
|
||||||
|
pass
|
||||||
|
class UnitError(Exception):
|
||||||
|
pass
|
290
units/backend/units.py
Normal file
290
units/backend/units.py
Normal file
|
@ -0,0 +1,290 @@
|
||||||
|
from collections import defaultdict
|
||||||
|
from .dimensional_values import Dimension, Scaling, DimensionalValue
|
||||||
|
from .errors import UnitError
|
||||||
|
|
||||||
|
class BaseUnit(object):
|
||||||
|
"""
|
||||||
|
The atomic unit type that cannot be expressed by other units
|
||||||
|
(well of course they can be expressed, but they should not)
|
||||||
|
|
||||||
|
#FIXME:
|
||||||
|
Should I compare symbols here?
|
||||||
|
"""
|
||||||
|
__slots__ = "symbol", "name", "description", "is_SI"
|
||||||
|
def __init__(self, symbol, name, description, is_SI):
|
||||||
|
self.symbol = symbol
|
||||||
|
self.name = name
|
||||||
|
self.description = description
|
||||||
|
self.is_SI = is_SI
|
||||||
|
|
||||||
|
def __mul__(self, other):
|
||||||
|
if(isinstance(other, CompoundUnit)):
|
||||||
|
return other.__rmul__(self)
|
||||||
|
if(isinstance(other, OutputUnit)):
|
||||||
|
return other.to_compound().__rmul__(self)
|
||||||
|
if(not isinstance(other, BaseUnit)):
|
||||||
|
raise TypeError("Cannot __mul__ BaseUnit and {}".format(type(other)))
|
||||||
|
return CompoundUnit([(self, 1), (other, 1)])
|
||||||
|
def __truediv__(self, other):
|
||||||
|
if(isinstance(other, CompoundUnit)):
|
||||||
|
return other.__rtruediv__(self)
|
||||||
|
if(isinstance(other, OutputUnit)):
|
||||||
|
return other.to_compound().__rtruediv__(self)
|
||||||
|
if(not isinstance(other, BaseUnit)):
|
||||||
|
raise TypeError("Cannot __mul__ BaseUnit and {}".format(type(other)))
|
||||||
|
return CompoundUnit([(self, 1), (other, -1)])
|
||||||
|
|
||||||
|
def __pow__(self, other):
|
||||||
|
if(not isinstance(other, (int, float, complex))):
|
||||||
|
raise TypeError("Cannot raise BaseUnit to non-scalar")
|
||||||
|
return CompoundUnit([(self, other)])
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return self.symbol
|
||||||
|
def __str__(self):
|
||||||
|
return self.symbol
|
||||||
|
|
||||||
|
def __matmul__(self, other):
|
||||||
|
return other @ Dimension(Scaling(1, 1), self)
|
||||||
|
def __rmatmul__(self, other):
|
||||||
|
return other @ Dimension(Scaling(1, 1), self)
|
||||||
|
|
||||||
|
def __rrshift__(self, other):
|
||||||
|
if(isinstance(other, DimensionalValue)):
|
||||||
|
return other >> Dimension(Scaling(1, 1), self)
|
||||||
|
if(isinstance(other, BaseUnit)):
|
||||||
|
if(other == self):
|
||||||
|
return self
|
||||||
|
raise UnitError("Cannot shift {} to {}".format(other, self))
|
||||||
|
if(isinstance(other, CompoundUnit)):
|
||||||
|
if(self in [base for base, exponent in other.bases]):
|
||||||
|
return OutputUnit([other / self, self])
|
||||||
|
raise UnitError("Cannot shift {} to {}".format(other, self))
|
||||||
|
|
||||||
|
if(isinstance(other, OutputUnit)):
|
||||||
|
try_here = other.components[0]
|
||||||
|
rest = other.components[1:]
|
||||||
|
return OutputUnit((try_here >> self).components.extend(rest))
|
||||||
|
raise TypeError("Cannot rightshift BaseUnit and {}".format(type(other)))
|
||||||
|
|
||||||
|
def __rshift__(self, other):
|
||||||
|
if(isinstance(other, (CompoundUnit, BaseUnit))):
|
||||||
|
return other.__rrshift__(self)
|
||||||
|
if(isinstance(other, OutputUnit)):
|
||||||
|
return other.to_compound().__rrshift__(self)
|
||||||
|
raise TypeError("Cannot rightshift BaseUnit and {}".format(type(other)))
|
||||||
|
|
||||||
|
def is_scalar(self):
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class CompoundUnit(object):
|
||||||
|
"""
|
||||||
|
A unit that can (and should) be expressed using other units.
|
||||||
|
This unit can be used as-is and can be broke down to BaseUnits.
|
||||||
|
|
||||||
|
Also it should be possible to automatically gather BaseUnits and
|
||||||
|
create CompoundUnits while multiplying.
|
||||||
|
|
||||||
|
``bases`` is a list of tuples ``[(BaseUnit, exponent)]`` where
|
||||||
|
exponent is an integer
|
||||||
|
"""
|
||||||
|
__slots__ = "is_SI", "bases"
|
||||||
|
def __check_and_split_bases(bases):
|
||||||
|
for base, exponent in bases:
|
||||||
|
if(isinstance(exponent, float) and exponent.is_integer()):
|
||||||
|
exponent = int(exponent)
|
||||||
|
if(not isinstance(exponent, int)):
|
||||||
|
raise ValueError("Unit Exponent must be integer")
|
||||||
|
if(isinstance(base, BaseUnit)):
|
||||||
|
yield base, exponent
|
||||||
|
continue
|
||||||
|
if(isinstance(base, CompoundUnit)):
|
||||||
|
yield from CompoundUnit.__check_and_split_bases(base.bases)
|
||||||
|
continue
|
||||||
|
raise TypeError("Unit bases must be BaseUnit or CompoundUnit")
|
||||||
|
|
||||||
|
def __cleanup_bases(bases):
|
||||||
|
dct = defaultdict(int)
|
||||||
|
for base, exponent in bases:
|
||||||
|
dct[base] += exponent
|
||||||
|
for k,v in dct.items():
|
||||||
|
# do not yield powers of 0
|
||||||
|
if(v):
|
||||||
|
yield (k, v)
|
||||||
|
def __init__(self, bases):
|
||||||
|
self.bases = list(CompoundUnit.__cleanup_bases(CompoundUnit.__check_and_split_bases(bases)))
|
||||||
|
self.is_SI = all([base.is_SI for base,_ in self.bases])
|
||||||
|
|
||||||
|
def split(self):
|
||||||
|
return self.bases
|
||||||
|
|
||||||
|
def __mul__(self, other):
|
||||||
|
if(isinstance(other, BaseUnit)):
|
||||||
|
return CompoundUnit(self.bases + [(other, 1)])
|
||||||
|
if(isinstance(other, CompoundUnit)):
|
||||||
|
return CompoundUnit(self.bases + other.bases)
|
||||||
|
raise TypeError("Cannot __mul__ CompoundUnit and {}".format(type(other)))
|
||||||
|
def __rmul__(self, other):
|
||||||
|
if(isinstance(other, BaseUnit)):
|
||||||
|
return CompoundUnit(self.bases + [(other, 1)])
|
||||||
|
if(isinstance(other, CompoundUnit)):
|
||||||
|
return CompoundUnit(self.bases + other.bases)
|
||||||
|
raise TypeError("Cannot __rmul__ CompoundUnit and {}".format(type(other)))
|
||||||
|
def __pow__(self, other):
|
||||||
|
if(not isinstance(other, (float, int, complex))):
|
||||||
|
raise TypeError("Cannot raise CompoundUnit to non-scalar")
|
||||||
|
return CompoundUnit([(base, other * exponent) for base, exponent in self.bases])
|
||||||
|
|
||||||
|
def __truediv__(self, other):
|
||||||
|
if(isinstance(other, BaseUnit)):
|
||||||
|
return CompoundUnit(self.bases + [(other, -1)])
|
||||||
|
if(isinstance(other, CompoundUnit)):
|
||||||
|
return CompoundUnit(self.bases + [(base, -exponent) for base, exponent in other.bases])
|
||||||
|
|
||||||
|
raise TypeError("Cannot __truediv__ CompoundUnit and {}".format(type(other)))
|
||||||
|
|
||||||
|
def __rtruediv__(self, other):
|
||||||
|
if(isinstance(other, BaseUnit)):
|
||||||
|
return CompoundUnit([(other, 1)] + [(base, -exponent) for base, exponent in self.bases])
|
||||||
|
if(isinstance(other, CompoundUnit)):
|
||||||
|
return CompoundUnit(other.bases + [(base, -exponent) for base, exponent in self.bases])
|
||||||
|
raise TypeError("Cannot __rtruediv__ CompoundUnit and {}".format(type(other)))
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "*".join(["{}**{}".format(base, exponent) for base, exponent in self.bases])
|
||||||
|
|
||||||
|
def __matmul__(self, other):
|
||||||
|
return other @ Dimension(Scaling(1, 1), self)
|
||||||
|
def __rmatmul__(self, other):
|
||||||
|
return other @ Dimension(Scaling(1, 1), self)
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if(isinstance(other, BaseUnit)):
|
||||||
|
return self.bases == [(other, 1)]
|
||||||
|
if(isinstance(other, CompoundUnit)):
|
||||||
|
return self.bases == other.bases
|
||||||
|
raise TypeError("Cannot compare CompoundUnit and {}".format(type(other)))
|
||||||
|
def ungroup(self):
|
||||||
|
"""
|
||||||
|
This is just used to implement the rightshift stuff.
|
||||||
|
"""
|
||||||
|
return CompoundUnit(self.bases)
|
||||||
|
|
||||||
|
def __rrshift__(self, other):
|
||||||
|
if(isinstance(other, DimensionalValue)):
|
||||||
|
return other >> Dimension(Scaling(1, 1), self)
|
||||||
|
|
||||||
|
if(isinstance(other, BaseUnit)):
|
||||||
|
chk = [base for base, exponent in other.bases]
|
||||||
|
if(other in chk):
|
||||||
|
return self.ungroup()
|
||||||
|
raise UnitError("Cannot shift {} to {}".format(other, self))
|
||||||
|
if(isinstance(other, CompoundUnit)):
|
||||||
|
chk = [base for base, exponent in other.bases]
|
||||||
|
for base, exponent in self.bases:
|
||||||
|
if(not base in chk):
|
||||||
|
raise UnitError("Cannot shift {} to {}".format(other, self))
|
||||||
|
|
||||||
|
return OutputUnit([(other.ungroup() / self.ungroup()), self])
|
||||||
|
if(isinstance(other, OutputUnit)):
|
||||||
|
try_here = other.components[0]
|
||||||
|
rest = other.components[1:]
|
||||||
|
return OutputUnit((try_here >> self).components.extend(rest))
|
||||||
|
|
||||||
|
raise TypeError("Cannot rightshift BaseUnit and {}".format(type(other)))
|
||||||
|
|
||||||
|
def __rshift__(self, other):
|
||||||
|
if(isinstance(other, (CompoundUnit, BaseUnit))):
|
||||||
|
return other.__rrshift__(self)
|
||||||
|
if(isinstance(other, OutputUnit)):
|
||||||
|
return other.to_compound().__rrshift__(self)
|
||||||
|
raise TypeError("Cannot rightshift CompoundUnit and {}".format(type(other)))
|
||||||
|
|
||||||
|
def is_scalar(self):
|
||||||
|
return len(self.bases) == 0
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class NamedCompoundUnit(CompoundUnit):
|
||||||
|
"""
|
||||||
|
A unit that can (and should) be expressed using other units.
|
||||||
|
This unit can be used as-is and can be broke down to BaseUnits.
|
||||||
|
|
||||||
|
Also it should be possible to automatically gather BaseUnits and
|
||||||
|
create CompoundUnits while multiplying.
|
||||||
|
|
||||||
|
``bases`` is a list of tuples ``[(BaseUnit, exponent)]`` where
|
||||||
|
exponent is an integer
|
||||||
|
"""
|
||||||
|
__slots__ = "symbol", "name", "description", "is_SI", "bases"
|
||||||
|
def __init__(self, symbol, name, description, bases):
|
||||||
|
CompoundUnit.__init__(self, bases)
|
||||||
|
self.symbol = symbol
|
||||||
|
self.name = name
|
||||||
|
self.description = description
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return self.symbol
|
||||||
|
def __str__(self):
|
||||||
|
return self.symbol
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class OutputUnit(object):
|
||||||
|
"""
|
||||||
|
Unit type for pretty output.
|
||||||
|
|
||||||
|
Will be created by __rrshift__ and others.
|
||||||
|
|
||||||
|
It has several compound units that are listed explicitly
|
||||||
|
for prettier output, if any operation is performed it will automatically
|
||||||
|
cast to a CompoundUnit.
|
||||||
|
"""
|
||||||
|
__slots__ = "components",
|
||||||
|
def __init__(self, components):
|
||||||
|
self.components = components
|
||||||
|
|
||||||
|
def to_compound(self):
|
||||||
|
bases = []
|
||||||
|
for compound in self.components:
|
||||||
|
if(isinstance(compound, BaseUnit)):
|
||||||
|
bases.append((compound, 1))
|
||||||
|
if(isinstance(compound, CompoundUnit)):
|
||||||
|
bases.extend(compound.bases)
|
||||||
|
|
||||||
|
return CompoundUnit(bases)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "*".join([repr(c) for c in self.components if c])
|
||||||
|
|
||||||
|
def __mul__(self, other):
|
||||||
|
return self.to_compound().__mul__(other)
|
||||||
|
def __rmul__(self, other):
|
||||||
|
return self.to_compound().__rmul__(other)
|
||||||
|
def __truediv__(self, other):
|
||||||
|
return self.to_compound().__truediv__(other)
|
||||||
|
def __rtruediv__(self, other):
|
||||||
|
return self.to_compound().__rtruediv__(other)
|
||||||
|
def __pow__(self, other):
|
||||||
|
return self.to_compound().__pow__(other)
|
||||||
|
def __matmul__(self, other):
|
||||||
|
return self.to_compound().__matmul__(other)
|
||||||
|
def __rmatmul__(self, other):
|
||||||
|
return self.to_compound().__rmatmul__(other)
|
||||||
|
def __eq__(self, other):
|
||||||
|
return self.to_compound().__eq__(other)
|
||||||
|
|
||||||
|
def split(self):
|
||||||
|
return self.to_compound().split()
|
||||||
|
def ungroup(self):
|
||||||
|
return self.to_compound().ungroup()
|
||||||
|
def __rrshift__(self, other):
|
||||||
|
return self.to_compound().__rrshift__(other)
|
||||||
|
|
||||||
|
def is_scalar(self):
|
||||||
|
return self.to_compound().is_scalar()
|
Loading…
Reference in New Issue
Block a user