Source code for scuq.cucomponents

## \file cucomponents.py
# \brief This file contains the module to model complex uncertain values.
#  \author <a href="http://thomas.reidemeister.org/" target="_blank">
#          Thomas Reidemeister</a>

## \namespace scuq::cucomponents
# \brief This namespace contains the classes to evaluate the
#        uncertainty of complex-valued functions.

## \defgroup cucomponents The Complex Uncertainty Module
#
# This module contains classes to model complex uncertain values.
# \attention You should either use the module ucomponents or this module.
#            Do not use both modules at once!
# \author <a href="http://thomas.reidemeister.org/" target="_blank">
#         Thomas Reidemeister</a>
# \addtogroup cucomponents
# @{

# builtin modules
import numpy
import warnings

# local modules
import scuq.arithmetic as arithmetic
import scuq.ucomponents as ucomponents
import scuq.quantities as quantities
import scuq.units as units


def _require_certain_component(value, name="value"):
    if not isinstance(value, CUncertainComponent):
        raise TypeError("%s must be a CUncertainComponent" % name)


def _require_context(value, name="context"):
    if not isinstance(value, Context):
        raise TypeError("%s must be a Context" % name)


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] def complex_to_matrix(value): """This function transforms a complex value into a 2x2 NumPy array. :param value: The complex value. :returns: A 2x2 ``numpy.ndarray`` containing the value. """ value = complex(value) re = value.real im = value.imag return numpy.array([[re, -im], [im, re]])
[docs] class CUncertainComponent: """This is the abstract super class of all complex valued uncertain components. Despite defining the interface for complex valued uncertain components, it also provides a set of factory methods that act as an interface for ``numpy``. """
[docs] def depends_on(self): """This abstact method should return the set of CUncertainInput instances, on which this instance depends on. :returns: A list of CUncertainInputs this instance depends on. """ raise NotImplementedError
[docs] def get_value(self): """This abstract method should return the complex value of this component. :returns: The value of this component. """ raise NotImplementedError
[docs] def get_uncertainty(self, x): """This abstact method should return the partial derivate of this component with respect to the input ``x``. :param x: An uncertain input. :returns: The uncertainty of this component with respect to the input. """ raise NotImplementedError
[docs] def get_a_value(self): """This method returns the value of this component as a 2x2 array. :returns: The complex value of this component as a ``numpy.ndarray``. """ return complex_to_matrix(self.get_value())
[docs] def exp(self): """Get the exponential of this instance. Let this instance be x then this method returns e^x. :returns: The exponential value of this instance. """ return Exp(self)
[docs] def log(self): r"""Get the natural logarithm of this instance. Let this instance be x then this method returns \ln(x). :returns: The natural logarithm of this instance. """ return Log(self, numpy.e)
[docs] def log10(self): r"""Get the decadic logarithm of this instance. Let this instance be x then this method returns \log_{10}(x). :returns: The decadic logarithm of this instance. """ return Log(self, 10.0)
[docs] def log2(self): r"""Get the binary logarithm of this instance. Let this instance be x then this method returns \log_{2}(x). :returns: The binary logarithm of this instance. """ return Log(self, 2.0)
[docs] def sqrt(self): r"""Get the square-root of this instance. Let this instance be x then this method returns \sqrt{x}. :returns: The square-root of this instance. """ return Sqrt(self)
[docs] def square(self): r"""Get the square of this instance. Let this instance be x then this method returns x \cdot x. :returns: The square of this instance. """ return self * self
[docs] def sin(self): r"""Get the sine of this instance. Let this instance be x then this method returns \sin(x). :returns: The sine of this instance. """ return Sin(self)
[docs] def cos(self): r"""Get the cosine of this instance. Let this instance be x then this method returns \cos(x). :returns: The cosine of this instance. """ return Cos(self)
[docs] def tan(self): r"""Get the tangent of this instance. Let this instance be x then this method returns \tan(x). :returns: The tangent of this instance. """ return Tan(self)
[docs] def arcsin(self): r"""Get the inverse sine of this instance. Let this instance be x then this method returns \sin^{-1}(x). :returns: The inverse sine of this instance. """ return ArcSin(self)
[docs] def arccos(self): r"""Get the inverse cosine of this instance. Let this instance be x then this method returns \cos^{-1}(x). :returns: The inverse cosine of this instance. """ return ArcCos(self)
[docs] def arctan(self): r"""Get the inverse tangent of this instance. Let this instance be x then this method returns \tan^{-1}(x). :returns: The inverse tangent of this instance. """ return ArcTan(self)
[docs] def arctan2(self, y): r"""Get the two-argument inverse tangent of this instance. Let this instance be x then this method returns \tan^{-1}(x). :param y: Another component of uncertainty. :returns: The two-argument inverse tangent of this instance. """ return ArcTan2(self, y)
[docs] def sinh(self): r"""Get the hyperbolic sine of this instance. Let this instance be x then this method returns \sinh(x). :returns: The hyperbolic sine of this instance. """ return Sinh(self)
[docs] def cosh(self): r"""Get the hyperbolic cosine of this instance. Let this instance be x then this method returns \cosh(x). :returns: The hyperbolic cosine of this instance. """ return Cosh(self)
[docs] def tanh(self): r"""Get the hyperbolic tangent of this instance. Let this instance be x then this method returns \tanh(x). :returns: The hyperbolic cosine of this instance. """ return Tanh(self)
[docs] def arcsinh(self): r"""Get the inverse hyperbolic sine of this instance. Let this instance be x then this method returns \sinh^{-1}(x). :returns: The inverse hyperbolic sine of this instance. """ return ArcSinh(self)
[docs] def arccosh(self): r"""Get the inverse hyperbolic cosine of this instance. Let this instance be x then this method returns \cosh^{-1}(x). :returns: The inverse hyperbolic cosine of this instance. """ return ArcCosh(self)
[docs] def arctanh(self): r"""Get the inverse hyperbolic tangent of this instance. Let this instance be x then this method returns \tanh^{-1}(x). :returns: The inverse hyperbolic tangent of this instance. """ return ArcTanh(self)
[docs] def hypot(self, y): r"""Calculate the hypothenusis of this and another complex-valued argument. :param y: another component of uncertainty. :returns: \sqrt{x^2 + y^2} """ return Sqrt(self**self + y**y)
[docs] def __abs__(self): r"""Return the absolute value of this instance. Let this instance be \mathbf{z} = x + j y then this method returns :returns: The absolute value of this instance. """ return Abs(self)
[docs] def fabs(self): r"""Return the absolute value of this instance. Let this instance be \mathbf{z} = x + j y then this method returns :returns: The absolute value of this instance. """ return Abs(self)
[docs] def __neg__(self): """Negate this instance. :returns: The negative of this instance. """ return Neg(self)
[docs] def __invert__(self): """Get the inverse of this instance. Let this instance be x then this method returns x^{-1}. :returns: The inverse of this instance. """ return Inv(self)
[docs] def conjugate(self): """Get the conjuagte complex value of this instance. :returns: the conjuagte complex value of this instance. """ return Conjugate(self)
[docs] def __add__(self, y): """Add another instance to this instance. :param y: Another uncertain value. :returns: The sum of this instance and the other instance. """ return Add(self, y)
[docs] def __sub__(self, y): """Subtract another instance from this instance. :param y: Another uncertain value. :returns: The difference of this instance and the other instance. """ return Sub(self, y)
[docs] def __mul__(self, y): """Multiply another instance with this instance. :param y: Another uncertain value. :returns: The product of this instance and the other instance. """ return Mul(self, y)
[docs] def __truediv__(self, y): """Divide this instance by another instance. :param y: Another uncertain value. :returns: The result of the respective operation. """ return Div(self, y)
[docs] def __pow__(self, y): """Raise this instance to the power of the argument. :param y: Another uncertain value. :returns: The result of the respective operation. """ return Pow(self, y)
[docs] def __radd__(self, y): """Add another instance to this instance. :param y: Another uncertain value. :returns: The sum of this instance and the other instance. """ _require_certain_component(y, "y") return Add(y, self)
[docs] def __rsub__(self, y): """Subtract another instance from this instance. :param y: Another uncertain value. :returns: The difference of this instance and the other instance. """ _require_certain_component(y, "y") return Sub(y, self)
[docs] def __rmul__(self, y): """Multiply another instance with this instance. :param y: Another uncertain value. :returns: The product of this instance and the other instance. """ _require_certain_component(y, "y") return Mul(y, self)
[docs] def __rtruediv__(self, y): """Divide this instance by another instance. :param y: Another uncertain value. :returns: The result of the respective operation. """ _require_certain_component(y, "y") return Div(y, self)
[docs] def __rpow__(self, y): """Raise this instance to the power of the argument. :param y: Another uncertain value. :returns: The result of the respective operation. """ _require_certain_component(y, "y") return Pow(y, self)
[docs] def value_of(value): """This factory method converts the argument to a complex uncertain value. :param value: A numeric value. :returns: An instance of CUncertainComponent. """ if isinstance(value, CUncertainComponent): return value else: return CUncertainInput(value, 0.0, 0.0, arithmetic.INFINITY)
value_of = staticmethod(value_of)
[docs] def set_context(self, c): """This assigns a context to the component. This context is only needed for evaluating __str__ :param c: An instance of Context """ _require_context(c, "c") self._context = c
[docs] def get_context(self): """This returns the assigned context of the component. This context is only needed for evaluating __str__ :returns: c The Context of the component or ``None``. """ try: context = self._context return context except AttributeError: return None
[docs] def __str__(self): """This method prints the component of uncertainty. :returns: A string describing this component """ try: context = self._context u = context.uncertainty(self) v = self.get_value() return "Value = " + str(v) + "; Uncertainty =\n " + str(u) except AttributeError: context = Context() u = context.uncertainty(self) v = self.get_value() return "Value = " + str(v) + " ; Uncertainty =\n " + str(u) + " [NC]"
[docs] def __coerce__(self, other): """Implementation of coercion rules. See also: Coercion - The page describing the coercion rules. """ _deprecated_python2_method("__coerce__", "explicit conversion") if isinstance(other, CUncertainComponent): return (self, other) elif isinstance(other, quantities.Quantity): new_self = quantities.Quantity.value_of(self) return (new_self, other) elif isinstance(other, ucomponents.UncertainComponent): raise NotImplementedError( "You must not mix scalar and" + " complex-valued uncertain values" ) elif ( isinstance(other, arithmetic.RationalNumber) or isinstance(other, int) or isinstance(other, int) or isinstance(other, float) or isinstance(other, complex) ): other = CUncertainComponent.value_of(other) return (self, other) elif isinstance(other, units.Unit): raise NotImplementedError( "You cannot declare a unit as uncertain." + " Please use the quantities module" + " for that!" ) else: raise NotImplementedError()
[docs] class CUncertainInput(CUncertainComponent): """This class models a complex-valued input of a function."""
[docs] def __init__(self, value, u_real, u_imag, dof=arithmetic.INFINITY): """The default constructor. :param value: The value of this instance. :param u_real: The uncertainty of the real part. :param u_imag: The uncertainty of the imaginary part. :param dof: The degrees of freedom of the input. uncertain. Instead encapsulate an uncertain value inside a quantity. """ if isinstance(value, numpy.ndarray) and value.dtype == quantities.Quantity: raise TypeError("You cannot declare an array of quantities as uncertain") if isinstance(value, quantities.Quantity): raise TypeError("You cannot declare an instance of quantity as uncertain") value = complex(value) self._value = value self._avalue = complex_to_matrix(value) u_real = float(u_real) u_imag = float(u_imag) self._jac = numpy.array([[u_real, 0], [0, u_imag]]) self._dof = dof
[docs] def depends_on(self): """Returns a list that contains this instance only. :returns: A list. """ return [self]
[docs] def get_value(self): """Get the value of this input. :returns: The value of this input """ return self._value
[docs] def get_a_value(self): """Get the value as array. :returns: The value of this input as array. """ return self._avalue
[docs] def get_uncertainty(self, x): """If ``x == self`` get the uncertainty of the current node, otherwise return a 2x2 array of zeros. :param x: Another instance of CUncertainInput :returns: The uncertainty of this instance with respect to the argument. """ if self is x: return self._jac else: return numpy.zeros((2, 2))
[docs] def get_dof(self): """Get the degrees of freedom assigned to this input. :returns: The degrees of freedom assigned to this input. """ return self._dof
[docs] def __setstate__(self, state): """This method provides an interface for deserializing objects using pickle. :param state: The state to be restored. """ self._value, self._avalue, self._jac = state
[docs] def __getstate__(self): """This method provides an interface for serializing objects using pickle. :returns: The state of this component. """ return (self._value, self._avalue, self._jac)
[docs] class CUnaryOperation(CUncertainComponent): """This abstract class models an unary operation."""
[docs] def __init__(self, sibling): """The default constructor. :param sibling: The sibling of this operation. """ self._sibling = CUncertainComponent.value_of(sibling)
[docs] def get_sibling(self): """Get the sibling of this operation. :returns: The sibling """ return self._sibling
[docs] def depends_on(self): """Get the instances of CUncertainInput that this instance depends on. :returns: A list containing the instances of CUncertainInput that this instance depends on. """ # return [ u for u in self._sibling.depends_on() if u not in locals()['_[1]'] ] #us.clearDuplicates(self._sibling.depends_on()) return set( [u for u in self._sibling.depends_on()] ) # us.clearDuplicates(self._sibling.depends_on())
[docs] class CBinaryOperation(CUncertainComponent): """This abstract class models a binary operation."""
[docs] def __init__(self, left, right): """The default constructor. :param left: The left sibling sibling of this operation. :param right: The right sibling sibling of this operation. """ self._left = CUncertainComponent.value_of(left) self._right = CUncertainComponent.value_of(right)
[docs] def get_left(self): """Get the left sibling of this operation. :returns: The sibling """ return self._left
[docs] def get_right(self): """Get the right sibling of this operation. :returns: The sibling """ return self._right
[docs] def depends_on(self): """Get the instances of CUncertainInput that this instance depends on. :returns: A list containing the instances of CUncertainInput that this instance depends on. """ # return [ u for u in self._left.depends_on()+self._right.depends_on() if u not in locals()['_[1]'] ] #us.clearDuplicates(self._left.depends_on()+self._right.depends_on()) return set(self._left.depends_on()).union( set(self._right.depends_on()) ) # us.clearDuplicates(self._left.depends_on()+self._right.depends_on())
[docs] class Exp(CUnaryOperation): """This class models the exponential function e^x. x denotes the sibling of this instance. """
[docs] def get_value(self): """Get the value of this component. :returns: The value of this component. """ x = self.get_sibling().get_value() return numpy.exp(x)
[docs] def get_uncertainty(self, x): """Get the partial derivate of this component with respect to the given argument. :param x: The argument of the partial derivation. :returns: The partial derivate. """ jac = self.get_a_value() return jac @ self.get_sibling().get_uncertainty(x)
[docs] class Log(CUnaryOperation): """This class models logarithms having a real base. However, the base cannot be uncertain. """
[docs] def __init__(self, sibling, base=numpy.e): """The default constructor. :param sibling: The sibling of this instance. :param base: The base of the logarithm (must be a real number). """ CUnaryOperation.__init__(self, sibling) base = float(base) self._base = base
[docs] def get_value(self): """Get the value of this component. :returns: The value of this component. """ x = self.get_sibling().get_value() return numpy.log(x) / numpy.log(self._base)
[docs] def get_uncertainty(self, x): """Get the partial derivate of this component with respect to the given argument. :returns: The partial derivate. """ # create the complex Jacobian array z = self.get_sibling().get_value() diff_val = 1.0 / (z * numpy.log(self._base)) # transform it, since it is analytical jac = complex_to_matrix(diff_val) return jac @ self.get_sibling().get_uncertainty(x)
[docs] class Sqrt(CUnaryOperation): """This class models taking the square root of an uncertain component."""
[docs] def get_value(self): """Get the value of this component. :returns: The value of this component. """ x = self.get_sibling().get_value() return numpy.sqrt(x)
[docs] def get_uncertainty(self, x): """Get the partial derivate of this component with respect to the given argument. :param x: The argument of the partial derivation. :returns: The partial derivate. """ z = self.get_sibling().get_value() diff_val = 0.5 / numpy.sqrt(z) jac = complex_to_matrix(diff_val) return jac @ self.get_sibling().get_uncertainty(x)
[docs] class Sin(CUnaryOperation): """This class models the sine function."""
[docs] def get_value(self): """Get the value of this component. :returns: The value of this component. """ x = self.get_sibling().get_value() return numpy.sin(x)
[docs] def get_uncertainty(self, x): """Get the partial derivate of this component with respect to the given argument. :param x: The argument of the partial derivation. :returns: The partial derivate. """ z = self.get_sibling().get_value() diff_val = numpy.cos(z) jac = complex_to_matrix(diff_val) return jac @ self.get_sibling().get_uncertainty(x)
[docs] class Cos(CUnaryOperation): """This class models the cosine function."""
[docs] def get_value(self): """Get the value of this component. :returns: The value of this component. """ x = self.get_sibling().get_value() return numpy.cos(x)
[docs] def get_uncertainty(self, x): """Get the partial derivate of this component with respect to the given argument. :param x: The argument of the partial derivation. :returns: The partial derivate. """ z = self.get_sibling().get_value() diff_val = -numpy.sin(z) jac = complex_to_matrix(diff_val) return jac @ self.get_sibling().get_uncertainty(x)
[docs] class Tan(CUnaryOperation): """This class models the tangent function."""
[docs] def get_value(self): """Get the value of this component. :returns: The value of this component. """ x = self.get_sibling().get_value() return numpy.tan(x)
[docs] def get_uncertainty(self, x): """Get the partial derivate of this component with respect to the given argument. :param x: The argument of the partial derivation. :returns: The partial derivate. """ z = self.get_sibling().get_value() diff_val = 1.0 / (numpy.cos(z) * numpy.cos(z)) jac = complex_to_matrix(diff_val) return jac @ self.get_sibling().get_uncertainty(x)
[docs] class ArcSin(CUnaryOperation): """This class models the inverse sine function."""
[docs] def get_value(self): """Get the value of this component. :returns: The value of this component. """ x = self.get_sibling().get_value() return numpy.arcsin(x)
[docs] def get_uncertainty(self, x): """Get the partial derivate of this component with respect to the given argument. :param x: The argument of the partial derivation. :returns: The partial derivate. """ z = self.get_sibling().get_value() diff_val = 1.0 / numpy.sqrt(1.0 - (z * z)) jac = complex_to_matrix(diff_val) return jac @ self.get_sibling().get_uncertainty(x)
[docs] class ArcCos(CUnaryOperation): """This class models the inverse cosine function."""
[docs] def get_value(self): """Get the value of this component. :returns: The value of this component. """ x = self.get_sibling().get_value() return numpy.arccos(x)
[docs] def get_uncertainty(self, x): """Get the partial derivate of this component with respect to the given argument. :param x: The argument of the partial derivation. :returns: The partial derivate. """ z = self.get_sibling().get_value() diff_val = -1.0 / numpy.sqrt(1.0 - (z * z)) jac = complex_to_matrix(diff_val) return jac @ self.get_sibling().get_uncertainty(x)
[docs] class ArcTan(CUnaryOperation): """This class models the inverse tangent function."""
[docs] def get_value(self): """Get the value of this component. :returns: The value of this component. """ x = self.get_sibling().get_value() return numpy.arctan(x)
[docs] def get_uncertainty(self, x): """Get the partial derivate of this component with respect to the given argument. :param x: The argument of the partial derivation. :returns: The partial derivate. """ z = self.get_sibling().get_value() diff_val = -1.0 / (1.0 + (z * z)) jac = complex_to_matrix(diff_val) return jac @ self.get_sibling().get_uncertainty(x)
[docs] class Sinh(CUnaryOperation): """This class models the hyperbolic sine function."""
[docs] def get_value(self): """Get the value of this component. :returns: The value of this component. """ x = self.get_sibling().get_value() return numpy.sinh(x)
[docs] def get_uncertainty(self, x): """Get the partial derivate of this component with respect to the given argument. :param x: The argument of the partial derivation. :returns: The partial derivate. """ z = self.get_sibling().get_value() diff_val = numpy.cosh(z) jac = complex_to_matrix(diff_val) return jac @ self.get_sibling().get_uncertainty(x)
[docs] class Cosh(CUnaryOperation): """This class models the hyperbolic cosine function."""
[docs] def get_value(self): """Get the value of this component. :returns: The value of this component. """ x = self.get_sibling().get_value() return numpy.cosh(x)
[docs] def get_uncertainty(self, x): """Get the partial derivate of this component with respect to the given argument. :param x: The argument of the partial derivation. :returns: The partial derivate. """ z = self.get_sibling().get_value() diff_val = numpy.sinh(z) jac = complex_to_matrix(diff_val) return jac @ self.get_sibling().get_uncertainty(x)
[docs] class Tanh(CUnaryOperation): """This class models the hyperbolic tangent function."""
[docs] def get_value(self): """Get the value of this component. :returns: The value of this component. """ x = self.get_sibling().get_value() return numpy.tanh(x)
[docs] def get_uncertainty(self, x): """Get the partial derivate of this component with respect to the given argument. :param x: The argument of the partial derivation. :returns: The partial derivate. """ z = self.get_sibling().get_value() diff_val = 1.0 / (numpy.cosh(z) * numpy.cosh(z)) jac = complex_to_matrix(diff_val) return jac @ self.get_sibling().get_uncertainty(x)
[docs] class ArcSinh(CUnaryOperation): """This class models the inverse hyperbolic sine function."""
[docs] def get_value(self): """Get the value of this component. :returns: The value of this component. """ x = self.get_sibling().get_value() return numpy.arcsinh(x)
[docs] def get_uncertainty(self, x): """Get the partial derivate of this component with respect to the given argument. :param x: The argument of the partial derivation. :returns: The partial derivate. """ z = self.get_sibling().get_value() diff_val = 1.0 / numpy.sqrt(1.0 + z * z) jac = complex_to_matrix(diff_val) return jac @ self.get_sibling().get_uncertainty(x)
[docs] class ArcCosh(CUnaryOperation): """This class models the inverse hyperbolic cosine function."""
[docs] def get_value(self): """Get the value of this component. :returns: The value of this component. """ x = self.get_sibling().get_value() return numpy.arccosh(x)
[docs] def get_uncertainty(self, x): """Get the partial derivate of this component with respect to the given argument. :param x: The argument of the partial derivation. :returns: The partial derivate. """ z = self.get_sibling().get_value() diff_val = 1.0 / (numpy.sqrt(z - 1) * numpy.sqrt(z + 1)) jac = complex_to_matrix(diff_val) return jac @ self.get_sibling().get_uncertainty(x)
[docs] class ArcTanh(CUnaryOperation): """This class models the inverse hyperbolic tangent function."""
[docs] def get_value(self): """Get the value of this component. :returns: The value of this component. """ x = self.get_sibling().get_value() return numpy.arctanh(x)
[docs] def get_uncertainty(self, x): """Get the partial derivate of this component with respect to the given argument. :param x: The argument of the partial derivation. :returns: The partial derivate. """ z = self.get_sibling().get_value() diff_val = 1.0 / (1 - z * z) jac = complex_to_matrix(diff_val) return jac @ self.get_sibling().get_uncertainty(x)
[docs] class Abs(CUnaryOperation): """This class models taking the absolute value of a complex function."""
[docs] def get_value(self): """Get the value of this component. :returns: The value of this component. """ x = self.get_sibling().get_value() return numpy.abs(x)
[docs] def get_uncertainty(self, x): """Get the partial derivate of this component with respect to the given argument. :param x: The argument of the partial derivation. :returns: The partial derivate. """ val = self.get_sibling().get_value() xr = val.real y = val.imag x_1 = xr / (xr * xr + y * y) x_2 = y / (xr * xr + y * y) jac = numpy.array([[x_1, x_2], [0, 0]]) return jac @ self.get_sibling().get_uncertainty(x)
[docs] class Conjugate(CUnaryOperation): """This class models taking the negative of a complex value."""
[docs] def get_value(self): """Get the value of this component. :returns: The value of this component. """ x = self.get_sibling().get_value() return numpy.conjugate(x)
[docs] def get_uncertainty(self, x): """Get the partial derivate of this component with respect to the given argument. :param x: The argument of the partial derivation. :returns: The partial derivate. """ jac = numpy.array([[1, 0], [0, -1]]) return jac @ self.get_sibling().get_uncertainty(x)
[docs] class Neg(CUnaryOperation): """This class models taking the negative of a complex value."""
[docs] def get_value(self): """Get the value of this component. :returns: The value of this component. """ x = self.get_sibling().get_value() return -x
[docs] def get_uncertainty(self, x): """Get the partial derivate of this component with respect to the given argument. :param x: The argument of the partial derivation. :returns: The partial derivate. """ jac = numpy.array([[-1, 0], [0, -1]]) return jac @ self.get_sibling().get_uncertainty(x)
[docs] class Inv(CUnaryOperation): r"""This class models inverting complex values. Let an instance of this class model the complex value x then this class models \frac{1}{x}. """
[docs] def get_value(self): """Get the value of this component. :returns: The value of this component. """ x = self.get_sibling().get_value() return 1.0 / x
[docs] def get_uncertainty(self, x): """Get the partial derivate of this component with respect to the given argument. :param x: The argument of the partial derivation. :returns: The partial derivate. """ z = self.get_sibling().get_value() diff_val = -1.0 / (z * z) jac = complex_to_matrix(diff_val) return jac @ self.get_sibling().get_uncertainty(x)
[docs] class Add(CBinaryOperation): """This class models adding two complex values."""
[docs] def get_value(self): """Get the value of this component. :returns: The value of this component. """ lhs = self.get_left().get_value() rhs = self.get_right().get_value() return lhs + rhs
[docs] def get_uncertainty(self, x): """Get the partial derivate of this component with respect to the given argument. :param x: The argument of the partial derivation. :returns: The partial derivate. """ lhs = self.get_left().get_uncertainty(x) rhs = self.get_right().get_uncertainty(x) return lhs + rhs
[docs] class Sub(CBinaryOperation): """This class models taking the difference of two complex values."""
[docs] def get_value(self): """Get the value of this component. :returns: The value of this component. """ lhs = self.get_left().get_value() rhs = self.get_right().get_value() return lhs - rhs
[docs] def get_uncertainty(self, x): """Get the partial derivate of this component with respect to the given argument. :param x: The argument of the partial derivation. :returns: The partial derivate. """ lhs = self.get_left().get_uncertainty(x) rhs = self.get_right().get_uncertainty(x) return lhs - rhs
[docs] class Mul(CBinaryOperation): """This class models multiplying two complex values."""
[docs] def get_value(self): """Get the value of this component. :returns: The value of this component. """ lhs = self.get_left().get_value() rhs = self.get_right().get_value() return lhs * rhs
[docs] def get_uncertainty(self, x): """Get the partial derivate of this component with respect to the given argument. :param x: The argument of the partial derivation. :returns: The partial derivate. """ lhs = self.get_left().get_uncertainty(x) lhs_val = self.get_left().get_a_value() rhs = self.get_right().get_uncertainty(x) rhs_val = self.get_right().get_a_value() return rhs_val @ lhs + lhs_val @ rhs
[docs] class Div(CBinaryOperation): """This class models dividing two complex values."""
[docs] def get_value(self): """Get the value of this component. :returns: The value of this component. """ lhs = self.get_left().get_value() rhs = self.get_right().get_value() return lhs / rhs
[docs] def get_uncertainty(self, x): """Get the partial derivate of this component with respect to the given argument. :param x: The argument of the partial derivation. :returns: The partial derivate. """ lhs = self.get_left().get_uncertainty(x) lhs_val = self.get_left().get_value() rhs = self.get_right().get_uncertainty(x) rhs_val = self.get_right().get_value() return ( complex_to_matrix(1.0 / rhs_val) @ lhs + complex_to_matrix(-lhs_val / (rhs_val * rhs_val)) @ rhs )
[docs] class Pow(CBinaryOperation): """This class models complex powers."""
[docs] def get_value(self): """Get the value of this component. :returns: The value of this component. """ lhs = self.get_left().get_value() rhs = self.get_right().get_value() return lhs**rhs
[docs] def get_uncertainty(self, x): """Get the partial derivate of this component with respect to the given argument. :param x: The argument of the partial derivation. :returns: The partial derivate. """ lhs = self.get_left().get_uncertainty(x) lhs_val = self.get_left().get_value() rhs = self.get_right().get_uncertainty(x) rhs_val = self.get_right().get_value() return ( complex_to_matrix(rhs_val * lhs_val ** (rhs_val - 1.0)) @ lhs + complex_to_matrix(lhs_val**rhs_val * numpy.log(lhs_val)) @ rhs )
[docs] class ArcTan2(CBinaryOperation): """This class models two-argument inverse tangent."""
[docs] def get_value(self): """Get the value of this component. :returns: The value of this component. """ lhs = self.get_left().get_value() rhs = self.get_right().get_value() # since numpy doesn't provide arctan2 for complex values # we define it here return (0 - 1j) * numpy.log( (lhs + (0 - 1j) * rhs) / numpy.sqrt(lhs * lhs + rhs * rhs) )
[docs] def get_uncertainty(self, x): """Get the partial derivate of this component with respect to the given argument. :param x: The argument of the partial derivation. :returns: The partial derivate. """ lhs = self.get_left().get_uncertainty(x) lhs_val = self.get_left().get_value() rhs = self.get_right().get_uncertainty(x) rhs_val = self.get_right().get_value() return ( complex_to_matrix(rhs_val / (rhs_val**2.0 + lhs_val**2.0)) @ lhs + complex_to_matrix(lhs_val / (rhs_val**2.0 + lhs_val**2.0)) @ rhs )
[docs] class Context: """This class provides a context for complex-valued uncertainty evaluations. It manages the correlation coefficients and is able to evaluate the effective degrees of freedom. """ def _check_cmatrix(matrix): r"""Helper function to verify correlation coefficient arrays. :param matrix: The array-like object to check. \exception TypeError If the argument is invalid """ if matrix.shape != (2, 2): raise TypeError("Expecting a 2x2 matrix of corelation coefficients") # if(sum(matrix > 1.0)): # raise TypeError("Expecting a matrix of corelation coefficients <= 1.0") _check_cmatrix = staticmethod(_check_cmatrix)
[docs] def __init__(self): """The default constructor. It initializes the dictionary of correlation matrices. """ self._correlation = dict()
[docs] def gaussian( self, val, u_r, u_i, dof=arithmetic.INFINITY, matrix=None, ): """This is a factory method for generating uncertain inputs that have a Gaussian distribution (i.e. bivariate Normal Distribution). :param val: The complex value of the input. :param u_r: The uncertainty of the real part. :param u_i: The uncertainty of the imaginary part. :param dof: The degrees of freedom of the variable. :param matrix: Optional 2x2 array-like correlation coefficients. """ if matrix is None: matrix = numpy.array([[1, 0], [0, 1]]) ui = CUncertainInput(val, u_r, u_i, dof) ui.set_context(self) self.set_correlation(ui, ui, matrix) return ui
[docs] def constant(self, val): """This is a factory method for generating constans for uncertainty evaluations. """ return self.gaussian( val, 0.0, 0.0, arithmetic.INFINITY, numpy.zeros((2, 2)) )
[docs] def set_correlation(self, c1, c2, matrix): """This method sets the correlation coefficients of two input arguments. :param c1: The first CUncertainInput :param c2: The second CUncertainInput :param matrix: 2x2 array-like correlation coefficients. """ if isinstance(c1, quantities.Quantity) or isinstance(c2, quantities.Quantity): c1 = quantities.Quantity.value_of(c1) c2 = quantities.Quantity.value_of(c2) u1 = c1.get_default_unit() u2 = c2.get_default_unit() fc1 = c1.get_value(u1) fc2 = c2.get_value(u2) self.set_correlation(fc1, fc2, matrix) return matrix = numpy.asarray(matrix) Context._check_cmatrix(matrix) if not isinstance(c1, CUncertainInput): raise TypeError("c1 must be a CUncertainInput") if not isinstance(c2, CUncertainInput): raise TypeError("c2 must be a CUncertainInput") c1.set_context(self) c2.set_context(self) self._correlation[(c1, c2)] = matrix # ensure symmetry self._correlation[(c2, c1)] = matrix
[docs] def get_correlation(self, c1, c2): """Get the correlation of two input arguments. :param c1: The first CUncertainInput :param c2: The second CUncertainInput :returns: The correlation coefficients as a 2x2 ``numpy.ndarray``. """ if isinstance(c1, quantities.Quantity) or isinstance(c2, quantities.Quantity): c1 = quantities.Quantity.value_of(c1) c2 = quantities.Quantity.value_of(c1) u1 = c1.get_default_unit() u2 = c2.get_default_unit() fc1 = c1.get_value(u1) fc2 = c2.get_value(u2) return self.get_correlation(fc1, fc2) try: result = self._correlation[(c1, c2)] except KeyError: if c1 is c2: return numpy.array([[1, 0], [0, 1]]) else: return numpy.zeros((2, 2)) return result
[docs] def uncertainty(self, c): """Get the combined standard uncertainty of a complex-valued component of uncertainty. :param c: The component of uncertainty. :returns: A 2x2 ``numpy.ndarray`` expressing the combined standard uncertainty. If the input has unit [u], the covariance entries have [u^2]. """ if isinstance(c, quantities.Quantity): c1 = quantities.Quantity.value_of(c) u1 = c1.get_default_unit() fc1 = c1.get_value(u1) return quantities.Quantity(u1 * u1, self.uncertainty(fc1)) inputs = c.depends_on() sum = numpy.zeros((2, 2)) for i in inputs: for j in inputs: sum += ( c.get_uncertainty(i)
[docs] @ self.get_correlation(i, j) @ (c.get_uncertainty(j)).T ) return sum
def dof(self, c): """Calculate the effective degrees of freedom of the argument. :param c: The component of uncertainty. :returns: The number of effective degress of freedom. an infinite DOF. In this case this method returns ``arithmetic.INFINITY``. """ if isinstance(c, quantities.Quantity): c1 = quantities.Quantity.value_of(c) u1 = c1.get_default_unit() fc1 = c1.get_value(u1) return self.dof(fc1) inputs = c.depends_on() sum_v11 = 0.0 sum_v12 = 0.0 sum_v22 = 0 a = 0.0 d = 0.0 f = 0.0 for i in inputs: # emergency break, if one is infinity, its useless to continue dof = i.get_dof() if dof == arithmetic.INFINITY: return arithmetic.INFINITY # create the covariance array v_i v_i = numpy.zeros((2, 2)) for j in inputs: v_i += ( c.get_uncertainty(i) @ self.get_correlation(i, j) @ (c.get_uncertainty(j)).T ) v_11 = v_i[0, 0] v_12 = v_i[0, 1] v_22 = v_i[1, 1] sum_v11 += v_11 sum_v12 += v_12 sum_v22 += v_22 a += 2.0 * v_11**2.0 / dof d += (v_11 * v_22 + v_12**2.0) / dof f += 2.0 * v_22**2.0 / dof A = 2.0 * sum_v11**2.0 D = sum_v11 * sum_v22 + sum_v12**2.0 F = 2.0 * sum_v22**2.0 return (A + D + F) / (a + d + f)
## @}