diff options
author | Sebastian Berg <sebastian@sipsolutions.net> | 2020-05-11 16:34:29 -0500 |
---|---|---|
committer | Sebastian Berg <sebastian@sipsolutions.net> | 2020-05-11 18:42:14 -0500 |
commit | b5c2f94df50bab697eaa7d9727a4f3b44f89d329 (patch) | |
tree | 4e30d6749434bdc565077367d27949ad0c4516ff | |
parent | 87cb35f371c5711ea20979b9e2569ca544b237e6 (diff) | |
download | numpy-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.c | 13 | ||||
-rw-r--r-- | numpy/core/tests/test_ufunc.py | 15 |
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: |