summaryrefslogtreecommitdiff
path: root/numpy
diff options
context:
space:
mode:
authorSebastian Berg <sebastian@sipsolutions.net>2022-04-21 10:34:54 +0200
committerSebastian Berg <sebastian@sipsolutions.net>2022-06-13 09:36:57 -0700
commita5bf28b0167356098e6ed1ad69e3bc232dac7a26 (patch)
tree19ed5e437b3e27e460759b601f29c2bd27640335 /numpy
parent4c2a3d0030c244e152eb77365c8973dc07621186 (diff)
downloadnumpy-a5bf28b0167356098e6ed1ad69e3bc232dac7a26.tar.gz
ENH: Add floating point error handling to advanced indexing
Diffstat (limited to 'numpy')
-rw-r--r--numpy/core/src/common/lowlevel_strided_loops.h8
-rw-r--r--numpy/core/src/multiarray/lowlevel_strided_loops.c.src40
-rw-r--r--numpy/core/src/multiarray/mapping.c156
-rw-r--r--numpy/core/src/multiarray/mapping.h2
-rw-r--r--numpy/core/tests/test_casting_floatingpoint_errors.py13
5 files changed, 142 insertions, 77 deletions
diff --git a/numpy/core/src/common/lowlevel_strided_loops.h b/numpy/core/src/common/lowlevel_strided_loops.h
index c06cd4551..924a34db5 100644
--- a/numpy/core/src/common/lowlevel_strided_loops.h
+++ b/numpy/core/src/common/lowlevel_strided_loops.h
@@ -336,10 +336,14 @@ mapiter_trivial_set(PyArrayObject *self, PyArrayObject *ind,
PyArrayObject *result);
NPY_NO_EXPORT int
-mapiter_get(PyArrayMapIterObject *mit);
+mapiter_get(
+ PyArrayMapIterObject *mit, NPY_cast_info *cast_info,
+ NPY_ARRAYMETHOD_FLAGS flags, int is_aligned);
NPY_NO_EXPORT int
-mapiter_set(PyArrayMapIterObject *mit);
+mapiter_set(
+ PyArrayMapIterObject *mit, NPY_cast_info *cast_info,
+ NPY_ARRAYMETHOD_FLAGS flags, int is_aligned);
/*
* Prepares shape and strides for a simple raw array iteration.
diff --git a/numpy/core/src/multiarray/lowlevel_strided_loops.c.src b/numpy/core/src/multiarray/lowlevel_strided_loops.c.src
index c46f733f3..827f0b615 100644
--- a/numpy/core/src/multiarray/lowlevel_strided_loops.c.src
+++ b/numpy/core/src/multiarray/lowlevel_strided_loops.c.src
@@ -1559,14 +1559,16 @@ mapiter_trivial_@name@(PyArrayObject *self, PyArrayObject *ind,
* General advanced indexing iteration.
*/
NPY_NO_EXPORT int
-mapiter_@name@(PyArrayMapIterObject *mit)
+mapiter_@name@(
+ PyArrayMapIterObject *mit, NPY_cast_info *cast_info,
+ NPY_ARRAYMETHOD_FLAGS flags, int is_aligned)
{
npy_intp *counter, count;
- int i, is_aligned;
+ int i;
/* Cached mit info */
int numiter = mit->numiter;
- int needs_api = mit->needs_api;
+ int needs_api = (flags & NPY_METH_REQUIRES_PYAPI) != 0;
/* Constant information */
npy_intp fancy_dims[NPY_MAXDIMS];
npy_intp fancy_strides[NPY_MAXDIMS];
@@ -1588,13 +1590,6 @@ mapiter_@name@(PyArrayMapIterObject *mit)
fancy_strides[i] = mit->fancy_strides[i];
}
- /*
- * Alignment information (swapping is never needed, since we buffer),
- * could also check extra_op is buffered, but it should rarely matter.
- */
-
- is_aligned = IsUintAligned(array) && IsUintAligned(mit->extra_op);
-
if (mit->size == 0) {
return 0;
}
@@ -1744,9 +1739,6 @@ mapiter_@name@(PyArrayMapIterObject *mit)
if (!needs_api) {
NPY_BEGIN_THREADS;
}
- if (!(flags & NPY_METH_NO_FLOATINGPOINT_ERRORS)) {
- npy_clear_floatstatus_barrier((char *)mit);
- }
/* Outer iteration (safe because mit->size != 0) */
do {
@@ -1757,7 +1749,6 @@ mapiter_@name@(PyArrayMapIterObject *mit)
#if @isget@ && @one_iter@
if (check_and_adjust_index(&indval, fancy_dims[i],
iteraxis, _save) < 0 ) {
- NPY_cast_info_xfree(&cast_info);
return -1;
}
#else
@@ -1789,7 +1780,6 @@ mapiter_@name@(PyArrayMapIterObject *mit)
&errmsg)) {
NPY_END_THREADS;
PyErr_SetString(PyExc_ValueError, errmsg);
- NPY_cast_info_xfree(&cast_info);
return -1;
}
if (is_subiter_trivial != 0) {
@@ -1819,7 +1809,6 @@ mapiter_@name@(PyArrayMapIterObject *mit)
* not at all...
*/
if (needs_api && PyErr_Occurred()) {
- NPY_cast_info_xfree(&cast_info);
return -1;
}
#endif
@@ -1827,21 +1816,19 @@ mapiter_@name@(PyArrayMapIterObject *mit)
do {
#if @isget@
- if (NPY_UNLIKELY(cast_info.func(&cast_info.context,
+ if (NPY_UNLIKELY(cast_info->func(&cast_info->context,
subspace_ptrs, counter, subspace_strides,
- cast_info.auxdata) < 0)) {
+ cast_info->auxdata) < 0)) {
NPY_END_THREADS;
- NPY_cast_info_xfree(&cast_info);
return -1;
}
#else
/* The operand order is reversed here */
char *args[2] = {subspace_ptrs[1], subspace_ptrs[0]};
npy_intp strides[2] = {subspace_strides[1], subspace_strides[0]};
- if (NPY_UNLIKELY(cast_info.func(&cast_info.context,
- args, counter, strides, cast_info.auxdata) < 0)) {
+ if (NPY_UNLIKELY(cast_info->func(&cast_info->context,
+ args, counter, strides, cast_info->auxdata) < 0)) {
NPY_END_THREADS;
- NPY_cast_info_xfree(&cast_info);
return -1;
}
#endif
@@ -1852,15 +1839,6 @@ mapiter_@name@(PyArrayMapIterObject *mit)
NPY_END_THREADS;
}
/**end repeat1**/
-
- NPY_cast_info_xfree(&cast_info);
-
- if (!(flags & NPY_METH_NO_FLOATINGPOINT_ERRORS)) {
- int fpes = npy_get_floatstatus_barrier((char *)mit);
- if (fpes && PyUFunc_GiveFloatingpointErrors("cast", fpes) < 0) {
- return -1;
- }
- }
}
return 0;
}
diff --git a/numpy/core/src/multiarray/mapping.c b/numpy/core/src/multiarray/mapping.c
index 646a570dd..2f3b2795f 100644
--- a/numpy/core/src/multiarray/mapping.c
+++ b/numpy/core/src/multiarray/mapping.c
@@ -26,6 +26,9 @@
#include "mem_overlap.h"
#include "array_assign.h"
#include "array_coercion.h"
+/* TODO: Only for `NpyIter_GetTransferFlags` until it is public */
+#define NPY_ITERATOR_IMPLEMENTATION_CODE
+#include "nditer_impl.h"
#include "umathmodule.h"
@@ -1432,6 +1435,8 @@ array_subscript(PyArrayObject *self, PyObject *op)
int index_type;
int index_num;
int i, ndim, fancy_ndim;
+ NPY_cast_info cast_info = {.func = NULL};
+
/*
* Index info array. We can have twice as many indices as dimensions
* (because of None). The + 1 is to not need to check as much.
@@ -1597,7 +1602,43 @@ array_subscript(PyArrayObject *self, PyObject *op)
goto finish;
}
- if (mapiter_get(mit) < 0) {
+ /*
+ * Alignment information (swapping is never needed, since we buffer),
+ * could also check extra_op is buffered, but it should rarely matter.
+ */
+ int is_aligned = IsUintAligned(self) && IsUintAligned(mit->extra_op);
+ /*
+ * NOTE: Getting never actually casts, so we currently do not bother to do
+ * the full checks (floating point errors) here (unlike assignment).
+ */
+ int meth_flags = NpyIter_GetTransferFlags(mit->outer);
+ if (mit->extra_op_iter) {
+ int extra_op_flags = NpyIter_GetTransferFlags(mit->extra_op_iter);
+ meth_flags = PyArrayMethod_COMBINED_FLAGS(meth_flags, extra_op_flags);
+ }
+
+ if (mit->subspace_iter != NULL) {
+ int extra_op_flags = NpyIter_GetTransferFlags(mit->subspace_iter);
+ meth_flags = PyArrayMethod_COMBINED_FLAGS(meth_flags, extra_op_flags);
+
+ NPY_ARRAYMETHOD_FLAGS transfer_flags;
+ npy_intp fixed_strides[2];
+ /*
+ * Get a dtype transfer function, since there are no
+ * buffers, this is safe.
+ */
+ NpyIter_GetInnerFixedStrideArray(mit->subspace_iter, fixed_strides);
+
+ if (PyArray_GetDTypeTransferFunction(is_aligned,
+ fixed_strides[0], fixed_strides[1],
+ PyArray_DESCR(self), PyArray_DESCR(mit->extra_op),
+ 0, &cast_info, &transfer_flags) != NPY_SUCCEED) {
+ goto finish;
+ }
+ meth_flags = PyArrayMethod_COMBINED_FLAGS(meth_flags, transfer_flags);
+ }
+
+ if (mapiter_get(mit, &cast_info, meth_flags, is_aligned) < 0) {
goto finish;
}
@@ -1632,6 +1673,7 @@ array_subscript(PyArrayObject *self, PyObject *op)
}
finish:
+ NPY_cast_info_xfree(&cast_info);
Py_XDECREF(mit);
Py_XDECREF(view);
/* Clean up indices */
@@ -1717,6 +1759,9 @@ array_assign_subscript(PyArrayObject *self, PyObject *ind, PyObject *op)
PyArrayMapIterObject *mit = NULL;
+ /* When a subspace is used, casting is done manually. */
+ NPY_cast_info cast_info = {.func = NULL};
+
if (op == NULL) {
PyErr_SetString(PyExc_ValueError,
"cannot delete array elements");
@@ -1953,12 +1998,50 @@ array_assign_subscript(PyArrayObject *self, PyObject *ind, PyObject *op)
}
}
- /* Can now reset the outer iterator (delayed bufalloc) */
- if (NpyIter_Reset(mit->outer, NULL) < 0) {
+ if (PyArray_MapIterCheckIndices(mit) < 0) {
goto fail;
}
- if (PyArray_MapIterCheckIndices(mit) < 0) {
+ /*
+ * Alignment information (swapping is never needed, since we buffer),
+ * could also check extra_op is buffered, but it should rarely matter.
+ */
+ int is_aligned = IsUintAligned(self) && IsUintAligned(mit->extra_op);
+ int meth_flags = NpyIter_GetTransferFlags(mit->outer);
+
+ if (mit->extra_op_iter) {
+ int extra_op_flags = NpyIter_GetTransferFlags(mit->extra_op_iter);
+ meth_flags = PyArrayMethod_COMBINED_FLAGS(meth_flags, extra_op_flags);
+ }
+
+ if (mit->subspace_iter != NULL) {
+ int extra_op_flags = NpyIter_GetTransferFlags(mit->subspace_iter);
+ meth_flags = PyArrayMethod_COMBINED_FLAGS(meth_flags, extra_op_flags);
+
+ NPY_ARRAYMETHOD_FLAGS transfer_flags;
+ npy_intp fixed_strides[2];
+
+ /*
+ * Get a dtype transfer function, since there are no
+ * buffers, this is safe.
+ */
+ NpyIter_GetInnerFixedStrideArray(mit->subspace_iter, fixed_strides);
+
+ if (PyArray_GetDTypeTransferFunction(is_aligned,
+ fixed_strides[1], fixed_strides[0],
+ PyArray_DESCR(mit->extra_op), PyArray_DESCR(self),
+ 0, &cast_info, &transfer_flags) != NPY_SUCCEED) {
+ goto fail;
+ }
+ meth_flags = PyArrayMethod_COMBINED_FLAGS(meth_flags, transfer_flags);
+ }
+
+ if (!(meth_flags & NPY_METH_NO_FLOATINGPOINT_ERRORS)) {
+ npy_clear_floatstatus_barrier((char *)mit);
+ }
+
+ /* Can now reset the outer iterator (delayed bufalloc) */
+ if (NpyIter_Reset(mit->outer, NULL) < 0) {
goto fail;
}
@@ -1966,11 +2049,17 @@ array_assign_subscript(PyArrayObject *self, PyObject *ind, PyObject *op)
* Could add a casting check, but apparently most assignments do
* not care about safe casting.
*/
-
- if (mapiter_set(mit) < 0) {
+ if (mapiter_set(mit, &cast_info, meth_flags, is_aligned) < 0) {
goto fail;
}
+ if (!(meth_flags & NPY_METH_NO_FLOATINGPOINT_ERRORS)) {
+ int fpes = npy_get_floatstatus_barrier((char *)mit);
+ if (fpes && PyUFunc_GiveFloatingpointErrors("cast", fpes) < 0) {
+ goto fail;
+ }
+ }
+
Py_DECREF(mit);
goto success;
@@ -1979,6 +2068,8 @@ array_assign_subscript(PyArrayObject *self, PyObject *ind, PyObject *op)
Py_XDECREF((PyObject *)view);
Py_XDECREF((PyObject *)tmp_arr);
Py_XDECREF((PyObject *)mit);
+ NPY_cast_info_xfree(&cast_info);
+
for (i=0; i < index_num; i++) {
Py_XDECREF(indices[i].object);
}
@@ -1987,6 +2078,8 @@ array_assign_subscript(PyArrayObject *self, PyObject *ind, PyObject *op)
success:
Py_XDECREF((PyObject *)view);
Py_XDECREF((PyObject *)tmp_arr);
+ NPY_cast_info_xfree(&cast_info);
+
for (i=0; i < index_num; i++) {
Py_XDECREF(indices[i].object);
}
@@ -2107,7 +2200,7 @@ _nonzero_indices(PyObject *myBool, PyArrayObject **arrays)
/* Reset the map iterator to the beginning */
-NPY_NO_EXPORT void
+NPY_NO_EXPORT int
PyArray_MapIterReset(PyArrayMapIterObject *mit)
{
npy_intp indval;
@@ -2115,12 +2208,16 @@ PyArray_MapIterReset(PyArrayMapIterObject *mit)
int i;
if (mit->size == 0) {
- return;
+ return 0;
}
- NpyIter_Reset(mit->outer, NULL);
+ if (!NpyIter_Reset(mit->outer, NULL)) {
+ return -1;
+ }
if (mit->extra_op_iter) {
- NpyIter_Reset(mit->extra_op_iter, NULL);
+ if (!NpyIter_Reset(mit->extra_op_iter, NULL)) {
+ return -1;
+ }
baseptrs[1] = mit->extra_op_ptrs[0];
}
@@ -2137,14 +2234,16 @@ PyArray_MapIterReset(PyArrayMapIterObject *mit)
mit->dataptr = baseptrs[0];
if (mit->subspace_iter) {
- NpyIter_ResetBasePointers(mit->subspace_iter, baseptrs, NULL);
+ if (!NpyIter_ResetBasePointers(mit->subspace_iter, baseptrs, NULL)) {
+ return -1;
+ }
mit->iter_count = *NpyIter_GetInnerLoopSizePtr(mit->subspace_iter);
}
else {
mit->iter_count = *NpyIter_GetInnerLoopSizePtr(mit->outer);
}
- return;
+ return 0;
}
@@ -2981,7 +3080,8 @@ PyArray_MapIterNew(npy_index_info *indices , int index_num, int index_type,
mit->extra_op_iter = NpyIter_AdvancedNew(1, &extra_op,
NPY_ITER_ZEROSIZE_OK |
NPY_ITER_REFS_OK |
- NPY_ITER_GROWINNER,
+ NPY_ITER_GROWINNER |
+ NPY_ITER_DELAY_BUFALLOC,
NPY_CORDER,
NPY_NO_CASTING,
&extra_op_flags,
@@ -3080,30 +3180,6 @@ PyArray_MapIterNew(npy_index_info *indices , int index_num, int index_type,
mit->subspace_ptrs = NpyIter_GetDataPtrArray(mit->subspace_iter);
mit->subspace_strides = NpyIter_GetInnerStrideArray(mit->subspace_iter);
- NPY_cast_info *cast_info = (NPY_cast_info *)&mit->subspace_castinfo;
- NPY_ARRAYMETHOD_FLAGS flags;
- npy_intp fixed_strides[2];
-
- /*
- * Get a dtype transfer function, since there are no
- * buffers, this is safe.
- */
- NpyIter_GetInnerFixedStrideArray(mit->subspace_iter, fixed_strides);
-
- if (PyArray_GetDTypeTransferFunction(is_aligned,
-#if @isget@
- fixed_strides[0], fixed_strides[1],
- PyArray_DESCR(array), PyArray_DESCR(mit->extra_op),
-#else
- fixed_strides[1], fixed_strides[0],
- PyArray_DESCR(mit->extra_op), PyArray_DESCR(array),
-#endif
- 0,
- &cast_info,
- &flags) != NPY_SUCCEED) {
- goto fail;
- }
-
if (NpyIter_IterationNeedsAPI(mit->subspace_iter)) {
mit->needs_api = 1;
/*
@@ -3255,9 +3331,12 @@ PyArray_MapIterArrayCopyIfOverlap(PyArrayObject * a, PyObject * index,
goto fail;
}
+ if (PyArray_MapIterReset(mit) < 0) {
+ goto fail;
+ }
+
Py_XDECREF(a_copy);
Py_XDECREF(subspace);
- PyArray_MapIterReset(mit);
for (i=0; i < index_num; i++) {
Py_XDECREF(indices[i].object);
@@ -3311,7 +3390,6 @@ arraymapiter_dealloc(PyArrayMapIterObject *mit)
}
if (mit->subspace_iter != NULL) {
NpyIter_Deallocate(mit->subspace_iter);
- NPY_cast_info_xfree((NPY_cast_info *)(&(mit->subspace_castinfo)));
}
if (mit->extra_op_iter != NULL) {
NpyIter_Deallocate(mit->extra_op_iter);
diff --git a/numpy/core/src/multiarray/mapping.h b/numpy/core/src/multiarray/mapping.h
index e929b8b3f..4e5d06238 100644
--- a/numpy/core/src/multiarray/mapping.h
+++ b/numpy/core/src/multiarray/mapping.h
@@ -51,7 +51,7 @@ array_assign_item(PyArrayObject *self, Py_ssize_t i, PyObject *v);
* Prototypes for Mapping calls --- not part of the C-API
* because only useful as part of a getitem call.
*/
-NPY_NO_EXPORT void
+NPY_NO_EXPORT int
PyArray_MapIterReset(PyArrayMapIterObject *mit);
NPY_NO_EXPORT void
diff --git a/numpy/core/tests/test_casting_floatingpoint_errors.py b/numpy/core/tests/test_casting_floatingpoint_errors.py
index 797dc3387..70a59a72b 100644
--- a/numpy/core/tests/test_casting_floatingpoint_errors.py
+++ b/numpy/core/tests/test_casting_floatingpoint_errors.py
@@ -47,17 +47,21 @@ def check_operations(dtype, value):
floating point errors which occurred during those casts.
"""
if dtype.kind != 'i':
+ # These assignments use the stricter setitem logic:
def assignment():
arr = np.empty(3, dtype=dtype)
arr[0] = value
yield assignment
- def fill():
- arr = np.empty(3, dtype=dtype)
- arr.fill(value)
+ # TODO: This constraint is a bug in arr.fill() and should be removed
+ # e.g. by gh-20924
+ if value != 10**100:
+ def fill():
+ arr = np.empty(3, dtype=dtype)
+ arr.fill(value)
- yield fill
+ yield fill
def copyto_scalar():
arr = np.empty(3, dtype=dtype)
@@ -133,3 +137,4 @@ def test_floatingpoint_errors_casting(dtype, value):
with np.errstate(all="raise"):
with pytest.raises(FloatingPointError, match=match):
operation()
+