Source code for scuq.operators

## \file operators.py
#  \brief This file contains the classes necessary to define, use, and
#  handle operations on units.
#  \author <a href="http://thomas.reidemeister.org/" target="_blank">
#          Thomas Reidemeister</a>

## \namespace scuq::operators
# \brief This namespace contains operators that are used for unit conversions.

## \defgroup operators The Operators Module
#
# This module contains the classes necessary to define, handle, and use
# operators on units.
# \author <a href="http://thomas.reidemeister.org/" target="_blank">
#         Thomas Reidemeister</a>
# \addtogroup operators
# @{

# standard modules
import numpy

# import operator
import numbers
import pickle

# local modules
import scuq.arithmetic as arithmetic

# import scuq.quantities as quantities


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


def _require_operator(value, name="operator"):
    if not isinstance(value, UnitOperator):
        raise TypeError("%s must be a UnitOperator" % name)


[docs] class UnitOperator: """Abstract base class for unit conversion operators."""
[docs] def __mul__(self, otherOperator): """Perform the current operation on another operator. Another operation g(x) will be performed on this operator f(x). So that the new Operator is :param otherOperator: The other operator to concat. :returns: The resulting operator. """ _require_operator(otherOperator, "otherOperator") if otherOperator == IDENTITY: return self if self == IDENTITY: return otherOperator return CompoundOperator(otherOperator, self)
[docs] def __invert__(self): """Invert this operation. This method returns a new operator that represents the inversion of this operator. silblings of this class override it in order to get an effect. :returns: The inverse operation of this operation. """ raise NotImplementedError()
[docs] def is_linear(self): """Return whether this operator is linear. Subclasses must implement this method. """ raise NotImplementedError
[docs] def convert(self, value): """Convert ``value`` according to the operator implementation. Subclasses must implement this method. """ raise NotImplementedError
[docs] def __str__(self): """Represent this operation by a string. silblings of this class override it in order to get an effect. :returns: A string describing this operation. """ raise NotImplementedError
[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 __eq__(self, other): """Test for equality. :param other: Another UnitOperator. """ # global test for IDENTITY if (self is IDENTITY) and (other is IDENTITY): return True return False
class _ExpOperator(UnitOperator): """This class provides an Interface for exponential operators. It is used as helper for the LogOperator. """ def __init__(self, exponent): """Default constructor. Initializes the operator and assigns the base to the current operator. :param exponent: the exponent. """ _require_number(exponent, "exponent") self._exponent = exponent self._logExponent = numpy.log(exponent) def __invert__(self): """Invert this operation. This method returns the inverse operation of the current operation. :returns: The inverse operation of the current operation. """ return LogOperator(self.get_exponent()) def is_linear(self): """Check if the operator is linear. This operator is not linear. :returns: False; this operator is not linear. """ return False def convert(self, value): """Convert a value. This method performs raises the current value to the exponent. :param value: The value to convert. :returns: The converted value """ _require_number(value) return numpy.exp(self._logExponent * float(value)) def get_exponent(self): """Get the base of logarithm. This method returns the exponent. :returns: The base of the logarithm """ return self._exponent def __str__(self): """Represent this operation by a string. :returns: A string describing this operation. """ return "^" + str(self._exponent) def __getstate__(self): """Serialization using pickle. :returns: A string that represents the serialized form of this instance. """ return self._exponent def __setstate__(self, state): """Deserialization using pickle. :param state: The state of the object. """ self._exponent = state def __eq__(self, other): """Test for equality. :param other: Another UnitOperator. """ if not isinstance(other, _ExpOperator): return False return self._exponent == other._exponent
[docs] class LogOperator(UnitOperator): """This class provides an interface for logarithmic operators."""
[docs] def __init__(self, base): """Default constructor. Initializes this operator and assigns the base to it. :param base: The base of the logarithm. """ _require_number(base, "base") self._base = base self._logBase = numpy.log(base)
[docs] def __invert__(self): """Invert the current operation. This method returns the inverse Operation of the current operation. :returns: The inverse Operation of the current Operation. """ return _ExpOperator(self.get_base())
[docs] def is_linear(self): """Check if this operator is linear. This operator is not linear. :returns: False """ return False
[docs] def convert(self, value): """Apply the configured logarithm to ``value``.""" _require_number(value) return numpy.log(float(value)) / self._logBase
[docs] def get_base(self): """Get the base of this logarithm. This method returns the base of the logarithm. :returns: The base of the logarithm """ return self._base
[docs] def __str__(self): """Represent this operation by a string. :returns: A string describing this operation. """ return "_" + str(self._base)
[docs] def __getstate__(self): """Serialization using pickle. :returns: A string that represents the serialized form of this instance. """ return self._base
[docs] def __setstate__(self, state): """Deserialization using pickle. :param state: The state of the object. """ self._base = state self._logBase = numpy.log(self._base)
[docs] def __eq__(self, other): """Test for equality. :param other: Another UnitOperator. """ if not isinstance(other, LogOperator): return False return self._base == other._base
[docs] class AddOperator(UnitOperator): """This class provides an Interface for offset operators. This class adds a constant value to an existing Operator. """ def _isNegative(positvieOp, negativeOp): """Helper method to optimize comparsions. :param negativeOp: An AddOperator. :param positvieOp: An AddOperator. :returns: ``negativeOp.get_offset() == -positvieOp.get_offset()`` """ if not isinstance(positvieOp, AddOperator): raise TypeError("positvieOp must be an AddOperator") if not isinstance(negativeOp, AddOperator): raise TypeError("negativeOp must be an AddOperator") negOffset = negativeOp.get_offset() posOffset = positvieOp.get_offset() # convert to rational number try: negOffset = arithmetic.RationalNumber.value_of(negOffset) posOffset = arithmetic.RationalNumber.value_of(posOffset) except TypeError: return False return posOffset == -negOffset _isNegative = staticmethod(_isNegative)
[docs] def __init__(self, offset): """Default constructor. Initializes the operator and assigns the offset to the current operator. :param offset: The offset of this operator. """ _require_number(offset, "offset") self._offset = offset
[docs] def __mul__(self, otherOperator): """Perform the current operation on another operator. The current operation (adding an offset a) will be performed on another operator f(x). So that the new Operator is If the other Operator is an AddOperator, the offset is updated. :param otherOperator: The other operator to concat. :returns: The resulting operator. """ _require_operator(otherOperator, "otherOperator") if isinstance(otherOperator, AddOperator): # optimize for identity if AddOperator._isNegative(self, otherOperator): return IDENTITY otherOffset = otherOperator.get_offset() newOffset = self.get_offset() + otherOffset return AddOperator(newOffset) else: return UnitOperator.__mul__(self, otherOperator)
[docs] def __invert__(self): """Invert the current operation. This method returns the inverse operation of the current operation. :returns: The inverse operation of this operation. """ return AddOperator(-self._offset)
[docs] def is_linear(self): """Check if this operator is linear. This operator is not linear. :returns: False """ return False
[docs] def convert(self, value): """Convert a value. This method performs the addition of an offset on an absolute value. :param value: The value to convert. :returns: The converted value """ _require_number(value) return value + self.get_offset()
[docs] def get_offset(self): """Get the offset. This method returns the offset of this operator. :returns: The offset of this operator """ return self._offset
[docs] def __str__(self): """Represent this operation by a string. :returns: A string describing this operation. """ offset = abs(self._offset) if self._offset < 0.0: return "-" + str(offset) else: return "+" + str(offset)
[docs] def __getstate__(self): """Serialization using pickle. :returns: A string that represents the serialized form of this instance. """ return self._offset
[docs] def __setstate__(self, state): """Deserialization using pickle. :param state: The state of the object. """ self._offset = state
[docs] def __eq__(self, other): """Test for equality. :param other: Another UnitOperator. """ if not isinstance(other, AddOperator): return False return self._offset == other._offset
[docs] class MultiplyOperator(UnitOperator): """This class provides an Interface for factor operators. This class multiplies a constant with an existing Operator. """ def _isNegative(positvieOp, negativeOp): """Helper method to optimize comparsions. :param negativeOp: An MultiplyOperator. :param positvieOp: An MultiplyOperator. :returns: ``negativeOp.get_factor() == ~positvieOp.get_factor()`` """ if not isinstance(positvieOp, MultiplyOperator): raise TypeError("positvieOp must be a MultiplyOperator") if not isinstance(negativeOp, MultiplyOperator): raise TypeError("negativeOp must be a MultiplyOperator") negFactor = negativeOp.get_factor() posFactor = positvieOp.get_factor() # convert to rational number try: negFactor = arithmetic.RationalNumber.value_of(negFactor) posFactor = arithmetic.RationalNumber.value_of(posFactor) except TypeError: return False return negFactor == ~posFactor _isNegative = staticmethod(_isNegative)
[docs] def __init__(self, factor): """Default constructor. Initializes this operator and assigns the factor to the current operator. :param factor: The offset of this operator. """ _require_number(factor, "factor") self._factor = factor
[docs] def __mul__(self, otherOperator): """Perform the current operation on another operator. The current operation (muliplying by a) will be performed on another operator f(x). So that the new operator is a \times g(x). In order to avoid numerical quirks, this method checks wether the parameter is an instance of a MuliplyOperator. If yes, then only the factor is updated. :param otherOperator: The other operator to concat. :returns: The resulting operator. """ _require_operator(otherOperator, "otherOperator") if isinstance(otherOperator, MultiplyOperator): if MultiplyOperator._isNegative(self, otherOperator): return IDENTITY otherFactor = otherOperator.get_factor() newFactor = self.get_factor() * otherFactor return MultiplyOperator(newFactor) else: return UnitOperator.__mul__(self, otherOperator)
[docs] def __invert__(self): r"""Invert the current operation. For example let this operator be a \times f(x) then the inverse is \frac{1}{a} \times f(x) . :returns: The inverse Operation of the current Operation. """ # Optimize for integer accuracy if isinstance(self._factor, int) or isinstance(self._factor, int): return MultiplyOperator(arithmetic.RationalNumber(1, self._factor)) # Optimize rational factors if isinstance(self._factor, arithmetic.RationalNumber): return MultiplyOperator(~self._factor) # no optimization possible for other types return MultiplyOperator(1.0 / self._factor)
[docs] def is_linear(self): """Check if the operator is linear. This operator is linear. :returns: True """ return True
[docs] def convert(self, value): """Convert a value. This method performs the multiplication with an factor on an absolute value. :param value: The value to convert. :returns: The converted value. """ _require_number(value) newval = value * self._factor # if isinstance(value, quantities.Quantity): if hasattr(newval, "_unit"): newval._unit = self.toUnit return newval
[docs] def get_factor(self): """Get the factor. This method returns the factor of this operator. :returns: The factor of this operator. """ return self._factor
[docs] def __str__(self): """Represent this operation by a string. :returns: A string describing this operation. """ return "*" + str(self._factor)
[docs] def __getstate__(self): """Serialization using pickle. :returns: A string that represents the serialized form of this instance. """ return self._factor
[docs] def __setstate__(self, state): """Deserialization using pickle. :param state: The state of the object. """ self._factor = state
[docs] def __eq__(self, other): """Test for equality. :param other: Another UnitOperator. """ if not isinstance(other, MultiplyOperator): return False return self._factor == other._factor
[docs] class CompoundOperator(UnitOperator): """Compose two operators and apply them in sequence."""
[docs] def __init__(self, firstOp, secondOp): """Default Constructor For example let the secondOp be g(x) and the firstOp be f(x) then the compound Operator models f(g(x)) . :param firstOp: The operator that is performed at first. :param secondOp: The operator that is performed at last. """ _require_operator(firstOp, "firstOp") _require_operator(secondOp, "secondOp") self._firstOperator = firstOp self._secondOperator = secondOp
[docs] def __invert__(self): """Invert the current operation. This method returns the inverse Operation of the current operation. Since this Operation is based on two Operations the operations are inverted in the reverse order. For example let this Operator model y = f(g(x)) the inverse Operator models x = g^{-1}(f^{-1}(y)). :returns: The inverse operation of the current operation. """ return CompoundOperator( self._secondOperator.__invert__(), self._firstOperator.__invert__() )
[docs] def is_linear(self): """Check if the Operator is linear. This operator is linear if the underlying operators are linear. :returns: ``True`` if both underlying operators are linear. """ return self._firstOperator.is_linear() and self._secondOperator.is_linear()
[docs] def convert(self, value): """Convert a value. This method performs the desired operation on an absolute value. :param value: The value to convert. :returns: the converted value """ innerValue = self._firstOperator.convert(value) return self._secondOperator.convert(innerValue)
[docs] def __str__(self): """Represent this operation by a string. :returns: A string describing this operation. """ return str(self._firstOperator) + "(" + str(self._secondOperator) + ")"
[docs] def __getstate__(self): """Serialization using pickle. :returns: A string that represents the serialized form of this instance. """ return (self._firstOperator, self._secondOperator)
[docs] def __setstate__(self, state): """Deserialization using pickle. :param state: The state of the object. """ self._firstOperator, self._secondOperator = state
[docs] def __eq__(self, other): """Test for equality. :param other: Another UnitOperator. """ if not isinstance(other, CompoundOperator): return False return ( self._firstOperator == other._firstOperator and self._secondOperator == other._secondOperator )
[docs] class Identity(UnitOperator): """Identity operator that returns values unchanged."""
[docs] def __mul__(self, otherOperator): """Perform the current operation on another operator. This method returns the parameter. :param otherOperator: The other operator to concat. :returns: The other Operator. """ _require_operator(otherOperator, "otherOperator") return otherOperator
[docs] def __invert__(self): """Invert the current operation. This method returns the inverse Operation of the current operation. :returns: The inverse Operation of the current Operation. have to override it in order to get any effect. """ return self
[docs] def is_linear(self): """Check if the Operator is linear. Identity is a linear operator. Thus, this method always returns True. :returns: ``True``. """ return True
[docs] def convert(self, value): """Convert a value. This method returns the parameter. :param value: The value to convert (will be returned). :returns: The parameter value """ return value
[docs] def __str__(self): """Represent this operation by a string. :returns: A string describing this operation. """ return "*1"
[docs] def __getstate__(self): """Serialization using pickle. :returns: A string that represents the serialized form of this instance. """ return 1
[docs] def __setstate__(self, state): """Deserialization using pickle. :param state: The state of the object. """ if state != 1: raise ValueError("Identity state must be 1")
[docs] def __eq__(self, other): """Test for equality. :param other: Another UnitOperator. """ return isinstance(other, Identity)
## Global Identity Operator. # # Since there is only one Identity, it is defined global here. IDENTITY = Identity() ## @}