Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DEP: Deprecate boolean math operations #4105

Merged
merged 2 commits into from Feb 15, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion numpy/core/code_generators/generate_umath.py
Expand Up @@ -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'),
Expand Down
7 changes: 6 additions & 1 deletion numpy/core/numeric.py
Expand Up @@ -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):
Expand All @@ -2154,7 +2159,7 @@ 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)))
r = all(less_equal(abs(x - y), atol + rtol * abs(y)))

return r

Expand Down
46 changes: 44 additions & 2 deletions numpy/core/src/umath/ufunc_type_resolution.c
Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down
7 changes: 7 additions & 0 deletions numpy/core/src/umath/ufunc_type_resolution.h
Expand Up @@ -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,
Expand Down
6 changes: 3 additions & 3 deletions numpy/core/tests/test_defchararray.py
Expand Up @@ -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):
Expand Down
23 changes: 23 additions & 0 deletions numpy/core/tests/test_deprecations.py
Expand Up @@ -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()
7 changes: 7 additions & 0 deletions numpy/core/tests/test_numeric.py
Expand Up @@ -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
Expand Down
5 changes: 4 additions & 1 deletion numpy/core/tests/test_regression.py
Expand Up @@ -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)

Expand Down
10 changes: 10 additions & 0 deletions numpy/ma/core.py
Expand Up @@ -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.
Expand All @@ -6927,13 +6934,16 @@ def allclose (a, b, masked_equal=True, rtol=1e-5, atol=1e-8):
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)),
masked_equal)

return np.all(d)

#..............................................................................
Expand Down
4 changes: 2 additions & 2 deletions numpy/ma/extras.py
Expand Up @@ -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
Expand Down Expand Up @@ -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),
Expand Down
4 changes: 4 additions & 0 deletions numpy/ma/tests/test_core.py
Expand Up @@ -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],
Expand Down
8 changes: 8 additions & 0 deletions numpy/testing/tests/test_utils.py
Expand Up @@ -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
Expand Down Expand Up @@ -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")
Expand Down
11 changes: 10 additions & 1 deletion numpy/testing/utils.py
Expand Up @@ -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):
Expand All @@ -810,13 +810,22 @@ def compare(x, y):
y = y[~yinfid]
except (TypeError, NotImplementedError):
pass

# 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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to import array in line 796.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Heh, I seem to keep forgotting to run the tests... should those imports really be there and not on the module level?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then again, they are for all, so it would be a larger change to really do that right.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a lot of imports like that in this file. I'd be inclined to clean them up sometime, but it isn't needed for this PR.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, put the imports there, if you guys prefer another way to do this (I mean the result_type thing), I can change it. Added a simple extra test, because I was a bit worried I overlooked things with result_type and array-likes.

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.
Expand Down