8000 Merge pull request #4105 from seberg/deprecate-boolean-math · numpy/numpy@2868dc4 · GitHub
[go: up one dir, main page]

Skip to content

Commit 2868dc4

Browse files
committed
Merge pull request #4105 from seberg/deprecate-boolean-math
DEP: Deprecate boolean math operations
2 parents e246cc7 + ab04e1a commit 2868dc4

13 files changed

+129
-11
lines changed

numpy/core/code_generators/generate_umath.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,7 @@ def english_upper(s):
369369
'negative':
370370
Ufunc(1, 1, None,
371371
docstrings.get('numpy.core.umath.negative'),
372-
'PyUFunc_SimpleUnaryOperationTypeResolver',
372+
'PyUFunc_NegativeTypeResolver',
373373
TD(bints+flts+timedeltaonly),
374374
TD(cmplx, f='neg'),
375375
TD(O, f='PyNumber_Negative'),

numpy/core/numeric.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2139,6 +2139,11 @@ def allclose(a, b, rtol=1.e-5, atol=1.e-8):
21392139
x = array(a, copy=False, ndmin=1)
21402140
y = array(b, copy=False, ndmin=1)
21412141

2142+
# make sure y is an inexact type to avoid abs(MIN_INT); will cause
2143+
# casting of x later.
2144+
dtype = multiarray.result_type(y, 1.)
2145+
y = array(y, dtype=dtype, copy=False)
2146+
21422147
xinf = isinf(x)
21432148
yinf = isinf(y)
21442149
if any(xinf) or any(yinf):
@@ -2154,7 +2159,7 @@ def allclose(a, b, rtol=1.e-5, atol=1.e-8):
21542159

21552160
# ignore invalid fpe's
21562161
with errstate(invalid='ignore'):
2157-
r = all(less_equal(abs(x-y), atol + rtol * abs(y)))
2162+
r = all(less_equal(abs(x - y), atol + rtol * abs(y)))
21582163

21592164
return r
21602165

numpy/core/src/umath/ufunc_type_resolution.c

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,34 @@ PyUFunc_SimpleUnaryOperationTypeResolver(PyUFuncObject *ufunc,
367367
return 0;
368368
}
369369

370+
371+
NPY_NO_EXPORT int
372+
PyUFunc_NegativeTypeResolver(PyUFuncObject *ufunc,
373+
NPY_CASTING casting,
374+
PyArrayObject **operands,
375+
PyObject *type_tup,
376+
PyArray_Descr **out_dtypes)
377+
{
378+
int ret;
379+
ret = PyUFunc_SimpleUnaryOperationTypeResolver(ufunc, casting, operands,
380+
type_tup, out_dtypes);
381+
if (ret < 0) {
382+
return ret;
383+
}
384+
385+
/* The type resolver would have upcast already */
386+
if (out_dtypes[0]->type_num == NPY_BOOL) {
387+
if (DEPRECATE("numpy boolean negative (the unary `-` operator) is "
388+
"deprecated, use the bitwise_xor (the `^` operator) "
389+
"or the logical_xor function instead.") < 0) {
390+
return -1;
391+
}
392+
}
393+
394+
return ret;
395+
}
396+
397+
370398
/*
371399
* The ones_like function shouldn't really be a ufunc, but while it
372400
* still is, this provides type resolution that always forces UNSAFE
@@ -762,8 +790,22 @@ PyUFunc_SubtractionTypeResolver(PyUFuncObject *ufunc,
762790

763791
/* Use the default when datetime and timedelta are not involved */
764792
if (!PyTypeNum_ISDATETIME(type_num1) && !PyTypeNum_ISDATETIME(type_num2)) {
765-
return PyUFunc_SimpleBinaryOperationTypeResolver(ufunc, casting,
766-
operands, type_tup, out_dtypes);
793+
int ret;
794+
ret = PyUFunc_SimpleBinaryOperationTypeResolver(ufunc, casting,
795+
operands, type_tup, out_dtypes);
796+
if (ret < 0) {
797+
return ret;
798+
}
799+
800+
/* The type resolver would have upcast already */
801+
if (out_dtypes[0]->type_num == NPY_BOOL) {
802+
if (DEPRECATE("numpy boolean subtract (the binary `-` operator) is "
803+
"deprecated, use the bitwise_xor (the `^` operator) "
804+
"or the logical_xor function instead.") < 0) {
805+
return -1;
806+
}
807+
}
808+
return ret;
767809
}
768810

769811
if (type_num1 == NPY_TIMEDELTA) {

numpy/core/src/umath/ufunc_type_resolution.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@ PyUFunc_SimpleUnaryOperationTypeResolver(PyUFuncObject *ufunc,
1515
PyObject *type_tup,
1616
PyArray_Descr **out_dtypes);
1717

18+
NPY_NO_EXPORT int
19+
PyUFunc_NegativeTypeResolver(PyUFuncObject *ufunc,
20+
NPY_CASTING casting,
21+
PyArrayObject **operands,
22+
PyObject *type_tup,
23+
PyArray_Descr **out_dtypes);
24+
1825
NPY_NO_EXPORT int
1926
PyUFunc_OnesLikeTypeResolver(PyUFuncObject *ufunc,
2027
NPY_CASTING casting,

numpy/core/tests/test_defchararray.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -128,9 +128,9 @@ def test1(self):
128128
assert_(all(self.A == self.B))
129129
assert_(all(self.A >= self.B))
130130
assert_(all(self.A <= self.B))
131-
assert_(all(negative(self.A > self.B)))
132-
assert_(all(negative(self.A < self.B)))
133-
assert_(all(negative(self.A != self.B)))
131+
assert_(not any(self.A > self.B))
132+
assert_(not any(self.A < self.B))
133+
assert_(not any(self.A != self.B))
134134

135135
class TestChar(TestCase):
136136
def setUp(self):

numpy/core/tests/test_deprecations.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,5 +343,28 @@ def test_basic(self):
343343
assert_raises(IndexError, a.__getitem__, ((Ellipsis, ) * 3,))
344344

345345

346+
class TestBooleanSubtractDeprecations(_DeprecationTestCase):
347+
"""Test deprecation of boolean `-`. While + and * are well
348+
defined, - is not and even a corrected form seems to have
349+
no real uses.
350+
351+
The deprecation process was started in NumPy 1.9.
352+
"""
353+
message = r"numpy boolean .* \(the .* `-` operator\) is deprecated, " \
354+
"use the bitwise"
355+
356+
def test_operator_deprecation(self):
357+
array = np.array([True])
358+
generic = np.bool_(True)
359+
360+
# Minus operator/subtract ufunc:
361+
self.assert_deprecated(operator.sub, args=(array, array))
362+
self.assert_deprecated(operator.sub, args=(generic, generic))
363+
364+
# Unary minus/negative ufunc:
365+
self.assert_deprecated(operator.neg, args=(array,))
366+
self.assert_deprecated(operator.neg, args=(generic,))
367+
368+
346369
if __name__ == "__main__":
347370
run_module_suite()

numpy/core/tests/test_numeric.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1420,6 +1420,13 @@ def test_no_parameter_modification(self):
14201420
assert_array_equal(y, array([0, inf]))
14211421

14221422

1423+
def test_min_int(self):
1424+
# Could make problems because of abs(min_int) == min_int
1425+
min_int = np.iinfo(np.int_).min
1426+
a = np.array([min_int], dtype=np.int_)
1427+
assert_(allclose(a, a))
1428+
1429+
14231430
class TestIsclose(object):
14241431
rtol = 1e-5
14251432
atol = 1e-8

numpy/core/tests/test_regression.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -447,7 +447,10 @@ def test_method_args(self, level=rlevel):
447447
res1 = getattr(arr, func_meth)()
448448
res2 = getattr(np, func)(arr2)
449449
if res1 is None:
450-
assert_(abs(arr-res2).max() < 1e-8, func)
450+
res1 = arr
451+
452+
if res1.dtype.kind in 'uib':
453+
assert_((res1 == res2).all(), func)
451454
else:
452455
assert_(abs(res1-res2).max() < 1e-8, func)
453456

numpy/ma/core.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6926,6 +6926,13 @@ def allclose (a, b, masked_equal=True, rtol=1e-5, atol=1e-8):
69266926
"""
69276927
x = masked_array(a, copy=False)
69286928
y = masked_array(b, copy=False)
6929+
6930+
# make sure y is an inexact type to avoid abs(MIN_INT); will cause
6931+
# casting of x later.
6932+
dtype = np.result_type(y, 1.)
6933+
if y.dtype != dtype:
6934+
y = masked_array(y, dtype=dtype, copy=False)
6935+
69296936
m = mask_or(getmask(x), getmask(y))
69306937
xinf = np.isinf(masked_array(x, copy=False, mask=m)).filled(False)
69316938
# If we have some infs, they should fall at the same place.
@@ -6937,13 +6944,16 @@ def allclose (a, b, masked_equal=True, rtol=1e-5, atol=1e-8):
69376944
atol + rtol * umath.absolute(y)),
69386945
masked_equal)
69396946
return np.all(d)
6947+
69406948
if not np.all(filled(x[xinf] == y[xinf], masked_equal)):
69416949
return False
69426950
x = x[~xinf]
69436951
y = y[~xinf]
6952+
69446953
d = filled(umath.less_equal(umath.absolute(x - y),
69456954
atol + rtol * umath.absolute(y)),
69466955
masked_equal)
6956+
69476957
return np.all(d)
69486958

69496959
#..............................................................................

numpy/ma/extras.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -540,7 +540,7 @@ def average(a, axis=None, weights=None, returned=False):
540540
else:
541541
if weights is None:
542542
n = add.reduce(a, axis)
543-
d = umath.add.reduce((-mask), axis=axis, dtype=float)
543+
d = umath.add.reduce((~mask), axis=axis, dtype=float)
544544
else:
545545
w = filled(weights, 0.0)
546546
wsh = w.shape
@@ -1735,7 +1735,7 @@ def _ezclump(mask):
17351735
#def clump_masked(a):
17361736
if mask.ndim > 1:
17371737
mask = mask.ravel()
1738-
idx = (mask[1:] - mask[:-1]).nonzero()
1738+
idx = (mask[1:] ^ mask[:-1]).nonzero()
17391739
idx = idx[0] + 1
17401740
slices = [slice(left, right)
17411741
for (left, right) in zip(itertools.chain([0], idx),

numpy/ma/tests/test_core.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1995,6 +1995,10 @@ def test_allclose(self):
19951995
a[0] = 0
19961996
self.assertTrue(allclose(a, 0, masked_equal=True))
19971997

1998+
# Test that the function works for MIN_INT integer typed arrays
1999+
a = masked_array([np.iinfo(np.int_).min], dtype=np.int_)
2000+
self.assertTrue(allclose(a, a))
2001+
19982002
def test_allany(self):
19992003
# Checks the any/all methods/functions.
20002004
x = np.array([[0.13, 0.26, 0.90],

numpy/testing/tests/test_utils.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ def test_objarray(self):
5353
a = np.array([1, 1], dtype=np.object)
5454
self._test_equal(a, 1)
5555

56+
def test_array_likes(self):
57+
self._test_equal([1, 2, 3], (1, 2, 3))
58+
5659
class TestArrayEqual(_GenericTest, unittest.TestCase):
5760
def setUp(self):
5861
self._assert_func = assert_array_equal
@@ -373,6 +376,11 @@ def test_simple(self):
373376
assert_allclose(6, 10, rtol=0.5)
374377
self.assertRaises(AssertionError, assert_allclose, 10, 6, rtol=0.5)
375378

379+
def test_min_int(self):
380+
a = np.array([np.iinfo(np.int_).min], dtype=np.int_)
381+
# Should not raise:
382+
assert_allclose(a, a)
383+
376384

377385
class TestArrayAlmostEqualNulp(unittest.TestCase):
378386
@dec.knownfailureif(True, "Github issue #347")

numpy/testing/utils.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -793,7 +793,7 @@ def assert_array_almost_equal(x, y, decimal=6, err_msg='', verbose=True):
793793
y: array([ 1. , 2.33333, 5. ])
794794
795795
"""
796-
from numpy.core import around, number, float_
796+
from numpy.core import around, number, float_, result_type, array
797797
from numpy.core.numerictypes import issubdtype
798798
from numpy.core.fromnumeric import any as npany
799799
def compare(x, y):
@@ -810,13 +810,22 @@ def compare(x, y):
810810
y = y[~yinfid]
811811
except (TypeError, NotImplementedError):
812812
pass
813+
814+
# make sure y is an inexact type to avoid abs(MIN_INT); will cause
815+
# casting of x later.
816+
dtype = result_type(y, 1.)
817+
y = array(y, dtype=dtype, copy=False)
813818
z = abs(x-y)
819+
814820
if not issubdtype(z.dtype, number):
815821
z = z.astype(float_) # handle object arrays
822+
816823
return around(z, decimal) <= 10.0**(-decimal)
824+
817825
assert_array_compare(compare, x, y, err_msg=err_msg, verbose=verbose,
818826
header=('Arrays are not almost equal to %d decimals' % decimal))
819827

828+
820829
def assert_array_less(x, y, err_msg='', verbose=True):
821830
"""
822831
Raise an assertion if two array_like objects are not ordered by less than.

0 commit comments

Comments
 (0)
0