Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions tests/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,18 @@ def numbers_close(x, y, tolerance=1e-6):
return abs(x or y) < tolerance


def nominal_and_std_dev_close(x, y, tolerance=1e-6):
"""
Tests if two numbers with uncertainties are close, NOT as random
variables. Checks whether the magnitude of the nominal
values and standard deviations are close.

The tolerance is applied to both the nominal value and the
standard deviation of the difference between the numbers.
"""
return numbers_close(x.n, y.n, tolerance) and numbers_close(x.s, y.s, tolerance)


def ufloats_close(x, y, tolerance=1e-6):
"""
Tests if two numbers with uncertainties are close, as random
Expand Down
247 changes: 247 additions & 0 deletions tests/test_ufloatnumpy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
from uncertainties import umath, ufloat
from helpers import nominal_and_std_dev_close
import numpy as np
import pytest

a = ufloat(1, 0.1)
b = ufloat(2, 0.2)


class TestArithmetic:
@pytest.mark.parametrize(
"first, second, expected",
[
(a, b, ufloat(3.0, 0.223606797749979)),
(a, a, ufloat(2.0, 0.2)),
],
)
def test_add(self, first, second, expected):
result = first + second
assert nominal_and_std_dev_close(result, expected)

result = np.add(first, second)
assert nominal_and_std_dev_close(result, expected)

@pytest.mark.parametrize(
"first, second, expected",
[
(a, b, ufloat(-1.00, 0.223606797749979)),
(a, a, ufloat(0.0, 0.0)),
],
)
def test_subtact(self, first, second, expected):
result = first - second
assert nominal_and_std_dev_close(result, expected)

result = np.subtract(first, second)
assert nominal_and_std_dev_close(result, expected)

@pytest.mark.parametrize(
"first, second, expected",
[
(a, b, ufloat(2.0, 0.28284271247461906)),
(a, a, ufloat(1.0, 0.2)),
],
)
def test_multiply(self, first, second, expected):
result = first * second
assert nominal_and_std_dev_close(result, expected)

result = np.multiply(first, second)
assert nominal_and_std_dev_close(result, expected)

@pytest.mark.parametrize(
"first, second, expected",
[
(a, b, ufloat(0.5, 0.07071067811865477)),
(a, a, ufloat(1.0, 0.0)),
],
)
def test_divide(self, first, second, expected):
result = first / second
assert nominal_and_std_dev_close(result, expected)

result = np.divide(first, second)
assert nominal_and_std_dev_close(result, expected)

result = np.true_divide(first, second)
assert nominal_and_std_dev_close(result, expected)

@pytest.mark.parametrize(
"first, second, expected",
[
(a, b, ufloat(0.0, 0.0)),
(a, a, ufloat(1.0, 0.0)),
],
)
def test_floor_divide(self, first, second, expected):
result = first // second
assert nominal_and_std_dev_close(result, expected)

result = np.floor_divide(first, second)
assert nominal_and_std_dev_close(result, expected)


class TestComparative:
@pytest.mark.parametrize(
"first, second, expected",
[
(a, b, False),
(a, a, True),
],
)
def test_equal(self, first, second, expected):
result = first == second
assert result == expected

result = np.equal(first, second)
assert result == expected

@pytest.mark.parametrize(
"first, second, expected",
[
(a, b, True),
(a, a, False),
],
)
def test_not_equal(self, first, second, expected):
result = first != second
assert result == expected

result = np.not_equal(first, second)
assert result == expected

@pytest.mark.parametrize(
"first, second, expected",
[
(a, b, True),
(a, a, False),
],
)
def test_less(self, first, second, expected):
result = first < second
assert result == expected

result = np.less(first, second)
assert result == expected

@pytest.mark.parametrize(
"first, second, expected",
[
(a, b, True),
(a, a, True),
],
)
def test_less_equal(self, first, second, expected):
result = first <= second
assert result == expected

result = np.less_equal(first, second)
assert result == expected

@pytest.mark.parametrize(
"first, second, expected",
[
(a, b, False),
(a, a, False),
],
)
def test_greater(self, first, second, expected):
result = first > second
assert result == expected

result = np.greater(first, second)
assert result == expected

@pytest.mark.parametrize(
"first, second, expected",
[
(a, b, False),
(a, a, True),
],
)
def test_greater_equal(self, first, second, expected):
result = first >= second
assert result == expected

result = np.greater_equal(first, second)
assert result == expected


class TestUfuncs:
zero = ufloat(0.0, 0.1)
one = ufloat(1.0, 0.1)
pi_4 = ufloat(0.7853981633974483, 0.1) # pi/4
pi_2 = ufloat(1.5707963267948966, 0.1) # pi/2

@pytest.mark.parametrize(
"numpy_func, umath_func, arg, expected",
[
("cos", "cos", zero, ufloat(1.0, 0.0)),
("cos", "cos", pi_4, ufloat(0.7071067811865476, 0.07071067811865477)),
("cos", "cos", pi_2, ufloat(6.123233995736766e-17, 0.1)),
("cosh", "cosh", zero, ufloat(1.0, 0.0)),
("cosh", "cosh", pi_4, ufloat(1.324609089252006, 0.08686709614860096)),
("cosh", "cosh", pi_2, ufloat(2.5091784786580567, 0.2301298902307295)),
("sin", "sin", zero, ufloat(0.0, 0.1)),
("sin", "sin", pi_4, ufloat(0.7071067811865476, 0.07071067811865477)),
("sin", "sin", pi_2, ufloat(1.0, 6.123233995736766e-18)),
("sinh", "sinh", zero, ufloat(0.0, 0.1)),
("sinh", "sinh", pi_4, ufloat(0.8686709614860095, 0.1324609089252006)),
("sinh", "sinh", pi_2, ufloat(2.3012989023072947, 0.2509178478658057)),
("tan", "tan", zero, ufloat(0.0, 0.1)),
("tan", "tan", pi_4, ufloat(0.9999999999999999, 0.19999999999999998)),
("tan", "tan", pi_2, ufloat(1.633123935319537e16, 2.6670937881135717e31)),
("tanh", "tanh", zero, ufloat(0.0, 0.1)),
("tanh", "tanh", pi_4, ufloat(0.6557942026326724, 0.05699339637933774)),
("tanh", "tanh", pi_2, ufloat(0.9171523356672744, 0.015883159318006324)),
("arccos", "acos", zero, ufloat(1.5707963267948966, 0.1)),
("arccos", "acos", one, ufloat(0.0, float("nan"))),
("arccosh", "acosh", one, ufloat(0.0, float("nan"))),
("arcsin", "asin", zero, ufloat(0.0, 0.1)),
("arcsin", "asin", one, ufloat(1.5707963267948966, float("nan"))),
("arcsinh", "asinh", zero, ufloat(0.0, 0.1)),
("arcsinh", "asinh", one, ufloat(0.8813735870195429, 0.07071067811865475)),
("arctan", "atan", zero, ufloat(0.0, 0.1)),
("arctan", "atan", one, ufloat(0.7853981633974483, 0.05)),
("arctanh", "atanh", zero, ufloat(0.0, 0.1)),
("exp", "exp", zero, ufloat(1.0, 0.1)),
("exp", "exp", one, ufloat(2.718281828459045, 0.27182818284590454)),
("exp2", None, zero, ufloat(1.0, 0.06931471805599453)),
("exp2", None, one, ufloat(2.0, 0.13862943611198905)),
("expm1", "expm1", zero, ufloat(0.0, 0.1)),
("expm1", "expm1", one, ufloat(1.718281828459045, 0.27182818284590454)),
("log10", "log10", one, ufloat(0.0, 0.04342944819032518)),
("log1p", "log1p", zero, ufloat(0.0, 0.1)),
("log1p", "log1p", one, ufloat(0.6931471805599453, 0.05)),
("degrees", "degrees", zero, ufloat(0.0, 5.729577951308233)),
("degrees", "degrees", one, ufloat(57.29577951308232, 5.729577951308233)),
("radians", "radians", zero, ufloat(0.0, 0.0017453292519943296)),
(
"radians",
"radians",
one,
ufloat(0.017453292519943295, 0.0017453292519943296),
),
("rad2deg", "degrees", zero, ufloat(0.0, 5.729577951308233)),
("rad2deg", "degrees", one, ufloat(57.29577951308232, 5.729577951308233)),
("deg2rad", "radians", zero, ufloat(0.0, 0.0017453292519943296)),
(
"deg2rad",
"radians",
one,
ufloat(0.017453292519943295, 0.0017453292519943296),
),
("sqrt", "sqrt", zero, ufloat(0.0, float("nan"))),
("sqrt", "sqrt", one, ufloat(1.0, 0.05)),
],
)
def test_single_arg(self, numpy_func, umath_func, arg, expected):
func = getattr(np, numpy_func)
result = func(arg)
assert nominal_and_std_dev_close(result, expected)

if umath_func:
func = getattr(umath, umath_func)
result = func(arg)
assert nominal_and_std_dev_close(result, expected)
14 changes: 13 additions & 1 deletion uncertainties/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ def isinfinite(x):
modified_operators,
modified_ops_with_reflection,
)
from .ufloatnumpy import UFloatNumpy


# Attributes that are always exported (some other attributes are
# exported only if the NumPy module is available...):
Expand Down Expand Up @@ -332,7 +334,15 @@ def __setstate__(self, state):
(self.linear_combo,) = state


class AffineScalarFunc(object):
try:
import numpy
except ImportError:
parent_classes = []
else:
parent_classes = [UFloatNumpy]


class AffineScalarFunc(*parent_classes):
"""
Affine functions that support basic mathematical operations
(addition, etc.). Such functions can for instance be used for
Expand Down Expand Up @@ -657,6 +667,8 @@ def __setstate__(self, data_dict):
ops.add_arithmetic_ops(AffineScalarFunc)
ops.add_comparative_ops(AffineScalarFunc)
to_affine_scalar = AffineScalarFunc._to_affine_scalar
AffineScalarFunc._add_numpy_arithmetic_ufuncs()
AffineScalarFunc._add_numpy_comparative_ufuncs()

# Nicer name, for users: isinstance(ufloat(...), UFloat) is
# True. Also: isinstance(..., UFloat) is the test for "is this a
Expand Down
Loading