347 lines
14 KiB
Python
347 lines
14 KiB
Python
|
import itertools
|
||
|
|
||
|
import numpy as np
|
||
|
|
||
|
from numba import jit
|
||
|
from numba.core import utils
|
||
|
from numba.tests.support import TestCase, forbid_codegen
|
||
|
from .enum_usecases import *
|
||
|
import unittest
|
||
|
|
||
|
DBL_EPSILON = 2**-52
|
||
|
FLT_EPSILON = 2**-23
|
||
|
|
||
|
INF = float('inf')
|
||
|
NAN = float('nan')
|
||
|
|
||
|
|
||
|
class TestAssertPreciseEqual(TestCase):
|
||
|
"""
|
||
|
Tests for TestCase.assertPreciseEqual().
|
||
|
"""
|
||
|
|
||
|
int_types = [int]
|
||
|
np_float_types = [np.float32, np.float64]
|
||
|
float_types = [float] + np_float_types
|
||
|
np_complex_types = [np.complex64, np.complex128]
|
||
|
complex_types = [complex] + np_complex_types
|
||
|
bool_types = [bool, np.bool_]
|
||
|
|
||
|
def eq(self, left, right, **kwargs):
|
||
|
def assert_succeed(left, right):
|
||
|
self.assertPreciseEqual(left, right, **kwargs)
|
||
|
self.assertPreciseEqual(right, left, **kwargs)
|
||
|
assert_succeed(left, right)
|
||
|
assert_succeed((left, left), (right, right))
|
||
|
assert_succeed([left, left], [right, right])
|
||
|
|
||
|
def ne(self, left, right, **kwargs):
|
||
|
def assert_fail(left, right):
|
||
|
try:
|
||
|
self.assertPreciseEqual(left, right, **kwargs)
|
||
|
except AssertionError:
|
||
|
pass
|
||
|
else:
|
||
|
self.fail("%s and %s unexpectedly considered equal" % (left, right))
|
||
|
assert_fail(left, right)
|
||
|
assert_fail(right, left)
|
||
|
assert_fail((left, left), (right, right))
|
||
|
assert_fail((right, right), (left, left))
|
||
|
assert_fail([left, left], [right, right])
|
||
|
assert_fail([right, right], [left, left])
|
||
|
|
||
|
def test_types(self):
|
||
|
# assertPreciseEqual() should test for type compatibility
|
||
|
# int-like, float-like, complex-like are not compatible
|
||
|
for i, f, c in itertools.product(self.int_types, self.float_types,
|
||
|
self.complex_types):
|
||
|
self.ne(i(1), f(1))
|
||
|
self.ne(f(1), c(1))
|
||
|
self.ne(i(1), c(1))
|
||
|
# int and long are compatible between each other
|
||
|
for u, v in itertools.product(self.int_types, self.int_types):
|
||
|
self.eq(u(1), v(1))
|
||
|
# int and bool are not compatible between each other
|
||
|
for u, v in itertools.product(self.int_types, self.bool_types):
|
||
|
self.ne(u(1), v(1))
|
||
|
# NumPy float types are not compatible between each other
|
||
|
for u, v in itertools.product(self.np_float_types, self.np_float_types):
|
||
|
if u is v:
|
||
|
self.eq(u(1), v(1))
|
||
|
else:
|
||
|
self.ne(u(1), v(1))
|
||
|
# NumPy complex types are not compatible between each other
|
||
|
for u, v in itertools.product(self.np_complex_types, self.np_complex_types):
|
||
|
if u is v:
|
||
|
self.eq(u(1), v(1))
|
||
|
else:
|
||
|
self.ne(u(1), v(1))
|
||
|
|
||
|
def test_int_values(self):
|
||
|
for tp in self.int_types:
|
||
|
for prec in ['exact', 'single', 'double']:
|
||
|
self.eq(tp(0), tp(0), prec=prec)
|
||
|
self.ne(tp(0), tp(1), prec=prec)
|
||
|
self.ne(tp(-1), tp(1), prec=prec)
|
||
|
self.ne(tp(2**80), tp(1+2**80), prec=prec)
|
||
|
|
||
|
def test_bool_values(self):
|
||
|
for tpa, tpb in itertools.product(self.bool_types, self.bool_types):
|
||
|
self.eq(tpa(True), tpb(True))
|
||
|
self.eq(tpa(False), tpb(False))
|
||
|
self.ne(tpa(True), tpb(False))
|
||
|
|
||
|
def test_abs_tol_parse(self):
|
||
|
# check invalid values in abs_tol kwarg raises
|
||
|
with self.assertRaises(ValueError):
|
||
|
self.eq(np.float64(1e-17), np.float64(1e-17), abs_tol="invalid")
|
||
|
with self.assertRaises(ValueError):
|
||
|
self.eq(np.float64(1), np.float64(2), abs_tol=int(7))
|
||
|
|
||
|
def test_float_values(self):
|
||
|
for tp in self.float_types:
|
||
|
for prec in ['exact', 'single', 'double']:
|
||
|
self.eq(tp(1.5), tp(1.5), prec=prec)
|
||
|
# Signed zeros
|
||
|
self.eq(tp(0.0), tp(0.0), prec=prec)
|
||
|
self.eq(tp(-0.0), tp(-0.0), prec=prec)
|
||
|
self.ne(tp(0.0), tp(-0.0), prec=prec)
|
||
|
self.eq(tp(0.0), tp(-0.0), prec=prec, ignore_sign_on_zero=True)
|
||
|
# Infinities
|
||
|
self.eq(tp(INF), tp(INF), prec=prec)
|
||
|
self.ne(tp(INF), tp(1e38), prec=prec)
|
||
|
self.eq(tp(-INF), tp(-INF), prec=prec)
|
||
|
self.ne(tp(INF), tp(-INF), prec=prec)
|
||
|
# NaNs
|
||
|
self.eq(tp(NAN), tp(NAN), prec=prec)
|
||
|
self.ne(tp(NAN), tp(0), prec=prec)
|
||
|
self.ne(tp(NAN), tp(INF), prec=prec)
|
||
|
self.ne(tp(NAN), tp(-INF), prec=prec)
|
||
|
|
||
|
def test_float64_values(self):
|
||
|
for tp in [float, np.float64]:
|
||
|
self.ne(tp(1.0 + DBL_EPSILON), tp(1.0))
|
||
|
|
||
|
def test_float32_values(self):
|
||
|
tp = np.float32
|
||
|
self.ne(tp(1.0 + FLT_EPSILON), tp(1.0))
|
||
|
|
||
|
def test_float64_values_inexact(self):
|
||
|
for tp in [float, np.float64]:
|
||
|
for scale in [1.0, -2**3, 2**-4, -2**-20]:
|
||
|
a = scale * 1.0
|
||
|
b = scale * (1.0 + DBL_EPSILON)
|
||
|
c = scale * (1.0 + DBL_EPSILON * 2)
|
||
|
d = scale * (1.0 + DBL_EPSILON * 4)
|
||
|
self.ne(tp(a), tp(b))
|
||
|
self.ne(tp(a), tp(b), prec='exact')
|
||
|
self.eq(tp(a), tp(b), prec='double')
|
||
|
self.eq(tp(a), tp(b), prec='double', ulps=1)
|
||
|
self.ne(tp(a), tp(c), prec='double')
|
||
|
self.eq(tp(a), tp(c), prec='double', ulps=2)
|
||
|
self.ne(tp(a), tp(d), prec='double', ulps=2)
|
||
|
self.eq(tp(a), tp(c), prec='double', ulps=3)
|
||
|
self.eq(tp(a), tp(d), prec='double', ulps=3)
|
||
|
# test absolute tolerance based on eps
|
||
|
self.eq(tp(1e-16), tp(3e-16), prec='double', abs_tol="eps")
|
||
|
self.ne(tp(1e-16), tp(4e-16), prec='double', abs_tol="eps")
|
||
|
# test absolute tolerance based on value
|
||
|
self.eq(tp(1e-17), tp(1e-18), prec='double', abs_tol=1e-17)
|
||
|
self.ne(tp(1e-17), tp(3e-17), prec='double', abs_tol=1e-17)
|
||
|
|
||
|
def test_float32_values_inexact(self):
|
||
|
tp = np.float32
|
||
|
for scale in [1.0, -2**3, 2**-4, -2**-20]:
|
||
|
# About the choice of 0.9: there seem to be issues when
|
||
|
# converting
|
||
|
a = scale * 1.0
|
||
|
b = scale * (1.0 + FLT_EPSILON)
|
||
|
c = scale * (1.0 + FLT_EPSILON * 2)
|
||
|
d = scale * (1.0 + FLT_EPSILON * 4)
|
||
|
self.ne(tp(a), tp(b))
|
||
|
self.ne(tp(a), tp(b), prec='exact')
|
||
|
self.ne(tp(a), tp(b), prec='double')
|
||
|
self.eq(tp(a), tp(b), prec='single')
|
||
|
self.ne(tp(a), tp(c), prec='single')
|
||
|
self.eq(tp(a), tp(c), prec='single', ulps=2)
|
||
|
self.ne(tp(a), tp(d), prec='single', ulps=2)
|
||
|
self.eq(tp(a), tp(c), prec='single', ulps=3)
|
||
|
self.eq(tp(a), tp(d), prec='single', ulps=3)
|
||
|
# test absolute tolerance based on eps
|
||
|
self.eq(tp(1e-7), tp(2e-7), prec='single', abs_tol="eps")
|
||
|
self.ne(tp(1e-7), tp(3e-7), prec='single', abs_tol="eps")
|
||
|
# test absolute tolerance based on value
|
||
|
self.eq(tp(1e-7), tp(1e-8), prec='single', abs_tol=1e-7)
|
||
|
self.ne(tp(1e-7), tp(3e-7), prec='single', abs_tol=1e-7)
|
||
|
|
||
|
def test_complex_values(self):
|
||
|
# Complex literals with signed zeros are confusing, better use
|
||
|
# the explicit constructor.
|
||
|
c_pp, c_pn, c_np, c_nn = [complex(0.0, 0.0), complex(0.0, -0.0),
|
||
|
complex(-0.0, 0.0), complex(-0.0, -0.0)]
|
||
|
for tp in self.complex_types:
|
||
|
for prec in ['exact', 'single', 'double']:
|
||
|
self.eq(tp(1 + 2j), tp(1 + 2j), prec=prec)
|
||
|
self.ne(tp(1 + 1j), tp(1 + 2j), prec=prec)
|
||
|
self.ne(tp(2 + 2j), tp(1 + 2j), prec=prec)
|
||
|
# Signed zeros
|
||
|
self.eq(tp(c_pp), tp(c_pp), prec=prec)
|
||
|
self.eq(tp(c_np), tp(c_np), prec=prec)
|
||
|
self.eq(tp(c_nn), tp(c_nn), prec=prec)
|
||
|
self.ne(tp(c_pp), tp(c_pn), prec=prec)
|
||
|
self.ne(tp(c_pn), tp(c_nn), prec=prec)
|
||
|
# Infinities
|
||
|
self.eq(tp(complex(INF, INF)), tp(complex(INF, INF)), prec=prec)
|
||
|
self.eq(tp(complex(INF, -INF)), tp(complex(INF, -INF)), prec=prec)
|
||
|
self.eq(tp(complex(-INF, -INF)), tp(complex(-INF, -INF)), prec=prec)
|
||
|
self.ne(tp(complex(INF, INF)), tp(complex(INF, -INF)), prec=prec)
|
||
|
self.ne(tp(complex(INF, INF)), tp(complex(-INF, INF)), prec=prec)
|
||
|
self.eq(tp(complex(INF, 0)), tp(complex(INF, 0)), prec=prec)
|
||
|
# NaNs
|
||
|
self.eq(tp(complex(NAN, 0)), tp(complex(NAN, 0)), prec=prec)
|
||
|
self.eq(tp(complex(0, NAN)), tp(complex(0, NAN)), prec=prec)
|
||
|
self.eq(tp(complex(NAN, NAN)), tp(complex(NAN, NAN)), prec=prec)
|
||
|
self.eq(tp(complex(INF, NAN)), tp(complex(INF, NAN)), prec=prec)
|
||
|
self.eq(tp(complex(NAN, -INF)), tp(complex(NAN, -INF)), prec=prec)
|
||
|
# FIXME
|
||
|
#self.ne(tp(complex(NAN, INF)), tp(complex(NAN, -INF)))
|
||
|
#self.ne(tp(complex(NAN, 0)), tp(complex(NAN, 1)))
|
||
|
#self.ne(tp(complex(INF, NAN)), tp(complex(-INF, NAN)))
|
||
|
#self.ne(tp(complex(0, NAN)), tp(complex(1, NAN)))
|
||
|
#self.ne(tp(complex(NAN, 0)), tp(complex(0, NAN)))
|
||
|
# XXX should work with other precisions as well?
|
||
|
self.ne(tp(complex(INF, 0)), tp(complex(INF, 1)), prec='exact')
|
||
|
|
||
|
def test_complex128_values_inexact(self):
|
||
|
for tp in [complex, np.complex128]:
|
||
|
for scale in [1.0, -2**3, 2**-4, -2**-20]:
|
||
|
a = scale * 1.0
|
||
|
b = scale * (1.0 + DBL_EPSILON)
|
||
|
c = scale * (1.0 + DBL_EPSILON * 2)
|
||
|
aa = tp(complex(a, a))
|
||
|
ab = tp(complex(a, b))
|
||
|
bb = tp(complex(b, b))
|
||
|
self.ne(tp(aa), tp(ab))
|
||
|
self.eq(tp(aa), tp(ab), prec='double')
|
||
|
self.eq(tp(ab), tp(bb), prec='double')
|
||
|
self.eq(tp(aa), tp(bb), prec='double')
|
||
|
ac = tp(complex(a, c))
|
||
|
cc = tp(complex(c, c))
|
||
|
self.ne(tp(aa), tp(ac), prec='double')
|
||
|
self.ne(tp(ac), tp(cc), prec='double')
|
||
|
self.eq(tp(aa), tp(ac), prec='double', ulps=2)
|
||
|
self.eq(tp(ac), tp(cc), prec='double', ulps=2)
|
||
|
self.eq(tp(aa), tp(cc), prec='double', ulps=2)
|
||
|
self.eq(tp(aa), tp(cc), prec='single')
|
||
|
|
||
|
def test_complex64_values_inexact(self):
|
||
|
tp = np.complex64
|
||
|
for scale in [1.0, -2**3, 2**-4, -2**-20]:
|
||
|
a = scale * 1.0
|
||
|
b = scale * (1.0 + FLT_EPSILON)
|
||
|
c = scale * (1.0 + FLT_EPSILON * 2)
|
||
|
aa = tp(complex(a, a))
|
||
|
ab = tp(complex(a, b))
|
||
|
bb = tp(complex(b, b))
|
||
|
self.ne(tp(aa), tp(ab))
|
||
|
self.ne(tp(aa), tp(ab), prec='double')
|
||
|
self.eq(tp(aa), tp(ab), prec='single')
|
||
|
self.eq(tp(ab), tp(bb), prec='single')
|
||
|
self.eq(tp(aa), tp(bb), prec='single')
|
||
|
ac = tp(complex(a, c))
|
||
|
cc = tp(complex(c, c))
|
||
|
self.ne(tp(aa), tp(ac), prec='single')
|
||
|
self.ne(tp(ac), tp(cc), prec='single')
|
||
|
self.eq(tp(aa), tp(ac), prec='single', ulps=2)
|
||
|
self.eq(tp(ac), tp(cc), prec='single', ulps=2)
|
||
|
self.eq(tp(aa), tp(cc), prec='single', ulps=2)
|
||
|
|
||
|
def test_enums(self):
|
||
|
values = [Color.red, Color.green, Color.blue, Shake.mint,
|
||
|
Shape.circle, Shape.square, Planet.EARTH, Planet.MERCURY]
|
||
|
for val in values:
|
||
|
self.eq(val, val)
|
||
|
self.ne(val, val.value)
|
||
|
for a, b in itertools.combinations(values, 2):
|
||
|
self.ne(a, b)
|
||
|
|
||
|
def test_arrays(self):
|
||
|
a = np.arange(1, 7, dtype=np.int16).reshape((2, 3))
|
||
|
b = a.copy()
|
||
|
self.eq(a, b)
|
||
|
# Different values
|
||
|
self.ne(a, b + 1)
|
||
|
self.ne(a, b[:-1])
|
||
|
self.ne(a, b.T)
|
||
|
# Different dtypes
|
||
|
self.ne(a, b.astype(np.int32))
|
||
|
# Different layout
|
||
|
self.ne(a, b.T.copy().T)
|
||
|
# Different ndim
|
||
|
self.ne(a, b.flatten())
|
||
|
# Different writeability
|
||
|
b.flags.writeable = False
|
||
|
self.ne(a, b)
|
||
|
# Precision
|
||
|
a = np.arange(1, 3, dtype=np.float64)
|
||
|
b = a * (1.0 + DBL_EPSILON)
|
||
|
c = a * (1.0 + DBL_EPSILON * 2)
|
||
|
self.ne(a, b)
|
||
|
self.eq(a, b, prec='double')
|
||
|
self.ne(a, c, prec='double')
|
||
|
|
||
|
def test_npdatetime(self):
|
||
|
a = np.datetime64('1900', 'Y')
|
||
|
b = np.datetime64('1900', 'Y')
|
||
|
c = np.datetime64('1900-01-01', 'D')
|
||
|
d = np.datetime64('1901', 'Y')
|
||
|
self.eq(a, b)
|
||
|
# Different unit
|
||
|
self.ne(a, c)
|
||
|
# Different value
|
||
|
self.ne(a, d)
|
||
|
|
||
|
def test_nptimedelta(self):
|
||
|
a = np.timedelta64(1, 'h')
|
||
|
b = np.timedelta64(1, 'h')
|
||
|
c = np.timedelta64(60, 'm')
|
||
|
d = np.timedelta64(2, 'h')
|
||
|
self.eq(a, b)
|
||
|
# Different unit
|
||
|
self.ne(a, c)
|
||
|
# Different value
|
||
|
self.ne(a, d)
|
||
|
|
||
|
|
||
|
class TestMisc(TestCase):
|
||
|
|
||
|
def test_assertRefCount(self):
|
||
|
# Use floats to avoid integer interning
|
||
|
x = 55.
|
||
|
y = 66.
|
||
|
l = []
|
||
|
with self.assertRefCount(x, y):
|
||
|
pass
|
||
|
with self.assertRaises(AssertionError) as cm:
|
||
|
# y gains a reference
|
||
|
with self.assertRefCount(x, y):
|
||
|
l.append(y)
|
||
|
self.assertIn("66", str(cm.exception))
|
||
|
|
||
|
def test_forbid_codegen(self):
|
||
|
"""
|
||
|
Test that forbid_codegen() prevents code generation using the @jit
|
||
|
decorator.
|
||
|
"""
|
||
|
def f():
|
||
|
return 1
|
||
|
with forbid_codegen():
|
||
|
with self.assertRaises(RuntimeError) as raises:
|
||
|
cfunc = jit(nopython=True)(f)
|
||
|
cfunc()
|
||
|
self.assertIn("codegen forbidden by test case", str(raises.exception))
|
||
|
|
||
|
|
||
|
if __name__ == '__main__':
|
||
|
unittest.main()
|