diff options
author | Marten van Kerkwijk <mhvk@astro.utoronto.ca> | 2019-12-20 19:25:25 -0500 |
---|---|---|
committer | Marten van Kerkwijk <mhvk@astro.utoronto.ca> | 2019-12-20 19:33:00 -0500 |
commit | 97a83887da945e884a684b10f8c59eb710b1a26b (patch) | |
tree | 80c7c4136e57ff5c592e22820d11b55c1ab3a5d9 | |
parent | 190ac3e99015ddded029d8811829da1b48b69b97 (diff) | |
download | numpy-97a83887da945e884a684b10f8c59eb710b1a26b.tar.gz |
BUG: ensure reduction output matches input along non-reduction axes.
Previously, things like
`np.add.reduce(np.ones((3, 3)), axis=0, out=np.empty(1))`
worked, even though the output should have shape (3,).
-rw-r--r-- | numpy/core/src/umath/reduction.c | 23 | ||||
-rw-r--r-- | numpy/core/tests/test_ufunc.py | 17 |
2 files changed, 38 insertions, 2 deletions
diff --git a/numpy/core/src/umath/reduction.c b/numpy/core/src/umath/reduction.c index 4ce8d8ab7..79c302755 100644 --- a/numpy/core/src/umath/reduction.c +++ b/numpy/core/src/umath/reduction.c @@ -84,10 +84,12 @@ allocate_reduce_result(PyArrayObject *arr, const npy_bool *axis_flags, * The return value is a view into 'out'. */ static PyArrayObject * -conform_reduce_result(int ndim, const npy_bool *axis_flags, +conform_reduce_result(PyArrayObject *in, const npy_bool *axis_flags, PyArrayObject *out, int keepdims, const char *funcname, int need_copy) { + int ndim = PyArray_NDIM(in); + npy_intp *shape_in = PyArray_DIMS(in); npy_intp strides[NPY_MAXDIMS], shape[NPY_MAXDIMS]; npy_intp *strides_out = PyArray_STRIDES(out); npy_intp *shape_out = PyArray_DIMS(out); @@ -118,6 +120,16 @@ conform_reduce_result(int ndim, const npy_bool *axis_flags, return NULL; } } + else { + if (shape_out[idim] != shape_in[idim]) { + PyErr_Format(PyExc_ValueError, + "output parameter for reduction operation %s " + "has a non-reduction dimension not equal to " + "the input one.", funcname); + return NULL; + } + } + } Py_INCREF(out); @@ -138,6 +150,13 @@ conform_reduce_result(int ndim, const npy_bool *axis_flags, "does not have enough dimensions", funcname); return NULL; } + if (shape_out[idim_out] != shape_in[idim]) { + PyErr_Format(PyExc_ValueError, + "output parameter for reduction operation %s " + "has a non-reduction dimension not equal to " + "the input one.", funcname); + return NULL; + } strides[idim] = strides_out[idim_out]; shape[idim] = shape_out[idim_out]; ++idim_out; @@ -240,7 +259,7 @@ PyArray_CreateReduceResult(PyArrayObject *operand, PyArrayObject *out, /* Steal the dtype reference */ Py_XDECREF(dtype); - result = conform_reduce_result(PyArray_NDIM(operand), axis_flags, + result = conform_reduce_result(operand, axis_flags, out, keepdims, funcname, need_copy); } diff --git a/numpy/core/tests/test_ufunc.py b/numpy/core/tests/test_ufunc.py index 7109de776..4c04aceb3 100644 --- a/numpy/core/tests/test_ufunc.py +++ b/numpy/core/tests/test_ufunc.py @@ -1888,6 +1888,23 @@ class TestUfunc(object): assert_equal(y_base[1,:], y_base_copy[1,:]) assert_equal(y_base[3,:], y_base_copy[3,:]) + @pytest.mark.parametrize('output_shape', + [(), (1,), (1, 1), (1, 3), (4, 3)]) + @pytest.mark.parametrize('f_reduce', [np.add.reduce, np.minimum.reduce]) + def test_reduce_wrong_dimension_output(self, f_reduce, output_shape): + # Test that we're not incorrectly broadcasting dimensions. + # See gh-15144 (failed for np.add.reduce previously). + a = np.arange(12.).reshape(4, 3) + out = np.empty(output_shape, a.dtype) + assert_raises(ValueError, f_reduce, a, axis=0, out=out) + if output_shape != (1, 3): + assert_raises(ValueError, f_reduce, a, axis=0, out=out, + keepdims=True) + else: + check = f_reduce(a, axis=0, out=out, keepdims=True) + assert_(check is out) + assert_array_equal(check, f_reduce(a, axis=0, keepdims=True)) + def test_no_doc_string(self): # gh-9337 assert_('\n' not in umt.inner1d_no_doc.__doc__) |