From c9d1f9e467155cec3030b0970816abe928244b9c Mon Sep 17 00:00:00 2001 From: Stephan Hoyer Date: Fri, 5 May 2017 18:57:51 -0700 Subject: [PATCH 1/5] ENH: add np.divmod ufunc --- doc/release/1.13.0-notes.rst | 6 ++ doc/source/reference/routines.math.rst | 1 + doc/source/reference/ufuncs.rst | 1 + numpy/core/code_generators/generate_umath.py | 7 ++ .../core/code_generators/ufunc_docstrings.py | 50 ++++++++++++++ numpy/core/src/umath/loops.c.src | 66 +++++++++++++++++++ numpy/core/src/umath/loops.h.src | 6 ++ numpy/core/tests/test_half.py | 1 + numpy/core/tests/test_umath.py | 18 +++++ 9 files changed, 156 insertions(+) diff --git a/doc/release/1.13.0-notes.rst b/doc/release/1.13.0-notes.rst index 2f32ddb283a6..3945639da0d3 100644 --- a/doc/release/1.13.0-notes.rst +++ b/doc/release/1.13.0-notes.rst @@ -344,6 +344,12 @@ New ``positive`` ufunc This ufunc corresponds to unary `+`, but unlike `+` on an ndarray it will raise an error if array values do not support numeric operations. +New ``divmod`` ufunc +-------------------- +This ufunc corresponds to the Python builtin `divmod`. ``np.divmod(x, y)`` +calculates a result equivalent to +``(np.floor_divide(x, y), np.remainder(x, y))`` but faster. + Better ``repr`` of object arrays -------------------------------- Object arrays that contain themselves no longer cause a recursion error. diff --git a/doc/source/reference/routines.math.rst b/doc/source/reference/routines.math.rst index 72e1462b02d9..a2fb06958f28 100644 --- a/doc/source/reference/routines.math.rst +++ b/doc/source/reference/routines.math.rst @@ -121,6 +121,7 @@ Arithmetic operations mod modf remainder + divmod Handling complex numbers ------------------------ diff --git a/doc/source/reference/ufuncs.rst b/doc/source/reference/ufuncs.rst index 94663a141931..e82571f6cf58 100644 --- a/doc/source/reference/ufuncs.rst +++ b/doc/source/reference/ufuncs.rst @@ -510,6 +510,7 @@ Math operations remainder mod fmod + divmod absolute fabs rint diff --git a/numpy/core/code_generators/generate_umath.py b/numpy/core/code_generators/generate_umath.py index dfba04c18148..7eb362f80aef 100644 --- a/numpy/core/code_generators/generate_umath.py +++ b/numpy/core/code_generators/generate_umath.py @@ -786,6 +786,13 @@ def english_upper(s): TD(intflt), TD(O, f='PyNumber_Remainder'), ), +'divmod': + Ufunc(2, 2, None, + docstrings.get('numpy.core.umath.divmod'), + None, + TD(intflt), + TD(O, f='PyNumber_Divmod'), + ), 'hypot': Ufunc(2, 1, Zero, docstrings.get('numpy.core.umath.hypot'), diff --git a/numpy/core/code_generators/ufunc_docstrings.py b/numpy/core/code_generators/ufunc_docstrings.py index ed9e05b15b25..8677e4328b9f 100644 --- a/numpy/core/code_generators/ufunc_docstrings.py +++ b/numpy/core/code_generators/ufunc_docstrings.py @@ -1290,6 +1290,7 @@ def add_newdoc(place, name, doc): See Also -------- remainder : Remainder complementary to floor_divide. + divmod : Simultaneous floor division and remainder. divide : Standard division. floor : Round a number to the nearest integer toward minus infinity. ceil : Round a number to the nearest integer toward infinity. @@ -2474,6 +2475,11 @@ def add_newdoc(place, name, doc): ----- For integer input the return values are floats. + See Also + -------- + divmod : ``divmod(x, 1)`` is equivalent to ``modf`` with the return values + switched, except it always has a positive remainder. + Examples -------- >>> np.modf([0, 3.5]) @@ -2541,6 +2547,8 @@ def add_newdoc(place, name, doc): """ Numerical positive, element-wise. + .. versionadded:: 1.13.0 + Parameters ---------- x : array_like or scalar @@ -2843,6 +2851,7 @@ def add_newdoc(place, name, doc): See Also -------- floor_divide : Equivalent of Python ``//`` operator. + divmod : Simultaneous floor division and remainder. fmod : Equivalent of the Matlab(TM) ``rem`` function. divide, floor @@ -2860,6 +2869,47 @@ def add_newdoc(place, name, doc): """) +add_newdoc('numpy.core.umath', 'divmod', + """ + Return element-wise quotient and remainder simultaneously. + + .. versionadded:: 1.13.0 + + ``np.divmod(x, y)`` is equivalent to ``(x // y, x % y)``, but faster + because it avoids redundant work. It is used to implement the Python + built-in function ``divmod`` on NumPy arrays. + + Parameters + ---------- + x1 : array_like + Dividend array. + x2 : array_like + Divisor array. + out : tuple of ndarray, optional + Arrays into which the output is placed. Their types are preserved and + must be of the right shape to hold the output. + + Returns + ------- + out1 : ndarray + Element-wise quotient resulting from floor division. + out2 : ndarray + Element-wise remainder from division. + + See Also + -------- + floor_divide : Equivalent to Python's ``//`` operator. + remainder : Equivalent to Python's ``%`` operator. + modf : Equivalent to ``divmod(x, 1)`` for positive ``x`` with the return + values switched. + + Examples + -------- + >>> np.divmod(np.arange(5), 3) + (array([0, 0, 0, 1, 1]), array([0, 1, 2, 0, 1])) + + """) + add_newdoc('numpy.core.umath', 'right_shift', """ Shift the bits of an integer to the right. diff --git a/numpy/core/src/umath/loops.c.src b/numpy/core/src/umath/loops.c.src index 47faaf180262..596b50a1c99f 100644 --- a/numpy/core/src/umath/loops.c.src +++ b/numpy/core/src/umath/loops.c.src @@ -1114,6 +1114,34 @@ NPY_NO_EXPORT void } } +NPY_NO_EXPORT void +@TYPE@_divmod(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)) +{ + BINARY_LOOP_TWO_OUT { + const @type@ in1 = *(@type@ *)ip1; + const @type@ in2 = *(@type@ *)ip2; + /* see FIXME note for divide above */ + if (in2 == 0 || (in1 == NPY_MIN_@TYPE@ && in2 == -1)) { + npy_set_floatstatus_divbyzero(); + *((@type@ *)op1) = 0; + *((@type@ *)op2) = 0; + } + else { + /* handle mixed case the way Python does */ + const @type@ quo = in1 / in2; + const @type@ rem = in1 % in2; + if ((in1 > 0) == (in2 > 0) || rem == 0) { + *((@type@ *)op1) = quo; + *((@type@ *)op2) = rem; + } + else { + *((@type@ *)op1) = quo - 1; + *((@type@ *)op2) = rem + in2; + } + } + } +} + /**end repeat**/ /**begin repeat @@ -1168,6 +1196,24 @@ NPY_NO_EXPORT void } } +NPY_NO_EXPORT void +@TYPE@_divmod(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)) +{ + BINARY_LOOP_TWO_OUT { + const @type@ in1 = *(@type@ *)ip1; + const @type@ in2 = *(@type@ *)ip2; + if (in2 == 0) { + npy_set_floatstatus_divbyzero(); + *((@type@ *)op1) = 0; + *((@type@ *)op2) = 0; + } + else { + *((@type@ *)op1)= in1/in2; + *((@type@ *)op2) = in1 % in2; + } + } +} + /**end repeat**/ /* @@ -1831,6 +1877,16 @@ NPY_NO_EXPORT void } } +NPY_NO_EXPORT void +@TYPE@_divmod(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)) +{ + BINARY_LOOP_TWO_OUT { + const @type@ in1 = *(@type@ *)ip1; + const @type@ in2 = *(@type@ *)ip2; + *((@type@ *)op1) = npy_divmod@c@(in1, in2, (@type@ *)op2); + } +} + NPY_NO_EXPORT void @TYPE@_square(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(data)) { @@ -2159,6 +2215,16 @@ HALF_remainder(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNU } } +NPY_NO_EXPORT void +HALF_divmod(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)) +{ + BINARY_LOOP_TWO_OUT { + const npy_half in1 = *(npy_half *)ip1; + const npy_half in2 = *(npy_half *)ip2; + *((npy_half *)op1) = npy_half_divmod(in1, in2, (npy_half *)op2); + } +} + NPY_NO_EXPORT void HALF_square(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(data)) { diff --git a/numpy/core/src/umath/loops.h.src b/numpy/core/src/umath/loops.h.src index d20b776a0132..a3d81e908fb4 100644 --- a/numpy/core/src/umath/loops.h.src +++ b/numpy/core/src/umath/loops.h.src @@ -140,6 +140,9 @@ NPY_NO_EXPORT void NPY_NO_EXPORT void @S@@TYPE@_remainder(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)); +NPY_NO_EXPORT void +@S@@TYPE@_divmod(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)); + /**end repeat1**/ /**end repeat**/ @@ -219,6 +222,9 @@ NPY_NO_EXPORT void NPY_NO_EXPORT void @TYPE@_remainder(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)); +NPY_NO_EXPORT void +@TYPE@_divmod(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)); + NPY_NO_EXPORT void @TYPE@_square(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(data)); diff --git a/numpy/core/tests/test_half.py b/numpy/core/tests/test_half.py index 75b74f407626..7a4d36333dd3 100644 --- a/numpy/core/tests/test_half.py +++ b/numpy/core/tests/test_half.py @@ -317,6 +317,7 @@ def test_half_ufuncs(self): assert_equal(np.floor_divide(a, b), [0, 0, 2, 1, 0]) assert_equal(np.remainder(a, b), [0, 1, 0, 0, 2]) + assert_equal(np.divmod(a, b), ([0, 0, 2, 1, 0], [0, 1, 0, 0, 2])) assert_equal(np.square(b), [4, 25, 1, 16, 9]) assert_equal(np.reciprocal(b), [-0.5, 0.199951171875, 1, 0.25, 0.333251953125]) assert_equal(np.ones_like(b), [1, 1, 1, 1, 1]) diff --git a/numpy/core/tests/test_umath.py b/numpy/core/tests/test_umath.py index f13c056c3a96..7d147f72744f 100644 --- a/numpy/core/tests/test_umath.py +++ b/numpy/core/tests/test_umath.py @@ -286,6 +286,13 @@ def test_remainder_basic(self): else: assert_(b > rem >= 0, msg) + div, rem = np.divmod(a, b) + assert_equal(div*b + rem, a, err_msg=msg) + if sg2 == -1: + assert_(b < rem <= 0, msg) + else: + assert_(b > rem >= 0, msg) + def test_float_remainder_exact(self): # test that float results are exact for small integers. This also # holds for the same integers scaled by powers of two. @@ -312,6 +319,10 @@ def test_float_remainder_exact(self): assert_equal(div, tgtdiv, err_msg=msg) assert_equal(rem, tgtrem, err_msg=msg) + div, rem = np.divmod(fa, fb) + assert_equal(div, tgtdiv, err_msg=msg) + assert_equal(rem, tgtrem, err_msg=msg) + def test_float_remainder_roundoff(self): # gh-6127 dt = np.typecodes['Float'] @@ -330,6 +341,13 @@ def test_float_remainder_roundoff(self): else: assert_(b > rem >= 0, msg) + div, rem = np.divmod(a, b) + assert_equal(div*b + rem, a, err_msg=msg) + if sg2 == -1: + assert_(b < rem <= 0, msg) + else: + assert_(b > rem >= 0, msg) + def test_float_remainder_corner_cases(self): # Check remainder magnitude. for dt in np.typecodes['Float']: From d51b538ba80d36841cc57911d77ea61cd1d3fb25 Mon Sep 17 00:00:00 2001 From: Stephan Hoyer Date: Fri, 5 May 2017 21:50:18 -0700 Subject: [PATCH 2/5] ENH: add divmod support to NDArrayOperatorsMixin --- numpy/lib/mixins.py | 8 +-- numpy/lib/tests/test_mixins.py | 92 ++++++++++++++++++++-------------- 2 files changed, 60 insertions(+), 40 deletions(-) diff --git a/numpy/lib/mixins.py b/numpy/lib/mixins.py index bbeed1437f83..fbdc2edfb872 100644 --- a/numpy/lib/mixins.py +++ b/numpy/lib/mixins.py @@ -70,8 +70,7 @@ class NDArrayOperatorsMixin(object): implement. This class does not yet implement the special operators corresponding - to ``divmod`` or ``matmul`` (``@``), because these operation do not yet - have corresponding NumPy ufuncs. + to ``matmul`` (``@``), because ``np.matmul`` is not yet a NumPy ufunc. It is useful for writing classes that do not inherit from `numpy.ndarray`, but that should support arithmetic and numpy universal functions like @@ -161,7 +160,10 @@ def __repr__(self): um.true_divide, 'truediv') __floordiv__, __rfloordiv__, __ifloordiv__ = _numeric_methods( um.floor_divide, 'floordiv') - __mod__, __rmod__, __imod__ = _numeric_methods(um.mod, 'mod') + __mod__, __rmod__, __imod__ = _numeric_methods(um.remainder, 'mod') + __divmod__ = _binary_method(um.divmod, 'divmod') + __rdivmod__ = _reflected_binary_method(um.divmod, 'divmod') + # __idivmod__ does not exist # TODO: handle the optional third argument for __pow__? __pow__, __rpow__, __ipow__ = _numeric_methods(um.power, 'pow') __lshift__, __rlshift__, __ilshift__ = _numeric_methods( diff --git a/numpy/lib/tests/test_mixins.py b/numpy/lib/tests/test_mixins.py index 287d4ed2955a..db38bdfd678c 100644 --- a/numpy/lib/tests/test_mixins.py +++ b/numpy/lib/tests/test_mixins.py @@ -56,11 +56,47 @@ def __repr__(self): return '%s(%r)' % (type(self).__name__, self.value) +def wrap_array_like(result): + if type(result) is tuple: + return tuple(ArrayLike(r) for r in result) + else: + return ArrayLike(result) + + def _assert_equal_type_and_value(result, expected, err_msg=None): assert_equal(type(result), type(expected), err_msg=err_msg) - assert_equal(result.value, expected.value, err_msg=err_msg) - assert_equal(getattr(result.value, 'dtype', None), - getattr(expected.value, 'dtype', None), err_msg=err_msg) + if isinstance(result, tuple): + assert_equal(len(result), len(expected), err_msg=err_msg) + for result_item, expected_item in zip(result, expected): + _assert_equal_type_and_value(result_item, expected_item, err_msg) + else: + assert_equal(result.value, expected.value, err_msg=err_msg) + assert_equal(getattr(result.value, 'dtype', None), + getattr(expected.value, 'dtype', None), err_msg=err_msg) + + +_ALL_BINARY_OPERATORS = [ + operator.lt, + operator.le, + operator.eq, + operator.ne, + operator.gt, + operator.ge, + operator.add, + operator.sub, + operator.mul, + operator.truediv, + operator.floordiv, + # TODO: test div on Python 2, only + operator.mod, + divmod, + pow, + operator.lshift, + operator.rshift, + operator.and_, + operator.xor, + operator.or_, +] class TestNDArrayOperatorsMixin(TestCase): @@ -148,52 +184,34 @@ def test_unary_methods(self): operator.invert]: _assert_equal_type_and_value(op(array_like), ArrayLike(op(array))) - def test_binary_methods(self): + def test_forward_binary_methods(self): array = np.array([-1, 0, 1, 2]) array_like = ArrayLike(array) - operators = [ - operator.lt, - operator.le, - operator.eq, - operator.ne, - operator.gt, - operator.ge, - operator.add, - operator.sub, - operator.mul, - operator.truediv, - operator.floordiv, - # TODO: test div on Python 2, only - operator.mod, - # divmod is not yet implemented - pow, - operator.lshift, - operator.rshift, - operator.and_, - operator.xor, - operator.or_, - ] - for op in operators: - expected = ArrayLike(op(array, 1)) + for op in _ALL_BINARY_OPERATORS: + expected = wrap_array_like(op(array, 1)) actual = op(array_like, 1) err_msg = 'failed for operator {}'.format(op) _assert_equal_type_and_value(expected, actual, err_msg=err_msg) + def test_reflected_binary_methods(self): + for op in _ALL_BINARY_OPERATORS: + expected = wrap_array_like(op(2, 1)) + actual = op(2, ArrayLike(1)) + err_msg = 'failed for operator {}'.format(op) + _assert_equal_type_and_value(expected, actual, err_msg=err_msg) + def test_ufunc_at(self): array = ArrayLike(np.array([1, 2, 3, 4])) assert_(np.negative.at(array, np.array([0, 1])) is None) _assert_equal_type_and_value(array, ArrayLike([-1, -2, 3, 4])) def test_ufunc_two_outputs(self): - def check(result): - assert_(type(result) is tuple) - assert_equal(len(result), 2) - mantissa, exponent = np.frexp(2 ** -3) - _assert_equal_type_and_value(result[0], ArrayLike(mantissa)) - _assert_equal_type_and_value(result[1], ArrayLike(exponent)) - - check(np.frexp(ArrayLike(2 ** -3))) - check(np.frexp(ArrayLike(np.array(2 ** -3)))) + mantissa, exponent = np.frexp(2 ** -3) + expected = (ArrayLike(mantissa), ArrayLike(exponent)) + _assert_equal_type_and_value( + np.frexp(ArrayLike(2 ** -3)), expected) + _assert_equal_type_and_value( + np.frexp(ArrayLike(np.array(2 ** -3))), expected) if __name__ == "__main__": From 614463780dbce3b46817d76c56d0c0d11252804f Mon Sep 17 00:00:00 2001 From: Stephan Hoyer Date: Sat, 6 May 2017 14:26:43 -0700 Subject: [PATCH 3/5] ENH: switch ndarray.__divmod__ to use np.divmod --- doc/release/1.13.0-notes.rst | 7 +- .../core/code_generators/ufunc_docstrings.py | 2 +- numpy/core/src/multiarray/number.c | 39 ++----- numpy/core/src/multiarray/number.h | 1 + numpy/core/tests/test_multiarray.py | 63 ++++++----- numpy/core/tests/test_scalarmath.py | 102 ++++++++--------- numpy/core/tests/test_umath.py | 103 ++++++++---------- 7 files changed, 146 insertions(+), 171 deletions(-) diff --git a/doc/release/1.13.0-notes.rst b/doc/release/1.13.0-notes.rst index 3945639da0d3..f7a7e0bb1253 100644 --- a/doc/release/1.13.0-notes.rst +++ b/doc/release/1.13.0-notes.rst @@ -346,9 +346,10 @@ an error if array values do not support numeric operations. New ``divmod`` ufunc -------------------- -This ufunc corresponds to the Python builtin `divmod`. ``np.divmod(x, y)`` -calculates a result equivalent to -``(np.floor_divide(x, y), np.remainder(x, y))`` but faster. +This ufunc corresponds to the Python builtin `divmod`, and is used to implement +`divmod` when called on numpy arrays. ``np.divmod(x, y)`` calculates a result +equivalent to ``(np.floor_divide(x, y), np.remainder(x, y))`` but is +approximately twice as fast as calling the functions separately. Better ``repr`` of object arrays -------------------------------- diff --git a/numpy/core/code_generators/ufunc_docstrings.py b/numpy/core/code_generators/ufunc_docstrings.py index 8677e4328b9f..b84a80a577ea 100644 --- a/numpy/core/code_generators/ufunc_docstrings.py +++ b/numpy/core/code_generators/ufunc_docstrings.py @@ -2894,7 +2894,7 @@ def add_newdoc(place, name, doc): out1 : ndarray Element-wise quotient resulting from floor division. out2 : ndarray - Element-wise remainder from division. + Element-wise remainder from floor division. See Also -------- diff --git a/numpy/core/src/multiarray/number.c b/numpy/core/src/multiarray/number.c index d6598cdb66cf..b8239c9729b0 100644 --- a/numpy/core/src/multiarray/number.c +++ b/numpy/core/src/multiarray/number.c @@ -83,6 +83,7 @@ PyArray_SetNumericOps(PyObject *dict) SET(multiply); SET(divide); SET(remainder); + SET(divmod); SET(power); SET(square); SET(reciprocal); @@ -135,6 +136,7 @@ PyArray_GetNumericOps(void) GET(multiply); GET(divide); GET(remainder); + GET(divmod); GET(power); GET(square); GET(reciprocal); @@ -344,6 +346,12 @@ array_remainder(PyArrayObject *m1, PyObject *m2) return PyArray_GenericBinaryFunction(m1, m2, n_ops.remainder); } +static PyObject * +array_divmod(PyArrayObject *m1, PyObject *m2) +{ + BINOP_GIVE_UP_IF_NEEDED(m1, m2, nb_divmod, array_divmod); + return PyArray_GenericBinaryFunction(m1, m2, n_ops.divmod); +} #if PY_VERSION_HEX >= 0x03050000 /* Need this to be version dependent on account of the slot check */ @@ -796,37 +804,6 @@ _array_nonzero(PyArrayObject *mp) } - -static PyObject * -array_divmod(PyArrayObject *op1, PyObject *op2) -{ - PyObject *divp, *modp, *result; - - BINOP_GIVE_UP_IF_NEEDED(op1, op2, nb_divmod, array_divmod); - - divp = array_floor_divide(op1, op2); - if (divp == NULL) { - return NULL; - } - else if(divp == Py_NotImplemented) { - return divp; - } - modp = array_remainder(op1, op2); - if (modp == NULL) { - Py_DECREF(divp); - return NULL; - } - else if(modp == Py_NotImplemented) { - Py_DECREF(divp); - return modp; - } - result = Py_BuildValue("OO", divp, modp); - Py_DECREF(divp); - Py_DECREF(modp); - return result; -} - - NPY_NO_EXPORT PyObject * array_int(PyArrayObject *v) { diff --git a/numpy/core/src/multiarray/number.h b/numpy/core/src/multiarray/number.h index 86f681c10e65..113fc247573d 100644 --- a/numpy/core/src/multiarray/number.h +++ b/numpy/core/src/multiarray/number.h @@ -7,6 +7,7 @@ typedef struct { PyObject *multiply; PyObject *divide; PyObject *remainder; + PyObject *divmod; PyObject *power; PyObject *square; PyObject *reciprocal; diff --git a/numpy/core/tests/test_multiarray.py b/numpy/core/tests/test_multiarray.py index 3f4b183aad2f..089ac6d9c781 100644 --- a/numpy/core/tests/test_multiarray.py +++ b/numpy/core/tests/test_multiarray.py @@ -2881,7 +2881,7 @@ def test_ufunc_binop_interaction(self): 'truediv': (np.true_divide, True, float), 'floordiv': (np.floor_divide, True, float), 'mod': (np.remainder, True, float), - 'divmod': (None, False, float), + 'divmod': (np.divmod, False, float), 'pow': (np.power, True, int), 'lshift': (np.left_shift, True, int), 'rshift': (np.right_shift, True, int), @@ -2944,13 +2944,15 @@ def make_obj(base, array_priority=False, array_ufunc=False, def check(obj, binop_override_expected, ufunc_override_expected, inplace_override_expected, check_scalar=True): for op, (ufunc, has_inplace, dtype) in ops.items(): + err_msg = ('op: %s, ufunc: %s, has_inplace: %s, dtype: %s' + % (op, ufunc, has_inplace, dtype)) check_objs = [np.arange(3, 5, dtype=dtype)] if check_scalar: check_objs.append(check_objs[0][0]) for arr in check_objs: arr_method = getattr(arr, "__{0}__".format(op)) - def norm(result): + def first_out_arg(result): if op == "divmod": assert_(isinstance(result, tuple)) return result[0] @@ -2959,77 +2961,78 @@ def norm(result): # arr __op__ obj if binop_override_expected: - assert_equal(arr_method(obj), NotImplemented) + assert_equal(arr_method(obj), NotImplemented, err_msg) elif ufunc_override_expected: - assert_equal(norm(arr_method(obj))[0], - "__array_ufunc__") + assert_equal(arr_method(obj)[0], "__array_ufunc__", + err_msg) else: if (isinstance(obj, np.ndarray) and (type(obj).__array_ufunc__ is np.ndarray.__array_ufunc__)): # __array__ gets ignored - res = norm(arr_method(obj)) - assert_(res.__class__ is obj.__class__) + res = first_out_arg(arr_method(obj)) + assert_(res.__class__ is obj.__class__, err_msg) else: assert_raises((TypeError, Coerced), - arr_method, obj) + arr_method, obj, err_msg=err_msg) # obj __op__ arr arr_rmethod = getattr(arr, "__r{0}__".format(op)) if ufunc_override_expected: - res = norm(arr_rmethod(obj)) - assert_equal(res[0], "__array_ufunc__") - if ufunc is not None: - assert_equal(res[1], ufunc) + res = arr_rmethod(obj) + assert_equal(res[0], "__array_ufunc__", + err_msg=err_msg) + assert_equal(res[1], ufunc, err_msg=err_msg) else: if (isinstance(obj, np.ndarray) and (type(obj).__array_ufunc__ is np.ndarray.__array_ufunc__)): # __array__ gets ignored - res = norm(arr_rmethod(obj)) - assert_(res.__class__ is obj.__class__) + res = first_out_arg(arr_rmethod(obj)) + assert_(res.__class__ is obj.__class__, err_msg) else: # __array_ufunc__ = "asdf" creates a TypeError assert_raises((TypeError, Coerced), - arr_rmethod, obj) + arr_rmethod, obj, err_msg=err_msg) # arr __iop__ obj # array scalars don't have in-place operators if has_inplace and isinstance(arr, np.ndarray): arr_imethod = getattr(arr, "__i{0}__".format(op)) if inplace_override_expected: - assert_equal(arr_method(obj), NotImplemented) + assert_equal(arr_method(obj), NotImplemented, + err_msg=err_msg) elif ufunc_override_expected: res = arr_imethod(obj) - assert_equal(res[0], "__array_ufunc__") - if ufunc is not None: - assert_equal(res[1], ufunc) - assert_(type(res[-1]["out"]) is tuple) - assert_(res[-1]["out"][0] is arr) + assert_equal(res[0], "__array_ufunc__", err_msg) + assert_equal(res[1], ufunc, err_msg) + assert_(type(res[-1]["out"]) is tuple, err_msg) + assert_(res[-1]["out"][0] is arr, err_msg) else: if (isinstance(obj, np.ndarray) and (type(obj).__array_ufunc__ is np.ndarray.__array_ufunc__)): # __array__ gets ignored - assert_(arr_imethod(obj) is arr) + assert_(arr_imethod(obj) is arr, err_msg) else: assert_raises((TypeError, Coerced), - arr_imethod, obj) + arr_imethod, obj, + err_msg=err_msg) op_fn = getattr(operator, op, None) if op_fn is None: op_fn = getattr(operator, op + "_", None) if op_fn is None: op_fn = getattr(builtins, op) - assert_equal(op_fn(obj, arr), "forward") + assert_equal(op_fn(obj, arr), "forward", err_msg) if not isinstance(obj, np.ndarray): if binop_override_expected: - assert_equal(op_fn(arr, obj), "reverse") + assert_equal(op_fn(arr, obj), "reverse", err_msg) elif ufunc_override_expected: - assert_equal(norm(op_fn(arr, obj))[0], - "__array_ufunc__") - if ufunc_override_expected and ufunc is not None: - assert_equal(norm(ufunc(obj, arr))[0], - "__array_ufunc__") + assert_equal(op_fn(arr, obj)[0], "__array_ufunc__", + err_msg) + if ufunc_override_expected: + assert_equal(ufunc(obj, arr)[0], "__array_ufunc__", + err_msg) # No array priority, no array_ufunc -> nothing called check(make_obj(object), False, False, False) diff --git a/numpy/core/tests/test_scalarmath.py b/numpy/core/tests/test_scalarmath.py index 1cafde5a0e24..c76db98f8954 100644 --- a/numpy/core/tests/test_scalarmath.py +++ b/numpy/core/tests/test_scalarmath.py @@ -189,30 +189,34 @@ def test_modular_power(self): assert_raises(TypeError, operator.pow, np.array(t(a)), b, c) -class TestModulus(TestCase): +def floordiv_and_mod(x, y): + return (x // y, x % y) + + +def _signs(dt): + if dt in np.typecodes['UnsignedInteger']: + return (+1,) + else: + return (+1, -1) - floordiv = operator.floordiv - mod = operator.mod + +class TestModulus(TestCase): def test_modulus_basic(self): dt = np.typecodes['AllInteger'] + np.typecodes['Float'] - for dt1, dt2 in itertools.product(dt, dt): - for sg1, sg2 in itertools.product((+1, -1), (+1, -1)): - if sg1 == -1 and dt1 in np.typecodes['UnsignedInteger']: - continue - if sg2 == -1 and dt2 in np.typecodes['UnsignedInteger']: - continue - fmt = 'dt1: %s, dt2: %s, sg1: %s, sg2: %s' - msg = fmt % (dt1, dt2, sg1, sg2) - a = np.array(sg1*71, dtype=dt1)[()] - b = np.array(sg2*19, dtype=dt2)[()] - div = self.floordiv(a, b) - rem = self.mod(a, b) - assert_equal(div*b + rem, a, err_msg=msg) - if sg2 == -1: - assert_(b < rem <= 0, msg) - else: - assert_(b > rem >= 0, msg) + for op in [floordiv_and_mod, divmod]: + for dt1, dt2 in itertools.product(dt, dt): + for sg1, sg2 in itertools.product(_signs(dt1), _signs(dt2)): + fmt = 'op: %s, dt1: %s, dt2: %s, sg1: %s, sg2: %s' + msg = fmt % (op.__name__, dt1, dt2, sg1, sg2) + a = np.array(sg1*71, dtype=dt1)[()] + b = np.array(sg2*19, dtype=dt2)[()] + div, rem = op(a, b) + assert_equal(div*b + rem, a, err_msg=msg) + if sg2 == -1: + assert_(b < rem <= 0, msg) + else: + assert_(b > rem >= 0, msg) def test_float_modulus_exact(self): # test that float results are exact for small integers. This also @@ -231,42 +235,42 @@ def test_float_modulus_exact(self): tgtdiv = np.where((tgtdiv == 0.0) & ((b < 0) ^ (a < 0)), -0.0, tgtdiv) tgtrem = np.where((tgtrem == 0.0) & (b < 0), -0.0, tgtrem) - for dt in np.typecodes['Float']: - msg = 'dtype: %s' % (dt,) - fa = a.astype(dt) - fb = b.astype(dt) - # use list comprehension so a_ and b_ are scalars - div = [self.floordiv(a_, b_) for a_, b_ in zip(fa, fb)] - rem = [self.mod(a_, b_) for a_, b_ in zip(fa, fb)] - assert_equal(div, tgtdiv, err_msg=msg) - assert_equal(rem, tgtrem, err_msg=msg) + for op in [floordiv_and_mod, divmod]: + for dt in np.typecodes['Float']: + msg = 'op: %s, dtype: %s' % (op.__name__, dt) + fa = a.astype(dt) + fb = b.astype(dt) + # use list comprehension so a_ and b_ are scalars + div, rem = zip(*[op(a_, b_) for a_, b_ in zip(fa, fb)]) + assert_equal(div, tgtdiv, err_msg=msg) + assert_equal(rem, tgtrem, err_msg=msg) def test_float_modulus_roundoff(self): # gh-6127 dt = np.typecodes['Float'] - for dt1, dt2 in itertools.product(dt, dt): - for sg1, sg2 in itertools.product((+1, -1), (+1, -1)): - fmt = 'dt1: %s, dt2: %s, sg1: %s, sg2: %s' - msg = fmt % (dt1, dt2, sg1, sg2) - a = np.array(sg1*78*6e-8, dtype=dt1)[()] - b = np.array(sg2*6e-8, dtype=dt2)[()] - div = self.floordiv(a, b) - rem = self.mod(a, b) - # Equal assertion should hold when fmod is used - assert_equal(div*b + rem, a, err_msg=msg) - if sg2 == -1: - assert_(b < rem <= 0, msg) - else: - assert_(b > rem >= 0, msg) + for op in [floordiv_and_mod, divmod]: + for dt1, dt2 in itertools.product(dt, dt): + for sg1, sg2 in itertools.product((+1, -1), (+1, -1)): + fmt = 'op: %s, dt1: %s, dt2: %s, sg1: %s, sg2: %s' + msg = fmt % (op.__name__, dt1, dt2, sg1, sg2) + a = np.array(sg1*78*6e-8, dtype=dt1)[()] + b = np.array(sg2*6e-8, dtype=dt2)[()] + div, rem = op(a, b) + # Equal assertion should hold when fmod is used + assert_equal(div*b + rem, a, err_msg=msg) + if sg2 == -1: + assert_(b < rem <= 0, msg) + else: + assert_(b > rem >= 0, msg) def test_float_modulus_corner_cases(self): # Check remainder magnitude. for dt in np.typecodes['Float']: b = np.array(1.0, dtype=dt) a = np.nextafter(np.array(0.0, dtype=dt), -b) - rem = self.mod(a, b) + rem = operator.mod(a, b) assert_(rem <= b, 'dt: %s' % dt) - rem = self.mod(-a, -b) + rem = operator.mod(-a, -b) assert_(rem >= -b, 'dt: %s' % dt) # Check nans, inf @@ -277,14 +281,14 @@ def test_float_modulus_corner_cases(self): fzer = np.array(0.0, dtype=dt) finf = np.array(np.inf, dtype=dt) fnan = np.array(np.nan, dtype=dt) - rem = self.mod(fone, fzer) + rem = operator.mod(fone, fzer) assert_(np.isnan(rem), 'dt: %s' % dt) # MSVC 2008 returns NaN here, so disable the check. - #rem = self.mod(fone, finf) + #rem = operator.mod(fone, finf) #assert_(rem == fone, 'dt: %s' % dt) - rem = self.mod(fone, fnan) + rem = operator.mod(fone, fnan) assert_(np.isnan(rem), 'dt: %s' % dt) - rem = self.mod(finf, fone) + rem = operator.mod(finf, fone) assert_(np.isnan(rem), 'dt: %s' % dt) diff --git a/numpy/core/tests/test_umath.py b/numpy/core/tests/test_umath.py index 7d147f72744f..51bf7c9425df 100644 --- a/numpy/core/tests/test_umath.py +++ b/numpy/core/tests/test_umath.py @@ -264,34 +264,34 @@ def test_floor_division_complex(self): assert_equal(y, [1.e+110, 0], err_msg=msg) +def floor_divide_and_remainder(x, y): + return (np.floor_divide(x, y), np.remainder(x, y)) + + +def _signs(dt): + if dt in np.typecodes['UnsignedInteger']: + return (+1,) + else: + return (+1, -1) + + class TestRemainder(TestCase): def test_remainder_basic(self): dt = np.typecodes['AllInteger'] + np.typecodes['Float'] - for dt1, dt2 in itertools.product(dt, dt): - for sg1, sg2 in itertools.product((+1, -1), (+1, -1)): - if sg1 == -1 and dt1 in np.typecodes['UnsignedInteger']: - continue - if sg2 == -1 and dt2 in np.typecodes['UnsignedInteger']: - continue - fmt = 'dt1: %s, dt2: %s, sg1: %s, sg2: %s' - msg = fmt % (dt1, dt2, sg1, sg2) - a = np.array(sg1*71, dtype=dt1) - b = np.array(sg2*19, dtype=dt2) - div = np.floor_divide(a, b) - rem = np.remainder(a, b) - assert_equal(div*b + rem, a, err_msg=msg) - if sg2 == -1: - assert_(b < rem <= 0, msg) - else: - assert_(b > rem >= 0, msg) - - div, rem = np.divmod(a, b) - assert_equal(div*b + rem, a, err_msg=msg) - if sg2 == -1: - assert_(b < rem <= 0, msg) - else: - assert_(b > rem >= 0, msg) + for op in [floor_divide_and_remainder, np.divmod]: + for dt1, dt2 in itertools.product(dt, dt): + for sg1, sg2 in itertools.product(_signs(dt1), _signs(dt2)): + fmt = 'op: %s, dt1: %s, dt2: %s, sg1: %s, sg2: %s' + msg = fmt % (op.__name__, dt1, dt2, sg1, sg2) + a = np.array(sg1*71, dtype=dt1) + b = np.array(sg2*19, dtype=dt2) + div, rem = op(a, b) + assert_equal(div*b + rem, a, err_msg=msg) + if sg2 == -1: + assert_(b < rem <= 0, msg) + else: + assert_(b > rem >= 0, msg) def test_float_remainder_exact(self): # test that float results are exact for small integers. This also @@ -310,43 +310,32 @@ def test_float_remainder_exact(self): tgtdiv = np.where((tgtdiv == 0.0) & ((b < 0) ^ (a < 0)), -0.0, tgtdiv) tgtrem = np.where((tgtrem == 0.0) & (b < 0), -0.0, tgtrem) - for dt in np.typecodes['Float']: - msg = 'dtype: %s' % (dt,) - fa = a.astype(dt) - fb = b.astype(dt) - div = np.floor_divide(fa, fb) - rem = np.remainder(fa, fb) - assert_equal(div, tgtdiv, err_msg=msg) - assert_equal(rem, tgtrem, err_msg=msg) - - div, rem = np.divmod(fa, fb) - assert_equal(div, tgtdiv, err_msg=msg) - assert_equal(rem, tgtrem, err_msg=msg) + for op in [floor_divide_and_remainder, np.divmod]: + for dt in np.typecodes['Float']: + msg = 'op: %s, dtype: %s' % (op.__name__, dt) + fa = a.astype(dt) + fb = b.astype(dt) + div, rem = op(fa, fb) + assert_equal(div, tgtdiv, err_msg=msg) + assert_equal(rem, tgtrem, err_msg=msg) def test_float_remainder_roundoff(self): # gh-6127 dt = np.typecodes['Float'] - for dt1, dt2 in itertools.product(dt, dt): - for sg1, sg2 in itertools.product((+1, -1), (+1, -1)): - fmt = 'dt1: %s, dt2: %s, sg1: %s, sg2: %s' - msg = fmt % (dt1, dt2, sg1, sg2) - a = np.array(sg1*78*6e-8, dtype=dt1) - b = np.array(sg2*6e-8, dtype=dt2) - div = np.floor_divide(a, b) - rem = np.remainder(a, b) - # Equal assertion should hold when fmod is used - assert_equal(div*b + rem, a, err_msg=msg) - if sg2 == -1: - assert_(b < rem <= 0, msg) - else: - assert_(b > rem >= 0, msg) - - div, rem = np.divmod(a, b) - assert_equal(div*b + rem, a, err_msg=msg) - if sg2 == -1: - assert_(b < rem <= 0, msg) - else: - assert_(b > rem >= 0, msg) + for op in [floor_divide_and_remainder, np.divmod]: + for dt1, dt2 in itertools.product(dt, dt): + for sg1, sg2 in itertools.product((+1, -1), (+1, -1)): + fmt = 'op: %s, dt1: %s, dt2: %s, sg1: %s, sg2: %s' + msg = fmt % (op.__name__, dt1, dt2, sg1, sg2) + a = np.array(sg1*78*6e-8, dtype=dt1) + b = np.array(sg2*6e-8, dtype=dt2) + div, rem = op(a, b) + # Equal assertion should hold when fmod is used + assert_equal(div*b + rem, a, err_msg=msg) + if sg2 == -1: + assert_(b < rem <= 0, msg) + else: + assert_(b > rem >= 0, msg) def test_float_remainder_corner_cases(self): # Check remainder magnitude. From a1489789d27cb98d0f79606e9c79f7c1ba11feb6 Mon Sep 17 00:00:00 2001 From: Stephan Hoyer Date: Sat, 6 May 2017 14:58:43 -0700 Subject: [PATCH 4/5] DOC: update ufunc overides NEP with __divmod__ --- doc/neps/ufunc-overrides.rst | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/doc/neps/ufunc-overrides.rst b/doc/neps/ufunc-overrides.rst index f468a8ca1477..451b55a62491 100644 --- a/doc/neps/ufunc-overrides.rst +++ b/doc/neps/ufunc-overrides.rst @@ -662,15 +662,15 @@ Symbol Operator NumPy Ufunc(s) ``/`` ``div`` :func:`divide` (Python 2) ``//`` ``floordiv`` :func:`floor_divide` -``%`` ``mod`` :func:`mod` +``%`` ``mod`` :func:`remainder` +NA ``divmod`` :func:`divmod` ``**`` ``pow`` :func:`power` ``<<`` ``lshift`` :func:`left_shift` ``>>`` ``rshift`` :func:`right_shift` ``&`` ``and_`` :func:`bitwise_and` ``^`` ``xor_`` :func:`bitwise_xor` ``|`` ``or_`` :func:`bitwise_or` -NA ``divmod`` :func:`floor_divide`, :func:`mod` [10]_ -``@`` ``matmul`` Not yet implemented as a ufunc +``@`` ``matmul`` Not yet implemented as a ufunc [10]_ ====== ============ ========================================= And here is the list of unary operators: @@ -684,7 +684,10 @@ NA ``abs`` :func:`absolute` ``~`` ``invert`` :func:`invert` ====== ============ ========================================= -.. [10] In the future, NumPy may switch to use a single ufunc ``divmod_`` instead. +.. [10] Because NumPy's :func:`matmul` is not a ufunc, it is + `currently not possible `_ + to override ``numpy_array @ other`` with ``other`` taking precedence + if ``other`` implements ``__array_func__``. .. [11] :class:`ndarray` currently does a copy instead of using this ufunc. Future extensions to other functions From 8fbf75e499196c05da46302f035909897c9ec272 Mon Sep 17 00:00:00 2001 From: Stephan Hoyer Date: Sat, 6 May 2017 15:31:52 -0700 Subject: [PATCH 5/5] DOC: fix docstring for np.isin --- numpy/lib/arraysetops.py | 1 + 1 file changed, 1 insertion(+) diff --git a/numpy/lib/arraysetops.py b/numpy/lib/arraysetops.py index 9a1448991ace..d29e555b8bf2 100644 --- a/numpy/lib/arraysetops.py +++ b/numpy/lib/arraysetops.py @@ -522,6 +522,7 @@ def isin(element, test_elements, assume_unique=False, invert=False): in1d : Flattened version of this function. numpy.lib.arraysetops : Module with a number of other functions for performing set operations on arrays. + Notes -----