From 58e9e27c0c110f9be1558a53fb547dc1abc76fa4 Mon Sep 17 00:00:00 2001 From: Sebastian Berg Date: Thu, 5 Dec 2013 22:53:26 +0100 Subject: [PATCH 1/2] DEP: Deprecate boolean `-` operations Boolean - is not well defined, especially the unary and binary operator are not compatible. In general boolean minus seems to have no real application and does not do what might be expected. All "allclose" type functions (numpy, tests, masked) have to now check for boolean to avoid the deprecation warning. In the future one could think about removing it again and just allowing the upcast. --- numpy/core/code_generators/generate_umath.py | 2 +- numpy/core/numeric.py | 7 ++- numpy/core/src/umath/ufunc_type_resolution.c | 46 +++++++++++++++++++- numpy/core/src/umath/ufunc_type_resolution.h | 7 +++ numpy/core/tests/test_defchararray.py | 6 +-- numpy/core/tests/test_deprecations.py | 23 ++++++++++ numpy/core/tests/test_regression.py | 5 ++- numpy/ma/core.py | 17 ++++++-- numpy/ma/extras.py | 4 +- numpy/testing/utils.py | 7 ++- 10 files changed, 109 insertions(+), 15 deletions(-) diff --git a/numpy/core/code_generators/generate_umath.py b/numpy/core/code_generators/generate_umath.py index e02cb87093f2..e3c9cf28bee0 100644 --- a/numpy/core/code_generators/generate_umath.py +++ b/numpy/core/code_generators/generate_umath.py @@ -369,7 +369,7 @@ def english_upper(s): 'negative': Ufunc(1, 1, None, docstrings.get('numpy.core.umath.negative'), - 'PyUFunc_SimpleUnaryOperationTypeResolver', + 'PyUFunc_NegativeTypeResolver', TD(bints+flts+timedeltaonly), TD(cmplx, f='neg'), TD(O, f='PyNumber_Negative'), diff --git a/numpy/core/numeric.py b/numpy/core/numeric.py index 39e5a4cd5258..6b078ae31e9b 100644 --- a/numpy/core/numeric.py +++ b/numpy/core/numeric.py @@ -2154,7 +2154,12 @@ def allclose(a, b, rtol=1.e-5, atol=1.e-8): # ignore invalid fpe's with errstate(invalid='ignore'): - r = all(less_equal(abs(x-y), atol + rtol * abs(y))) + if not x.dtype.kind == 'b' and not y.dtype.kind == 'b': + diff = abs(x - y) + else: + diff = x ^ y + + r = all(less_equal(diff, atol + rtol * abs(y))) return r diff --git a/numpy/core/src/umath/ufunc_type_resolution.c b/numpy/core/src/umath/ufunc_type_resolution.c index 12d8e406b0b6..ffdb15bbe8da 100644 --- a/numpy/core/src/umath/ufunc_type_resolution.c +++ b/numpy/core/src/umath/ufunc_type_resolution.c @@ -367,6 +367,34 @@ PyUFunc_SimpleUnaryOperationTypeResolver(PyUFuncObject *ufunc, return 0; } + +NPY_NO_EXPORT int +PyUFunc_NegativeTypeResolver(PyUFuncObject *ufunc, + NPY_CASTING casting, + PyArrayObject **operands, + PyObject *type_tup, + PyArray_Descr **out_dtypes) +{ + int ret; + ret = PyUFunc_SimpleUnaryOperationTypeResolver(ufunc, casting, operands, + type_tup, out_dtypes); + if (ret < 0) { + return ret; + } + + /* The type resolver would have upcast already */ + if (out_dtypes[0]->type_num == NPY_BOOL) { + if (DEPRECATE("numpy boolean negative (the unary `-` operator) is " + "deprecated, use the bitwise_xor (the `^` operator) " + "or the logical_xor function instead.") < 0) { + return -1; + } + } + + return ret; +} + + /* * The ones_like function shouldn't really be a ufunc, but while it * still is, this provides type resolution that always forces UNSAFE @@ -762,8 +790,22 @@ PyUFunc_SubtractionTypeResolver(PyUFuncObject *ufunc, /* Use the default when datetime and timedelta are not involved */ if (!PyTypeNum_ISDATETIME(type_num1) && !PyTypeNum_ISDATETIME(type_num2)) { - return PyUFunc_SimpleBinaryOperationTypeResolver(ufunc, casting, - operands, type_tup, out_dtypes); + int ret; + ret = PyUFunc_SimpleBinaryOperationTypeResolver(ufunc, casting, + operands, type_tup, out_dtypes); + if (ret < 0) { + return ret; + } + + /* The type resolver would have upcast already */ + if (out_dtypes[0]->type_num == NPY_BOOL) { + if (DEPRECATE("numpy boolean subtract (the binary `-` operator) is " + "deprecated, use the bitwise_xor (the `^` operator) " + "or the logical_xor function instead.") < 0) { + return -1; + } + } + return ret; } if (type_num1 == NPY_TIMEDELTA) { diff --git a/numpy/core/src/umath/ufunc_type_resolution.h b/numpy/core/src/umath/ufunc_type_resolution.h index a1241827e100..a1e28d75b9d0 100644 --- a/numpy/core/src/umath/ufunc_type_resolution.h +++ b/numpy/core/src/umath/ufunc_type_resolution.h @@ -15,6 +15,13 @@ PyUFunc_SimpleUnaryOperationTypeResolver(PyUFuncObject *ufunc, PyObject *type_tup, PyArray_Descr **out_dtypes); +NPY_NO_EXPORT int +PyUFunc_NegativeTypeResolver(PyUFuncObject *ufunc, + NPY_CASTING casting, + PyArrayObject **operands, + PyObject *type_tup, + PyArray_Descr **out_dtypes); + NPY_NO_EXPORT int PyUFunc_OnesLikeTypeResolver(PyUFuncObject *ufunc, NPY_CASTING casting, diff --git a/numpy/core/tests/test_defchararray.py b/numpy/core/tests/test_defchararray.py index 09fcff0d0666..c210f8bf6bc6 100644 --- a/numpy/core/tests/test_defchararray.py +++ b/numpy/core/tests/test_defchararray.py @@ -128,9 +128,9 @@ def test1(self): assert_(all(self.A == self.B)) assert_(all(self.A >= self.B)) assert_(all(self.A <= self.B)) - assert_(all(negative(self.A > self.B))) - assert_(all(negative(self.A < self.B))) - assert_(all(negative(self.A != self.B))) + assert_(not any(self.A > self.B)) + assert_(not any(self.A < self.B)) + assert_(not any(self.A != self.B)) class TestChar(TestCase): def setUp(self): diff --git a/numpy/core/tests/test_deprecations.py b/numpy/core/tests/test_deprecations.py index 41f1cf744e6c..f7fbb94e5b23 100644 --- a/numpy/core/tests/test_deprecations.py +++ b/numpy/core/tests/test_deprecations.py @@ -343,5 +343,28 @@ def test_basic(self): assert_raises(IndexError, a.__getitem__, ((Ellipsis, ) * 3,)) +class TestBooleanSubtractDeprecations(_DeprecationTestCase): + """Test deprecation of boolean `-`. While + and * are well + defined, - is not and even a corrected form seems to have + no real uses. + + The deprecation process was started in NumPy 1.9. + """ + message = r"numpy boolean .* \(the .* `-` operator\) is deprecated, " \ + "use the bitwise" + + def test_operator_deprecation(self): + array = np.array([True]) + generic = np.bool_(True) + + # Minus operator/subtract ufunc: + self.assert_deprecated(operator.sub, args=(array, array)) + self.assert_deprecated(operator.sub, args=(generic, generic)) + + # Unary minus/negative ufunc: + self.assert_deprecated(operator.neg, args=(array,)) + self.assert_deprecated(operator.neg, args=(generic,)) + + if __name__ == "__main__": run_module_suite() diff --git a/numpy/core/tests/test_regression.py b/numpy/core/tests/test_regression.py index 9f2abc5c0699..cb3791daf89f 100644 --- a/numpy/core/tests/test_regression.py +++ b/numpy/core/tests/test_regression.py @@ -447,7 +447,10 @@ def test_method_args(self, level=rlevel): res1 = getattr(arr, func_meth)() res2 = getattr(np, func)(arr2) if res1 is None: - assert_(abs(arr-res2).max() < 1e-8, func) + res1 = arr + + if res1.dtype.kind in 'uib': + assert_((res1 == res2).all(), func) else: assert_(abs(res1-res2).max() < 1e-8, func) diff --git a/numpy/ma/core.py b/numpy/ma/core.py index 42787e3c70ac..c62e55c45f06 100644 --- a/numpy/ma/core.py +++ b/numpy/ma/core.py @@ -6923,16 +6923,25 @@ def allclose (a, b, masked_equal=True, rtol=1e-5, atol=1e-8): return False # No infs at all if not np.any(xinf): - d = filled(umath.less_equal(umath.absolute(x - y), - atol + rtol * umath.absolute(y)), + if not x.dtype.kind == 'b' and not y.dtype.kind == 'b': + diff = umath.absolute(x - y) + else: + diff = x ^ y + + d = filled(umath.less_equal(diff, atol + rtol * umath.absolute(y)), masked_equal) return np.all(d) if not np.all(filled(x[xinf] == y[xinf], masked_equal)): return False x = x[~xinf] y = y[~xinf] - d = filled(umath.less_equal(umath.absolute(x - y), - atol + rtol * umath.absolute(y)), + + if not x.dtype.kind == 'b' and not y.dtype.kind == 'b': + diff = umath.absolute(x - y) + else: + diff = x ^ y + + d = filled(umath.less_equal(diff, atol + rtol * umath.absolute(y)), masked_equal) return np.all(d) diff --git a/numpy/ma/extras.py b/numpy/ma/extras.py index 058bde710390..c2d1055845bb 100644 --- a/numpy/ma/extras.py +++ b/numpy/ma/extras.py @@ -540,7 +540,7 @@ def average(a, axis=None, weights=None, returned=False): else: if weights is None: n = add.reduce(a, axis) - d = umath.add.reduce((-mask), axis=axis, dtype=float) + d = umath.add.reduce((~mask), axis=axis, dtype=float) else: w = filled(weights, 0.0) wsh = w.shape @@ -1735,7 +1735,7 @@ def _ezclump(mask): #def clump_masked(a): if mask.ndim > 1: mask = mask.ravel() - idx = (mask[1:] - mask[:-1]).nonzero() + idx = (mask[1:] ^ mask[:-1]).nonzero() idx = idx[0] + 1 slices = [slice(left, right) for (left, right) in zip(itertools.chain([0], idx), diff --git a/numpy/testing/utils.py b/numpy/testing/utils.py index 2a99fe5cb44f..82aa1e39c153 100644 --- a/numpy/testing/utils.py +++ b/numpy/testing/utils.py @@ -810,7 +810,12 @@ def compare(x, y): y = y[~yinfid] except (TypeError, NotImplementedError): pass - z = abs(x-y) + + if x.dtype.kind == 'b' and y.dtype.kind == 'b': + z = x ^ y + else: + z = abs(x-y) + if not issubdtype(z.dtype, number): z = z.astype(float_) # handle object arrays return around(z, decimal) <= 10.0**(-decimal) From ab04e1ae0e8eca717bc7e42f3b0a60c9ff764289 Mon Sep 17 00:00:00 2001 From: Sebastian Berg Date: Wed, 12 Feb 2014 11:57:27 +0100 Subject: [PATCH 2/2] BUG: Force allclose logic to use inexact type Casting y to an inexact type fixes problems such as abs(MIN_INT) < 0, and generally makes sense since the allclose logic is inherently for float types. --- numpy/core/numeric.py | 12 ++++++------ numpy/core/tests/test_numeric.py | 7 +++++++ numpy/ma/core.py | 25 +++++++++++++------------ numpy/ma/tests/test_core.py | 4 ++++ numpy/testing/tests/test_utils.py | 8 ++++++++ numpy/testing/utils.py | 14 +++++++++----- 6 files changed, 47 insertions(+), 23 deletions(-) diff --git a/numpy/core/numeric.py b/numpy/core/numeric.py index 6b078ae31e9b..3b8e52e71d39 100644 --- a/numpy/core/numeric.py +++ b/numpy/core/numeric.py @@ -2139,6 +2139,11 @@ def allclose(a, b, rtol=1.e-5, atol=1.e-8): x = array(a, copy=False, ndmin=1) y = array(b, copy=False, ndmin=1) + # make sure y is an inexact type to avoid abs(MIN_INT); will cause + # casting of x later. + dtype = multiarray.result_type(y, 1.) + y = array(y, dtype=dtype, copy=False) + xinf = isinf(x) yinf = isinf(y) if any(xinf) or any(yinf): @@ -2154,12 +2159,7 @@ def allclose(a, b, rtol=1.e-5, atol=1.e-8): # ignore invalid fpe's with errstate(invalid='ignore'): - if not x.dtype.kind == 'b' and not y.dtype.kind == 'b': - diff = abs(x - y) - else: - diff = x ^ y - - r = all(less_equal(diff, atol + rtol * abs(y))) + r = all(less_equal(abs(x - y), atol + rtol * abs(y))) return r diff --git a/numpy/core/tests/test_numeric.py b/numpy/core/tests/test_numeric.py index ac341468c528..12a39a522b10 100644 --- a/numpy/core/tests/test_numeric.py +++ b/numpy/core/tests/test_numeric.py @@ -1420,6 +1420,13 @@ def test_no_parameter_modification(self): assert_array_equal(y, array([0, inf])) + def test_min_int(self): + # Could make problems because of abs(min_int) == min_int + min_int = np.iinfo(np.int_).min + a = np.array([min_int], dtype=np.int_) + assert_(allclose(a, a)) + + class TestIsclose(object): rtol = 1e-5 atol = 1e-8 diff --git a/numpy/ma/core.py b/numpy/ma/core.py index c62e55c45f06..16df1ea761e4 100644 --- a/numpy/ma/core.py +++ b/numpy/ma/core.py @@ -6916,6 +6916,13 @@ def allclose (a, b, masked_equal=True, rtol=1e-5, atol=1e-8): """ x = masked_array(a, copy=False) y = masked_array(b, copy=False) + + # make sure y is an inexact type to avoid abs(MIN_INT); will cause + # casting of x later. + dtype = np.result_type(y, 1.) + if y.dtype != dtype: + y = masked_array(y, dtype=dtype, copy=False) + m = mask_or(getmask(x), getmask(y)) xinf = np.isinf(masked_array(x, copy=False, mask=m)).filled(False) # If we have some infs, they should fall at the same place. @@ -6923,26 +6930,20 @@ def allclose (a, b, masked_equal=True, rtol=1e-5, atol=1e-8): return False # No infs at all if not np.any(xinf): - if not x.dtype.kind == 'b' and not y.dtype.kind == 'b': - diff = umath.absolute(x - y) - else: - diff = x ^ y - - d = filled(umath.less_equal(diff, atol + rtol * umath.absolute(y)), + d = filled(umath.less_equal(umath.absolute(x - y), + atol + rtol * umath.absolute(y)), masked_equal) return np.all(d) + if not np.all(filled(x[xinf] == y[xinf], masked_equal)): return False x = x[~xinf] y = y[~xinf] - if not x.dtype.kind == 'b' and not y.dtype.kind == 'b': - diff = umath.absolute(x - y) - else: - diff = x ^ y - - d = filled(umath.less_equal(diff, atol + rtol * umath.absolute(y)), + d = filled(umath.less_equal(umath.absolute(x - y), + atol + rtol * umath.absolute(y)), masked_equal) + return np.all(d) #.............................................................................. diff --git a/numpy/ma/tests/test_core.py b/numpy/ma/tests/test_core.py index 8d8e1c9475dd..19f13a8c4bbe 100644 --- a/numpy/ma/tests/test_core.py +++ b/numpy/ma/tests/test_core.py @@ -1995,6 +1995,10 @@ def test_allclose(self): a[0] = 0 self.assertTrue(allclose(a, 0, masked_equal=True)) + # Test that the function works for MIN_INT integer typed arrays + a = masked_array([np.iinfo(np.int_).min], dtype=np.int_) + self.assertTrue(allclose(a, a)) + def test_allany(self): # Checks the any/all methods/functions. x = np.array([[0.13, 0.26, 0.90], diff --git a/numpy/testing/tests/test_utils.py b/numpy/testing/tests/test_utils.py index 94fc4d655815..5956a42943ac 100644 --- a/numpy/testing/tests/test_utils.py +++ b/numpy/testing/tests/test_utils.py @@ -53,6 +53,9 @@ def test_objarray(self): a = np.array([1, 1], dtype=np.object) self._test_equal(a, 1) + def test_array_likes(self): + self._test_equal([1, 2, 3], (1, 2, 3)) + class TestArrayEqual(_GenericTest, unittest.TestCase): def setUp(self): self._assert_func = assert_array_equal @@ -373,6 +376,11 @@ def test_simple(self): assert_allclose(6, 10, rtol=0.5) self.assertRaises(AssertionError, assert_allclose, 10, 6, rtol=0.5) + def test_min_int(self): + a = np.array([np.iinfo(np.int_).min], dtype=np.int_) + # Should not raise: + assert_allclose(a, a) + class TestArrayAlmostEqualNulp(unittest.TestCase): @dec.knownfailureif(True, "Github issue #347") diff --git a/numpy/testing/utils.py b/numpy/testing/utils.py index 82aa1e39c153..97908c7e8117 100644 --- a/numpy/testing/utils.py +++ b/numpy/testing/utils.py @@ -793,7 +793,7 @@ def assert_array_almost_equal(x, y, decimal=6, err_msg='', verbose=True): y: array([ 1. , 2.33333, 5. ]) """ - from numpy.core import around, number, float_ + from numpy.core import around, number, float_, result_type, array from numpy.core.numerictypes import issubdtype from numpy.core.fromnumeric import any as npany def compare(x, y): @@ -811,17 +811,21 @@ def compare(x, y): except (TypeError, NotImplementedError): pass - if x.dtype.kind == 'b' and y.dtype.kind == 'b': - z = x ^ y - else: - z = abs(x-y) + # make sure y is an inexact type to avoid abs(MIN_INT); will cause + # casting of x later. + dtype = result_type(y, 1.) + y = array(y, dtype=dtype, copy=False) + z = abs(x-y) if not issubdtype(z.dtype, number): z = z.astype(float_) # handle object arrays + return around(z, decimal) <= 10.0**(-decimal) + assert_array_compare(compare, x, y, err_msg=err_msg, verbose=verbose, header=('Arrays are not almost equal to %d decimals' % decimal)) + def assert_array_less(x, y, err_msg='', verbose=True): """ Raise an assertion if two array_like objects are not ordered by less than.