text: from __future__ import division
  
  import math
  import cmath
  import numbers
  import warnings
  
  try:
      from itertools import izip  # Python 2
  except ImportError:
      izip = zip
  
  from GTC import lib
  from GTC import reporting
  from GTC import type_b
  from GTC import type_a
  from GTC import persistence
  from GTC import function
  
  from GTC import (   
      inf,
      nan,
      is_sequence,
      copyright,
      version
  )
  
  # aliases 
  rp = reporting
  tb = type_b
  ta = type_a
  fn = function
  pr = persistence
  
  value = type_b.value
  
  UncertainReal = lib.UncertainReal
  UncertainComplex = lib.UncertainComplex
  
  __all__ = (
          'ureal'
      ,   'multiple_ureal'
      ,   'multiple_ucomplex'
      ,   'ucomplex'
      ,   'constant'
      ,   'value'
      ,   'uncertainty'
      ,   'variance'
      ,   'dof'
      ,   'label'
      ,   'component'
      ,   'inf'
      ,   'nan'
      ,   'get_covariance'
      ,   'get_correlation'
      ,   'set_correlation'
      ,   'result'
      ,   'cos'
      ,   'sin'
      ,   'tan'
      ,   'acos'
      ,   'asin'
      ,   'atan'
      ,   'atan2'
      ,   'exp'
      ,   'pow'
      ,   'log'
      ,   'log10'
      ,   'sqrt'
      ,   'sinh'
      ,   'cosh'
      ,   'tanh'
      ,   'acosh'
      ,   'asinh'
      ,   'atanh'
      ,   'mag_squared'
      ,   'magnitude'
      ,   'phase'
      ,   'copyright'
      ,   'version'
      ,   'reporting',    'rp'
      ,   'function',     'fn'
      ,   'type_b',       'tb'
      ,   'type_a',       'ta'
      ,   'persistence',  'pr'
      ,   'linear_algebra', 'la'
      ,   'math'
      ,   'cmath'
  )
  
  
  # #----------------------------------------------------------------------------
  # def value(x):
      # """Return the value 
      
      # Returns a complex number if ``x`` is an uncertain complex number
      
      # Returns a real number if ``x`` is an uncertain real number
      
      # Returns ``x`` otherwise.
  
      # **Example**::
  
          # >>> un = ureal(3,1)
          # >>> value(un)
          # 3.0
          # >>> un.x
          # 3.0
  
      # """
      # try:
          # return x.x
      # except AttributeError:
          # return x
      
  #----------------------------------------------------------------------------
  def uncertainty(x):
      """
      Return the standard uncertainty
  
      If ``x`` is an uncertain complex number,
      return a 2-element sequence containing the standard
      uncertainties of the real and imaginary components. 
      
      If ``x`` is an uncertain real number,
      return the standard uncertainty.
      
      Otherwise, return 0.    
  
      **Examples**::
  
          >>> ur = ureal(2.5,0.5,3,label='x')
          >>> uncertainty(ur)
          0.5
          >>> ur.u
          0.5
          
          >>> uc = ucomplex(1+2j,(.5,.5),3,label='x')
          >>> uncertainty(uc)
          StandardUncertainty(real=0.5, imag=0.5)
      
      """
      try:
          return x.u
      except AttributeError:
          return 0.0
      
  #----------------------------------------------------------------------------
  def variance(x):
      """
      Return the standard variance 
  
      If ``x`` is an uncertain real number, return the
      standard variance.
      
      If ``x`` is an uncertain complex number, return
      a 4-element sequence containing elements of the
      variance-covariance matrix.
      
      Otherwise, return 0.    
      
      **Examples**::
  
          >>> ur = ureal(2.5,0.5,3,label='x')
          >>> variance(ur)
          0.25
          >>> ur.v
          0.25
          
          >>> uc = ucomplex(1+2j,(.5,.5),3,label='x')
          >>> variance(uc)
          VarianceCovariance(rr=0.25, ri=0.0, ir=0.0, ii=0.25)
      
      """
      try:
          return x.v
      except AttributeError:
          return 0.0
      
  #----------------------------------------------------------------------------
  def dof(x):
      """
      Return the degrees-of-freedom 
  
      Returns ``inf`` when the degrees of freedom is greater than 1E6
      
      **Examples**::
  
          >>> ur = ureal(2.5,0.5,3,label='x')
          >>> dof(ur)
          3.0
          >>> ur.df
          3.0
          
          >>> uc = ucomplex(1+2j,(.3,.2),3,label='x')
          >>> dof(uc)
          3.0
      
      """
      try:
          return x.df
      except AttributeError:
          return inf
  
  #---------------------------------------------------------------------------
  def component(y,x):
      """
      Return the magnitude of the component of uncertainty 
      in ``y`` due to ``x``.
  
      :arg y: an uncertain number
      :type y: :class:`~lib.UncertainReal` or :class:`~lib.UncertainComplex`
  
      :arg x: an uncertain number
      :type x: :class:`~lib.UncertainReal` or :class:`~lib.UncertainComplex`
  
      :rtype: float
      
      If ``x`` and ``y`` are uncertain real, the function calls 
      :func:`reporting.u_component` and returns the magnitude 
      of the result. 
      
      If either ``x`` or ``y`` is uncertain complex,
      the returned value represents the magnitude 
      of the component of uncertainty matrix (this is 
      obtained by applying :func:`reporting.u_bar`    
      to the result obtained from :func:`reporting.u_component`).
  
      If either ``x`` or ``y`` is a number, zero is returned.
      
      ``component`` can also e used in conjunction with :func:`~core.result` 
      to evaluate a component of uncertainty with respect to an 
      intermediate uncertain number. 
      
      **Examples**::
      
          >>> x1 = ureal(2,1)
          >>> x2 = ureal(5,1)
          >>> y = x1/x2
          >>> reporting.u_component(y,x2)
          -0.08
          >>> component(y,x2)
          0.08
          
          >>> z1 = ucomplex(1+2j,1)
          >>> z2 = ucomplex(3-2j,1)
          >>> y = z1 - z2
          >>> reporting.u_component(y,z2)
          ComponentOfUncertainty(rr=-1.0, ri=0.0, ir=0.0, ii=-1.0)
          >>> component(y,z2)
          1.0
  
          >>> I = ureal(1E-3,1E-5)
          >>> R = ureal(1E3,1)
          >>> V = result( I*R )
          >>> P = V**2/R  
          >>> component(P,V)   
          2.0099751242241783e-05
          
      """
      return reporting.u_bar( y.u_component(x) )
              
  #----------------------------------------------------------------------------
  def label(x):
      """
      Return the label
      
      """
      return x.label 
      
  #----------------------------------------------------------------------------
  def ureal(x,u,df=inf,label=None,independent=True):
      """
      Create an elementary uncertain real number
  
      :arg x: the value (estimate)
      :type x: float
      
      :arg u: the standard uncertainty
      :type u: float
      
      :arg df: the degrees-of-freedom
      :type df: float
      
      :arg label: a string label 
      :type label: str 
      
      :arg independent: not correlated with other UNs
      :type independent: bool
      
      :rtype: :class:`~lib.UncertainReal`
      
      **Example**::
      
          >>> ur = ureal(2.5,0.5,3,label='x')
          >>> ur
          ureal(2.5,0.5,3.0, label='x')
      
      """    
      # Arguments to these math functions must be compatible with float
      if math.isnan(x) or math.isinf(x):
          raise ValueError("invalid: '{!r}'".format(x) )
  
      if u < 0 or math.isinf(u) or math.isnan(u):
          raise ValueError("invalid uncertainty: '{!r}'".format(u) )
  
      # inf is allowed, but not nan
      if df < 1 or math.isnan(df):
          raise ValueError("invalid dof: '{!r}'".format(df) )
      
      if u == 0:
          # Is this what we want? Perhaps not.
          return UncertainReal._constant(float(x),label)
  
      else:
          return UncertainReal._elementary(
              float(x),
              float(u),
              float(df),
              label,
              independent
          )
          
  #---------------------------------------------------------------------------
  # TODO: think of a better name! Perhaps `ureal_ensemble`
  def multiple_ureal(x_seq,u_seq,df,label_seq=None):
      """Return a sequence of related elementary uncertain real numbers
  
      :arg x_seq: a sequence of values (estimates)
      :arg u_seq: a sequence of standard uncertainties
      :arg df: the degrees-of-freedom 
      :arg label_seq: a sequence of labels
      
      :rtype: a sequence of :class:`~lib.UncertainReal`
  
      Defines an set of uncertain real numbers with 
      the same number of degrees-of-freedom.
      
      Correlation between any pairs of this set of uncertain  
      numbers defined will not invalidate degrees-of-freedom 
      calculations.
      (see: R Willink, *Metrologia* 44 (2007) 340-349, Sec. 4.1)
      
      **Example**::
      
          # Example from GUM-H2
          >>> x = [4.999,19.661E-3,1.04446]
          >>> u = [3.2E-3,9.5E-6,7.5E-4]
          >>> labels = ['V','I','phi']
          >>> v,i,phi = multiple_ureal(x,u,4,labels)
     
          >>> set_correlation(-0.36,v,i)
          >>> set_correlation(0.86,v,phi)
          >>> set_correlation(-0.65,i,phi)
  
          >>> r = v/i*cos(phi)
          >>> r
          ureal(127.732169928102...,0.0699787279883717...,4.0)
  
      """
      if len(x_seq) != len(u_seq):
          raise RuntimeError(
              "unequal length sequences: x={!r} u={!r}".format(x_seq,u_seq)
          )
  
      if label_seq is None:
          label_seq = [None]*len(x_seq)
      elif is_sequence(label_seq):
          if len(x_seq) != len(label_seq):
              raise RuntimeError(
                  "unequal length sequences: x={!r} label_seq={!r}".format(x_seq,u_seq)
              )
      else:
          raise RuntimeError(
              "invalid `label_seq`: {!r}".format(label_seq)
          )
          
      rtn = [
          # NB `ureal` creates constant objects when u == 0
          ureal(x_i,u_i,df,label=l_i,independent=False)
              for x_i,u_i,l_i in izip(
                  x_seq,u_seq,label_seq
              )
      ]
  
      # Only non-constant UNs can be collected in an ensemble
      lib.real_ensemble( 
          [ un_i 
              for un_i in rtn 
                  if not lib._is_uncertain_real_constant(un_i) 
          ], df 
      )
  
      # All uncertain numbers are returned, including the constants
      return rtn
  
  #----------------------------------------------------------------------------
  def constant(x,label=None):
      """
      Create a constant uncertain number (with no uncertainty)
  
      :arg x: a number
      :type x: float or complex
  
      :rtype: :class:`~lib.UncertainReal` or :class:`~lib.UncertainComplex`
  
      If ``x`` is complex, return an uncertain complex number.
      
      If ``x`` is real return an uncertain real number.
          
      **Example**::
      
          >>> e = constant(math.e,label='Euler')
          >>> e
          ureal(2.718281828459045,0.0,inf, label='Euler')
      
      """
      if isinstance(x,numbers.Real):
          return UncertainReal._constant(x,label)
      elif isinstance(x,numbers.Complex):
          return UncertainComplex._constant(x,label)
      else:
          raise TypeError(
              "Cannot make a constant: {!r}".format( x )
          )
    
  #----------------------------------------------------------------------------
  def result(un,label=None):
      """
      Create an uncertain number to represent an intermediate result
  
      :arg un: :class:`~lib.UncertainReal` or :class:`~lib.UncertainComplex` or :class:`.UncertainArray`
      :arg label: str or a sequence of str
      :rtype: :class:`~lib.UncertainReal` or :class:`~lib.UncertainComplex` or :class:`.UncertainArray`
          
      When ``un`` is an array, an :class:`.UncertainArray` is returned  
      containing the intermediate uncertain number objects.
  
      The component of uncertainty, or the sensitivity, of an uncertain number 
      with respect to an intermediate result can be evaluated. 
  
      Declaring intermediate results also enables uncertain 
      numbers to be stored in an archive.
          
      .. note::
      
          This function will usually be applied to a temporary object.
  
      .. note::
      
          Applying this function to an elementary uncertain number does not create 
          an intermediate intermediate result. If a label is already assigned to the 
          elementary uncertain number, it will not be changed and a warning 
          will be raised, otherwise :func:`~core.result` will assign a label.
      
      **Example**::
  
          >>> I = ureal(1.3E-3,0.01E-3)
          >>> R = ureal(995,7)
          >>> V = result( I*R )
          >>> P = V**2/R
          >>> component(P,V)
          3.505784505642068e-05  
      
      """
      if hasattr(un,'_intermediate'):
          return un._intermediate(label)
          
      elif isinstance(un,numbers.Complex):
          # This covers any pure number type
          if label is not None:
              warnings.warn(
                  "A label cannot be applied to a pure number by result():"
                  " the label {!r} has been ignored".format(label),
                  RuntimeWarning
              )
          
          return un
          
      else:
          raise TypeError(
              "undefined for {!r}'".format(un)
          )
            
  
  #----------------------------------------------------------------------------
  def ucomplex(z,u,df=inf,label=None,independent=True):
      """
      Create an elementary uncertain complex number
  
      :arg z: the value (estimate)
      :type z: complex
  
      :arg u: the standard uncertainty or variance
      :type u: float, 2-element or 4-element sequence
  
      :arg df: the degrees-of-freedom
      :type df: float
  
      :type label: str 
      
      :rtype: :class:`~lib.UncertainComplex`
      :raises: :exc:`ValueError` if ``df`` or ``u`` have illegal values.
  
      ``u`` can be a float, a 2-element or 4-element sequence.
  
      If ``u`` is a float, the standard uncertainty in both
      the real and imaginary components is taken to be ``u``.
  
      If ``u`` is a 2-element sequence, the first element is
      taken to be the standard uncertainty in the real component 
      and the second element is taken to be the standard 
      uncertainty in the imaginary component.
  
      If ``u`` is a 4-element sequence, the sequence is 
      interpreted as a variance-covariance matrix.
  
      **Examples**::
  
          >>> uc = ucomplex(1+2j,(.5,.5),3,label='x')
          >>> uc
          ucomplex((1+2j), u=[0.5,0.5], r=0.0, df=3.0, label=x)
     
      >>> cv = (1.2,0.7,0.7,2.2)
      >>> uc = ucomplex(0.2-.5j, cv)
      >>> variance(uc)
      VarianceCovariance(rr=1.1999999999999997, ri=0.7, ir=0.7, ii=2.2)
      
      """
      # Arguments to these math functions must be compatible with float
      # otherwise a TypeError is raised by Python
      if cmath.isnan(z) or cmath.isinf(z):
          raise ValueError("invalid: '{!r}'".format(z) )
          
      if df < 1 or math.isnan(df):
          raise ValueError("invalid dof: '{!r}'".format(df) )
          
      if is_sequence(u):
      
          case = len(u)
          
          if case == 2:
              u_r = float(u[0])
              u_i = float(u[1])
              r = None
  
          elif case == 4:
              u_r,cv1,cv2,u_i = u
  
              # nan != nan is True
              if math.isinf(cv1) or cv1 != cv2:
                  raise ValueError(
                      "covariance elements not equal: {!r} and {!r}".format(cv1,cv2) 
                  )
              u_r = math.sqrt(u_r)
              u_i = math.sqrt(u_i)
              r = cv1 / (u_r*u_i) if cv1 != 0 else None
  
              # Allow a little tolerance for numerical imprecision
              if r is not None and abs(r) > 1 + 1E-10:
                  raise ValueError(
                      "invalid correlation: {!r}, cv={}".format(r,u)
                  )
              if r is not None:
                  # This overrides an initial assignment
                  independent = False
                  
          else:
              raise ValueError(
                  "invalid uncertainty sequence: '{!r}'".format(u)
              )
          
      elif not math.isinf(u) and not math.isnan(u):
          u_r = u_i = float(u)
          r = None
      else:
          raise TypeError("invalid uncertainty: '{!r}'".format(u) )
  
      # Checking of valid uncertainty values
      # Note, comparisons with nan are always false
      if not( 0 <= u_r and u_r < inf ):
          raise ValueError("invalid real uncertainty: '{!r}'".format(u_r) )
  
      if not ( 0 <= u_i and u_i < inf ):
          raise ValueError("invalid imag uncertainty: '{!r}'".format(u_i) )
          
      # TODO: is this what we want? Perhaps not!
      if u_r == 0 and u_i == 0:
          return UncertainComplex._constant(complex(z),label)
      else:
          return UncertainComplex._elementary(
              complex(z),
              u_r,u_i,r,
              float(df),
              label,
              independent
          )
          
  #---------------------------------------------------------------------------
  # TODO: think of a better name! Perhaps `ucomplex_ensemble`
  def multiple_ucomplex(x_seq,u_seq,df,label_seq=None):
      """Return a sequence of uncertain complex numbers
  
      :arg x_seq: a sequence of complex values
      :arg u_seq: a sequence of standard uncertainties or covariances
      :arg df: the degrees-of-freedom
      :arg label_seq: a sequence of labels for the uncertain numbers
  
      :rtype: a sequence of :class:`~lib.UncertainComplex`
      
      This function defines an set of uncertain complex
      numbers with the same number of degrees-of-freedom.
      
      Correlation between any pairs of these uncertain  
      numbers will not invalidate degrees-of-freedom calculations.
      (see: R Willink, *Metrologia* 44 (2007) 340-349, Sec. 4.1)
      
      **Example**::
      
          # GUM Appendix H2
          >>> values = [4.999+0j,0.019661+0j,1.04446j]
          >>> uncert = [(0.0032,0.0),(0.0000095,0.0),(0.0,0.00075)]
          >>> v,i,phi = multiple_ucomplex(values,uncert,5)
          
          >>> set_correlation(-0.36,v.real,i.real)
          >>> set_correlation(0.86,v.real,phi.imag)
          >>> set_correlation(-0.65,i.real,phi.imag)
          
          >>> z = v * exp(phi)/ i
          >>> print(z)
          (+127.732(70)+219.847(296)j)
          >>> z.r
          -0.591484610818998...
  
      """
      if len(x_seq) != len(u_seq):
          raise RuntimeError(
              "unequal length sequences: x={!r} u={!r}".format(x_seq,u_seq)
          )
   
      if label_seq is None:
          label_seq = [None]*len(x_seq)
      elif is_sequence(label_seq):
          if len(x_seq) != len(label_seq):
              raise RuntimeError(
                  "unequal length sequences: x={!r} label_seq={!r}".format(x_seq,u_seq)
              )
      else:
          raise RuntimeError(
              "invalid `label_seq`: {!r}".format(label_seq)
          )
   
      rtn = [
          # When u_i == 0 constant objects are created
          ucomplex(x_i,u_i,df,label=l_i,independent=False)
              for x_i,u_i,l_i in izip(
                  x_seq,u_seq,label_seq
              )
      ]
  
      # Only non-constant UNs can be collected in an ensemble
      lib.complex_ensemble(
          [ 
              un_i for un_i in rtn
                  if not lib._is_uncertain_complex_constant(un_i) 
          ], 
          df
      )
  
      # All uncertain numbers are returned, including the constants
      return rtn
  
  #----------------------------------------------------------------------------
  def set_correlation(r,arg1,arg2=None):
      """Set correlation between elementary uncertain numbers
  
      The input arguments can be a pair of uncertain numbers
      (the same type, real or complex), or a single
      uncertain complex number.
      
      The uncertain number arguments must be elementary 
      uncertain numbers.
      
      If the arguments have finite degrees of 
      freedom, they must be declared together using either 
      :func:`~core.multiple_ureal` or :func:`~multiple_ucomplex`.
      
      If the uncertain number arguments have infinite degrees of 
      freedom they can, alternatively, be declared by setting the 
      argument `independent=False` when calling 
      :func:`~ureal` or :func:`~ucomplex`.
          
      A :exc:`ValueError` is raised when illegal arguments are used
  
      When a pair of uncertain real numbers is provided,
      ``r`` is the correlation coefficient between them. 
      
      When a pair of uncertain complex number arguments is provided,
      ``r`` must be a 4-element sequence containing correlation
      coefficients between the components of the complex quantities.
      
      **Examples**::
  
          >>> x1 = ureal(2,1,independent=False)
          >>> x2 = ureal(5,1,independent=False)
          >>> set_correlation(.3,x1,x2)
          >>> get_correlation(x1,x2)
          0.3
  
          >>> z = ucomplex(1+0j,(1,1),independent=False)
          >>> z
          ucomplex((1+0j), u=[1.0,1.0], r=0.0, df=inf)
          >>> set_correlation(0.5,z)
          >>> z
          ucomplex((1+0j), u=[1.0,1.0], r=0.0, df=inf)
  
          >>> x1 = ucomplex(1,(1,1),independent=False)
          >>> x2 = ucomplex(1,(1,1),independent=False)
          >>> correlation_mat = (0.25,0.5,0.75,0.5)
          >>> set_correlation(correlation_mat,x1,x2)
          >>> get_correlation(x1,x2)
          CorrelationMatrix(rr=0.25, ri=0.5, ir=0.75, ii=0.5)
      
      """
      # This requires no action, because no correlation 
      # is the same as zero.
      # NB, we don't check that a correlation coefficient 
      # is being re-defined, ever! If someone were doing that 
      # and tried to set to zero, they'd be disappointed :-)
      if r == 0.0: return 
      
      # This relies on checking done in the calling functions 
      # to ensure that arguments are elementary, declared dependent,
      # and that the correlation coefficient value is OK.
      if isinstance( arg1,(UncertainReal,UncertainComplex) ):
          arg1.set_correlation(r,arg2)
      else:
          raise TypeError(
              "illegal arguments: {}, {}, {}".format( 
                  repr(r), repr(arg1), repr(arg2) 
              )
          )
  
  #---------------------------------------------------------------------------
  def get_correlation(arg1,arg2=None):
      """Return correlation 
      
      The input arguments may be a pair of uncertain numbers, 
      or a single uncertain complex number.
      
      When a pair of uncertain real numbers is provided,
      the correlation between the arguments is returned as 
      a real number. 
      
      When one, or both, arguments are uncertain complex numbers,
      a :obj:`~named_tuples.CorrelationMatrix` is returned, 
      representing a 2-by-2 matrix of correlation coefficients.
      
      """
      # Return zero if numerical arguments are given
      
      # If the arg is any number type, it matches `numbers.Complex`
      if isinstance(arg1,numbers.Complex): 
          arg1 = constant(arg1)
      
      if isinstance(arg1,(UncertainReal,UncertainComplex)):
          return arg1.get_correlation(arg2)
          
      else:
          raise TypeError(
              "illegal first argument {!r}".format(arg1)
          )
          
  #---------------------------------------------------------------------------
  def get_covariance(arg1,arg2=None):
      """Evaluate covariance.
      
      The input arguments can be a pair of uncertain numbers, 
      or a single uncertain complex number.
      
      When a pair of uncertain real numbers is supplied,
      the correlation between the two arguments is returned 
      as a real number. 
      
      When one, or both, arguments are uncertain complex numbers,
      a :class:`~named_tuples.CovarianceMatrix` is returned, 
      representing a 2-by-2 variance-covariance matrix.
      
      """
      # If numerical arguments are given then return zero. 
      # NB if the arg is any number type, it matches `numbers.Complex`
      if isinstance(arg1,numbers.Complex): 
          arg1 = constant(arg1)
      
      if isinstance( arg1,(UncertainReal,UncertainComplex) ):
          return arg1.get_covariance(arg2)
          
      else:
          raise TypeError(
              "illegal first argument {!r}".format(arg1)
          )
          
  #---------------------------------------------------------------------------
  def log(x):
      """
      Uncertain number natural logarithm
  
      .. note::
          In the complex case there is one branch cut,
          from 0 along the negative real axis to :math:`-\infty`,
          continuous from above.
          
      """
      try:
          return x._log()
      except AttributeError:
          if isinstance(x,numbers.Real):
              return math.log(x)
          elif isinstance(x,numbers.Complex):
              return cmath.log(x)
          else:
              raise TypeError(
                  "illegal argument: {!r}".format(x)
              )
      
  #---------------------------------------------------------------------------
  def log10(x):
      """
      Uncertain number common logarithm (base-10)
  
      .. note::
          In the complex case there is one branch cut,
          from 0 along the negative real
          axis to :math:`-\infty`, continuous from above.
          
      """
      try:
          return x._log10()
      except AttributeError:
          if isinstance(x,numbers.Real):
              return math.log10(x)
          elif isinstance(x,numbers.Complex):
              return cmath.log10(x)
          else:
              raise TypeError(
                  "illegal argument: {!r}".format(x)
              )
      
  #---------------------------------------------------------------------------
  def exp(x):
      """
      Uncertain number exponential function
  
      """
      try:
          return x._exp()
      except AttributeError:
          if isinstance(x,numbers.Real):
              return math.exp(x)
          elif isinstance(x,numbers.Complex):
              return cmath.exp(x)
          else:
              raise TypeError(
                  "illegal argument: {!r}".format(x)
              )
      
  #---------------------------------------------------------------------------
  def pow(x,y):
      """
      Uncertain number power function
      
      Raises ``x`` to the power of ``y``
      
      """
      return x**y
          
  #---------------------------------------------------------------------------
  def sqrt(x):
      """
      Uncertain number square root function
  
      .. note::
          In the complex case there is one branch cut,
          from 0 along the negative real
          axis to :math:`-\infty`, continuous from above.
          
      """
      try:
          return x._sqrt()
      except AttributeError:
          if isinstance(x,numbers.Real):
              return math.sqrt(x)
          elif isinstance(x,numbers.Complex):
              return cmath.sqrt(x)
          else:
              raise TypeError(
                  "illegal argument: {!r}".format(x)
              )
  
  #----------------------------------------------------------------------------
  def sin(x):
      """
      Uncertain number sine function
  
      """
      try:
          return x._sin()
      except AttributeError:
          if isinstance(x,numbers.Real):
              return math.sin(x)
          elif isinstance(x,numbers.Complex):
              return cmath.sin(x)
          else:
              raise TypeError(
                  "illegal argument: {!r}".format(x)
              )
  
  #----------------------------------------------------------------------------
  def cos(x):
      """
      Uncertain number cosine function
  
      """
      try:
          return x._cos()
      except AttributeError:
          if isinstance(x,numbers.Real):
              return math.cos(x)
          elif isinstance(x,numbers.Complex):
              return cmath.cos(x)
          else:
              raise TypeError(
                  "illegal argument: {!r}".format(x)
              )
  #----------------------------------------------------------------------------
  def tan(x):
      """
      Uncertain number tangent function
  
      """
      try:
          return x._tan()
      except AttributeError:
          if isinstance(x,numbers.Real):
              return math.tan(x)
          elif isinstance(x,numbers.Complex):
              return cmath.tan(x)
          else:
              raise TypeError(
                  "illegal argument: {!r}".format(x)
              )
  
  #---------------------------------------------------------------------------
  def asin(x):
      """
      Uncertain number arcsine function
  
      .. note::
          In the complex case there are two branch cuts: one extends
          right, from 1 along the real axis to :math:`\infty`,
          continuous from below; the other extends left, from -1 along
          the real axis to :math:`-\infty`, continuous from above.
          
      """
      try:
          return x._asin()
      except AttributeError:
          if isinstance(x,numbers.Real):
              return math.asin(x)
          elif isinstance(x,numbers.Complex):
              return cmath.asin(x)
          else:
              raise TypeError(
                  "illegal argument: {!r}".format(x)
              )
  #---------------------------------------------------------------------------
  def acos(x):
      """
      Uncertain number arc-cosine function
  
      .. note::
          In the complex case there are two branch cuts: one extends
          right, from 1 along the real axis to :math:`\infty`, continuous
          from below; the other extends left, from -1 along the real axis
          to :math:`-\infty`, continuous from above.
          
      """
      try:
          return x._acos()
      except AttributeError:
          if isinstance(x,numbers.Real):
              return math.acos(x)
          elif isinstance(x,numbers.Complex):
              return cmath.acos(x)
          else:
              raise TypeError(
                  "illegal argument: {!r}".format(x)
              )
              
  #---------------------------------------------------------------------------
  def atan(x):
      """
      Uncertain number arctangent function
  
      .. note::
      
          In the complex case there are two branch cuts:
          One extends from :math:`\mathrm{j}` along the imaginary axis to
          :math:`\mathrm{j}\infty`, continuous from the right.
          The other extends from :math:`-\mathrm{j}` along the imaginary
          axis to :math:`-\mathrm{j}\infty`, continuous from the left.
          
      """
      try:
          return x._atan()
      except AttributeError:
          if isinstance(x,numbers.Real):
              return math.atan(x)
          elif isinstance(x,numbers.Complex):
              return cmath.atan(x)
          else:
              raise TypeError(
                  "illegal argument: {!r}".format(x)
              )
              
  #----------------------------------------------------------------------------
  def atan2(y,x):
      """
      Two-argument uncertain number arctangent function    
  
      :arg x: abscissa
      :type x: :class:`~lib.UncertainReal`
      :arg y: ordinate
      :type y: :class:`~lib.UncertainReal`
      
      .. note::   this function is not defined for uncertain complex numbers
                  (use :func:`phase`)
  
      **Example**::
  
          >>> x = ureal(math.sqrt(3)/2,1)
          >>> y = ureal(0.5,1)
          >>> theta = atan2(y,x)
          >>> theta
          ureal(0.5235987755982989,1.0,inf)
          >>> math.degrees( theta.x )
          30.000000000000004
      
      """
      try:
          return y._atan2(x)
      except AttributeError:
          try:
              return x._ratan2(y)
          except AttributeError:
              if isinstance(x,numbers.Real) and isinstance(y,numbers.Real):
                  return math.atan2(y,x)
              else:
                  raise TypeError(
                      "illegal arguments: x={!r} y={!r}".format(x,y)
                  )
  
  #---------------------------------------------------------------------------
  def sinh(x):
      """
      Uncertain number hyperbolic sine function
  
      """
      try:
          return x._sinh()
      except AttributeError:
          if isinstance(x,numbers.Real):
              return math.sinh(x)
          elif isinstance(x,numbers.Complex):
              return cmath.sinh(x)
          else:
              raise TypeError(
                  "illegal argument: {!r}".format(x)
              )
  #---------------------------------------------------------------------------
  def cosh(x):
      """
      Uncertain number hyperbolic cosine function
  
      """
      try:
          return x._cosh()
      except AttributeError:
          if isinstance(x,numbers.Real):
              return math.cosh(x)
          elif isinstance(x,numbers.Complex):
              return cmath.cosh(x)
          else:
              raise TypeError(
                  "illegal argument: {!r}".format(x)
              )
      
  #---------------------------------------------------------------------------
  def tanh(x):
      """
      Uncertain number hyperbolic tangent function
  
      """
      try:
          return x._tanh()
      except AttributeError:
          if isinstance(x,numbers.Real):
              return math.tanh(x)
          elif isinstance(x,numbers.Complex):
              return cmath.tanh(x)
          else:
              raise TypeError(
                  "illegal argument: {!r}".format(x)
              )
              
  #---------------------------------------------------------------------------
  def asinh(x):
      """
      Uncertain number hyperbolic arcsine function
  
      .. note::
      
          In the complex case there are two branch cuts: one extends
          from :math:`\mathrm{j}` along the imaginary axis to
          :math:`\mathrm{j}\infty`, continuous from the right;
          the other extends from :math:`-\mathrm{j}` along the
          imaginary axis to :math:`-\mathrm{j}\infty`, continuous
          from the left.
          
      """
      try:
          return x._asinh()
      except AttributeError:
          if isinstance(x,numbers.Real):
              return math.asinh(x)
          elif isinstance(x,numbers.Complex):
              return cmath.asinh(x)
          else:
              raise TypeError(
                  "illegal argument: {!r}".format(x)
              )
  
  #---------------------------------------------------------------------------
  def acosh(x):
      """
      Uncertain number hyperbolic arc-cosine function
  
      .. note::
          In the complex case there is one branch cut,
          extending left from 1 along the
          real axis to :math:`-\infty`, continuous from above.
          
      """
      try:
          return x._acosh()
      except AttributeError:
          if isinstance(x,numbers.Real):
              return math.acosh(x)
          elif isinstance(x,numbers.Complex):
              return cmath.acosh(x)
          else:
              raise TypeError(
                  "illegal argument: {!r}".format(x)
              )
  
  #---------------------------------------------------------------------------
  def atanh(x):
      """
      Uncertain number hyperbolic arctangent function
  
      .. note::
          In the complex case there are two branch cuts:
          one extends from 1 along the real axis to :math:`\infty`,
          continuous from below; the other extends from -1
          along the real axis to :math:`-\infty`, continuous
          from above.
          
      """
      try:
          return x._atanh()
      except AttributeError:
          if isinstance(x,numbers.Real):
              return math.atanh(x)
          elif isinstance(x,numbers.Complex):
              return cmath.atanh(x)
          else:
              raise TypeError(
                  "illegal argument: {!r}".format(x)
              )
              
  #----------------------------------------------------------------------------
  def magnitude(x):
      """
      Return the magnitude of ``x``
  
      .. note::
  
          If ``x`` is not an uncertain number type,
          returns :func:`abs(x)<abs>`.    
      
      """
      try:
          return x._magnitude()
      except AttributeError:
          return abs(x)
  
  #---------------------------------------------------------------------------
  def phase(z):
      """
      :arg z: an uncertain complex number    
      :type z: :class:`~lib.UncertainComplex`
  
      :returns:   the phase in radians
      :rtype:     :class:`~lib.UncertainReal`
      
      """
      try:
          return z._phase()
      except AttributeError:
          if isinstance(z,numbers.Complex):
              return cmath.phase(z)
          else:
              raise TypeError(
                  "illegal argument: {!r}".format(z)
              )
  
  #---------------------------------------------------------------------------
  def mag_squared(x):
      """
      Return the squared magnitude of ``x``.
  
      .. note::
      
          If ``x`` is an uncertain number, the magnitude
          squared is returned as an uncertain real number, 
          otherwise :func:``abs(x)**2`` is returned.
      
      """
      try:
          return x._mag_squared()
      except AttributeError:
          return abs(x)**2
  
  # import here to avoid circular imports
  from GTC import linear_algebra
  la = linear_algebra
  
  #============================================================================    
  if __name__ == "__main__":
      import doctest       
      # from GTC import *
      doctest.testmod(  optionflags=doctest.NORMALIZE_WHITESPACE )
  