summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSebastian Berg <sebastian@sipsolutions.net>2020-05-11 16:34:29 -0500
committerSebastian Berg <sebastian@sipsolutions.net>2020-05-11 18:42:14 -0500
commitb5c2f94df50bab697eaa7d9727a4f3b44f89d329 (patch)
tree4e30d6749434bdc565077367d27949ad0c4516ff
parent87cb35f371c5711ea20979b9e2569ca544b237e6 (diff)
downloadnumpy-b5c2f94df50bab697eaa7d9727a4f3b44f89d329.tar.gz
BUG: Avoid incorrect broadcasts on non-core outputs in gufuncs
Mark the core dimensions as reduce axis. This allows them to be freely broadcast (basically ignored for most practical purposes) while other axes are normally broadcast even though the operand is read-only or write-only. If we were to use read-write operands with `REDUCE_OK` these additional dimensions are currently simply absored as reduction dimensions... (I hope this is understandable, please see the issues/test for an example...) Fixes gh-15139, replaces gh-15142 Co-Authored-By: Marten van Kerkwijk <mhvk@astro.utoronto.ca>
-rw-r--r--numpy/core/src/umath/ufunc_object.c13
-rw-r--r--numpy/core/tests/test_ufunc.py15
2 files changed, 22 insertions, 6 deletions
diff --git a/numpy/core/src/umath/ufunc_object.c b/numpy/core/src/umath/ufunc_object.c
index c57199c79..19876d641 100644
--- a/numpy/core/src/umath/ufunc_object.c
+++ b/numpy/core/src/umath/ufunc_object.c
@@ -2697,9 +2697,13 @@ PyUFunc_GeneralizedFunction(PyUFuncObject *ufunc,
}
}
- /* Any output core dimensions shape should be ignored */
+ /*
+ * Any output core dimensions shape should be ignored, so we add
+ * it as a Reduce dimension (which can be broadcast with the rest).
+ * These will be removed before the actual iteration for gufuncs.
+ */
for (idim = broadcast_ndim; idim < iter_ndim; ++idim) {
- op_axes_arrays[i][idim] = -1;
+ op_axes_arrays[i][idim] = NPY_ITER_REDUCTION_AXIS(-1);
}
/* Except for when it belongs to this output */
@@ -2771,7 +2775,7 @@ PyUFunc_GeneralizedFunction(PyUFuncObject *ufunc,
*/
_ufunc_setup_flags(ufunc, NPY_ITER_COPY | NPY_UFUNC_DEFAULT_INPUT_FLAGS,
NPY_ITER_UPDATEIFCOPY |
- NPY_ITER_READWRITE |
+ NPY_ITER_WRITEONLY |
NPY_UFUNC_DEFAULT_OUTPUT_FLAGS,
op_flags);
/* For the generalized ufunc, we get the loop right away too */
@@ -2820,7 +2824,6 @@ PyUFunc_GeneralizedFunction(PyUFuncObject *ufunc,
iter_flags = ufunc->iter_flags |
NPY_ITER_MULTI_INDEX |
NPY_ITER_REFS_OK |
- NPY_ITER_REDUCE_OK |
NPY_ITER_ZEROSIZE_OK |
NPY_ITER_COPY_IF_OVERLAP;
@@ -3646,7 +3649,7 @@ PyUFunc_Reduce(PyUFuncObject *ufunc, PyArrayObject *arr, PyArrayObject *out,
result = PyUFunc_ReduceWrapper(arr, out, wheremask, dtype, dtype,
NPY_UNSAFE_CASTING,
axis_flags, reorderable,
- keepdims, 0,
+ keepdims,
initial,
reduce_loop,
ufunc, buffersize, ufunc_name, errormask);
diff --git a/numpy/core/tests/test_ufunc.py b/numpy/core/tests/test_ufunc.py
index abdaeeb93..e47365293 100644
--- a/numpy/core/tests/test_ufunc.py
+++ b/numpy/core/tests/test_ufunc.py
@@ -610,7 +610,17 @@ class TestUfunc:
warnings.simplefilter("always")
u += v
assert_equal(len(w), 1)
- assert_(x[0,0] != u[0, 0])
+ assert_(x[0, 0] != u[0, 0])
+
+ # Output reduction should not be allowed.
+ # See gh-15139
+ a = np.arange(6).reshape(3, 2)
+ b = np.ones(2)
+ out = np.empty(())
+ assert_raises(ValueError, umt.inner1d, a, b, out)
+ out2 = np.empty(3)
+ c = umt.inner1d(a, b, out2)
+ assert_(c is out2)
def test_type_cast(self):
msg = "type cast"
@@ -942,7 +952,10 @@ class TestUfunc:
assert_array_equal(result, np.vstack((np.zeros(3), a[2], -a[1])))
assert_raises(ValueError, umt.cross1d, np.eye(4), np.eye(4))
assert_raises(ValueError, umt.cross1d, a, np.arange(4.))
+ # Wrong output core dimension.
assert_raises(ValueError, umt.cross1d, a, np.arange(3.), np.zeros((3, 4)))
+ # Wrong output broadcast dimension (see gh-15139).
+ assert_raises(ValueError, umt.cross1d, a, np.arange(3.), np.zeros(3))
def test_can_ignore_signature(self):
# Comparing the effects of ? in signature: