Source code for scuq.units

## \file units.py
#  \brief This file is evaluated whenever the units module is loaded.
#
#  It loads the classes necessary to operate the units module and
#  performs some global initialization.
#  \author <a href="http://thomas.reidemeister.org/" target="_blank">
#          Thomas Reidemeister</a>

## \namespace scuq::units
# \brief This namespace contains the classes and constants to model
#        units.

## \defgroup units Units Module
#
# This module contains the classes necessary to define, handle, and
# work with physical units and dimensions.
# \author <a href="http://thomas.reidemeister.org/" target="_blank">
#         Thomas Reidemeister</a>
# \addtogroup units
# @{

# standard module
# import operator
import numbers
import warnings

# local modules
import scuq.arithmetic as arithmetic
import scuq.operators as operators
import scuq.qexceptions as qexceptions


def _require_physical_model(value, name="physicalModel"):
    if not isinstance(value, PhysicalModel):
        raise TypeError("%s must be a PhysicalModel" % name)


def _require_symbol(value, name="symbol"):
    if not isinstance(value, str):
        raise TypeError("%s must be a string" % name)
    if len(value) == 0:
        raise ValueError("%s must not be empty" % name)


def _require_dimension(value, name="dimension"):
    if not isinstance(value, Dimension):
        raise TypeError("%s must be a Dimension" % name)


def _require_unit(value, name="unit"):
    if not isinstance(value, Unit):
        raise TypeError("%s must be a Unit" % name)


def _require_unit_or_number(value, name="value"):
    if not isinstance(value, Unit) and not isinstance(value, numbers.Number):
        raise TypeError("%s must be a Unit or number" % name)


def _require_int(value, name="value"):
    if not isinstance(value, int):
        raise TypeError("%s must be an int" % name)


def _require_product_index(elements, index):
    _require_int(index, "index")
    if index < 0:
        raise IndexError("index must be greater than or equal to zero")
    if index >= len(elements):
        raise IndexError("index out of range")


def _deprecated_python2_method(name, replacement):
    warnings.warn(
        "%s is deprecated and kept only for Python 2 compatibility; use %s instead"
        % (name, replacement),
        DeprecationWarning,
        stacklevel=2,
    )


[docs] class PhysicalModel(object): """This class models the abstract interface for physical models. This class provides an interface for defining physical models. override it in order to get any effect. """
[docs] def __init__(self): """This is the default constructor.""" raise NotImplementedError
[docs] def get_dimension(self, unit): """Get the pysical dimension that corresponds to the given unit. :param unit: to check the dimension for. :returns: The corresponding physical dimension. """ raise NotImplementedError
[docs] def set_default_model(physicalModel): """Set the default physical model to use. :param physicalModel: The physical model to use. """ _require_physical_model(physicalModel) _UNITS_MANAGER.set_model(physicalModel)
[docs] def get_default_model(): """Get the physical model currently in use. This function returns None, if no model is currently in use. :returns: The physical model that is currently in use. """ return _UNITS_MANAGER.get_model()
[docs] class Dimension(object): """This class provides an interface to model physical dimensions. In order to distinguish between different dimensions, we add an unique symbol to each dimension. In order to aviod confusion, the symbols for physical dimensions must not interfer with the symbols used for base units and alternate units. the any phyiscal dimension correctly. """ ## A pseudo unit representing the physical unit. # All Operations that can be performed on units are also # applicable to Dimensions. Therefore each dimension is # represented internally by a pseudo Unit. _pseudoUnit = None
[docs] def __init__(self, value): """This is the default constructor. This function creates a physical dimension. :param value: An unique symbol that is used to model the dimension. exists in the dictionary of units. """ # Because physical dimensions could also be created using operations, # the pseudo unit can also be assigned. if isinstance(value, Unit): self._pseudoUnit = value else: _require_symbol(value, "value") self._pseudoUnit = BaseUnit(value)
[docs] def __str__(self): """Return a string describing the physical dimension. The physical dimensions are described in the same way as units. :returns: A string describing this dimension. """ return str(self._pseudoUnit)
[docs] def __mul__(self, other): """Return a dimension that describes the product of the current and another dimension. :param other: Another instance of a Dimension. :returns: A new dimension representing the product. """ _require_dimension(other, "other") return Dimension(self._pseudoUnit * other._pseudoUnit)
[docs] def __truediv__(self, other): """Return a dimension that describes the fraction of the current and another dimension. :param other: Another instance of a Dimension. :returns: A new dimension representing the fraction. """ _require_dimension(other, "other") return Dimension(self._pseudoUnit // other._pseudoUnit)
[docs] def __pow__(self, value): """Return a dimension that represents the current dimension raised to an integer power. :param value: An integer to be used as power. :returns: A new dimension representing the power. """ _require_int(value) value = int(value) return Dimension(self._pseudoUnit**value)
[docs] def root(self, value): """Return a dimension that represents the root of the current dimension. :param value: An integer to be used as root. :returns: A new dimension representing the root. """ _require_int(value) value = int(value) return Dimension(self._pseudoUnit.root(value))
[docs] def __eq__(self, other): """This function checks if two dimensions are equal. :param other: Another dimension. :returns: True, if the dimensions are equal. """ if not isinstance(other, Dimension): return False return self._pseudoUnit == other._pseudoUnit
[docs] def get_symbol(self): """Same as __eq__""" return self.__str__()
[docs] def __getstate__(self): """Serialization using pickle. :returns: A string that represents the serialized form of this instance. """ return self._pseudoUnit
[docs] def __setstate__(self, state): """Deserialization using pickle. :param state: The state of the object. """ self._pseudoUnit = state
[docs] class UnitsManager(object): """This manages the alternate and base units as well as the physical dimensions. """ ## Physical Model used for units. _physicalModel = None ## Dictionary of BaseUnits and AlternateUnits created. # # It maps the symbol from the respective base or alternate unit # to an instance of the BaseUnit created. _unitsDictionary = {None: None}
[docs] def __init__(self): """This is the default constructor.""" self._unitsDictionary.clear()
[docs] def addUnit(self, unit): """This is a helper function to add the units to the dictionary. :param unit: An instance of an BaseUnit or AlternateUnit to add. exists in the dictionary of units. """ if not isinstance(unit, BaseUnit) and not isinstance(unit, AlternateUnit): raise TypeError("unit must be a BaseUnit or AlternateUnit") if self.existsUnit(unit): raise qexceptions.UnitExistsException( unit, "The following base unit has already been defined" ) self._unitsDictionary[unit.get_symbol()] = unit
[docs] def existsUnit(self, unit): """Check if a BaseUnit, AlternateUnit or Dimension is already contained. This function checks only for the existence of a Symbol. So that no Symbols of units and dimensions are defined twice. :param unit: An instance of a BaseUnit, AlternateUnit or Dimension to be checked for. :returns: True, if the Symbol of the unit/dimension already existed. False, otherwise. """ if not ( isinstance(unit, BaseUnit) or isinstance(unit, AlternateUnit) or isinstance(unit, Dimension) ): raise TypeError("unit must be a BaseUnit, AlternateUnit, or Dimension") # print unit.get_symbol(), repr(unit.get_symbol()) # print self._unitsDictionary # print self._unitsDictionary.has_key( unit.get_symbol() ) return unit.get_symbol() in self._unitsDictionary
[docs] def set_model(self, physicalModel): """Set the global physical model to be used. :param physicalModel: The model to be used. """ _require_physical_model(physicalModel) self._physicalModel = physicalModel
[docs] def get_model(self): """Return the global physical model used. :returns: The current physical model used. """ return self._physicalModel
[docs] class Unit(object): """An abstract class to model physical units. This class provides an interface to model physical units. """
[docs] def __eq__(self, other): """Check for if two units are equal. :param other: Another instance of a Unit or its subclasses. :returns: True, if this unit and the argument are equal. """ raise NotImplementedError
[docs] def __ne__(self, other): """Check for if two units are unequal. :param other: Another instance of a Unit or its subclasses. :returns: True, if this unit and the argument are unequal. """ return not (self == other)
[docs] def get_system_unit(self): """Get the corresponding system unit. The physical model is used to determine the mapping to the system unit. :returns: The system unit. calling Unit.get_system_unit has no effect. """ raise NotImplementedError
[docs] def get_dimension(self): """Get the corresponding physical dimension. :returns: The corresponding physical dimension. silblings of Unit. You only have to override it if you are directly inheriting from Unit. """ sysUnit = self.get_system_unit() if isinstance(sysUnit, BaseUnit): return _UNITS_MANAGER.get_model().get_dimension(sysUnit) if isinstance(sysUnit, AlternateUnit) or isinstance(sysUnit, TransformedUnit): return sysUnit.get_parent().get_dimension() if isinstance(sysUnit, CompoundUnit): return sysUnit.get_first().get_dimension() # only product Unit left if not isinstance(sysUnit, ProductUnit): raise TypeError("system unit must be a ProductUnit") dimension = NONE for i in range(0, sysUnit.get_unitCount()): unit = sysUnit.get_unit(i) dim = (unit.get_dimension() ** sysUnit.get_unitPow(i)).root( sysUnit.get_unitRoot(i) ) dimension = dimension * dim return dimension
[docs] def is_compatible(self, other): """Check if two units can be converted to each other. Two units are compatible if their corresponding system units match, or if both units describe the same physical dimension. :param other: Another unit to compare to. :returns: True, if the units are compatible. """ _require_unit(other, "other") return ( self.get_system_unit() == other.get_system_unit() ) or self.get_dimension() == other.get_dimension()
[docs] def __add__(self, other): """Support of Adding an offset to the current unit (i.e. Celsus = Kelvin + 253.15). This function returns a TransformedUnit that represents adding the offset to the current unit. :param other: A numerical value to add to the unit. :returns: A transformed unit that represents adding the offset to the current unit. """ if not isinstance(other, numbers.Number): raise TypeError("other must be a number") return self.__transformUnit(operators.AddOperator(other))
[docs] def __sub__(self, other): """Support for Substracting an offset. This function works in a similar way as Unit.__add__. :param other: A numerical value to substract from the unit. :returns: A transformed unit that represents substracting the offset from the current unit. """ return self + (-other)
[docs] def __mul__(self, other): """Suport for multiplying a numeric value or unit. This function returns a new Unit that represents multiplying the factor or unit to the current unit. :param other: A number or unit. :returns: A new unit representing the operation. """ _require_unit_or_number(other, "other") if isinstance(other, Unit): if other is ONE: return self if self is ONE: return other if other == ONE: return self if self == ONE: return other return ProductUnit(self, other) else: return self.__transformUnit(operators.MultiplyOperator(other))
[docs] def __pow__(self, other): """Support integer powers. This function returns a new Unit that represents the power of the current unit. allow integers or rational numbers as powers of units. :param other: An integer. :returns: A new unit representing the operation. """ if isinstance(other, int): if other == 1: return self if other > 0: return self.__mul__(self.__pow__(other - 1)) elif other == 0: return ONE else: return ONE.__truediv__(self.__pow__(-other)) if self is ONE or self == ONE or other == ONE: return self if isinstance(other, arithmetic.RationalNumber): power = other.get_dividend() root = other.get_divisor() powered = self**power rooted = powered.root(root) return rooted # unknown type, raise error raise TypeError("other must be an int or RationalNumber")
[docs] def root(self, other): """Support of integer roots. This function returns a new Unit that represents the root of the current unit. :param other: An integer. :returns: A new unit representing the operation. """ _require_int(other, "other") value = int(other) if value > 0: return self.__rootInstance(self, value) elif value == 0: raise ArithmeticError("The root cannot be zero.") else: return ONE.__truediv__(self.root(-value))
[docs] def sqrt(self): """Support of square root. This function returns a new Unit that represents the square root of the current unit. :returns: A new unit representing the operation. """ return self.root(2)
[docs] def __truediv__(self, other): """Support for dividing units and numeric values. This function returns a new Unit that represents the operation. :param other: A number or unit. is zero. :returns: A new unit representing the operation. """ _require_unit_or_number(other, "other") if isinstance(other, Unit): if other == ONE: return self return self.__mul__(~other) else: return self.__transformUnit(operators.MultiplyOperator(1.0 / other))
[docs] def __invert__(self): """Support inversion of units. This function returns a new Unit that represents the operation. Suppose your unit is [U], then the inverted unit is :returns: A new unit representing the operation. """ unit = ONE.__truediv__(self) return unit
[docs] def compound(self, other): """Support compound units. This method returns a compound unit of the current unit and the argument. Both units have to describe the same physical dimension. :param other: Another unit describing the same dimension. :returns: A new compound unit. """ _require_unit(other, "other") return CompoundUnit(self, other)
[docs] def __str__(self): """Support of printing units. The silblings of unit override this method. It should print the symbols of the units that form the unit. For example a ProductUnit might print ``kg*m*s^(-2)``. :returns: A string describing this unit. has no effect. """ raise NotImplementedError
[docs] def to_system_unit(self): """Abstract Function to convert to corresponding system unit. :returns: The corresponding system unit. Unit.to_system_unit has no effect. """ raise NotImplementedError
[docs] def get_operator_to(self, unit): """Convert units. This method returns an operator that converts values that have been formed with the current unit to another other unit. :param unit: The unit to convert to. :returns: A converter to the argument. possible an exception is raised (i.e. if the units describe different physical dimensions). """ _require_unit(unit) # same unit if unit == self: return operators.IDENTITY selfUnit = self.get_system_unit() otherUnit = unit.get_system_unit() # Same Base Unit -> convert own to SystemUnit and invert other # to SystemUnit if selfUnit == otherUnit: op = unit.to_system_unit() * (~self.to_system_unit()) op.fromUnit = selfUnit op.toUnit = unit return op # last chance: same physical dimension? selfDim = self.get_dimension() otherDim = unit.get_dimension() if not otherDim == selfDim: raise qexceptions.ConversionException( self, " has not the same physical dimension as " + str(unit) ) selfTransform = self.to_system_unit() * selfUnit._getTransformOf() otherTransform = unit.to_system_unit() * otherUnit._getTransformOf() op = (~otherTransform) * selfTransform op.fromUnit = selfUnit op.toUnit = unit return op
def _getTransformOf(self): """Helper function to get the transformation to the system unit. This method returns an operator that converts values that have been formed with the current unit to its system unit. :returns: A converter to the system unit. possible an exception is raised (e.g. if the unit has a fractional exponent, or if the transformation is not linear). """ # Abort condition if isinstance(self, BaseUnit): return operators.IDENTITY # Renamed unit? if isinstance(self, AlternateUnit): return self.get_parent()._getTransformOf() # Transformed Unit? if isinstance(self, TransformedUnit): return self.get_operator_to(self.get_system_unit()) # Product unit operator = operators.IDENTITY for i in range(0, self.get_unitCount()): unit = self.get_unit(i) op = unit._getTransformOf() if not op.is_linear(): raise qexceptions.ConversionException( unit, " has been created using non-linear operation" + str(op) ) if self.get_unitRoot(i) != 1: raise qexceptions.ConversionException( unit, " has has fractional exponent" ) pow_ = self.get_unitPow(i) if pow_ < 0: pow_ = -pow_ op = ~op for _ in range(0, pow_): operator = operator * op return operator def __rootInstance(self, unit, root): """Helper function to get the n-th root instance of the unit. :param unit: The unit to be rooted. :param root: An integer to be used as root. :returns: A new unit representing the operation. """ _require_unit(unit) if not isinstance(root, numbers.Number): raise TypeError("root must be a number") newElts = [] if isinstance(unit, ProductUnit): elts = unit.__elements__ for elt in elts: elt_pow = elt.get_pow() elt_root = elt.get_root() * root new_elt = __ProductElement__(elt.get_unit(), elt_pow, elt_root) new_elt.normalize() newElts += [new_elt] else: newElts += [__ProductElement__(unit, 1, root)] result = ProductUnit(ONE, ONE) result.__elements__ = newElts result.normalize() return result def __transformUnit(self, operation): """Helper function that applies a transformation to the current unit. :param operation: The operation to be performed. :returns: A new unit representing the current unit after applying the operation. """ if not isinstance(operation, operators.UnitOperator): raise TypeError("operation must be a UnitOperator") if isinstance(self, TransformedUnit): parent = self.get_parent() toParentOp = self.to_parent_unit() newOp = ~toParentOp * operation return TransformedUnit(parent, newOp) else: return TransformedUnit(self, operation)
[docs] def __getstate__(self): """Abstract method: Serialization using pickle. :returns: A string that represents the serialized form of this instance. """ raise NotImplementedError
[docs] def __setstate__(self, state): """Abstract method: Deserialization using pickle. :param state: The state of the object. """ raise NotImplementedError
[docs] def __coerce__(self, other): """Implementation of coercion rules. This implementation ensures that transformed units can be created from units. """ _deprecated_python2_method("__coerce__", "explicit conversion") if isinstance(other, Unit): return (self, other) elif ( isinstance(other, int) or isinstance(other, int) or isinstance(other, float) or isinstance(other, complex) or isinstance(other, arithmetic.RationalNumber) ): return (self, other) else: raise NotImplementedError()
[docs] class BaseUnit(Unit): """This class provides the interface to define and use base units. A base unit is a unit that describes a single physical dimension. It can not be formed from other base units from other physical dimensions. Therefore, a base unit has to be unique. In order to ensure this, we do assign a unique symbol to each base unit. """ ## Unique symbol describing the BaseUnit. __symbol__ = None
[docs] def __init__(self, symbol): """Default constructor. Assigns the desired symbol to the respective BaseUnit and checks if an other instance of a unit already exists that has the same symbol. :param symbol: A symbol identifying the BaseUnit. symbol already exists. """ _require_symbol(symbol) self.__symbol__ = symbol _UNITS_MANAGER.addUnit(self)
[docs] def get_symbol(self): """Return the symbol of this unit. :returns: The symbol of the BaseUnit. """ return self.__symbol__
[docs] def __str__(self): """Return a string describing this unit. :returns: The symbol of this unit. """ return self.get_symbol()
[docs] def __eq__(self, other): """Check two if two base units are equal. Two base units are equal if they have the same unit symbol. :param other: Another unit. """ # Not a BaseUnit if isinstance(other, ProductUnit): other = ProductUnit.strip_unit(other) if not isinstance(other, BaseUnit): return False # check if the units have the same symbols return self.get_symbol() == other.get_symbol()
[docs] def get_system_unit(self): """Get the corresponding system unit. Since it is a base unit, it returns itself. :returns: The corresponding system unit. """ return self
[docs] def to_system_unit(self): """Get the operator to the system unit. Since it is a system unit, it returns operators.IDENTITY :returns: operators.IDENTITY """ return operators.IDENTITY
[docs] def __getstate__(self): """Serialization using pickle. :returns: A string that represents the serialized form of this instance. """ return self.__symbol__
[docs] def __setstate__(self, state): """Deserialization using pickle. :param state: The state of the object. unpickled is not contained in the global repository _UNITS_MANAGER. """ self.__symbol__ = state # print state if not _UNITS_MANAGER.existsUnit(self): raise qexceptions.UnknownUnitException( self, " is unknown, and can" + " therefore not be unpickled" )
[docs] class DerivedUnit(Unit): """This class provides an abstract interface for all units that have been transformed from other units. makes no effect. """
[docs] def __init__(self): """abstract default constructor.""" raise NotImplementedError
[docs] class AlternateUnit(DerivedUnit): r"""This class provides an interface for units that describe the same dimension as another unit, but need to be distinguished from it by another symbol (e.g. to abbreviate them, or to distinguish their purpose). For examle [N] := \left[\frac{kg \times m}{s^2}\right] . """ ## Symbol for the alternate unit. __symbol__ = None ## System unit that parents this unit. __parentUnit__ = None
[docs] def __init__(self, symbol, parentUnit): """Default constructor. :param symbol: Symbol of the alternate Unit. :param parentUnit: Parent unit. symbol already exists. """ _require_unit(parentUnit, "parentUnit") _require_symbol(symbol) if ( not parentUnit.get_system_unit() .get_dimension() .__eq__(parentUnit.get_dimension()) ): raise TypeError("ParentUnit has to be a System unit") self.__symbol__ = symbol self.__parentUnit__ = parentUnit _UNITS_MANAGER.addUnit(self)
[docs] def get_parent(self): """Returns the parent unit of the this unit. :returns: Parent unit. """ return self.__parentUnit__
[docs] def get_symbol(self): """Returns the symbol of this unit. :returns: Symbol of current unit. """ return self.__symbol__
[docs] def get_system_unit(self): """Returns the corresponding system unit. Since the parent unit is a system unit, this unit is supposed to be a system unit too. Therefore this function returns this instance. :returns: self """ return self
[docs] def to_system_unit(self): """Get the operator to convert to the system unit. Since the parent unit is a system unit, this unit is supposed to be a system unit too. Therefore this function returns operators.IDENTITY. :returns: operators.IDENTITY """ return operators.IDENTITY
[docs] def __eq__(self, other): """Checks if two alternate units are equal. :param other: Another alternate unit to compare to. :returns: True, if the units are equal. """ if isinstance(other, ProductUnit): other = ProductUnit.strip_unit(other) if not isinstance(other, AlternateUnit): return False return self.get_symbol() == other.get_symbol()
[docs] def __str__(self): """Print the current unit. This function is an alias for AlternateUnit.get_symbol :returns: A string describing this unit. """ return self.get_symbol()
[docs] def __getstate__(self): """Serialization using pickle. :returns: A string that represents the serialized form of this instance. """ return (self.__symbol__, self.__parentUnit__)
[docs] def __setstate__(self, state): """Deserialization using pickle. :param state: The state of the object. unpickled is not contained in the global repository _UNITS_MANAGER. """ self.__symbol__, self.__parentUnit__ = state if not _UNITS_MANAGER.existsUnit(self): raise qexceptions.UnknownUnitException( self, " is unknown, and can therefore" + " not be unpickled" )
# Example for AlternateUnit ## This example shows how to create and use instances of # units.AlternateUnit. They are created from other units # using transformations. A symbol is assigned to the new # unit, to distinguish it from other units. This symbol # has to be unique and must not interfer with symbols of # other units already created. # We will show this by a simple example using SI units. # \see units.AlternateUnit # \example AlternateUnits.py
[docs] class CompoundUnit(DerivedUnit): r"""This class provides an interface for describing compound units. The units forming a compound unit have to describe the same physical dimension. For example time \left[hour:min:second\right] . """ ## The first unit. __first__ = None ## The next unit. __next__ = None
[docs] def __init__(self, firstUnit, nextUnit): """Default constructor. Both arguments have to describe the same physical dimension and they have to have the same system unit. :param firstUnit: The first unit. :param nextUnit: The unit to attach to the first unit. """ _require_unit(firstUnit, "firstUnit") _require_unit(nextUnit, "nextUnit") if not firstUnit.get_system_unit().__eq__(nextUnit.get_system_unit()): raise TypeError( "Both units have to describe the same" + " physical dimension" ) # Optimize CompoundUnits if isinstance(firstUnit, CompoundUnit): self.__first__ = firstUnit.get_first() self.__next__ = CompoundUnit(firstUnit.get_next(), nextUnit) else: self.__first__ = firstUnit self.__next__ = nextUnit
[docs] def get_first(self): """Get the first unit. :returns: The first unit of this compound unit. """ return self.__first__
[docs] def get_next(self): """Get the next unit. :returns: The first unit of this compound unit. """ return self.__next__
[docs] def get_system_unit(self): """Returns the corresponding system unit. :returns: The corresponding system unit. """ return self.__first__.get_system_unit()
[docs] def to_system_unit(self): """Get the operator to convert to the system unit. We assume that the operator of the first element of this compound unit performs the conversion correctly. :returns: The operator to the system unit of the first element """ return self.__first__.to_system_unit()
[docs] def __eq__(self, other): """This function checks if two compound units are equal. Two compound units are equal, if they have equal first and next units. (i.e. hh:mm not equal mm:hh)! :param other: Another compound unit to compare to. :returns: True, if the units are equal. """ # if not an instance, it is not comparable if not isinstance(other, CompoundUnit): return False # compare recursively return other.get_first().__eq__(self.__first__) and other.get_next().__eq__( self.__next__ )
[docs] def __str__(self): """Print the current unit. This function returns a string of the form ``first:next``. :returns: A string describing this unit. """ return str(self.get_first()) + ":" + str(self.get_next())
[docs] def __getstate__(self): """Serialization using pickle. :returns: A string that represents the serialized form of this instance. """ return (self.__first__, self.__next__)
[docs] def __setstate__(self, state): """Deserialization using pickle. :param state: The state of the object. """ self.__first__, self.__next__ = state
[docs] class __ProductElement__(object): """A helper class for ProductUnit classes. This class helps to maintain the factors of a product unit. """ ## The unit of the current factor. __unit__ = None ## The power of the current factor. __pow__ = None ## The root of the current factor. __root__ = None
[docs] def __init__(self, unit, power, root): """Default constructor. :param unit: The unit of the factor to create. :param power: The power assigned to this factor. :param root: The root assigned to this factor. """ _require_unit(unit) _require_int(power, "power") _require_int(root, "root") self.__unit__ = unit self.__pow__ = int(power) self.__root__ = int(root)
[docs] def get_unit(self): """Get the unit of this factor. :returns: The unit of this factor. """ return self.__unit__
[docs] def get_pow(self): """Get the power of this factor. :returns: The power of this factor. """ return int(self.__pow__)
[docs] def get_root(self): """Get the root of this factor. :returns: The root of this factor. """ return int(self.__root__)
[docs] def __eq__(self, other): """This method checks two factors for equality. Two factors are equal if they have the same units, powers, and roots. :param other: Another instance of a factor. :returns: True, if the factors are equal. """ if self is other: return True if not isinstance(other, __ProductElement__): return False return ( (self.__unit__ is other.__unit__ or self.__unit__ == other.__unit__) and self.__pow__ == other.__pow__ and self.__root__ == other.__root__ )
[docs] def set_pow(self, value): """This method changes the power. :param value: An interget to be used as new power. """ _require_int(value) self.__pow__ = int(value)
[docs] def set_root(self, value): """This method changes the root. :param value: An interger to be used as new root. """ _require_int(value) self.__root__ = int(value)
[docs] def normalize(self): """Transform the current factor into its canonical form.""" divisor = arithmetic.gcd(abs(self.__pow__), self.__root__) self.__pow__ //= divisor self.__root__ //= divisor
[docs] def __str__(self): """Print this factor. This function returns a string of the form ``unit^(pow/root)``. :returns: A string describing this factor. """ if self.__pow__ == 1 and self.__root__ == 1: return str(self.__unit__) elif self.__root__ == 1: return str(self.__unit__) + "^(" + str(self.__pow__) + ")" else: return ( str(self.__unit__) + "^(" + str(self.__pow__) + "/" + str(self.__root__) + ")" )
[docs] def clone(self): """Return a new instance of this factor. :returns: A new instance of this factor. """ return __ProductElement__(self.__unit__, self.__pow__, self.__root__)
[docs] def __getstate__(self): """Serialization using pickle. :returns: A string that represents the serialized form of this instance. """ return (self.__unit__, self.__pow__, self.__root__)
[docs] def __setstate__(self, state): """Deserialization using pickle. :param state: The state of the object. """ self.__unit__, self.__pow__, self.__root__ = state
[docs] class ProductUnit(DerivedUnit): r"""The unit is a combined unit of the product of the powers of units. The unit is stored in its canonical form. That is the simplest form. For example \left[m\right] := \left[\frac{m^2}{m}\right] . """ ## The factors forming the product unit. # \see __ProductElement__ __elements__ = []
[docs] def __init__(self, left=None, right=None): """Default constructor. :param left: A unit to left-multiply. :param right: A unit to right-multiply. """ self.__elements__ = [] if right is None or left is None: return _require_unit(right, "right") _require_unit(left, "left") if isinstance(left, ProductUnit): self.__elements__ += left.__cloneElements() else: self.__elements__ += [__ProductElement__(left, 1, 1)] if isinstance(right, ProductUnit): self.__elements__ += right.__cloneElements() else: self.__elements__ += [__ProductElement__(right, 1, 1)] self.normalize()
[docs] def get_system_unit(self): """Get the corresponding system unit. If no system unit is found, the unit is formed from the system units of the factors of the current unit. :returns: The corresponding system unit. """ if self.__isSystemUnit(): return self result = ONE for item in self.__elements__: unit = item.get_unit() unit = unit ** item.get_pow() unit = unit.root(item.get_root()) result = result * unit return result
[docs] def to_system_unit(self): """Get the operator to convert to the system unit. This method concatenates the individual operators and returns the joint operator to the system unit, if this unit is not a system unit. :returns: The operator to the system unit. system units is formed using a non linear operator, or if a factor has a rational exponent. """ if self.__isSystemUnit(): return operators.IDENTITY result = operators.IDENTITY for item in self.__elements__: operator = item.get_unit().to_system_unit() if not operator.is_linear(): raise qexceptions.ConversionException( self, "Can not concat nonlinear Operator" ) if item.get_root() != 1: raise qexceptions.ConversionException( self, "Unit has rational exponent" ) pow_ = item.get_pow() if pow_ < 0: pow_ = -pow_ operator = ~operator for _ in range(0, pow_): result = result * operator return result
[docs] def get_unit(self, index): """Returns the unit at the given index. :param index: Index of the desired unit. :returns: The unit at index. """ _require_product_index(self.__elements__, index) return self.__elements__[index].get_unit()
[docs] def get_unitCount(self): """Get the total count of factors of this product unit. :returns: The number of factors. """ return len(self.__elements__)
[docs] def get_unitPow(self, index): """Get the power exponent of a factor at the given index. ``get_unitPow`` and ``get_unitRoot`` in order to obtain the complete exponent of this unit. For example for ``get_unitPow`` and 2 for ``get_unitRoot``. :param index: Index of the desired unit. :returns: The (integer) power of the current unit """ _require_product_index(self.__elements__, index) return self.__elements__[index].get_pow()
[docs] def get_unitRoot(self, index): """Get the root exponent of a factor at the given index. ``get_unitPow`` and ``get_unitRoot`` in order to obtain the complete exponent of this unit. For example for ``get_unitPow`` and 2 for ``get_unitRoot``. :param index: Index of the desired unit. :returns: The (integer) root of the current unit """ _require_product_index(self.__elements__, index) return self.__elements__[index].get_root()
[docs] def __eq__(self, other): """Checks if two product units are equal. :param other: Unit to compare to :returns: True If the units are equal, False if the units are unequal. """ if self is other: return True # convert to product unit, this is necessary # to compare product units having one element. try: other = ProductUnit.value_of(other) except Exception: return False # not a unit if other is None: return False if self.get_unitCount() != other.get_unitCount(): return False for ownElt in self.__elements__: found = False # check wether current element is others list for otherElt in other.__elements__: if ownElt == otherElt: found = True break # not contained -> break if not found: return False # no break occured -> match return True
[docs] def __truediv__(self, other): """Divide two units. :param other: A divisor. """ if not isinstance(other, Unit): return Unit.__truediv__(self, other) elements = self.__cloneElements() if isinstance(other, ProductUnit): # Invert the Elements for item in other.__cloneElements(): item = item.clone() item.__pow__ = -item.__pow__ elements += [item] else: elements += [__ProductElement__(other, -1, 1)] unit = ProductUnit() unit.__elements__ = elements unit.normalize() return unit
[docs] def normalize(self): """This function merge duplicate factors and converts this unit into its canonical form. """ if len(self.__elements__) == 0: return newElts = [] # merge duplicates, concat elements for i in range(0, len(self.__elements__)): elt = self.__elements__[i] # already processed if elt == None: continue # neutral element if elt.get_pow() == 0: continue # mark as processed self.__elements__[i] = None for j in range(i, len(self.__elements__)): tmp = self.__elements__[j] # check wether already processed if tmp == None: continue # Same unit, update root, power + cancel if elt.get_unit() == tmp.get_unit(): rightPow = tmp.get_pow() * elt.get_root() leftPow = elt.get_pow() * tmp.get_root() divisor = tmp.get_root() * elt.get_root() newPow = rightPow + leftPow elt.set_pow(newPow) elt.set_root(divisor) elt.normalize() # mark as processed self.__elements__[j] = None newElts = newElts + [elt] # pass 2, remove all 0 powers self.__elements__ = [] for elt in newElts: if elt.get_pow() != 0: self.__elements__ += [elt]
[docs] def __str__(self): """Print the current unit. This function returns a string of the form ``factor1*factor2``. :returns: A string describing this unit. """ if self.get_unitCount() == 0: return "1" string = "" for i in range(0, len(self.__elements__)): if i == len(self.__elements__) - 1: string += str(self.__elements__[i]) else: string += str(self.__elements__[i]) + "*" return string
def __isSystemUnit(self): """Check if the current unit is a system unit. This product unit is a system unit, if all of its factors are system units. :returns: True if it is a system unit. """ for item in self.__elements__: unit = item.get_unit() if unit.get_system_unit() != unit: return False return True def __cloneElements(self): """Return a copy of the sequence of factors. :returns: A copy of __elements__. """ retElems = [] for elem in self.__elements__: if elem == None: continue retElems += [elem.clone()] return retElems
[docs] def value_of(unit): """Factory method for generating product units. Used to compare other units. :param unit: A unit. :returns: The argument, if it is a product unit, or a new instance of ProductUnit if the argument is not a product unit. """ if unit == None: return None _require_unit(unit) if isinstance(unit, ProductUnit): return unit else: return ProductUnit(unit, ONE)
value_of = staticmethod(value_of)
[docs] def strip_unit(unit): """Return the contained unit of a unit, if it is a product unit, contains only one element, and has an exponent equal to one. """ if not isinstance(unit, ProductUnit): return unit # strip unit if possible if ( (unit.get_unitCount() == 1) and (unit.get_unitPow(0) == 1) and (unit.get_unitRoot(0) == 1) ): return unit.get_unit(0) # unit can not be stripped return unit
strip_unit = staticmethod(strip_unit)
[docs] def __getstate__(self): """Serialization using pickle. :returns: A string that represents the serialized form of this instance. """ return self.__elements__
[docs] def __setstate__(self, state): """Deserialization using pickle. :param state: The state of the object. """ self.__elements__ = state
# Example for ProductUnit ## This example shows how to create and use instances of # units.ProductUnit. In general, instances of this class # are not created directly. They are created by # multiplying several other units. # We will show this by a simple example using SI units. # \see units.Unit.__mul__ # \see units.ProductUnit # \example ProductUnits.py
[docs] class TransformedUnit(DerivedUnit): """This class provides an interface for a unit that has been derived from a unit using an operator. For example feet can be derived from meter. """ ## What was the original unit. __parentUnit__ = None ## What is the operator to the parent unit. __operator__ = None
[docs] def __init__(self, parent, operator): """Default constructor. :param parent: The parent unit of the current unit. :param operator: The operator that forms this unit from the parent unit. """ _require_unit(parent, "parent") if not isinstance(operator, operators.UnitOperator): raise TypeError("operator must be a UnitOperator") self.__parentUnit__ = parent self.__operator__ = operator
[docs] def get_parent(self): """Return the parent unit. :returns: The unit before the transformation. """ return self.__parentUnit__
[docs] def get_system_unit(self): """Get the corresponding system unit. :returns: The corresponding system unit. """ return self.__parentUnit__.get_system_unit()
[docs] def to_parent_unit(self): """Get the operator to convert to the parent unit. :returns: The operator to the parent unit. """ return ~self.__operator__
[docs] def to_system_unit(self): """Get the operator to convert to the corresponding system unit. :returns: The operator to the system unit. """ return self.__parentUnit__.to_system_unit() * ~self.__operator__
[docs] def __eq__(self, other): """Compare two transformed units. Two transformed units are equal, if their transformation as well as their parent units are equal. :param other: Another instance of a transformed unit. :returns: True, if the units are equal. """ if isinstance(other, ProductUnit): other = ProductUnit.strip_unit(other) if not isinstance(other, TransformedUnit): return False else: return ( self.__parentUnit__ == other.__parentUnit__ and self.__operator__ == other.__operator__ )
[docs] def __str__(self): """Print the current unit. This function returns a string of the form example (K+273.15) for degrees celsius transformed from kelvins. :returns: A string describing this unit. """ return "(" + str(str(self.get_parent()) + str(~self.to_parent_unit())) + ")"
[docs] def __getstate__(self): """Serialization using pickle. :returns: A string that represents the serialized form of this instance. """ return (self.__parentUnit__, self.__operator__)
[docs] def __setstate__(self, state): """Deserialization using pickle. :param state: The state of the object. """ self.__parentUnit__, self.__operator__ = state
# Example for TransformedUnits ## This example shows how to use the instances of units.TransformedUnit. # In general, it is not necessary to instance the units.TransformedUnit # class directly. Instances of units.TransformedUnit are intended to be # created implicitly by applying transformation to other units # (i.e. BaseUnits). We will show this here by transforming # a SI base unit. # \see units.Unit.__add__ # \see units.Unit.__truediv__ # \see units.Unit.__mul__ # \see units.TransformedUnit # \example TransformedUnits.py ### Global Variables ### ## \brief Global units Manager that keeps track of the units and dimensions # created. _UNITS_MANAGER = UnitsManager() ## Predefined global dimension for the Length. LENGTH = Dimension("L") ## Predefined global dimension for the Mass. MASS = Dimension("M") ## Predefined global dimension for the Time. TIME = Dimension("t") ## Predefined global dimension for the Electric Current. CURRENT = Dimension("I") # __unicode = u"\u03b8" # __char = __unicode.encode( "UTF-8" ) ## Predefined global dimension for the Temperature. TEMPERATURE = Dimension('u"\N{GREEK SMALL LETTER THETA}"') ## Predefined global dimension for the Amount of Substance. SUBSTANCE = Dimension("n") ## Predefined global dimension for Luminous Intensity. LUMINOUS_INTENSITY = Dimension("Li") ## Dimensionless unit ONE. ONE = ProductUnit() ## Predefined global dimension for a dimensionless quantity. NONE = Dimension(ONE) ## @}