
import copy
import math
import sys
from math import isnan

import uncertainties.core as uncert_core
from uncertainties.core import ufloat, AffineScalarFunc, ufloat_fromstr
from uncertainties import umath
from helpers import (power_special_cases, power_all_cases, power_wrt_ref,numbers_close,
    ufloats_close, compare_derivatives, uarrays_close)


def test_value_construction():
    '''
    Tests the various means of constructing a constant number with
    uncertainty *without a string* (see test_ufloat_fromstr(), for this).
    '''

    ## Simple construction:
    x = ufloat(3, 0.14)
    assert x.nominal_value == 3
    assert x.std_dev == 0.14
    assert x.tag is None

    # ... with tag as positional argument:
    x = ufloat(3, 0.14, 'pi')
    assert x.nominal_value == 3
    assert x.std_dev == 0.14
    assert x.tag == 'pi'

    # ... with tag keyword:
    x = ufloat(3, 0.14, tag='pi')
    assert x.nominal_value == 3
    assert x.std_dev == 0.14
    assert x.tag == 'pi'

    # Negative standard deviations should be caught in a nice way
    # (with the right exception):
    try:
        x = ufloat(3, -0.1)
    except uncert_core.NegativeStdDev:
        pass

    ## Incorrect forms should not raise any deprecation warning, but
    ## raise an exception:

    try:
        ufloat(1)  # Form that has never been allowed
    except:
        pass
    else:
        raise Exception("An exception should be raised")

def test_ufloat_fromstr():
    "Input of numbers with uncertainties as a string"

    # String representation, and numerical values:
    tests = {
        "-1.23(3.4)": (-1.23, 3.4),  # (Nominal value, error)
        "  -1.23(3.4)  ": (-1.23, 3.4),  # Spaces ignored
        "-1.34(5)": (-1.34, 0.05),
        "1(6)": (1, 6),
        "3(4.2)": (3, 4.2),
        "-9(2)": (-9, 2),
        "1234567(1.2)": (1234567, 1.2),
        "12.345(15)": (12.345, 0.015),
        "-12.3456(78)e-6": (-12.3456e-6, 0.0078e-6),
        "0.29": (0.29, 0.01),
        "31.": (31, 1),
        "-31.": (-31, 1),
        # The following tests that the ufloat() routine does
        # not consider '31' like the tuple ('3', '1'), which would
        # make it expect two numbers (instead of 2 1-character
        # strings):
        "31": (31, 1),
        "-3.1e10": (-3.1e10, 0.1e10),
        "169.0(7)": (169, 0.7),
        "-0.1+/-1": (-0.1, 1),
        "-13e-2+/-1e2": (-13e-2, 1e2),
        '-14.(15)': (-14, 15),
        '-100.0(15)': (-100, 1.5),
        '14.(15)': (14, 15),
        # Global exponent:
        '(3.141+/-0.001)E+02': (314.1, 0.1),


        ## Pretty-print notation:

        # ± sign, global exponent (not pretty-printed):
        u'(3.141±0.001)E+02': (314.1, 0.1),
        # ± sign, individual exponent:
        u'3.141E+02±0.001e2': (314.1, 0.1),
        # ± sign, times symbol, superscript (= full pretty-print):
        u'(3.141 ± 0.001) × 10²': (314.1, 0.1),

        ## Others

        # Forced parentheses:
        '(2 +/- 0.1)': (2, 0.1),

        # NaN uncertainty:
        u'(3.141±nan)E+02': (314.1, float('nan')),
        '3.141e+02+/-nan': (314.1, float('nan')),
        '3.4(nan)e10': (3.4e10, float('nan')),
        # NaN value:
        'nan+/-3.14e2': (float('nan'), 314),
        # "Double-floats"
        '(-3.1415 +/- 1e-4)e+200': (-3.1415e200, 1e196),
        '(-3.1415e-10 +/- 1e-4)e+200': (-3.1415e190, 1e196),
        # Special float representation:
        '-3(0.)': (-3, 0)
        }

    for (representation, values) in tests.items():

        # We test the fact that surrounding spaces are removed:
        representation = u'  {}  '.format(representation)

        # Without tag:
        num = ufloat_fromstr(representation)
        assert numbers_close(num.nominal_value, values[0])
        assert numbers_close(num.std_dev, values[1])
        assert num.tag is None

        # With a tag as positional argument:
        num = ufloat_fromstr(representation, 'test variable')
        assert numbers_close(num.nominal_value, values[0])
        assert numbers_close(num.std_dev, values[1])
        assert num.tag == 'test variable'

        # With a tag as keyword argument:
        num = ufloat_fromstr(representation, tag='test variable')
        assert numbers_close(num.nominal_value, values[0])
        assert numbers_close(num.std_dev, values[1])
        assert num.tag == 'test variable'

###############################################################################

# Test of correctness of the fixed (usually analytical) derivatives:
def test_fixed_derivatives_basic_funcs():
    """
    Pre-calculated derivatives for operations on AffineScalarFunc.
    """

    def check_op(op, num_args):
        """
        Makes sure that the derivatives for function '__op__' of class
        AffineScalarFunc, which takes num_args arguments, are correct.

        If num_args is None, a correct value is calculated.
        """

        op_string = "__%s__" % op
        func = getattr(AffineScalarFunc, op_string)
        numerical_derivatives = uncert_core.NumericalDerivatives(
            # The __neg__ etc. methods of AffineScalarFunc only apply,
            # by definition, to AffineScalarFunc objects: we first map
            # possible scalar arguments (used for calculating
            # derivatives) to AffineScalarFunc objects:
            lambda *args: func(*map(uncert_core.to_affine_scalar, args)))
        compare_derivatives(func, numerical_derivatives, [num_args])

    # Operators that take 1 value:
    for op in uncert_core.modified_operators:
        check_op(op, 1)

    # Operators that take 2 values:
    for op in uncert_core.modified_ops_with_reflection:
        check_op(op, 2)


def test_copy():
    "Standard copy module integration"
    import gc

    x = ufloat(3, 0.1)
    assert x == x

    y = copy.copy(x)
    assert x != y
    assert not(x == y)
    assert y in y.derivatives.keys()  # y must not copy the dependence on x

    z = copy.deepcopy(x)
    assert x != z

    # Copy tests on expressions:
    t = x + 2*z
    # t depends on x:
    assert x in t.derivatives

    # The relationship between the copy of an expression and the
    # original variables should be preserved:
    t_copy = copy.copy(t)
    # Shallow copy: the variables on which t depends are not copied:
    assert x in t_copy.derivatives
    assert (uncert_core.covariance_matrix([t, z]) ==
            uncert_core.covariance_matrix([t_copy, z]))

    # However, the relationship between a deep copy and the original
    # variables should be broken, since the deep copy created new,
    # independent variables:
    t_deepcopy = copy.deepcopy(t)
    assert x not in t_deepcopy.derivatives
    assert (uncert_core.covariance_matrix([t, z]) !=
            uncert_core.covariance_matrix([t_deepcopy, z]))

    # Test of implementations with weak references:

    # Weak references: destroying a variable should never destroy the
    # integrity of its copies (which would happen if the copy keeps a
    # weak reference to the original, in its derivatives member: the
    # weak reference to the original would become invalid):
    del x

    gc.collect()

    assert y in list(y.derivatives.keys())

## Classes for the pickling tests (put at the module level, so that
## they can be unpickled):

# Subclass without slots:
class NewVariable_dict(uncert_core.Variable):
    pass

# Subclass with slots defined by a tuple:
class NewVariable_slots_tuple(uncert_core.Variable):
    __slots__ = ('new_attr',)

# Subclass with slots defined by a string:
class NewVariable_slots_str(uncert_core.Variable):
    __slots__ = 'new_attr'

def test_pickling():
    "Standard pickle module integration."

    import pickle

    x = ufloat(2, 0.1)

    x_unpickled = pickle.loads(pickle.dumps(x))

    assert x != x_unpickled  # Pickling creates copies

    ## Tests with correlations and AffineScalarFunc objects:
    f = 2*x
    assert isinstance(f, AffineScalarFunc)
    (f_unpickled, x_unpickled2) = pickle.loads(pickle.dumps((f, x)))
    # Correlations must be preserved:
    assert f_unpickled - x_unpickled2 - x_unpickled2 == 0

    ## Tests with subclasses:

    for subclass in (NewVariable_dict, NewVariable_slots_tuple,
                     NewVariable_slots_str):

        x = subclass(3, 0.14)

        # Pickling test with possibly uninitialized slots:
        pickle.loads(pickle.dumps(x))

        # Unpickling test:
        x.new_attr = 'New attr value'
        x_unpickled = pickle.loads(pickle.dumps(x))
        # Must exist (from the slots of the parent class):
        x_unpickled.nominal_value
        x_unpickled.new_attr  # Must exist

    ##

    # Corner case test: when an attribute is present both in __slots__
    # and in __dict__, it is first looked up from the slots
    # (references:
    # http://docs.python.org/2/reference/datamodel.html#invoking-descriptors,
    # http://stackoverflow.com/a/15139208/42973). As a consequence,
    # the pickling process must pickle the correct value (i.e., not
    # the value from __dict__):
    x = NewVariable_dict(3, 0.14)
    x._nominal_value = 'in slots'
    # Corner case: __dict__ key which is also a slot name (it is
    # shadowed by the corresponding slot, so this is very unusual,
    # though):
    x.__dict__['_nominal_value'] = 'in dict'
    # Additional __dict__ attribute:
    x.dict_attr = 'dict attribute'

    x_unpickled = pickle.loads(pickle.dumps(x))
    # We make sure that the data is still there and untouched:
    assert x_unpickled._nominal_value == 'in slots'
    assert x_unpickled.__dict__ == x.__dict__

    ##

    # Corner case that should have no impact on the code but which is
    # not prevented by the documentation: case of constant linear
    # terms (the potential gotcha is that if the linear_combo
    # attribute is empty, __getstate__()'s result could be false, and
    # so __setstate__() would not be called and the original empty
    # linear combination would not be set in linear_combo.
    x = uncert_core.LinearCombination({})
    assert pickle.loads(pickle.dumps(x)).linear_combo == {}

def test_int_div():
    "Integer division"
    # We perform all operations on floats, because derivatives can
    # otherwise be meaningless:
    x = ufloat(3.9, 2)//2
    assert x.nominal_value == 1.
    # All errors are supposed to be small, so the ufloat()
    # in x violates the assumption.  Therefore, the following is
    # correct:
    assert x.std_dev == 0.0

def test_comparison_ops():
    "Test of comparison operators"

    import random

    # Operations on quantities equivalent to Python numbers must still
    # be correct:
    a = ufloat(-3, 0)
    b = ufloat(10, 0)
    c = ufloat(10, 0)
    assert a < b
    assert a < 3
    assert 3 < b  # This is first given to int.__lt__()
    assert b == c

    x = ufloat(3, 0.1)

    # One constraint is that usual Python code for inequality testing
    # still work in a reasonable way (for instance, it is generally
    # desirable that functions defined by different formulas on
    # different intervals can still do "if 0 < x < 1:...".  This
    # supposes again that errors are "small" (as for the estimate of
    # the standard error).
    assert x > 1

    # The limit case is not obvious:
    assert not(x >= 3)
    assert not(x < 3)

    assert x == x
    # Comparaison between Variable and AffineScalarFunc:
    assert x == x + 0
    # Comparaison between 2 _different_ AffineScalarFunc objects
    # representing the same value:
    assert x/2 == x/2
    # With uncorrelated result that have the same behavior (value and
    # standard error):
    assert 2*ufloat(1, 0.1) != ufloat(2, 0.2)
    # Comparaison between 2 _different_ Variable objects
    # that are uncorrelated:
    assert x != ufloat(3, 0.1)

    assert x != ufloat(3, 0.2)

    # Comparison to other types should work:
    assert x != None  # Not comparable
    assert x-x == 0  # Comparable, even though the types are different
    assert x != [1, 2]


    ####################

    # Checks of the semantics of logical operations: they return True
    # iff they are always True when the parameters vary in an
    # infinitesimal interval inside sigma (sigma == 0 is a special
    # case):

    def test_all_comparison_ops(x, y):
        """
        Takes two Variable objects.

        Fails if any comparison operation fails to follow the proper
        semantics: a comparison only returns True if the correspond float
        comparison results are True for all the float values taken by
        the variables (of x and y) when they vary in an infinitesimal
        neighborhood within their uncertainty.

        This test is stochastic: it may, exceptionally, fail for
        correctly implemented comparison operators.
        """

        import random

        def random_float(var):
            """
            Returns a random value for Variable var, in an
            infinitesimal interval withing its uncertainty.  The case
            of a zero uncertainty is special.
            """
            return ((random.random()-0.5) * min(var.std_dev, 1e-5)
                    + var.nominal_value)

        # All operations are tested:
        for op in ["__%s__" % name
                   for name in('ne', 'eq', 'lt', 'le', 'gt', 'ge')]:

            try:
                float_func = getattr(float, op)
            except AttributeError:  # Python 2.3's floats don't have __ne__
                continue

            # Determination of the correct truth value of func(x, y):

            sampled_results = []

            # The "main" value is an important particular case, and
            # the starting value for the final result
            # (correct_result):

            sampled_results.append(float_func(x.nominal_value, y.nominal_value))

            for check_num in range(50):  # Many points checked
                sampled_results.append(float_func(random_float(x),
                                                  random_float(y)))

            min_result = min(sampled_results)
            max_result = max(sampled_results)

            if min_result == max_result:
                correct_result = min_result
            else:

                # Almost all results must be True, for the final value
                # to be True:
                num_min_result = sampled_results.count(min_result)

                # 1 exception is considered OK:
                correct_result = (num_min_result == 1)

            try:
                assert correct_result == getattr(x, op)(y)
            except AssertionError:
                print("Sampling results:", sampled_results)
                raise Exception("Semantic value of %s %s (%s) %s not"
                                " correctly reproduced."
                                % (x, op, y, correct_result))

    # With different numbers:
    test_all_comparison_ops(ufloat(3, 0.1),
                            ufloat(-2, 0.1))
    test_all_comparison_ops(ufloat(0, 0),  # Special number
                            ufloat(1, 1))
    test_all_comparison_ops(ufloat(0, 0),  # Special number
                            ufloat(0, 0.1))
    # With identical numbers:
    test_all_comparison_ops(ufloat(0, 0),
                            ufloat(0, 0))
    test_all_comparison_ops(ufloat(1, 1),
                            ufloat(1, 1))


def test_logic():
    "Boolean logic: __nonzero__, bool."

    x = ufloat(3, 0)
    y = ufloat(0, 0)
    z = ufloat(0, 0.1)
    t = ufloat(-1, 2)

    assert bool(x) == True
    assert bool(y) == False
    assert bool(z) == True
    assert bool(t) == True  # Only infinitseimal neighborhood are used


def test_basic_access_to_data():
    "Access to data from Variable and AffineScalarFunc objects."

    x = ufloat(3.14, 0.01, "x var")
    assert x.tag == "x var"
    assert x.nominal_value == 3.14
    assert x.std_dev == 0.01

    # Case of AffineScalarFunc objects:
    y = x + 0
    assert type(y) == AffineScalarFunc
    assert y.nominal_value == 3.14
    assert y.std_dev == 0.01

    # Details on the sources of error:
    a = ufloat(-1, 0.001)
    y = 2*x + 3*x + 2 + a
    error_sources = y.error_components()
    assert len(error_sources) == 2  # 'a' and 'x'
    assert error_sources[x] == 0.05
    assert error_sources[a] == 0.001

    # Derivative values should be available:
    assert y.derivatives[x] == 5

    # Modification of the standard deviation of variables:
    x.std_dev = 1
    assert y.error_components()[x] == 5  # New error contribution!

    # Calculated values with uncertainties should not have a settable
    # standard deviation:
    y = 2*x
    try:
        y.std_dev = 1
    except AttributeError:
        pass
    else:
        raise Exception(
            "std_dev should not be settable for calculated results")

    # Calculation of deviations in units of the standard deviations:
    assert 10/x.std_dev == x.std_score(10 + x.nominal_value)

    # "In units of the standard deviation" is not always meaningful:
    x.std_dev = 0
    try:
        x.std_score(1)
    except ValueError:
        pass  # Normal behavior

def test_correlations():
    "Correlations between variables"

    a = ufloat(1, 0)
    x = ufloat(4, 0.1)
    y = x*2 + a
    # Correlations cancel "naive" additions of uncertainties:
    assert y.std_dev != 0
    normally_zero = y - (x*2 + 1)
    assert normally_zero.nominal_value == 0
    assert normally_zero.std_dev == 0


def test_no_coercion():
    """
    Coercion of Variable object to a simple float.

    The coercion should be impossible, like for complex numbers.
    """

    x = ufloat(4, 1)
    try:
        assert float(x) == 4
    except TypeError:
        pass
    else:
        raise Exception("Conversion to float() should fail with TypeError")

def test_wrapped_func_no_args_no_kwargs():
    '''
    Wrap a function that takes only positional-or-keyword parameters.
    '''

    def f_auto_unc(x, y):
        return 2*x+umath.sin(y)

    # Like f_auto_unc, but does not accept numbers with uncertainties:
    def f(x, y):
        assert not isinstance(x, uncert_core.UFloat)
        assert not isinstance(y, uncert_core.UFloat)
        return f_auto_unc(x, y)

    x = uncert_core.ufloat(1, 0.1)
    y = uncert_core.ufloat(10, 2)

    ### Automatic numerical derivatives:

    ## Fully automatic numerical derivatives:
    f_wrapped = uncert_core.wrap(f)
    assert ufloats_close(f_auto_unc(x, y), f_wrapped(x, y))

    # Call with keyword arguments:
    assert ufloats_close(f_auto_unc(y=y, x=x), f_wrapped(y=y, x=x))

    ## Automatic additional derivatives for non-defined derivatives,
    ## and explicit None derivative:
    f_wrapped = uncert_core.wrap(f, [None])  # No derivative for y
    assert ufloats_close(f_auto_unc(x, y), f_wrapped(x, y))

    # Call with keyword arguments:
    assert ufloats_close(f_auto_unc(y=y, x=x), f_wrapped(y=y, x=x))

    ### Explicit derivatives:

    ## Fully defined derivatives:
    f_wrapped = uncert_core.wrap(f, [lambda x, y: 2,
                                     lambda x, y: math.cos(y)])

    assert ufloats_close(f_auto_unc(x, y), f_wrapped(x, y))

    # Call with keyword arguments:
    assert ufloats_close(f_auto_unc(y=y, x=x), f_wrapped(y=y, x=x))

    ## Automatic additional derivatives for non-defined derivatives:
    f_wrapped = uncert_core.wrap(f, [lambda x, y: 2])  # No derivative for y
    assert ufloats_close(f_auto_unc(x, y), f_wrapped(x, y))

    # Call with keyword arguments:
    assert ufloats_close(f_auto_unc(y=y, x=x), f_wrapped(y=y, x=x))

def test_wrapped_func_args_no_kwargs():
    '''
    Wrap a function that takes only positional-or-keyword and
    var-positional parameters.
    '''

    def f_auto_unc(x, y, *args):
        return 2*x+umath.sin(y)+3*args[1]

    # Like f_auto_unc, but does not accept numbers with uncertainties:
    def f(x, y, *args):
        assert not any(isinstance(value, uncert_core.UFloat)
                       for value in [x, y] + list(args))
        return f_auto_unc(x, y, *args)

    x = uncert_core.ufloat(1, 0.1)
    y = uncert_core.ufloat(10, 2)
    s = 'string arg'
    z = uncert_core.ufloat(100, 3)

    args = [s, z, s]  # var-positional parameters

    ### Automatic numerical derivatives:

    ## Fully automatic numerical derivatives:
    f_wrapped = uncert_core.wrap(f)
    assert ufloats_close(f_auto_unc(x, y, *args), f_wrapped(x, y, *args))

    ## Automatic additional derivatives for non-defined derivatives,
    ## and explicit None derivative:
    f_wrapped = uncert_core.wrap(f, [None])  # No derivative for y
    assert ufloats_close(f_auto_unc(x, y, *args), f_wrapped(x, y, *args))

    ### Explicit derivatives:

    ## Fully defined derivatives:
    f_wrapped = uncert_core.wrap(f, [lambda x, y, *args: 2,
                                     lambda x, y, *args: math.cos(y),
                                     None,
                                     lambda x, y, *args: 3])

    assert ufloats_close(f_auto_unc(x, y, *args), f_wrapped(x, y, *args))

    ## Automatic additional derivatives for non-defined derivatives:

    # No derivative for y:
    f_wrapped = uncert_core.wrap(f, [lambda x, y, *args: 2])
    assert ufloats_close(f_auto_unc(x, y, *args), f_wrapped(x, y, *args))

def test_wrapped_func_no_args_kwargs():
    '''
    Wrap a function that takes only positional-or-keyword and
    var-keyword parameters.
    '''

    def f_auto_unc(x, y, **kwargs):
        return 2*x+umath.sin(y)+3*kwargs['z']

    # Like f_auto_unc, but does not accept numbers with uncertainties:
    def f(x, y, **kwargs):
        assert not any(isinstance(value, uncert_core.UFloat)
                       for value in [x, y] + list(kwargs.values()))
        return f_auto_unc(x, y, **kwargs)

    x = uncert_core.ufloat(1, 0.1)
    y = uncert_core.ufloat(10, 2)
    s = 'string arg'
    z = uncert_core.ufloat(100, 3)

    kwargs = {'s': s, 'z': z}  # Arguments not in signature

    ### Automatic numerical derivatives:

    ## Fully automatic numerical derivatives:
    f_wrapped = uncert_core.wrap(f)
    assert ufloats_close(f_auto_unc(x, y, **kwargs),
                         f_wrapped(x, y, **kwargs))

    # Call with keyword arguments:
    assert ufloats_close(f_auto_unc(y=y, x=x, **kwargs),
                         f_wrapped(y=y, x=x, **kwargs))

    ## Automatic additional derivatives for non-defined derivatives,
    ## and explicit None derivative:

    # No derivative for positional-or-keyword parameter y, no
    # derivative for optional-keyword parameter z:
    f_wrapped = uncert_core.wrap(f, [None])
    assert ufloats_close(f_auto_unc(x, y, **kwargs),
                         f_wrapped(x, y, **kwargs))

    # Call with keyword arguments:
    assert ufloats_close(f_auto_unc(y=y, x=x, **kwargs),
                         f_wrapped(y=y, x=x, **kwargs))

    # No derivative for positional-or-keyword parameter y, no
    # derivative for optional-keyword parameter z:
    f_wrapped = uncert_core.wrap(f, [None], {'z': None})
    assert ufloats_close(f_auto_unc(x, y, **kwargs),
                         f_wrapped(x, y, **kwargs))

    # Call with keyword arguments:
    assert ufloats_close(f_auto_unc(y=y, x=x, **kwargs),
                         f_wrapped(y=y, x=x, **kwargs))

    # No derivative for positional-or-keyword parameter y, derivative
    # for optional-keyword parameter z:
    f_wrapped = uncert_core.wrap(f, [None],
                                 {'z': lambda x, y, **kwargs: 3})
    assert ufloats_close(f_auto_unc(x, y, **kwargs),
                         f_wrapped(x, y, **kwargs))

    # Call with keyword arguments:
    assert ufloats_close(f_auto_unc(y=y, x=x, **kwargs),
                         f_wrapped(y=y, x=x, **kwargs))

    ### Explicit derivatives:

    ## Fully defined derivatives:
    f_wrapped = uncert_core.wrap(
        f,
        [lambda x, y, **kwargs: 2, lambda x, y, **kwargs: math.cos(y)],
        {'z:': lambda x, y, **kwargs: 3})

    assert ufloats_close(f_auto_unc(x, y, **kwargs),
                          f_wrapped(x, y, **kwargs))
    # Call with keyword arguments:
    assert ufloats_close(f_auto_unc(y=y, x=x, **kwargs),
                          f_wrapped(y=y, x=x, **kwargs))

    ## Automatic additional derivatives for non-defined derivatives:

    # No derivative for y or z:
    f_wrapped = uncert_core.wrap(f, [lambda x, y, **kwargs: 2])
    assert ufloats_close(f_auto_unc(x, y, **kwargs),
                          f_wrapped(x, y, **kwargs))

    # Call with keyword arguments:
    assert ufloats_close(f_auto_unc(y=y, x=x, **kwargs),
                          f_wrapped(y=y, x=x, **kwargs))

def test_wrapped_func_args_kwargs():
    '''
    Wrap a function that takes positional-or-keyword, var-positional
    and var-keyword parameters.
    '''

    def f_auto_unc(x, y, *args, **kwargs):
        return 2*x+umath.sin(y)+4*args[1]+3*kwargs['z']

    # Like f_auto_unc, but does not accept numbers with uncertainties:
    def f(x, y, *args, **kwargs):
        assert not any(isinstance(value, uncert_core.UFloat)
                       for value in [x, y]+list(args)+list(kwargs.values()))
        return f_auto_unc(x, y, *args, **kwargs)

    x = uncert_core.ufloat(1, 0.1)
    y = uncert_core.ufloat(10, 2)
    t = uncert_core.ufloat(1000, 4)
    s = 'string arg'
    z = uncert_core.ufloat(100, 3)

    args = [s, t, s]
    kwargs = {'u': s, 'z': z}  # Arguments not in signature

    ### Automatic numerical derivatives:

    ## Fully automatic numerical derivatives:
    f_wrapped = uncert_core.wrap(f)

    assert ufloats_close(f_auto_unc(x, y, *args, **kwargs),
                          f_wrapped(x, y, *args, **kwargs), tolerance=1e-5)

    ## Automatic additional derivatives for non-defined derivatives,
    ## and explicit None derivative:

    # No derivative for positional-or-keyword parameter y, no
    # derivative for optional-keyword parameter z:
    f_wrapped = uncert_core.wrap(f, [None, None, None,
                                     lambda x, y, *args, **kwargs: 4])
    assert ufloats_close(f_auto_unc(x, y, *args, **kwargs),
                         f_wrapped(x, y, *args, **kwargs), tolerance=1e-5)

    # No derivative for positional-or-keyword parameter y, no
    # derivative for optional-keyword parameter z:
    f_wrapped = uncert_core.wrap(f, [None], {'z': None})
    assert ufloats_close(f_auto_unc(x, y, *args, **kwargs),
                         f_wrapped(x, y, *args, **kwargs), tolerance=1e-5)

    # No derivative for positional-or-keyword parameter y, derivative
    # for optional-keyword parameter z:
    f_wrapped = uncert_core.wrap(f, [None],
                                 {'z': lambda x, y, *args, **kwargs: 3})
    assert ufloats_close(f_auto_unc(x, y, *args, **kwargs),
                         f_wrapped(x, y, *args, **kwargs), tolerance=1e-5)

    ### Explicit derivatives:

    ## Fully defined derivatives:
    f_wrapped = uncert_core.wrap(
        f,
        [lambda x, y, *args, **kwargs: 2,
         lambda x, y, *args, **kwargs: math.cos(y)],
        {'z:': lambda x, y, *args, **kwargs: 3})

    assert ufloats_close(f_auto_unc(x, y, *args, **kwargs),
                          f_wrapped(x, y, *args, **kwargs), tolerance=1e-5)

    ## Automatic additional derivatives for non-defined derivatives:

    # No derivative for y or z:
    f_wrapped = uncert_core.wrap(f, [lambda x, y, *args, **kwargs: 2])
    assert ufloats_close(f_auto_unc(x, y, *args, **kwargs),
                         f_wrapped(x, y, *args, **kwargs), tolerance=1e-5)


def test_wrapped_func():
    """
    Test uncertainty-aware functions obtained through wrapping.
    """

    ########################################

    # Function which can automatically handle numbers with
    # uncertainties:
    def f_auto_unc(angle, *list_var):
        return umath.cos(angle) + sum(list_var)

    def f(angle, *list_var):
        # We make sure that this function is only ever called with
        # numbers with no uncertainty (since it is wrapped):
        assert not isinstance(angle, uncert_core.UFloat)
        assert not any(isinstance(arg, uncert_core.UFloat)
                       for arg in list_var)
        return f_auto_unc(angle, *list_var)

    f_wrapped = uncert_core.wrap(f)


    my_list = [1, 2, 3]

    ########################################
    # Test of a wrapped function that only calls the original
    # function: it should obtain the exact same result:
    assert f_wrapped(0, *my_list) == f(0, *my_list)
    # 1 == 1 +/- 0, so the type must be checked too:
    assert type(f_wrapped(0, *my_list)) == type(f(0, *my_list))

    ########################################
    # Call with uncertainties:

    angle = uncert_core.ufloat(1, 0.1)
    list_value = uncert_core.ufloat(3, 0.2)

    # The random variables must be the same (full correlation):

    assert ufloats_close(f_wrapped(angle, *[1, angle]),
                          f_auto_unc(angle, *[1, angle]))

    assert ufloats_close(f_wrapped(angle, *[list_value, angle]),
                          f_auto_unc(angle, *[list_value, angle]))

    ########################################
    # Non-numerical arguments, and  explicit and implicit derivatives:
    def f(x, y, z, t, u):
        return x+2*z+3*t+4*u

    f_wrapped = uncert_core.wrap(
        f, [lambda *args: 1, None, lambda *args:2, None])  # No deriv. for u

    assert f_wrapped(10, 'string argument', 1, 0, 0) == 12

    x = uncert_core.ufloat(10, 1)

    assert numbers_close(f_wrapped(x, 'string argument', x, x, x).std_dev,
                          (1+2+3+4)*x.std_dev)

def test_wrap_with_kwargs():
    '''
    Tests wrap() on functions with keyword arguments.

    Includes both wrapping a function that takes optional keyword
    arguments and calling a wrapped function with keyword arguments
    (optional or not).
    '''

    # Version of f() that automatically works with numbers with
    # uncertainties:
    def f_auto_unc(x, y, *args, **kwargs):
        return x + umath.sin(y) + 2*args[0] + 3*kwargs['t']

    # We also add keyword arguments in the function which is wrapped:
    def f(x, y, *args, **kwargs):
        # We make sure that f is not called directly with a number with
        # uncertainty:

        for value in [x, y]+list(args)+list(kwargs.values()):
            assert not isinstance(value, uncert_core.UFloat)

        return f_auto_unc(x, y, *args, **kwargs)

    f_wrapped = uncert_core.wrap(f)


    x = ufloat(1, 0.1)
    y = ufloat(10, 0.11)
    z = ufloat(100, 0.111)
    t = ufloat(0.1, 0.1111)

    assert ufloats_close(f_wrapped(x, y, z, t=t),
                          f_auto_unc(x, y, z, t=t), tolerance=1e-5)

    ########################################

    # We make sure that analytical derivatives are indeed used. We
    # also test the automatic handling of additional *args arguments
    # beyond the number of supplied derivatives.

    f_wrapped2 = uncert_core.wrap(
        f, [None, lambda x, y, *args, **kwargs: math.cos(y)])

    # The derivatives must be perfectly identical:

    # The *args parameter of f() is given as a keyword argument, so as
    # to try to confuse the code:

    assert (f_wrapped2(x, y, z, t=t).derivatives[y]
            == f_auto_unc(x, y, z, t=t).derivatives[y])

    # Derivatives supplied through the keyword-parameter dictionary of
    # derivatives, and also derivatives supplied for the
    # var-positional arguments (*args[0]):

    f_wrapped3 = uncert_core.wrap(
        f,
        [None, None, lambda x, y, *args, **kwargs: 2],
        {'t': lambda x, y, *args, **kwargs: 3})

    # The derivatives should be exactly the same, because they are
    # obtained with the exact same analytic formula:
    assert (f_wrapped3(x, y, z, t=t).derivatives[z]
            == f_auto_unc(x, y, z, t=t).derivatives[z])
    assert (f_wrapped3(x, y, z, t=t).derivatives[t]
            == f_auto_unc(x, y, z, t=t).derivatives[t])

    ########################################
    # Making sure that user-supplied derivatives are indeed called:

    class FunctionCalled(Exception):
        '''
        Raised to signal that a function is indeed called.
        '''
        pass

    def failing_func(x, y, *args, **kwargs):
        raise FunctionCalled

    f_wrapped4 = uncert_core.wrap(
        f,
        [None, failing_func],
        {'t': failing_func})

    try:
        f_wrapped4(x, 3.14, z, t=t)
    except FunctionCalled:
        pass
    else:
        raise Exception('User-supplied derivative should be called')

    try:
        f_wrapped4(x, y, z, t=3.14)
    except FunctionCalled:
        pass
    else:
        raise Exception('User-supplied derivative should be called')

    try:
        f_wrapped4(x, 3.14, z, t=3.14)
    except FunctionCalled:
        raise Exception('User-supplied derivative should *not* be called')

###############################################################################

def test_access_to_std_dev():
    "Uniform access to the standard deviation"

    x = ufloat(1, 0.1)
    y = 2*x

    # std_dev for Variable and AffineScalarFunc objects:
    assert uncert_core.std_dev(x) == x.std_dev
    assert uncert_core.std_dev(y) == y.std_dev

    # std_dev for other objects:
    assert uncert_core.std_dev([]) == 0
    assert uncert_core.std_dev(None) == 0

###############################################################################

def test_covariances():
    "Covariance matrix"

    x = ufloat(1, 0.1)
    y = -2*x+10
    z = -3*x
    covs = uncert_core.covariance_matrix([x, y, z])
    # Diagonal elements are simple:
    assert numbers_close(covs[0][0], 0.01)
    assert numbers_close(covs[1][1], 0.04)
    assert numbers_close(covs[2][2], 0.09)
    # Non-diagonal elements:
    assert numbers_close(covs[0][1], -0.02)


###############################################################################
def test_power_all_cases():
    '''
    Checks all cases for the value and derivatives of x**p.
    '''

    power_all_cases(pow)


###############################################################################

def test_power_special_cases():
    '''
    Checks special cases of x**p.
    '''
    power_special_cases(pow)

    # We want the same behavior for numbers with uncertainties and for
    # math.pow() at their nominal values:

    positive = ufloat(0.3, 0.01)
    negative = ufloat(-0.3, 0.01)

    # http://stackoverflow.com/questions/10282674/difference-between-the-built-in-pow-and-math-pow-for-floats-in-python

    try:
        pow(ufloat(0, 0), negative)
    except ZeroDivisionError:
        pass
    else:
        raise Exception("A proper exception should have been raised")

    try:
        pow(ufloat(0, 0.1), negative)
    except ZeroDivisionError:
        pass
    else:
        raise Exception('A proper exception should have been raised')

    try:
        result = pow(negative, positive)
    except ValueError:
        # The reason why it should also fail in Python 3 is that the
        # result of Python 3 is a complex number, which uncertainties
        # does not handle (no uncertainties on complex numbers). In
        # Python 2, this should always fail, since Python 2 does not
        # know how to calculate it.
        pass
    else:
        raise Exception('A proper exception should have been raised')

def test_power_wrt_ref():
    '''
    Checks special cases of the built-in pow() power operator.
    '''
    power_wrt_ref(pow, pow)


###############################################################################

def test_PDG_precision():
    '''
    Test of the calculation of the number of significant digits for
    the uncertainty.
    '''

    # The 3 cases of the rounding rules are covered in each case:
    tests = {
        # Very big floats:
        1.7976931348623157e308: (2, 1.7976931348623157e308),
        0.5e308: (1, 0.5e308),
        0.9976931348623157e+308: (2, 1e308),
        # Very small floats:
        1.3e-323: (2, 1.3e-323),
        5e-324: (1, 5e-324),
        9.99e-324: (2, 1e-323)
        }

    for (std_dev, result) in tests.items():
        assert uncert_core.PDG_precision(std_dev) == result

def test_repr():
    '''Test the representation of numbers with uncertainty.'''

    # The uncertainty is a power of 2, so that it can be exactly
    # represented:
    x = ufloat(3.14159265358979, 0.25)
    assert repr(x) == '3.14159265358979+/-0.25'

    x = ufloat(3.14159265358979, 0)
    assert repr(x) == '3.14159265358979+/-0'

    # Tagging:
    x = ufloat(3, 1, "length")
    assert repr(x) == '< length = 3.0+/-1.0 >'

def test_format():
    '''Test the formatting of numbers with uncertainty.'''

    # The way NaN is formatted with F, E and G depends on the version
    # of Python (NAN for Python 2.5+ at least):
    NaN_EFG = '%F' % float('nan')

    # !! The way NaN is formatted with F, E and G might depend on the
    # version of Python, if it is like NaN (could be tested with
    # Python 2.3 or 2.4 vs Python 2.7):
    Inf_EFG = '%F' % float('inf')

    # Tests of each point of the docstring of
    # AffineScalarFunc.__format__() in turn, mostly in the same order.

    # The LaTeX tests do not use the customization of
    # uncert_core.GROUP_SYMBOLS and uncert_core.EXP_PRINT: this
    # way, problems in the customization themselves are caught.

    tests = {  # (Nominal value, uncertainty): {format: result,...}

        # Usual float formatting, and individual widths, etc.:
        (3.1415, 0.0001): {
            '*^+7.2f': '*+3.14*+/-*0.00**',
            '+07.2f': '+003.14+/-0000.00',  # 0 fill
            '>10f': '  3.141500+/-  0.000100',  # Width and align
            '11.3e': '  3.142e+00+/-  0.000e+00',  # Duplicated exponent
            '0.4e': '3.1415e+00+/-0.0000e+00'  # Forced double exponent
        },

        # Full generalization of float formatting:
        (3.1415, 0.0001): {
            '+09.2uf': '+03.14150+/-000.00010',
            # Alignment is not available with the % formatting
            # operator of Python < 2.6:
            '*^+9.2uf': '+3.14150*+/-*0.00010*',
            '>9f': '  3.14150+/-  0.00010'  # Width and align
        },

        # Number of digits of the uncertainty fixed:
        (123.456789, 0.00123): {
            '.1uf': '123.457+/-0.001',
            '.2uf': '123.4568+/-0.0012',
            '.3uf': '123.45679+/-0.00123',
            '.2ue': '(1.234568+/-0.000012)e+02'
        },
        # Sign handling:
        (-123.456789, 0.00123): {
            '.1uf': '-123.457+/-0.001',
            '.2uf': '-123.4568+/-0.0012',
            '.3uf': '-123.45679+/-0.00123',
            '.2ue': '(-1.234568+/-0.000012)e+02'
        },
        # Uncertainty larger than the nominal value:
        (12.3, 456.78): {
            '': '12+/-457',
            '.1uf': '12+/-457',
            '.4uf': '12.3+/-456.8'
        },
        # ... Same thing, but with an exponent:
        (12.3, 456.78): {
            '.1ue': '(0+/-5)e+02',
            '.4ue': '(0.123+/-4.568)e+02',
            '.4ueS': '0.123(4.568)e+02'
        },

        (23456.789123, 1234.56789123): {
            '.6gS': '23456.8(1234.6)'
        },

        # Test of the various float formats: the nominal value should
        # have a similar representation as if it were directly
        # represented as a float:
        (1234567.89, 0.1): {
            '.0e': '(1+/-0)e+06',
            'e': '(1.23456789+/-0.00000010)e+06',
            'E': '(1.23456789+/-0.00000010)E+06',
            'f': '1234567.89+/-0.10',
            'F': '1234567.89+/-0.10',
            'g': '1234567.89+/-0.10',
            'G': '1234567.89+/-0.10',
            '%': '(123456789+/-10)%'
        },
        (1234567.89, 4.3): {
            'g': '1234568+/-4'
        },
        (1234567.89, 43): {  # Case where g triggers the exponent notation
            'g': '(1.23457+/-0.00004)e+06',
            'G': '(1.23457+/-0.00004)E+06'
        },


        (3.1415, 0.0001): {
            '+09.2uf': '+03.14150+/-000.00010'
            },

        (1234.56789, 0.1): {
            '.0f': '(1234+/-0.)',  # Approximate error indicated with "."
            'e': '(1.23456+/-0.00010)e+03',
            'E': '(1.23456+/-0.00010)E+03',
            'f': '1234.57+/-0.10',
            'F': '1234.57+/-0.10',
            'f': '1234.57+/-0.10',
            'F': '1234.57+/-0.10',
            '%': '123457+/-10%'
        },

        # Percent notation:
        (0.42, 0.0055): {
            # Because '%' does 0.0055*100, the value
            # 0.5499999999999999 is obtained, which rounds to 0.5. The
            # original rounded value is 0.006. The same behavior is
            # found in Python 2.7: '{:.1%}'.format(0.0055) is '0.5%'.
            '.1u%': '(42.0+/-0.5)%',
            '.1u%S': '42.0(5)%',
            '%P': u'(42.0±0.5)%'
        },

        # Particle Data Group automatic convention, including limit cases:
        (1.2345678, 0.354): {'': '1.23+/-0.35'},
        (1.2345678, 0.3549): {'': '1.23+/-0.35'},
        (1.2345678, 0.355): {'': '1.2+/-0.4'},
        (1.5678, 0.355): {'': '1.6+/-0.4'},
        (1.2345678, 0.09499): {'': '1.23+/-0.09'},
        (1.2345678, 0.095): {'': '1.23+/-0.10'},

        # Automatic extension of the uncertainty up to the decimal
        # point:
        (1000, 123): {
            '.1uf': '1000+/-123',
            # The nominal value has 1 <= mantissa < 10. The precision
            # is the number of significant digits of the uncertainty:
            '.1ue': '(1.0+/-0.1)e+03'
        },

        # Spectroscopic notation:
        (-1.23, 3.4): {
            'S': '-1.2(3.4)',
            '.2ufS': '-1.2(3.4)',
            '.3ufS': '-1.23(3.40)',
        },
        (-123.456, 0.123): {
            'S': '-123.46(12)',
            '.1ufS': '-123.5(1)',
            '.2ufS': '-123.46(12)',
            '.3ufS': '-123.456(123)',
        },
        (-123.456, 0.567): {
            'S': '-123.5(6)',
            '.1ufS': '-123.5(6)',
            '.2ufS': '-123.46(57)',
            '.3ufS': '-123.456(567)',
        },
        (-123.456, 0.004): {
            # The decimal point shows that the uncertainty is not
            # exact:
            '.2fS': '-123.46(0.00)'
        },

        # LaTeX notation:
        #
        (1234.56789, 0.1): {
            'eL': r'\left(1.23457 \pm 0.00010\right) \times 10^{3}',
            'EL': r'\left(1.23457 \pm 0.00010\right) \times 10^{3}',
            'fL': r'1234.57 \pm 0.10',
            'FL': r'1234.57 \pm 0.10',
            'fL': r'1234.57 \pm 0.10',
            'FL': r'1234.57 \pm 0.10',
            '%L': r'\left(123457 \pm 10\right) \%'
        },
        #
        # ... combined with the spectroscopic notation:
        (-1.23, 3.4): {
            'SL': '-1.2(3.4)',
            'LS': '-1.2(3.4)',
            '.2ufSL': '-1.2(3.4)',
            '.2ufLS': '-1.2(3.4)'
        },

        # Special cases for the uncertainty (0, nan) and format
        # strings (extension S, L, U,..., global width, etc.).
        #
        # Python 3.2 and 3.3 give 1.4e-12*1e+12 = 1.4000000000000001
        # instead of 1.4 for Python 3.1. The problem does not appear
        # with 1.2, so 1.2 is used.
        (-1.2e-12, 0): {
            '12.2gPL': u'  -1.2×10⁻¹²±           0',
            # Pure "width" formats are not accepted by the % operator,
            # and only %-compatible formats are accepted, for Python <
            # 2.6:
            '13S': '  -1.2(0)e-12',
            '10P': u'-1.2×10⁻¹²±         0',
            'L': r'\left(-1.2 \pm 0\right) \times 10^{-12}',
            # No factored exponent, LaTeX
            '1L': r'-1.2 \times 10^{-12} \pm 0',
            'SL': r'-1.2(0) \times 10^{-12}',
            'SP': u'-1.2(0)×10⁻¹²'
        },

        # Python 3.2 and 3.3 give 1.4e-12*1e+12 = 1.4000000000000001
        # instead of 1.4 for Python 3.1. The problem does not appear
        # with 1.2, so 1.2 is used.
        (-1.2e-12, float('nan')): {
            '.2uG': '(-1.2+/-%s)E-12' % NaN_EFG,  # u ignored, format used
            '15GS': '  -1.2(%s)E-12' % NaN_EFG,
            'SL': r'-1.2(\mathrm{nan}) \times 10^{-12}',  # LaTeX NaN
            # Pretty-print priority, but not for NaN:
            'PSL': r'-1.2(\mathrm{nan})×10⁻¹²',
            'L': r'\left(-1.2 \pm \mathrm{nan}\right) \times 10^{-12}',
            # Uppercase NaN and LaTeX:
            '.1EL': (r'\left(-1.2 \pm \mathrm{%s}\right) \times 10^{-12}'
                     % NaN_EFG),
            '10': '  -1.2e-12+/-       nan',
            '15S': '  -1.2(nan)e-12'
        },

        (3.14e-10, 0.01e-10): {
            # Character (Unicode) strings:
            u'P': u'(3.140±0.010)×10⁻¹⁰',  # PDG rules: 2 digits
            u'PL': u'(3.140±0.010)×10⁻¹⁰',  # Pretty-print has higher priority
            # Truncated non-zero uncertainty:
            '.1e': '(3.1+/-0.0)e-10',
            '.1eS': '3.1(0.0)e-10'
        },

        # Some special cases:
        (1, float('nan')): {
            'g': '1+/-nan',
            'G': '1+/-%s' % NaN_EFG,
            '%': '(100.000000+/-nan)%',  # The % format type is like f
            # Should be the same as '+05', for floats, but is not, in
            # Python 2.7:
            '+05g': '+0001+/-00nan',
            # 5 is the *minimal* width, 6 is the default number of
            # digits after the decimal point:
            '+05%': '(+100.000000+/-00nan)%',
            # There is a difference between '{}'.format(1.) and
            # '{:g}'.format(1.), which is not fully obvious in the
            # documentation, which indicates that a None format type
            # is like g. The reason is that the empty format string is
            # actually interpreted as str(), and that str() does not
            # have to behave like g ('{}'.format(1.234567890123456789)
            # and '{:g}'.format(1.234567890123456789) are different).
            '': '1.0+/-nan',
            # This is ugly, but consistent with
            # '{:+05}'.format(float('nan')) and format(1.) (which
            # differs from format(1)!):
            '+05': '+01.0+/-00nan'
        },

        (9.9, 0.1): {
            '.1ue': '(9.9+/-0.1)e+00',
            '.0fS': '10(0.)'
        },
        (9.99, 0.1): {
             # The precision has an effect on the exponent, like for
             # floats:
            '.2ue': '(9.99+/-0.10)e+00',  # Same exponent as for 9.99 alone
            '.1ue': '(1.00+/-0.01)e+01'  # Same exponent as for 9.99 alone
        },
        # 0 uncertainty: nominal value displayed like a float:
        (1.2345, 0): {
            '.2ue': '(1.23+/-0)e+00',
            '1.2ue': '1.23e+00+/-0',  # No factored exponent
            '.2uf': '1.23+/-0',
            '.2ufS': '1.23(0)',
            '.2fS': '1.23(0)',
            'g': '1.2345+/-0',
            '': '1.2345+/-0'
        },

        # Alignment and filling characters (supported in Python 2.6+):
        (3.1415e10, 0): {
            '<15': '31415000000.0  +/-0              ',
            '<20S': '31415000000.0(0)    ',
            # Trying to trip the format parsing with a fill character
            # which is an alignment character:
            '=>15': '==31415000000.0+/-==============0'
        },

        (1234.56789, 0): {
            '1.2ue': '1.23e+03+/-0',  # u ignored
            '1.2e': '1.23e+03+/-0',
            # Default precision = 6
            'eL': r'\left(1.234568 \pm 0\right) \times 10^{3}',
            'EL': r'\left(1.234568 \pm 0\right) \times 10^{3}',
            'fL': r'1234.567890 \pm 0',
            'FL': r'1234.567890 \pm 0',
            '%L': r'\left(123456.789000 \pm 0\right) \%'
        },

        (1e5, 0): {
            'g': '100000+/-0'
        },
        (1e6, 0): {
            # A default precision of 6 is used because the uncertainty
            # cannot be used for defining a default precision (it does
            # not have a magnitude):
            'g': '(1+/-0)e+06'
        },
        (1e6+10, 0): {
            # A default precision of 6 is used because the uncertainty
            # cannot be used for defining a default precision (it does
            # not have a magnitude):
            'g': '(1.00001+/-0)e+06'
        },
        # Rounding of the uncertainty that "changes" the number of
        # significant digits:
        (1, 0.994): {
            '.3uf': '1.000+/-0.994',
            '.2uf': '1.00+/-0.99',
            '.1uf': '1+/-1'  # Discontinuity in the number of digits
        },
        (12.3, 2.3): {
            '.2ufS': '12.3(2.3)'  # Decimal point on the uncertainty
        },
        (12.3, 2.3): {
            '.1ufS': '12(2)'  # No decimal point on the uncertainty
        },
        (0, 0): {  # Make defining the first significant digit problematic
            '.1f': '0.0+/-0',  # Simple float formatting
            'g': '0+/-0'
        },
        (1.2e-34, 5e-67): {
            '.6g': '(1.20000+/-0.00000)e-34',
            '13.6g': '  1.20000e-34+/-  0.00000e-34',
            '13.6G': '  1.20000E-34+/-  0.00000E-34',
            '.6GL': r'\left(1.20000 \pm 0.00000\right) \times 10^{-34}',
            '.6GLp': r'\left(1.20000 \pm 0.00000\right) \times 10^{-34}',
        },

        (float('nan'), 100): {  # NaN *nominal value*
            '': 'nan+/-100.0',  # Like '{}'.format(100.)
            'g': 'nan+/-100',  # Like '{:g}'.format(100.)
            '.1e': '(nan+/-1.0)e+02',  # Similar to 1±nan
            '.1E': '(%s+/-1.0)E+02' % NaN_EFG,
            '.1ue': '(nan+/-1)e+02',
            '10.1e': '       nan+/-   1.0e+02'
        },
        (float('nan'), 1e8): {  # NaN *nominal value*
            '': 'nan+/-100000000.0',  # Like '{}'.format(1e8)
            'g': '(nan+/-1)e+08',  # Like '{:g}'.format(1e8)
            '.1e': '(nan+/-1.0)e+08',
            '.1E': '(%s+/-1.0)E+08' % NaN_EFG,
            '.1ue': '(nan+/-1)e+08',
            '10.1e': '       nan+/-   1.0e+08'  # 'nane+08' would be strange
        },
        (float('nan'), 123456789): {  # NaN *nominal value*
            '': 'nan+/-123456789.0',  # Similar to '{}'.format(123456789.)
            'g': '(nan+/-1.23457)e+08',  # Similar to '{:g}'.format(123456789.)
            '.1e': '(nan+/-1.2)e+08',
            '.1E': '(%s+/-1.2)E+08' % NaN_EFG,
            '.1ue': '(nan+/-1)e+08',
            '.1ueL': r'\left(\mathrm{nan} \pm 1\right) \times 10^{8}',
            '10.1e': '       nan+/-   1.2e+08',
            '10.1eL': r'\mathrm{nan} \pm 1.2 \times 10^{8}'
        },
        (float('nan'), float('nan')): {  # *Double* NaN
            '': 'nan+/-nan',
            '.1e': 'nan+/-nan',
            '.1E': '%s+/-%s' % (NaN_EFG, NaN_EFG),
            '.1ue': 'nan+/-nan',
            'EL': r'\mathrm{%s} \pm \mathrm{%s}' % (NaN_EFG, NaN_EFG)
        },

        (float('inf'), 100): {  # Inf *nominal value*
            '': 'inf+/-100.0',  # Like '{}'.format(100.)
            'g': 'inf+/-100',  # Like '{:g}'.format(100.)
            '.1e': '(inf+/-1.0)e+02',  # Similar to 1±inf
            '.1E': '(%s+/-1.0)E+02' % Inf_EFG,
            '.1ue': '(inf+/-1)e+02',
            '10.1e': '       inf+/-   1.0e+02'
        },
        (float('inf'), 1e8): {  # Inf *nominal value*
            '': 'inf+/-100000000.0',  # Like '{}'.format(1e8)
            'g': '(inf+/-1)e+08',  # Like '{:g}'.format(1e8)
            '.1e': '(inf+/-1.0)e+08',
            '.1E': '(%s+/-1.0)E+08' % Inf_EFG,
            '.1ue': '(inf+/-1)e+08',
            '10.1e': '       inf+/-   1.0e+08'  # 'infe+08' would be strange
        },
        (float('inf'), 123456789): {  # Inf *nominal value*
            '': 'inf+/-123456789.0',  # Similar to '{}'.format(123456789.)
            'g': '(inf+/-1.23457)e+08',  # Similar to '{:g}'.format(123456789.)
            '.1e': '(inf+/-1.2)e+08',
            '.1ep': '(inf+/-1.2)e+08',
            '.1E': '(%s+/-1.2)E+08' % Inf_EFG,
            '.1ue': '(inf+/-1)e+08',
            '.1ueL': r'\left(\infty \pm 1\right) \times 10^{8}',
            '.1ueLp': r'\left(\infty \pm 1\right) \times 10^{8}',
            '10.1e': '       inf+/-   1.2e+08',
            '10.1eL': r'    \infty \pm 1.2 \times 10^{8}'
        },
        (float('inf'), float('inf')): {  # *Double* Inf
            '': 'inf+/-inf',
            '.1e': 'inf+/-inf',
            '.1E': '%s+/-%s' % (Inf_EFG, Inf_EFG),
            '.1ue': 'inf+/-inf',
            'EL': r'\infty \pm \infty',
            'ELp': r'\left(\infty \pm \infty\right)',
        },

        # Like the tests for +infinity, but for -infinity:
        (float('-inf'), 100): {  # Inf *nominal value*
            '': '-inf+/-100.0',  # Like '{}'.format(100.)
            'g': '-inf+/-100',  # Like '{:g}'.format(100.)
            '.1e': '(-inf+/-1.0)e+02',  # Similar to 1±inf
            '.1E': '(-%s+/-1.0)E+02' % Inf_EFG,
            '.1ue': '(-inf+/-1)e+02',
            '10.1e': '      -inf+/-   1.0e+02'
        },
        (float('-inf'), 1e8): {  # Inf *nominal value*
            '': '-inf+/-100000000.0',  # Like '{}'.format(1e8)
            'g': '(-inf+/-1)e+08',  # Like '{:g}'.format(1e8)
            '.1e': '(-inf+/-1.0)e+08',
            '.1E': '(-%s+/-1.0)E+08' % Inf_EFG,
            '.1ue': '(-inf+/-1)e+08',
            '10.1e': '      -inf+/-   1.0e+08'  # 'infe+08' would be strange
        },
        (float('-inf'), 123456789): {  # Inf *nominal value*
            '': '-inf+/-123456789.0',  # Similar to '{}'.format(123456789.)
            'g': '(-inf+/-1.23457)e+08',  # Similar to '{:g}'.format(123456789.)
            '.1e': '(-inf+/-1.2)e+08',
            '.1E': '(-%s+/-1.2)E+08' % Inf_EFG,
            '.1ue': '(-inf+/-1)e+08',
            '.1ueL': r'\left(-\infty \pm 1\right) \times 10^{8}',
            '10.1e': '      -inf+/-   1.2e+08',
            '10.1eL': r'   -\infty \pm 1.2 \times 10^{8}'
        },
        (float('-inf'), float('inf')): {  # *Double* Inf
            '': '-inf+/-inf',
            '.1e': '-inf+/-inf',
            '.1E': '-%s+/-%s' % (Inf_EFG, Inf_EFG),
            '.1ue': '-inf+/-inf',
            'EL': r'-\infty \pm \infty'
        },

        # The Particle Data Group convention trumps the "at least one
        # digit past the decimal point" for Python floats, but only
        # with a non-zero uncertainty:
        (724.2, 26.4): {
            '': '724+/-26',
            'p': '(724+/-26)'
        },
        (724, 0): {
            '': '724.0+/-0'
        },

        # More NaN and infinity, in particular with LaTeX and various
        # options:
        (float('-inf'), float('inf')): {
            'S': '-inf(inf)',
            'LS': r'-\infty(\infty)',
            'L': r'-\infty \pm \infty',
            'LP': r'-\infty±\infty',
            # The following is consistent with Python's own
            # formatting, which depends on the version of Python:
            # formatting float("-inf") with format(..., "020") gives
            # '-0000000000000000inf' with Python 2.7, but
            # '-00000000000000.0inf' with Python 2.6. However, Python
            # 2.6 gives the better, Python 2.7 form when format()ting
            # with "020g" instead, so this formatting would be better,
            # in principle, and similarly for "%020g" % ... Thus,
            # Python's format() breaks the official rule according to
            # which no format type is equivalent to "g", for
            # floats. If the better behavior was needed, internal
            # formatting could in principle force the "g" formatting
            # type when none is given; however, Python does not
            # actually fully treat the none format type in the same
            # was as the "g" format, so this solution cannot be used,
            # as it would break other formatting behaviors in this
            # code. It is thus best to mimic the native behavior of
            # none type formatting (even if it does not look so good
            # in Python 2.6).
            '020S': format(float("-inf"), '015')+'(inf)'
        },
        (-float('nan'), float('inf')): {
            'S': 'nan(inf)',
            'LS': r'\mathrm{nan}(\infty)',
            'L': r'\mathrm{nan} \pm \infty',
            'LP': r'\mathrm{nan}±\infty'
        },

        # Leading zeroes in the shorthand notation:
        (-2, 3): {
            "020S": "-000000000002.0(3.0)"
        }

    }

    # ',' format option: introduced in Python 2.7
    if sys.version_info >= (2, 7):

        tests.update({
            (1234.56789, 0.012): {
                ',.1uf': '1,234.57+/-0.01'
                },

            (123456.789123, 1234.5678): {
                ',f': '123,457+/-1,235',  # Particle Data Group convention
                ',.4f': '123,456.7891+/-1,234.5678'
                }
        })

    # True if we can detect that the Jython interpreter is running this code:
    try:
        jython_detected = sys.subversion[0] == 'Jython'
    except AttributeError:
        jython_detected = False

    for (values, representations) in tests.items():

        value = ufloat(*values)

        for (format_spec, result) in representations.items():

            # print "FORMATTING {} WITH '{}'".format(repr(value), format_spec)

            # Jython 2.5.2 does not always represent NaN as nan or NAN
            # in the CPython way: for example, '%.2g' % float('nan')
            # is '\ufffd'. The test is skipped, in this case:
            if jython_detected and (
                    isnan(value.std_dev) or isnan(value.nominal_value)):
                continue

            # Call that works with Python < 2.6 too:
            representation = value.format(format_spec)

            assert representation == result, (
                # The representation is used, for terminal that do not
                # support some characters like ±, and superscripts:
                'Incorrect representation %r for format %r of %r:'
                ' %r expected.'
                % (representation, format_spec, value, result))

            # An empty format string is like calling str()
            # (http://docs.python.org/2/library/string.html#formatspec):
            if not format_spec:
                assert representation == str(value), (
                    'Empty format should give the same thing as str():'
                    ' %s obtained instead of %s'
                    % (representation, str(value)))

            # Parsing back into a number with uncertainty (unless the
            # LaTeX or comma notation is used):
            if (not set(format_spec).intersection('L,*%')  # * = fill with *
                # "0nan"
                and '0nan' not in representation.lower()
                # "0inf"
                and '0inf' not in representation.lower()
                # Specific case:
                and '=====' not in representation):

                value_back = ufloat_fromstr(representation)

                # The original number and the new one should be consistent
                # with each other:
                try:

                    # The nominal value can be rounded to 0 when the
                    # uncertainty is larger (because p digits on the
                    # uncertainty can still show 0.00... for the
                    # nominal value). The relative error is infinite,
                    # so this should not cause an error:
                    if value_back.nominal_value:
                        assert numbers_close(value.nominal_value,
                                              value_back.nominal_value, 2.4e-1)

                    # If the uncertainty is zero, then the relative
                    # change can be large:
                    assert numbers_close(value.std_dev,
                                         value_back.std_dev, 3e-1)

                except AssertionError:
                    # !! The following string formatting requires
                    # str() to work (to not raise an exception) on the
                    # values (which have a non-standard class):
                    raise AssertionError(
                        'Original value %s and value %s parsed from %r'
                        ' (obtained through format specification %r)'
                        ' are not close enough'
                        % (value, value_back, representation, format_spec))

def test_unicode_format():
    '''Test of the unicode formatting of numbers with uncertainties'''

    x = ufloat(3.14159265358979, 0.25)

    assert isinstance(u'Résultat = %s' % x.format(''), str)
    assert isinstance(u'Résultat = %s' % x.format('P'), str)

def test_custom_pretty_print_and_latex():
    '''Test of the pretty-print and LaTeX format customizations'''

    x = ufloat(2, 0.1)*1e-11

    # We will later restore the defaults:
    PREV_CUSTOMIZATIONS = {
        var: getattr(uncert_core, var).copy()
        for var in ['PM_SYMBOLS', 'MULT_SYMBOLS', 'GROUP_SYMBOLS']}
    
    # Customizations:
    for format in ["pretty-print", "latex"]:
        uncert_core.PM_SYMBOLS[format] = u" ± "
        uncert_core.MULT_SYMBOLS[format] = u"⋅"
        uncert_core.GROUP_SYMBOLS[format] = ( "[", "]" )

    assert u"{:P}".format(x) == u'[2.00 ± 0.10]⋅10⁻¹¹'
    assert u"{:L}".format(x) == u'[2.00 ± 0.10] ⋅ 10^{-11}'

    # We restore the defaults:
    for (var, setting) in PREV_CUSTOMIZATIONS.items():
        setattr(uncert_core, var, setting)

###############################################################################

# The tests below require NumPy, which is an optional package:
try:
    import numpy
except ImportError:
    pass
else:

    def test_numpy_comparison():
        "Comparison with a NumPy array."

        x = ufloat(1, 0.1)

        # Comparison with a different type:
        assert x != [x, x]

        # NumPy arrays can be compared, through element-wise
        # comparisons.  Numbers with uncertainties should yield the
        # same kind of results as pure floats (i.e., a NumPy array,
        # etc.).

        # We test the comparison operators both for the uncertainties
        # package *and* the NumPy package:

        # Equalities, etc.:
        assert len(x == numpy.arange(10)) == 10
        assert len(numpy.arange(10) == x) == 10
        assert len(x != numpy.arange(10)) == 10
        assert len(numpy.arange(10) != x) == 10
        assert len(x == numpy.array([x, x, x])) == 3
        assert len(numpy.array([x, x, x]) == x) == 3
        assert numpy.all(x == numpy.array([x, x, x]))

        # Inequalities:
        assert len(x < numpy.arange(10)) == 10
        assert len(numpy.arange(10) > x) == 10
        assert len(x <= numpy.arange(10)) == 10
        assert len(numpy.arange(10) >= x) == 10
        assert len(x > numpy.arange(10)) == 10
        assert len(numpy.arange(10) < x) == 10
        assert len(x >= numpy.arange(10)) == 10
        assert len(numpy.arange(10) <= x) == 10

        # More detailed test, that shows that the comparisons are
        # meaningful (x >= 0, but not x <= 1):
        assert numpy.all((x >= numpy.arange(3)) == [True, False, False])

    def test_correlated_values():
        """
        Correlated variables.
        Test through the input of the (full) covariance matrix.
        """

        u = uncert_core.ufloat(1, 0.1)
        cov = uncert_core.covariance_matrix([u])
        # "1" is used instead of u.nominal_value because
        # u.nominal_value might return a float.  The idea is to force
        # the new variable u2 to be defined through an integer nominal
        # value:
        u2, = uncert_core.correlated_values([1], cov)
        expr = 2*u2  # Calculations with u2 should be possible, like with u

        ####################

        # Covariances between output and input variables:

        x = ufloat(1, 0.1)
        y = ufloat(2, 0.3)
        z = -3*x+y

        covs = uncert_core.covariance_matrix([x, y, z])

        # Test of the diagonal covariance elements:
        assert uarrays_close(
            numpy.array([v.std_dev**2 for v in (x, y, z)]),
            numpy.array(covs).diagonal())

        # "Inversion" of the covariance matrix: creation of new
        # variables:
        (x_new, y_new, z_new) = uncert_core.correlated_values(
            [x.nominal_value, y.nominal_value, z.nominal_value],
            covs,
            tags = ['x', 'y', 'z'])

        # Even the uncertainties should be correctly reconstructed:
        assert uarrays_close(numpy.array((x, y, z)),
                              numpy.array((x_new, y_new, z_new)))

        # ... and the covariances too:
        assert uarrays_close(
            numpy.array(covs),
            numpy.array(uncert_core.covariance_matrix([x_new, y_new, z_new])))

        assert uarrays_close(
            numpy.array([z_new]), numpy.array([-3*x_new+y_new]))

        ####################

        # ... as well as functional relations:

        u = ufloat(1, 0.05)
        v = ufloat(10, 0.1)
        sum_value = u+2*v

        # Covariance matrices:
        cov_matrix = uncert_core.covariance_matrix([u, v, sum_value])

        # Correlated variables can be constructed from a covariance
        # matrix, if NumPy is available:
        (u2, v2, sum2) = uncert_core.correlated_values(
            [x.nominal_value for x in [u, v, sum_value]],
            cov_matrix)

        # uarrays_close() is used instead of numbers_close() because
        # it compares uncertainties too:
        assert uarrays_close(numpy.array([u]), numpy.array([u2]))
        assert uarrays_close(numpy.array([v]), numpy.array([v2]))
        assert uarrays_close(numpy.array([sum_value]), numpy.array([sum2]))
        assert uarrays_close(numpy.array([0]),
                            numpy.array([sum2-(u2+2*v2)]))


        # Spot checks of the correlation matrix:
        corr_matrix = uncert_core.correlation_matrix([u, v, sum_value])
        assert numbers_close(corr_matrix[0,0], 1)
        assert numbers_close(corr_matrix[1,2], 2*v.std_dev/sum_value.std_dev)

        ####################

        # Test of numerical robustness despite wildly different
        # orders of magnitude (see
        # https://github.com/lebigot/uncertainties/issues/95):
        cov = numpy.diag([1e-70, 1e-70, 1e10])
        cov[0, 1] = cov[1, 0] = 0.9e-70
        cov[[0, 1], 2] = -3e-34
        cov[2, [0, 1]] = -3e-34
        variables = uncert_core.correlated_values([0]*3, cov)

        # Since the numbers are very small, we need to compare them
        # in a stricter way, that handles the case of a 0 variance
        # in `variables`:
        assert numbers_close(
                1e66*cov[0,0], 1e66*variables[0].s**2, tolerance=1e-5)
        assert numbers_close(
                1e66*cov[1,1], 1e66*variables[1].s**2, tolerance=1e-5)

        ####################

        # 0 variances are a bit special, since the correlation matrix
        # cannot be calculated naively, so we test that there is no
        # specific problem in this case:

        cov = numpy.diag([0, 0, 10])
        nom_values = [1, 2, 3]
        variables = uncert_core.correlated_values(nom_values, cov)

        for (variable, nom_value, variance) in zip(
            variables, nom_values, cov.diagonal()):
            
            assert numbers_close(variable.n, nom_value)
            assert numbers_close(variable.s**2, variance) 
        
        assert uarrays_close(
            cov,
            numpy.array(uncert_core.covariance_matrix(variables)))

    def test_correlated_values_correlation_mat():
        '''
        Tests the input of correlated value.

        Test through their correlation matrix (instead of the
        covariance matrix).
        '''

        x = ufloat(1, 0.1)
        y = ufloat(2, 0.3)
        z = -3*x+y

        cov_mat = uncert_core.covariance_matrix([x, y, z])

        std_devs = numpy.sqrt(numpy.array(cov_mat).diagonal())

        corr_mat = cov_mat/std_devs/std_devs[numpy.newaxis].T

        # We make sure that the correlation matrix is indeed diagonal:
        assert (corr_mat-corr_mat.T).max() <= 1e-15
        # We make sure that there are indeed ones on the diagonal:
        assert (corr_mat.diagonal()-1).max() <= 1e-15

        # We try to recover the correlated variables through the
        # correlation matrix (not through the covariance matrix):

        nominal_values = [v.nominal_value for v in (x, y, z)]
        std_devs = [v.std_dev for v in (x, y, z)]
        x2, y2, z2 = uncert_core.correlated_values_norm(
            list(zip(nominal_values, std_devs)), corr_mat)

        # uarrays_close() is used instead of numbers_close() because
        # it compares uncertainties too:

        # Test of individual variables:
        assert uarrays_close(numpy.array([x]), numpy.array([x2]))
        assert uarrays_close(numpy.array([y]), numpy.array([y2]))
        assert uarrays_close(numpy.array([z]), numpy.array([z2]))

        # Partial correlation test:
        assert uarrays_close(numpy.array([0]), numpy.array([z2-(-3*x2+y2)]))

        # Test of the full covariance matrix:
        assert uarrays_close(
            numpy.array(cov_mat),
            numpy.array(uncert_core.covariance_matrix([x2, y2, z2])))
