## \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
# 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)
## @}