diff options
| author | Sebastian Berg <sebastian@sipsolutions.net> | 2022-04-21 10:34:54 +0200 |
|---|---|---|
| committer | Sebastian Berg <sebastian@sipsolutions.net> | 2022-06-13 09:36:57 -0700 |
| commit | a5bf28b0167356098e6ed1ad69e3bc232dac7a26 (patch) | |
| tree | 19ed5e437b3e27e460759b601f29c2bd27640335 /numpy | |
| parent | 4c2a3d0030c244e152eb77365c8973dc07621186 (diff) | |
| download | numpy-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.h | 8 | ||||
| -rw-r--r-- | numpy/core/src/multiarray/lowlevel_strided_loops.c.src | 40 | ||||
| -rw-r--r-- | numpy/core/src/multiarray/mapping.c | 156 | ||||
| -rw-r--r-- | numpy/core/src/multiarray/mapping.h | 2 | ||||
| -rw-r--r-- | numpy/core/tests/test_casting_floatingpoint_errors.py | 13 |
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() + |
