summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSebastian Berg <sebastian@sipsolutions.net>2020-05-11 16:01:51 -0500
committerSebastian Berg <sebastian@sipsolutions.net>2020-05-11 18:42:14 -0500
commit07ea525dd627ee593591ae18c6d6599d943ca116 (patch)
tree8f31bdb4603a19cf2c26afe67dd889cc067bcaa2
parentf31aeefd7ff3ba5bd953a1e082226d08eb837a43 (diff)
downloadnumpy-07ea525dd627ee593591ae18c6d6599d943ca116.tar.gz
MAINT: Simplify strides and axis check in npyiter_new_temp_array
The stride calculation and axis check redid some of the work finding the number of dimensions again. This simplifies the logic and clarifies an error message a bit.
-rw-r--r--numpy/core/src/multiarray/nditer_constr.c124
-rw-r--r--numpy/core/tests/test_nditer.py15
2 files changed, 75 insertions, 64 deletions
diff --git a/numpy/core/src/multiarray/nditer_constr.c b/numpy/core/src/multiarray/nditer_constr.c
index 894d68d88..2937c3f85 100644
--- a/numpy/core/src/multiarray/nditer_constr.c
+++ b/numpy/core/src/multiarray/nditer_constr.c
@@ -2469,11 +2469,12 @@ npyiter_new_temp_array(NpyIter *iter, PyTypeObject *subtype,
{
npy_uint32 itflags = NIT_ITFLAGS(iter);
int idim, ndim = NIT_NDIM(iter);
+ int used_op_ndim;
int nop = NIT_NOP(iter);
npy_int8 *perm = NIT_PERM(iter);
- npy_intp new_shape[NPY_MAXDIMS], strides[NPY_MAXDIMS],
- stride = op_dtype->elsize;
+ npy_intp new_shape[NPY_MAXDIMS], strides[NPY_MAXDIMS];
+ npy_intp stride = op_dtype->elsize;
NpyIter_AxisData *axisdata;
npy_intp sizeof_axisdata;
int i;
@@ -2503,11 +2504,12 @@ npyiter_new_temp_array(NpyIter *iter, PyTypeObject *subtype,
sizeof_axisdata = NIT_AXISDATA_SIZEOF(itflags, ndim, nop);
/* Initialize the strides to invalid values */
- for (i = 0; i < NPY_MAXDIMS; ++i) {
+ for (i = 0; i < op_ndim; ++i) {
strides[i] = NPY_MAX_INTP;
}
if (op_axes != NULL) {
+ used_op_ndim = 0;
for (idim = 0; idim < ndim; ++idim, NIT_ADVANCE_AXISDATA(axisdata, 1)) {
/* Apply the perm to get the original axis */
@@ -2517,14 +2519,18 @@ npyiter_new_temp_array(NpyIter *iter, PyTypeObject *subtype,
NPY_IT_DBG_PRINT3("Iterator: Setting allocated stride %d "
"for iterator dimension %d to %d\n", (int)i,
(int)idim, (int)stride);
+ used_op_ndim += 1;
strides[i] = stride;
if (shape == NULL) {
new_shape[i] = NAD_SHAPE(axisdata);
stride *= new_shape[i];
if (i >= ndim) {
- PyErr_SetString(PyExc_ValueError,
+ PyErr_Format(PyExc_ValueError,
"automatically allocated output array "
- "specified with an inconsistent axis mapping");
+ "specified with an inconsistent axis mapping; "
+ "the axis mapping cannot include dimension %d "
+ "which is too large for the iterator dimension "
+ "of %d.", i, ndim);
return NULL;
}
}
@@ -2551,6 +2557,7 @@ npyiter_new_temp_array(NpyIter *iter, PyTypeObject *subtype,
}
}
else {
+ used_op_ndim = ndim;
for (idim = 0; idim < ndim; ++idim, NIT_ADVANCE_AXISDATA(axisdata, 1)) {
/* Apply the perm to get the original axis */
i = npyiter_undo_iter_axis_perm(idim, op_ndim, perm, NULL);
@@ -2571,73 +2578,57 @@ npyiter_new_temp_array(NpyIter *iter, PyTypeObject *subtype,
}
}
- /*
- * If custom axes were specified, some dimensions may not have been used.
- * Add the REDUCE itflag if this creates a reduction situation.
- */
if (shape == NULL) {
- /* Ensure there are no dimension gaps in op_axes, and find op_ndim */
- op_ndim = ndim;
- if (op_axes != NULL) {
- for (i = 0; i < ndim; ++i) {
- if (strides[i] == NPY_MAX_INTP) {
- if (op_ndim == ndim) {
- op_ndim = i;
- }
- }
- /*
- * If there's a gap in the array's dimensions, it's an error.
- * For example, op_axes of [0,2] for the automatically
- * allocated output.
- */
- else if (op_ndim != ndim) {
- PyErr_SetString(PyExc_ValueError,
- "automatically allocated output array "
- "specified with an inconsistent axis mapping");
- return NULL;
- }
+ /* If shape was NULL, use the shape we calculated */
+ op_ndim = used_op_ndim;
+ shape = new_shape;
+ /*
+ * If op_axes [0, 2] is specified, there will a place in the strides
+ * array where the value is not set.
+ */
+ for (i = 0; i < op_ndim; i++) {
+ if (strides[i] == NPY_MAX_INTP) {
+ PyErr_Format(PyExc_ValueError,
+ "automatically allocated output array "
+ "specified with an inconsistent axis mapping; "
+ "the axis mapping is missing an entry for "
+ "dimension %d.", i);
+ return NULL;
}
}
}
- else {
- for (i = 0; i < op_ndim; ++i) {
- if (strides[i] == NPY_MAX_INTP) {
- npy_intp factor, new_strides[NPY_MAXDIMS],
- itemsize;
-
- /* Fill in the missing strides in C order */
- factor = 1;
- itemsize = op_dtype->elsize;
- for (i = op_ndim-1; i >= 0; --i) {
- if (strides[i] == NPY_MAX_INTP) {
- new_strides[i] = factor * itemsize;
- factor *= shape[i];
- }
- }
-
- /*
- * Copy the missing strides, and multiply the existing strides
- * by the calculated factor. This way, the missing strides
- * are tighter together in memory, which is good for nested
- * loops.
- */
- for (i = 0; i < op_ndim; ++i) {
- if (strides[i] == NPY_MAX_INTP) {
- strides[i] = new_strides[i];
- }
- else {
- strides[i] *= factor;
- }
- }
+ else if (used_op_ndim < op_ndim) {
+ /*
+ * If custom axes were specified, some dimensions may not have
+ * been used. These are additional axes which are ignored in the
+ * iterator but need to be handled here.
+ */
+ npy_intp factor, itemsize, new_strides[NPY_MAXDIMS];
- break;
+ /* Fill in the missing strides in C order */
+ factor = 1;
+ itemsize = op_dtype->elsize;
+ for (i = op_ndim-1; i >= 0; --i) {
+ if (strides[i] == NPY_MAX_INTP) {
+ new_strides[i] = factor * itemsize;
+ factor *= shape[i];
}
}
- }
- /* If shape was NULL, set it to the shape we calculated */
- if (shape == NULL) {
- shape = new_shape;
+ /*
+ * Copy the missing strides, and multiply the existing strides
+ * by the calculated factor. This way, the missing strides
+ * are tighter together in memory, which is good for nested
+ * loops.
+ */
+ for (i = 0; i < op_ndim; ++i) {
+ if (strides[i] == NPY_MAX_INTP) {
+ strides[i] = new_strides[i];
+ }
+ else {
+ strides[i] *= factor;
+ }
+ }
}
/* Allocate the temporary array */
@@ -2650,6 +2641,11 @@ npyiter_new_temp_array(NpyIter *iter, PyTypeObject *subtype,
/* Double-check that the subtype didn't mess with the dimensions */
if (subtype != &PyArray_Type) {
+ /*
+ * TODO: the dtype could have a subarray, which adds new dimensions
+ * to `ret`, that should typically be fine, but will break
+ * in this branch.
+ */
if (PyArray_NDIM(ret) != op_ndim ||
!PyArray_CompareLists(shape, PyArray_DIMS(ret), op_ndim)) {
PyErr_SetString(PyExc_RuntimeError,
diff --git a/numpy/core/tests/test_nditer.py b/numpy/core/tests/test_nditer.py
index c106c528d..7b3c3a40d 100644
--- a/numpy/core/tests/test_nditer.py
+++ b/numpy/core/tests/test_nditer.py
@@ -1520,6 +1520,13 @@ def test_iter_allocate_output_errors():
[['readonly'], ['writeonly', 'allocate']],
op_dtypes=[None, np.dtype('f4')],
op_axes=[None, [0, 2, 1, 0]])
+ # Not all axes may be specified if a reduction. If there is a hole
+ # in op_axes, this is an error.
+ a = arange(24, dtype='i4').reshape(2, 3, 4)
+ assert_raises(ValueError, nditer, [a, None], ["reduce_ok"],
+ [['readonly'], ['readwrite', 'allocate']],
+ op_dtypes=[None, np.dtype('f4')],
+ op_axes=[None, [0, np.newaxis, 2]])
def test_iter_remove_axis():
a = arange(24).reshape(2, 3, 4)
@@ -2661,6 +2668,14 @@ def test_iter_allocated_array_dtypes():
b[1] = a + 1
assert_equal(it.operands[1], [[0, 2], [2, 4], [19, 21]])
+ # Check the same (less sensitive) thing when `op_axes` with -1 is given.
+ it = np.nditer(([[1, 3, 20]], None), op_dtypes=[None, ('i4', (2,))],
+ flags=["reduce_ok"], op_axes=[None, (-1, 0)])
+ for a, b in it:
+ b[0] = a - 1
+ b[1] = a + 1
+ assert_equal(it.operands[1], [[0, 2], [2, 4], [19, 21]])
+
# Make sure this works for scalars too
it = np.nditer((10, 2, None), op_dtypes=[None, None, ('i4', (2, 2))])
for a, b, c in it: