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