summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarten van Kerkwijk <mhvk@astro.utoronto.ca>2019-12-20 19:25:25 -0500
committerMarten van Kerkwijk <mhvk@astro.utoronto.ca>2019-12-20 19:33:00 -0500
commit97a83887da945e884a684b10f8c59eb710b1a26b (patch)
tree80c7c4136e57ff5c592e22820d11b55c1ab3a5d9
parent190ac3e99015ddded029d8811829da1b48b69b97 (diff)
downloadnumpy-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.c23
-rw-r--r--numpy/core/tests/test_ufunc.py17
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__)