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