summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--numpy/core/src/multiarray/nditer_constr.c96
-rw-r--r--numpy/core/tests/test_nditer.py29
2 files changed, 67 insertions, 58 deletions
diff --git a/numpy/core/src/multiarray/nditer_constr.c b/numpy/core/src/multiarray/nditer_constr.c
index b6acce570..248397196 100644
--- a/numpy/core/src/multiarray/nditer_constr.c
+++ b/numpy/core/src/multiarray/nditer_constr.c
@@ -1410,9 +1410,9 @@ check_mask_for_writemasked_reduction(NpyIter *iter, int iop)
static int
npyiter_check_reduce_ok_and_set_flags(
NpyIter *iter, npy_uint32 flags, npyiter_opitflags *op_itflags,
- int dim) {
+ int iop, int maskop, int dim) {
/* If it's writeable, this means a reduction */
- if (*op_itflags & NPY_OP_ITFLAG_WRITE) {
+ if (op_itflags[iop] & NPY_OP_ITFLAG_WRITE) {
if (!(flags & NPY_ITER_REDUCE_OK)) {
PyErr_Format(PyExc_ValueError,
"output operand requires a reduction along dimension %d, "
@@ -1420,17 +1420,35 @@ npyiter_check_reduce_ok_and_set_flags(
"does not match the expected output shape.", dim);
return 0;
}
- if (!(*op_itflags & NPY_OP_ITFLAG_READ)) {
+ if (!(op_itflags[iop] & NPY_OP_ITFLAG_READ)) {
PyErr_SetString(PyExc_ValueError,
"output operand requires a reduction, but is flagged as "
"write-only, not read-write");
return 0;
}
+ /*
+ * The ARRAYMASK can't be a reduction, because
+ * it would be possible to write back to the
+ * array once when the ARRAYMASK says 'True',
+ * then have the reduction on the ARRAYMASK
+ * later flip to 'False', indicating that the
+ * write back should never have been done,
+ * and violating the strict masking semantics
+ */
+ if (iop == maskop) {
+ PyErr_SetString(PyExc_ValueError,
+ "output operand requires a "
+ "reduction, but is flagged as "
+ "the ARRAYMASK operand which "
+ "is not permitted to be the "
+ "result of a reduction");
+ return 0;
+ }
NPY_IT_DBG_PRINT("Iterator: Indicating that a reduction is"
"occurring\n");
NIT_ITFLAGS(iter) |= NPY_ITFLAG_REDUCE;
- *op_itflags |= NPY_OP_ITFLAG_REDUCE;
+ op_itflags[iop] |= NPY_OP_ITFLAG_REDUCE;
}
return 1;
}
@@ -1613,42 +1631,9 @@ npyiter_fill_axisdata(NpyIter *iter, npy_uint32 flags, npyiter_opitflags *op_itf
goto operand_different_than_broadcast;
}
/* If it's writeable, this means a reduction */
- if (op_itflags[iop] & NPY_OP_ITFLAG_WRITE) {
- if (!(flags & NPY_ITER_REDUCE_OK)) {
- PyErr_SetString(PyExc_ValueError,
- "output operand requires a "
- "reduction, but reduction is "
- "not enabled");
- return 0;
- }
- if (!(op_itflags[iop] & NPY_OP_ITFLAG_READ)) {
- PyErr_SetString(PyExc_ValueError,
- "output operand requires a "
- "reduction, but is flagged as "
- "write-only, not read-write");
- return 0;
- }
- /*
- * The ARRAYMASK can't be a reduction, because
- * it would be possible to write back to the
- * array once when the ARRAYMASK says 'True',
- * then have the reduction on the ARRAYMASK
- * later flip to 'False', indicating that the
- * write back should never have been done,
- * and violating the strict masking semantics
- */
- if (iop == maskop) {
- PyErr_SetString(PyExc_ValueError,
- "output operand requires a "
- "reduction, but is flagged as "
- "the ARRAYMASK operand which "
- "is not permitted to be the "
- "result of a reduction");
- return 0;
- }
-
- NIT_ITFLAGS(iter) |= NPY_ITFLAG_REDUCE;
- op_itflags[iop] |= NPY_OP_ITFLAG_REDUCE;
+ if (!npyiter_check_reduce_ok_and_set_flags(
+ iter, flags, op_itflags, iop, maskop, idim)) {
+ return 0;
}
}
else {
@@ -1697,7 +1682,7 @@ npyiter_fill_axisdata(NpyIter *iter, npy_uint32 flags, npyiter_opitflags *op_itf
goto operand_different_than_broadcast;
}
if (!npyiter_check_reduce_ok_and_set_flags(
- iter, flags, &op_itflags[iop], i)) {
+ iter, flags, op_itflags, iop, maskop, i)) {
return 0;
}
}
@@ -1707,8 +1692,14 @@ npyiter_fill_axisdata(NpyIter *iter, npy_uint32 flags, npyiter_opitflags *op_itf
}
else {
strides[iop] = 0;
+ /*
+ * If deleting this axis produces a reduction, but
+ * reduction wasn't enabled, throw an error.
+ * NOTE: We currently always allow new-axis if the iteration
+ * size is 1 (thus allowing broadcasting sometimes).
+ */
if (!npyiter_check_reduce_ok_and_set_flags(
- iter, flags, &op_itflags[iop], i)) {
+ iter, flags, op_itflags, iop, maskop, i)) {
return 0;
}
}
@@ -2545,6 +2536,11 @@ npyiter_new_temp_array(NpyIter *iter, PyTypeObject *subtype,
i = npyiter_undo_iter_axis_perm(idim, ndim, perm, NULL);
i = npyiter_get_op_axis(op_axes[i], &reduction_axis);
+ /*
+ * If i < 0, this is a new axis (the operand does not have it)
+ * so we can ignore it here. The iterator setup will have
+ * ensured already that a potential reduction/broadcast is valid.
+ */
if (i >= 0) {
NPY_IT_DBG_PRINT3("Iterator: Setting allocated stride %d "
"for iterator dimension %d to %d\n", (int)i,
@@ -2575,22 +2571,6 @@ npyiter_new_temp_array(NpyIter *iter, PyTypeObject *subtype,
stride *= shape[i];
}
}
- else {
- if (shape == NULL) {
- /*
- * If deleting this axis produces a reduction, but
- * reduction wasn't enabled, throw an error.
- * NOTE: We currently always allow new-axis if the iteration
- * size is 1 (thus allowing broadcasting sometimes).
- */
- if (!reduction_axis && NAD_SHAPE(axisdata) != 1) {
- if (!npyiter_check_reduce_ok_and_set_flags(
- iter, flags, op_itflags, i)) {
- return NULL;
- }
- }
- }
- }
}
}
else {
diff --git a/numpy/core/tests/test_nditer.py b/numpy/core/tests/test_nditer.py
index b43bc50e9..08f44568c 100644
--- a/numpy/core/tests/test_nditer.py
+++ b/numpy/core/tests/test_nditer.py
@@ -2728,6 +2728,7 @@ def test_iter_writemasked_badinput():
op_dtypes=['f4', None],
casting='same_kind')
+
def _is_buffered(iterator):
try:
iterator.itviews
@@ -2803,6 +2804,34 @@ def test_iter_writemasked(a):
# were copied back
assert_equal(a, np.broadcast_to([3, 3, 2.5] * reps, shape))
+
+@pytest.mark.parametrize(["mask", "mask_axes"], [
+ # Allocated operand (only broadcasts with -1)
+ (None, [-1, 0]),
+ # Reduction along the first dimension (with and without op_axes)
+ (np.zeros((1, 4), dtype="bool"), [0, 1]),
+ (np.zeros((1, 4), dtype="bool"), None),
+ # Test 0-D and -1 op_axes
+ (np.zeros(4, dtype="bool"), [-1, 0]),
+ (np.zeros((), dtype="bool"), [-1, -1]),
+ (np.zeros((), dtype="bool"), None)])
+def test_iter_writemasked_broadcast_error(mask, mask_axes):
+ # This assumes that a readwrite mask makes sense. This is likely not the
+ # case and should simply be deprecated.
+ arr = np.zeros((3, 4))
+ itflags = ["reduce_ok"]
+ mask_flags = ["arraymask", "readwrite", "allocate"]
+ a_flags = ["writeonly", "writemasked"]
+ if mask_axes is None:
+ op_axes = None
+ else:
+ op_axes = [mask_axes, [0, 1]]
+
+ with assert_raises(ValueError):
+ np.nditer((mask, arr), flags=itflags, op_flags=[mask_flags, a_flags],
+ op_axes=op_axes)
+
+
def test_iter_writemasked_decref():
# force casting (to make it interesting) by using a structured dtype.
arr = np.arange(10000).astype(">i,O")