diff options
29 files changed, 452 insertions, 148 deletions
diff --git a/doc/release/1.9.0-notes.rst b/doc/release/1.9.0-notes.rst index f999f29b5..2e457ad84 100644 --- a/doc/release/1.9.0-notes.rst +++ b/doc/release/1.9.0-notes.rst @@ -227,10 +227,26 @@ integer/float array that is being casted. Previously the casting was allowed even if the result was truncated. +NDIter +~~~~~~ +When ``NpyIter_RemoveAxis`` is now called, the iterator range will be reset. + +When a multi index is being tracked and an iterator is not buffered, it is +possible to use ``NpyIter_RemoveAxis``. In this case an iterator can shrink +in size. Because the total size of an iterator is limited, the iterator +may be too large before these calls. In this case its size will be set to ``-1`` +and an error issued not at construction time but when removing the multi +index, setting the iterator range, or getting the next function. + +This has no effect on currently working code, but highlights the necessity +of checking for an error return if these conditions can occur. In most +cases the arrays being iterated are as large as the iterator so that such +a problem cannot occur. + + C-API ~~~~~ -None Deprecations ============ diff --git a/doc/source/reference/c-api.iterator.rst b/doc/source/reference/c-api.iterator.rst index 569d8ec56..084fdcbce 100644 --- a/doc/source/reference/c-api.iterator.rst +++ b/doc/source/reference/c-api.iterator.rst @@ -369,7 +369,14 @@ Construction and Destruction Causes the iterator to track a multi-index. This prevents the iterator from coalescing axes to - produce bigger inner loops. + produce bigger inner loops. If the loop is also not buffered + and no index is being tracked (`NpyIter_RemoveAxis` can be called), + then the iterator size can be ``-1`` to indicate that the iterator + is too large. This can happen due to complex broadcasting and + will result in errors being created when the setting the iterator + range, removing the multi index, or getting the next function. + However, it is possible to remove axes again and use the iterator + normally if the size is small enough after removal. .. cvar:: NPY_ITER_EXTERNAL_LOOP @@ -412,8 +419,9 @@ Construction and Destruction Indicates that arrays with a size of zero should be permitted. Since the typical iteration loop does not naturally work with - zero-sized arrays, you must check that the IterSize is non-zero - before entering the iteration loop. + zero-sized arrays, you must check that the IterSize is larger + than zero before entering the iteration loop. + Currently only the operands are checked, not a forced shape. .. cvar:: NPY_ITER_REDUCE_OK @@ -721,7 +729,7 @@ Construction and Destruction **WARNING**: This function may change the internal memory layout of the iterator. Any cached functions or pointers from the iterator - must be retrieved again! + must be retrieved again! The iterator range will be reset as well. Returns ``NPY_SUCCEED`` or ``NPY_FAIL``. @@ -887,7 +895,11 @@ Construction and Destruction .. cfunction:: npy_intp NpyIter_GetIterSize(NpyIter* iter) Returns the number of elements being iterated. This is the product - of all the dimensions in the shape. + of all the dimensions in the shape. When a multi index is being tracked + (and `NpyIter_RemoveAxis` may be called) the size may be ``-1`` to + indicate an iterator is too large. Such an iterator is invalid, but + may become valid after `NpyIter_RemoveAxis` is called. It is not + necessary to check for this case. .. cfunction:: npy_intp NpyIter_GetIterIndex(NpyIter* iter) diff --git a/numpy/core/include/numpy/ndarraytypes.h b/numpy/core/include/numpy/ndarraytypes.h index 27970ff77..cc0206d44 100644 --- a/numpy/core/include/numpy/ndarraytypes.h +++ b/numpy/core/include/numpy/ndarraytypes.h @@ -928,10 +928,11 @@ typedef int (PyArray_FinalizeFunc)(PyArrayObject *, PyObject *); #define PyArray_IS_C_CONTIGUOUS(m) PyArray_CHKFLAGS(m, NPY_ARRAY_C_CONTIGUOUS) #define PyArray_IS_F_CONTIGUOUS(m) PyArray_CHKFLAGS(m, NPY_ARRAY_F_CONTIGUOUS) +/* the variable is used in some places, so always define it */ +#define NPY_BEGIN_THREADS_DEF PyThreadState *_save=NULL; #if NPY_ALLOW_THREADS #define NPY_BEGIN_ALLOW_THREADS Py_BEGIN_ALLOW_THREADS #define NPY_END_ALLOW_THREADS Py_END_ALLOW_THREADS -#define NPY_BEGIN_THREADS_DEF PyThreadState *_save=NULL; #define NPY_BEGIN_THREADS do {_save = PyEval_SaveThread();} while (0); #define NPY_END_THREADS do {if (_save) PyEval_RestoreThread(_save);} while (0); #define NPY_BEGIN_THREADS_THRESHOLDED(loop_size) do { if (loop_size > 500) \ @@ -951,7 +952,6 @@ typedef int (PyArray_FinalizeFunc)(PyArrayObject *, PyObject *); #else #define NPY_BEGIN_ALLOW_THREADS #define NPY_END_ALLOW_THREADS -#define NPY_BEGIN_THREADS_DEF #define NPY_BEGIN_THREADS #define NPY_END_THREADS #define NPY_BEGIN_THREADS_THRESHOLDED(loop_size) diff --git a/numpy/core/src/multiarray/array_assign_array.c b/numpy/core/src/multiarray/array_assign_array.c index ba18684a2..67c98d8b5 100644 --- a/numpy/core/src/multiarray/array_assign_array.c +++ b/numpy/core/src/multiarray/array_assign_array.c @@ -99,9 +99,7 @@ raw_array_assign_array(int ndim, npy_intp *shape, dst_data, dst_strides_it, src_data, src_strides_it); - if (!needs_api) { - NPY_END_THREADS; - } + NPY_END_THREADS; NPY_AUXDATA_FREE(transferdata); @@ -194,9 +192,7 @@ raw_array_wheremasked_assign_array(int ndim, npy_intp *shape, src_data, src_strides_it, wheremask_data, wheremask_strides_it); - if (!needs_api) { - NPY_END_THREADS; - } + NPY_END_THREADS; NPY_AUXDATA_FREE(transferdata); diff --git a/numpy/core/src/multiarray/array_assign_scalar.c b/numpy/core/src/multiarray/array_assign_scalar.c index 93bc4e4b4..7c1b1f16a 100644 --- a/numpy/core/src/multiarray/array_assign_scalar.c +++ b/numpy/core/src/multiarray/array_assign_scalar.c @@ -86,9 +86,7 @@ raw_array_assign_scalar(int ndim, npy_intp *shape, } NPY_RAW_ITER_ONE_NEXT(idim, ndim, coord, shape_it, dst_data, dst_strides_it); - if (!needs_api) { - NPY_END_THREADS; - } + NPY_END_THREADS; NPY_AUXDATA_FREE(transferdata); @@ -165,9 +163,7 @@ raw_array_wheremasked_assign_scalar(int ndim, npy_intp *shape, dst_data, dst_strides_it, wheremask_data, wheremask_strides_it); - if (!needs_api) { - NPY_END_THREADS; - } + NPY_END_THREADS; NPY_AUXDATA_FREE(transferdata); diff --git a/numpy/core/src/multiarray/arraytypes.c.src b/numpy/core/src/multiarray/arraytypes.c.src index 0f75c8aa5..a430e1ee4 100644 --- a/numpy/core/src/multiarray/arraytypes.c.src +++ b/numpy/core/src/multiarray/arraytypes.c.src @@ -3501,15 +3501,22 @@ static int NPY_CLIPMODE clipmode) { npy_intp i, j, k, tmp; + NPY_BEGIN_THREADS_DEF; + + NPY_BEGIN_THREADS; switch(clipmode) { case NPY_RAISE: for (i = 0; i < n_outer; i++) { for (j = 0; j < m_middle; j++) { tmp = indarray[j]; - /* We don't know what axis we're operating on, so don't report it in case of an error. */ - if (check_and_adjust_index(&tmp, nindarray, -1) < 0) + /* + * We don't know what axis we're operating on, + * so don't report it in case of an error. + */ + if (check_and_adjust_index(&tmp, nindarray, -1, _save) < 0) { return 1; + } if (NPY_LIKELY(nelem == 1)) { *dest++ = *(src + tmp); } @@ -3571,6 +3578,8 @@ static int } break; } + + NPY_END_THREADS; return 0; } /**end repeat**/ diff --git a/numpy/core/src/multiarray/common.c b/numpy/core/src/multiarray/common.c index 48d13cd6e..0e8a21394 100644 --- a/numpy/core/src/multiarray/common.c +++ b/numpy/core/src/multiarray/common.c @@ -618,7 +618,7 @@ index2ptr(PyArrayObject *mp, npy_intp i) return NULL; } dim0 = PyArray_DIMS(mp)[0]; - if (check_and_adjust_index(&i, dim0, 0) < 0) + if (check_and_adjust_index(&i, dim0, 0, NULL) < 0) return NULL; if (i == 0) { return PyArray_DATA(mp); diff --git a/numpy/core/src/multiarray/common.h b/numpy/core/src/multiarray/common.h index ff1d4ec77..d64411115 100644 --- a/numpy/core/src/multiarray/common.h +++ b/numpy/core/src/multiarray/common.h @@ -2,9 +2,21 @@ #define _NPY_PRIVATE_COMMON_H_ #include <numpy/npy_common.h> #include <numpy/npy_cpu.h> +#include <numpy/ndarraytypes.h> #define error_converting(x) (((x) == -1) && PyErr_Occurred()) +#ifdef NPY_ALLOW_THREADS +#define NPY_BEGIN_THREADS_NDITER(iter) \ + do { \ + if (!NpyIter_IterationNeedsAPI(iter)) { \ + NPY_BEGIN_THREADS_THRESHOLDED(NpyIter_GetIterSize(iter)); \ + } \ + } while(0) +#else +#define NPY_BEGIN_THREADS_NDITER(iter) +#endif + /* * Recursively examines the object to determine an appropriate dtype * to use for converting to an ndarray. @@ -70,12 +82,17 @@ convert_shape_to_string(npy_intp n, npy_intp *vals, char *ending); * 0 <= *index < max_item, and returns 0. * 'axis' should be the array axis that is being indexed over, if known. If * unknown, use -1. + * If _save is NULL it is assumed the GIL is taken + * If _save is not NULL it is assumed the GIL is not taken and it + * is acquired in the case of an error */ static NPY_INLINE int -check_and_adjust_index(npy_intp *index, npy_intp max_item, int axis) +check_and_adjust_index(npy_intp *index, npy_intp max_item, int axis, + PyThreadState * _save) { /* Check that index is valid, taking into account negative indices */ if (NPY_UNLIKELY((*index < -max_item) || (*index >= max_item))) { + NPY_END_THREADS; /* Try to be as clear as possible about what went wrong. */ if (axis >= 0) { PyErr_Format(PyExc_IndexError, @@ -85,8 +102,7 @@ check_and_adjust_index(npy_intp *index, npy_intp max_item, int axis) } else { PyErr_Format(PyExc_IndexError, "index %"NPY_INTP_FMT" is out of bounds " - "for size %"NPY_INTP_FMT, - *index, max_item); + "for size %"NPY_INTP_FMT, *index, max_item); } return -1; } diff --git a/numpy/core/src/multiarray/ctors.c b/numpy/core/src/multiarray/ctors.c index 5bdd5b552..2ab46d258 100644 --- a/numpy/core/src/multiarray/ctors.c +++ b/numpy/core/src/multiarray/ctors.c @@ -2635,9 +2635,7 @@ PyArray_CopyAsFlat(PyArrayObject *dst, PyArrayObject *src, NPY_ORDER order) } } - if (!needs_api) { - NPY_END_THREADS; - } + NPY_END_THREADS; NPY_AUXDATA_FREE(transferdata); NpyIter_Deallocate(dst_iter); @@ -2845,6 +2843,7 @@ PyArray_Arange(double start, double stop, double step, int type_num) PyArray_ArrFuncs *funcs; PyObject *obj; int ret; + NPY_BEGIN_THREADS_DEF; if (_safe_ceil_to_intp((stop - start)/step, &length)) { PyErr_SetString(PyExc_OverflowError, @@ -2892,7 +2891,9 @@ PyArray_Arange(double start, double stop, double step, int type_num) Py_DECREF(range); return NULL; } + NPY_BEGIN_THREADS_DESCR(PyArray_DESCR(range)); funcs->fill(PyArray_DATA(range), length, range); + NPY_END_THREADS; if (PyErr_Occurred()) { goto fail; } @@ -2989,6 +2990,7 @@ PyArray_ArangeObj(PyObject *start, PyObject *stop, PyObject *step, PyArray_Descr npy_intp length; PyArray_Descr *native = NULL; int swap; + NPY_BEGIN_THREADS_DEF; /* Datetime arange is handled specially */ if ((dtype != NULL && (dtype->type_num == NPY_DATETIME || @@ -3105,7 +3107,9 @@ PyArray_ArangeObj(PyObject *start, PyObject *stop, PyObject *step, PyArray_Descr Py_DECREF(range); goto fail; } + NPY_BEGIN_THREADS_DESCR(PyArray_DESCR(range)); funcs->fill(PyArray_DATA(range), length, range); + NPY_END_THREADS; if (PyErr_Occurred()) { goto fail; } diff --git a/numpy/core/src/multiarray/einsum.c.src b/numpy/core/src/multiarray/einsum.c.src index ebd381b06..b3148f573 100644 --- a/numpy/core/src/multiarray/einsum.c.src +++ b/numpy/core/src/multiarray/einsum.c.src @@ -22,6 +22,7 @@ #include <ctype.h> #include "convert.h" +#include "common.h" #ifdef NPY_HAVE_SSE_INTRINSICS #define EINSUM_USE_SSE1 1 @@ -2969,7 +2970,6 @@ PyArray_EinsteinSum(char *subscripts, npy_intp nop, char **dataptr; npy_intp *stride; npy_intp *countptr; - int needs_api = NpyIter_IterationNeedsAPI(iter); NPY_BEGIN_THREADS_DEF; iternext = NpyIter_GetIterNext(iter, NULL); @@ -2982,19 +2982,15 @@ PyArray_EinsteinSum(char *subscripts, npy_intp nop, stride = NpyIter_GetInnerStrideArray(iter); countptr = NpyIter_GetInnerLoopSizePtr(iter); - if (!needs_api) { - NPY_BEGIN_THREADS; - } + NPY_BEGIN_THREADS_NDITER(iter); NPY_EINSUM_DBG_PRINT("Einsum loop\n"); do { sop(nop, dataptr, stride, *countptr); } while(iternext(iter)); - if (!needs_api) { - NPY_END_THREADS; - } + NPY_END_THREADS; /* If the API was needed, it may have thrown an error */ - if (needs_api && PyErr_Occurred()) { + if (NpyIter_IterationNeedsAPI(iter) && PyErr_Occurred()) { Py_DECREF(ret); ret = NULL; } diff --git a/numpy/core/src/multiarray/item_selection.c b/numpy/core/src/multiarray/item_selection.c index 5294cebd0..71c17c9c9 100644 --- a/numpy/core/src/multiarray/item_selection.c +++ b/numpy/core/src/multiarray/item_selection.c @@ -129,12 +129,15 @@ PyArray_TakeFrom(PyArrayObject *self0, PyObject *indices0, int axis, func = PyArray_DESCR(self)->f->fasttake; if (func == NULL) { + NPY_BEGIN_THREADS_DEF; + NPY_BEGIN_THREADS_DESCR(PyArray_DESCR(self)); switch(clipmode) { case NPY_RAISE: for (i = 0; i < n; i++) { for (j = 0; j < m; j++) { tmp = ((npy_intp *)(PyArray_DATA(indices)))[j]; - if (check_and_adjust_index(&tmp, max_item, axis) < 0) { + if (check_and_adjust_index(&tmp, max_item, axis, + _save) < 0) { goto fail; } tmp_src = src + tmp * chunk; @@ -216,8 +219,10 @@ PyArray_TakeFrom(PyArrayObject *self0, PyObject *indices0, int axis, } break; } + NPY_END_THREADS; } else { + /* no gil release, need it for error reporting */ err = func(dest, src, (npy_intp *)(PyArray_DATA(indices)), max_item, n, m, nelem, clipmode); if (err) { @@ -300,7 +305,7 @@ PyArray_PutTo(PyArrayObject *self, PyObject* values0, PyObject *indices0, for (i = 0; i < ni; i++) { src = PyArray_BYTES(values) + chunk*(i % nv); tmp = ((npy_intp *)(PyArray_DATA(indices)))[i]; - if (check_and_adjust_index(&tmp, max_item, 0) < 0) { + if (check_and_adjust_index(&tmp, max_item, 0, NULL) < 0) { goto fail; } PyArray_Item_INCREF(src, PyArray_DESCR(self)); @@ -350,7 +355,7 @@ PyArray_PutTo(PyArrayObject *self, PyObject* values0, PyObject *indices0, for (i = 0; i < ni; i++) { src = PyArray_BYTES(values) + chunk * (i % nv); tmp = ((npy_intp *)(PyArray_DATA(indices)))[i]; - if (check_and_adjust_index(&tmp, max_item, 0) < 0) { + if (check_and_adjust_index(&tmp, max_item, 0, NULL) < 0) { goto fail; } memmove(dest + tmp * chunk, src, chunk); @@ -2424,10 +2429,7 @@ PyArray_Nonzero(PyArrayObject *self) PyObject *ret_tuple; npy_intp ret_dims[2]; PyArray_NonzeroFunc *nonzero = PyArray_DESCR(self)->f->nonzero; - char *data; - npy_intp stride, count; npy_intp nonzero_count; - npy_intp *multi_index; NpyIter *iter; NpyIter_IterNextFunc *iternext; @@ -2454,23 +2456,51 @@ PyArray_Nonzero(PyArrayObject *self) /* If it's a one-dimensional result, don't use an iterator */ if (ndim <= 1) { - npy_intp j; + npy_intp * multi_index = (npy_intp *)PyArray_DATA(ret); + char * data = PyArray_BYTES(self); + npy_intp stride = (ndim == 0) ? 0 : PyArray_STRIDE(self, 0); + npy_intp count = (ndim == 0) ? 1 : PyArray_DIM(self, 0); + NPY_BEGIN_THREADS_DEF; - multi_index = (npy_intp *)PyArray_DATA(ret); - data = PyArray_BYTES(self); - stride = (ndim == 0) ? 0 : PyArray_STRIDE(self, 0); - count = (ndim == 0) ? 1 : PyArray_DIM(self, 0); + /* nothing to do */ + if (nonzero_count == 0) { + goto finish; + } + + NPY_BEGIN_THREADS_THRESHOLDED(count); + /* avoid function call for bool */ if (PyArray_ISBOOL(self)) { - /* avoid function call for bool */ - for (j = 0; j < count; ++j) { - if (*data != 0) { - *multi_index++ = j; + /* + * use fast memchr variant for sparse data, see gh-4370 + * the fast bool count is followed by this sparse path is faster + * than combining the two loops, even for larger arrays + */ + if (((double)nonzero_count / count) <= 0.1) { + npy_intp subsize; + npy_intp j = 0; + while (1) { + npy_memchr(data + j * stride, 0, stride, count - j, + &subsize, 1); + j += subsize; + if (j >= count) { + break; + } + *multi_index++ = j++; + } + } + else { + npy_intp j; + for (j = 0; j < count; ++j) { + if (*data != 0) { + *multi_index++ = j; + } + data += stride; } - data += stride; } } else { + npy_intp j; for (j = 0; j < count; ++j) { if (nonzero(data, self)) { *multi_index++ = j; @@ -2479,6 +2509,8 @@ PyArray_Nonzero(PyArrayObject *self) } } + NPY_END_THREADS; + goto finish; } @@ -2498,6 +2530,8 @@ PyArray_Nonzero(PyArrayObject *self) } if (NpyIter_GetIterSize(iter) != 0) { + npy_intp * multi_index; + NPY_BEGIN_THREADS_DEF; /* Get the pointers for inner loop iteration */ iternext = NpyIter_GetIterNext(iter, NULL); if (iternext == NULL) { @@ -2511,6 +2545,9 @@ PyArray_Nonzero(PyArrayObject *self) Py_DECREF(ret); return NULL; } + + NPY_BEGIN_THREADS_NDITER(iter); + dataptr = NpyIter_GetDataPtrArray(iter); multi_index = (npy_intp *)PyArray_DATA(ret); @@ -2533,6 +2570,8 @@ PyArray_Nonzero(PyArrayObject *self) } } while(iternext(iter)); } + + NPY_END_THREADS; } NpyIter_Deallocate(iter); @@ -2558,7 +2597,7 @@ finish: else { for (i = 0; i < ndim; ++i) { PyArrayObject *view; - stride = ndim*NPY_SIZEOF_INTP; + npy_intp stride = ndim * NPY_SIZEOF_INTP; view = (PyArrayObject *)PyArray_New(Py_TYPE(self), 1, &nonzero_count, @@ -2601,7 +2640,7 @@ PyArray_MultiIndexGetItem(PyArrayObject *self, npy_intp *multi_index) npy_intp shapevalue = shape[idim]; npy_intp ind = multi_index[idim]; - if (check_and_adjust_index(&ind, shapevalue, idim) < 0) { + if (check_and_adjust_index(&ind, shapevalue, idim, NULL) < 0) { return NULL; } data += ind * strides[idim]; @@ -2630,7 +2669,7 @@ PyArray_MultiIndexSetItem(PyArrayObject *self, npy_intp *multi_index, npy_intp shapevalue = shape[idim]; npy_intp ind = multi_index[idim]; - if (check_and_adjust_index(&ind, shapevalue, idim) < 0) { + if (check_and_adjust_index(&ind, shapevalue, idim, NULL) < 0) { return -1; } data += ind * strides[idim]; diff --git a/numpy/core/src/multiarray/iterators.c b/numpy/core/src/multiarray/iterators.c index e04ac3bec..81c6b2a8e 100644 --- a/numpy/core/src/multiarray/iterators.c +++ b/numpy/core/src/multiarray/iterators.c @@ -71,7 +71,7 @@ parse_index_entry(PyObject *op, npy_intp *step_size, *n_steps = SINGLE_INDEX; *step_size = 0; if (check_index) { - if (check_and_adjust_index(&i, max, axis) < 0) { + if (check_and_adjust_index(&i, max, axis, NULL) < 0) { goto fail; } } @@ -668,7 +668,7 @@ iter_subscript_int(PyArrayIterObject *self, PyArrayObject *ind) itemsize = PyArray_DESCR(self->ao)->elsize; if (PyArray_NDIM(ind) == 0) { num = *((npy_intp *)PyArray_DATA(ind)); - if (check_and_adjust_index(&num, self->size, -1) < 0) { + if (check_and_adjust_index(&num, self->size, -1, NULL) < 0) { PyArray_ITER_RESET(self); return NULL; } @@ -702,7 +702,7 @@ iter_subscript_int(PyArrayIterObject *self, PyArrayObject *ind) swap = (PyArray_ISNOTSWAPPED(ret) != PyArray_ISNOTSWAPPED(self->ao)); while (counter--) { num = *((npy_intp *)(ind_it->dataptr)); - if (check_and_adjust_index(&num, self->size, -1) < 0) { + if (check_and_adjust_index(&num, self->size, -1, NULL) < 0) { Py_DECREF(ind_it); Py_DECREF(ret); PyArray_ITER_RESET(self); @@ -926,7 +926,7 @@ iter_ass_sub_int(PyArrayIterObject *self, PyArrayObject *ind, copyswap = PyArray_DESCR(self->ao)->f->copyswap; if (PyArray_NDIM(ind) == 0) { num = *((npy_intp *)PyArray_DATA(ind)); - if (check_and_adjust_index(&num, self->size, -1) < 0) { + if (check_and_adjust_index(&num, self->size, -1, NULL) < 0) { return -1; } PyArray_ITER_GOTO1D(self, num); @@ -940,7 +940,7 @@ iter_ass_sub_int(PyArrayIterObject *self, PyArrayObject *ind, counter = ind_it->size; while (counter--) { num = *((npy_intp *)(ind_it->dataptr)); - if (check_and_adjust_index(&num, self->size, -1) < 0) { + if (check_and_adjust_index(&num, self->size, -1, NULL) < 0) { Py_DECREF(ind_it); return -1; } @@ -1017,7 +1017,7 @@ iter_ass_subscript(PyArrayIterObject *self, PyObject *ind, PyObject *val) PyErr_Clear(); } else { - if (check_and_adjust_index(&start, self->size, -1) < 0) { + if (check_and_adjust_index(&start, self->size, -1, NULL) < 0) { goto finish; } retval = 0; diff --git a/numpy/core/src/multiarray/lowlevel_strided_loops.c.src b/numpy/core/src/multiarray/lowlevel_strided_loops.c.src index 042aa2a62..bd69c5ca9 100644 --- a/numpy/core/src/multiarray/lowlevel_strided_loops.c.src +++ b/numpy/core/src/multiarray/lowlevel_strided_loops.c.src @@ -1396,7 +1396,7 @@ mapiter_trivial_@name@(PyArrayObject *self, PyArrayObject *ind, /* Check the indices beforehand */ while (itersize--) { indval = *((npy_intp*)ind_ptr); - if (check_and_adjust_index(&indval, fancy_dim, 1) < 0 ) { + if (check_and_adjust_index(&indval, fancy_dim, 1, NULL) < 0 ) { return -1; } ind_ptr += ind_stride; @@ -1426,7 +1426,7 @@ mapiter_trivial_@name@(PyArrayObject *self, PyArrayObject *ind, while (itersize--) { indval = *((npy_intp*)ind_ptr); #if @isget@ - if (check_and_adjust_index(&indval, fancy_dim, 1) < 0 ) { + if (check_and_adjust_index(&indval, fancy_dim, 1, NULL) < 0 ) { return -1; } #else @@ -1560,7 +1560,7 @@ mapiter_@name@(PyArrayMapIterObject *mit) #if @isget@ && @one_iter@ if (check_and_adjust_index(&indval, fancy_dims[i], - iteraxis) < 0 ) { + iteraxis, NULL) < 0 ) { return -1; } #else @@ -1662,7 +1662,7 @@ mapiter_@name@(PyArrayMapIterObject *mit) #if @isget@ && @one_iter@ if (check_and_adjust_index(&indval, fancy_dims[i], - iteraxis) < 0 ) { + iteraxis, NULL) < 0 ) { NPY_AUXDATA_FREE(transferdata); return -1; } diff --git a/numpy/core/src/multiarray/mapping.c b/numpy/core/src/multiarray/mapping.c index 6d7e4ffe3..cafec7689 100644 --- a/numpy/core/src/multiarray/mapping.c +++ b/numpy/core/src/multiarray/mapping.c @@ -799,7 +799,7 @@ get_item_pointer(PyArrayObject *self, char **ptr, *ptr = PyArray_BYTES(self); for (i=0; i < index_num; i++) { if ((check_and_adjust_index(&(indices[i].value), - PyArray_DIMS(self)[i], i)) < 0) { + PyArray_DIMS(self)[i], i, NULL)) < 0) { return -1; } *ptr += PyArray_STRIDE(self, i) * indices[i].value; @@ -842,7 +842,8 @@ get_view_from_index(PyArrayObject *self, PyArrayObject **view, switch (indices[i].type) { case HAS_INTEGER: if ((check_and_adjust_index(&indices[i].value, - PyArray_DIMS(self)[orig_dim], orig_dim)) < 0) { + PyArray_DIMS(self)[orig_dim], orig_dim, + NULL)) < 0) { return -1; } data_ptr += PyArray_STRIDE(self, orig_dim) * indices[i].value; @@ -2366,7 +2367,7 @@ PyArray_MapIterCheckIndices(PyArrayMapIterObject *mit) while (itersize--) { indval = *((npy_intp*)data); if (check_and_adjust_index(&indval, - outer_dim, outer_axis) < 0) { + outer_dim, outer_axis, NULL) < 0) { return -1; } data += stride; @@ -2400,7 +2401,7 @@ PyArray_MapIterCheckIndices(PyArrayMapIterObject *mit) while (itersize--) { indval = *((npy_intp*)*iterptr); if (check_and_adjust_index(&indval, - outer_dim, outer_axis) < 0) { + outer_dim, outer_axis, NULL) < 0) { Py_DECREF(intp_type); NpyIter_Deallocate(op_iter); diff --git a/numpy/core/src/multiarray/methods.c b/numpy/core/src/multiarray/methods.c index 91f639650..b68745085 100644 --- a/numpy/core/src/multiarray/methods.c +++ b/numpy/core/src/multiarray/methods.c @@ -647,7 +647,7 @@ array_toscalar(PyArrayObject *self, PyObject *args) return NULL; } - if (check_and_adjust_index(&value, size, -1) < 0) { + if (check_and_adjust_index(&value, size, -1, NULL) < 0) { return NULL; } @@ -724,7 +724,7 @@ array_setscalar(PyArrayObject *self, PyObject *args) return NULL; } - if (check_and_adjust_index(&value, size, -1) < 0) { + if (check_and_adjust_index(&value, size, -1, NULL) < 0) { return NULL; } diff --git a/numpy/core/src/multiarray/multiarray_tests.c.src b/numpy/core/src/multiarray/multiarray_tests.c.src index 6285d0d82..bd0366bd5 100644 --- a/numpy/core/src/multiarray/multiarray_tests.c.src +++ b/numpy/core/src/multiarray/multiarray_tests.c.src @@ -720,6 +720,109 @@ array_indexing(PyObject *NPY_UNUSED(self), PyObject *args) } +/* + * Test nditer of too large arrays using remove axis, etc. + */ +static PyObject * +test_nditer_too_large(PyObject *NPY_UNUSED(self), PyObject *args) { + NpyIter *iter; + PyObject *array_tuple, *arr; + PyArrayObject *arrays[NPY_MAXARGS]; + npy_uint32 op_flags[NPY_MAXARGS]; + Py_ssize_t nop; + int i, axis, mode; + + npy_intp index[NPY_MAXARGS] = {0}; + char *msg; + + if (!PyArg_ParseTuple(args, "Oii", &array_tuple, &axis, &mode)) { + return NULL; + } + + if (!PyTuple_CheckExact(array_tuple)) { + PyErr_SetString(PyExc_ValueError, "tuple required as first argument"); + return NULL; + } + nop = PyTuple_Size(array_tuple); + if (nop > NPY_MAXARGS) { + PyErr_SetString(PyExc_ValueError, "tuple must be smaller then maxargs"); + return NULL; + } + + for (i=0; i < nop; i++) { + arr = PyTuple_GET_ITEM(array_tuple, i); + if (!PyArray_CheckExact(arr)) { + PyErr_SetString(PyExc_ValueError, "require base class ndarray"); + return NULL; + } + arrays[i] = (PyArrayObject *)arr; + op_flags[i] = NPY_ITER_READONLY; + } + + iter = NpyIter_MultiNew(nop, arrays, NPY_ITER_MULTI_INDEX | NPY_ITER_RANGED, + NPY_KEEPORDER, NPY_NO_CASTING, op_flags, NULL); + + if (iter == NULL) { + return NULL; + } + + /* Remove an axis (negative, do not remove any) */ + if (axis >= 0) { + if (!NpyIter_RemoveAxis(iter, axis)) { + goto fail; + } + } + + switch (mode) { + /* Test IterNext getting */ + case 0: + if (NpyIter_GetIterNext(iter, NULL) == NULL) { + goto fail; + } + break; + case 1: + if (NpyIter_GetIterNext(iter, &msg) == NULL) { + PyErr_SetString(PyExc_ValueError, msg); + goto fail; + } + break; + /* Test Multi Index removal */ + case 2: + if (!NpyIter_RemoveMultiIndex(iter)) { + goto fail; + } + break; + /* Test GotoMultiIndex (just 0 hardcoded) */ + case 3: + if (!NpyIter_GotoMultiIndex(iter, index)) { + goto fail; + } + break; + /* Test setting iterrange (hardcoded range of 0, 1) */ + case 4: + if (!NpyIter_ResetToIterIndexRange(iter, 0, 1, NULL)) { + goto fail; + } + break; + case 5: + if (!NpyIter_ResetToIterIndexRange(iter, 0, 1, &msg)) { + PyErr_SetString(PyExc_ValueError, msg); + goto fail; + } + break; + /* Do nothing */ + default: + break; + } + + NpyIter_Deallocate(iter); + Py_RETURN_NONE; + fail: + NpyIter_Deallocate(iter); + return NULL; +} + + static PyMethodDef Multiarray_TestsMethods[] = { {"test_neighborhood_iterator", test_neighborhood_iterator, @@ -747,6 +850,9 @@ static PyMethodDef Multiarray_TestsMethods[] = { {"array_indexing", array_indexing, METH_VARARGS, NULL}, + {"test_nditer_too_large", + test_nditer_too_large, + METH_VARARGS, NULL}, {NULL, NULL, 0, NULL} /* Sentinel */ }; diff --git a/numpy/core/src/multiarray/multiarraymodule.c b/numpy/core/src/multiarray/multiarraymodule.c index cfaf7799a..2b8fedf9a 100644 --- a/numpy/core/src/multiarray/multiarraymodule.c +++ b/numpy/core/src/multiarray/multiarraymodule.c @@ -2774,9 +2774,7 @@ PyArray_Where(PyObject *condition, PyObject *x, PyObject *y) needs_api = NpyIter_IterationNeedsAPI(iter); - if (!needs_api) { - NPY_BEGIN_THREADS_THRESHOLDED(NpyIter_GetIterSize(iter)); - } + NPY_BEGIN_THREADS_NDITER(iter); if (NpyIter_GetIterSize(iter) != 0) { NpyIter_IterNextFunc *iternext = NpyIter_GetIterNext(iter, NULL); @@ -2836,9 +2834,7 @@ PyArray_Where(PyObject *condition, PyObject *x, PyObject *y) } while (iternext(iter)); } - if (!needs_api) { - NPY_END_THREADS; - } + NPY_END_THREADS; /* Get the result from the iterator object array */ ret = (PyObject*)NpyIter_GetOperandArray(iter)[0]; diff --git a/numpy/core/src/multiarray/nditer_api.c b/numpy/core/src/multiarray/nditer_api.c index d2c956426..ca407b1c1 100644 --- a/numpy/core/src/multiarray/nditer_api.c +++ b/numpy/core/src/multiarray/nditer_api.c @@ -14,6 +14,7 @@ /* Indicate that this .c file is allowed to include the header */ #define NPY_ITERATOR_IMPLEMENTATION_CODE #include "nditer_impl.h" +#include "scalarmathmodule.h" /* Internal helper functions private to this file */ static npy_intp @@ -127,13 +128,23 @@ NpyIter_RemoveAxis(NpyIter *iter, int axis) perm[idim] = p; } - /* Adjust the iteration size */ - NIT_ITERSIZE(iter) /= NAD_SHAPE(axisdata_del); - /* Shift all the axisdata structures by one */ axisdata = NIT_INDEX_AXISDATA(axisdata_del, 1); memmove(axisdata_del, axisdata, (ndim-1-xdim)*sizeof_axisdata); + /* Adjust the iteration size and reset iterend */ + NIT_ITERSIZE(iter) = 1; + axisdata = NIT_AXISDATA(iter); + for (idim = 0; idim < ndim-1; ++idim) { + if (npy_mul_with_overflow_intp(&NIT_ITERSIZE(iter), + NIT_ITERSIZE(iter), NAD_SHAPE(axisdata))) { + NIT_ITERSIZE(iter) = -1; + break; + } + NIT_ADVANCE_AXISDATA(axisdata, 1); + } + NIT_ITEREND(iter) = NIT_ITERSIZE(iter); + /* Shrink the iterator */ NIT_NDIM(iter) = ndim - 1; /* If it is now 0-d fill the singleton dimension */ @@ -166,6 +177,11 @@ NpyIter_RemoveMultiIndex(NpyIter *iter) itflags = NIT_ITFLAGS(iter); if (itflags&NPY_ITFLAG_HASMULTIINDEX) { + if (NIT_ITERSIZE(iter) < 0) { + PyErr_SetString(PyExc_ValueError, "iterator is too large"); + return NPY_FAIL; + } + NIT_ITFLAGS(iter) = itflags & ~NPY_ITFLAG_HASMULTIINDEX; npyiter_coalesce_axes(iter); } @@ -349,6 +365,15 @@ NpyIter_ResetToIterIndexRange(NpyIter *iter, } if (istart < 0 || iend > NIT_ITERSIZE(iter)) { + if (NIT_ITERSIZE(iter) < 0) { + if (errmsg == NULL) { + PyErr_SetString(PyExc_ValueError, "iterator is too large"); + } + else { + *errmsg = "iterator is too large"; + } + return NPY_FAIL; + } if (errmsg == NULL) { PyErr_Format(PyExc_ValueError, "Out-of-bounds range [%d, %d) passed to " @@ -454,6 +479,10 @@ NpyIter_GotoMultiIndex(NpyIter *iter, npy_intp *multi_index) } if (iterindex < NIT_ITERSTART(iter) || iterindex >= NIT_ITEREND(iter)) { + if (NIT_ITERSIZE(iter) < 0) { + PyErr_SetString(PyExc_ValueError, "iterator is too large"); + return NPY_FAIL; + } PyErr_SetString(PyExc_IndexError, "Iterator GotoMultiIndex called with a multi-index outside the " "restricted iteration range"); @@ -574,6 +603,10 @@ NpyIter_GotoIterIndex(NpyIter *iter, npy_intp iterindex) } if (iterindex < NIT_ITERSTART(iter) || iterindex >= NIT_ITEREND(iter)) { + if (NIT_ITERSIZE(iter) < 0) { + PyErr_SetString(PyExc_ValueError, "iterator is too large"); + return NPY_FAIL; + } PyErr_SetString(PyExc_IndexError, "Iterator GotoIterIndex called with an iterindex outside the " "iteration range."); diff --git a/numpy/core/src/multiarray/nditer_constr.c b/numpy/core/src/multiarray/nditer_constr.c index 47523863e..41f0b97c4 100644 --- a/numpy/core/src/multiarray/nditer_constr.c +++ b/numpy/core/src/multiarray/nditer_constr.c @@ -1656,8 +1656,21 @@ npyiter_fill_axisdata(NpyIter *iter, npy_uint32 flags, npyiter_opitflags *op_itf for (idim = 0; idim < ndim; ++idim) { if (npy_mul_with_overflow_intp(&NIT_ITERSIZE(iter), NIT_ITERSIZE(iter), broadcast_shape[idim])) { - PyErr_SetString(PyExc_ValueError, "iterator is too large"); - return 0; + if ((itflags & NPY_ITFLAG_HASMULTIINDEX) && + !(itflags & NPY_ITFLAG_HASINDEX) && + !(itflags & NPY_ITFLAG_BUFFER)) { + /* + * If RemoveAxis may be called, the size check is delayed + * until either the multi index is removed, or GetIterNext + * is called. + */ + NIT_ITERSIZE(iter) = -1; + break; + } + else { + PyErr_SetString(PyExc_ValueError, "iterator is too large"); + return 0; + } } } /* The range defaults to everything */ diff --git a/numpy/core/src/multiarray/nditer_pywrap.c b/numpy/core/src/multiarray/nditer_pywrap.c index 339a713cd..1ca5de8d6 100644 --- a/numpy/core/src/multiarray/nditer_pywrap.c +++ b/numpy/core/src/multiarray/nditer_pywrap.c @@ -37,12 +37,16 @@ struct NewNpyArrayIterObject_tag { char writeflags[NPY_MAXARGS]; }; -static void npyiter_cache_values(NewNpyArrayIterObject *self) +static int npyiter_cache_values(NewNpyArrayIterObject *self) { NpyIter *iter = self->iter; /* iternext and get_multi_index functions */ self->iternext = NpyIter_GetIterNext(iter, NULL); + if (self->iternext == NULL) { + return -1; + } + if (NpyIter_HasMultiIndex(iter) && !NpyIter_HasDelayedBufAlloc(iter)) { self->get_multi_index = NpyIter_GetGetMultiIndex(iter, NULL); } @@ -67,6 +71,7 @@ static void npyiter_cache_values(NewNpyArrayIterObject *self) /* The read/write settings */ NpyIter_GetReadFlags(iter, self->readflags); NpyIter_GetWriteFlags(iter, self->writeflags); + return 0; } static PyObject * @@ -803,7 +808,9 @@ npyiter_init(NewNpyArrayIterObject *self, PyObject *args, PyObject *kwds) } /* Cache some values for the member functions to use */ - npyiter_cache_values(self); + if (npyiter_cache_values(self) < 0) { + goto fail; + } if (NpyIter_GetIterSize(self->iter) == 0) { self->started = 1; @@ -1068,7 +1075,10 @@ NpyIter_NestedIters(PyObject *NPY_UNUSED(self), } /* Cache some values for the member functions to use */ - npyiter_cache_values(iter); + if (npyiter_cache_values(iter) < 0) { + Py_DECREF(ret); + goto fail; + } if (NpyIter_GetIterSize(iter->iter) == 0) { iter->started = 1; @@ -1242,7 +1252,10 @@ npyiter_copy(NewNpyArrayIterObject *self) } /* Cache some values for the member functions to use */ - npyiter_cache_values(iter); + if (npyiter_cache_values(iter) < 0) { + Py_DECREF(iter); + return NULL; + } iter->started = self->started; iter->finished = self->finished; @@ -1287,7 +1300,9 @@ npyiter_remove_axis(NewNpyArrayIterObject *self, PyObject *args) return NULL; } /* RemoveAxis invalidates cached values */ - npyiter_cache_values(self); + if (npyiter_cache_values(self) < 0) { + return NULL; + } /* RemoveAxis also resets the iterator */ if (NpyIter_GetIterSize(self->iter) == 0) { self->started = 1; diff --git a/numpy/core/src/multiarray/nditer_templ.c.src b/numpy/core/src/multiarray/nditer_templ.c.src index 59aae244b..8976b132e 100644 --- a/numpy/core/src/multiarray/nditer_templ.c.src +++ b/numpy/core/src/multiarray/nditer_templ.c.src @@ -347,6 +347,16 @@ NpyIter_GetIterNext(NpyIter *iter, char **errmsg) int ndim = NIT_NDIM(iter); int nop = NIT_NOP(iter); + if (NIT_ITERSIZE(iter) < 0) { + if (errmsg == NULL) { + PyErr_SetString(PyExc_ValueError, "iterator is too large"); + } + else { + *errmsg = "iterator is too large"; + } + return NULL; + } + /* * When there is just one iteration and buffering is disabled * the iternext function is very simple. diff --git a/numpy/core/src/umath/ufunc_object.c b/numpy/core/src/umath/ufunc_object.c index dcf5c16b5..233855460 100644 --- a/numpy/core/src/umath/ufunc_object.c +++ b/numpy/core/src/umath/ufunc_object.c @@ -1153,9 +1153,7 @@ trivial_two_operand_loop(PyArrayObject **op, innerloop(data, count, stride, innerloopdata); - if (!needs_api) { - NPY_END_THREADS; - } + NPY_END_THREADS; } static void @@ -1186,9 +1184,7 @@ trivial_three_operand_loop(PyArrayObject **op, innerloop(data, count, stride, innerloopdata); - if (!needs_api) { - NPY_END_THREADS; - } + NPY_END_THREADS; } /* @@ -1270,7 +1266,6 @@ iterator_loop(PyUFuncObject *ufunc, npy_uint32 op_flags[NPY_MAXARGS]; NpyIter *iter; char *baseptrs[NPY_MAXARGS]; - int needs_api; NpyIter_IterNextFunc *iternext; char **dataptr; @@ -1325,8 +1320,6 @@ iterator_loop(PyUFuncObject *ufunc, return -1; } - needs_api = NpyIter_IterationNeedsAPI(iter); - /* Copy any allocated outputs */ op_it = NpyIter_GetOperandArray(iter); for (i = nin; i < nop; ++i) { @@ -1370,9 +1363,7 @@ iterator_loop(PyUFuncObject *ufunc, stride = NpyIter_GetInnerStrideArray(iter); count_ptr = NpyIter_GetInnerLoopSizePtr(iter); - if (!needs_api) { - NPY_BEGIN_THREADS_THRESHOLDED(NpyIter_GetIterSize(iter)); - } + NPY_BEGIN_THREADS_NDITER(iter); /* Execute the loop */ do { @@ -1380,9 +1371,7 @@ iterator_loop(PyUFuncObject *ufunc, innerloop(dataptr, count_ptr, stride, innerloopdata); } while (iternext(iter)); - if (!needs_api) { - NPY_END_THREADS; - } + NPY_END_THREADS; } NpyIter_Deallocate(iter); @@ -1579,8 +1568,6 @@ execute_fancy_ufunc_loop(PyUFuncObject *ufunc, PyArrayObject **op_it; npy_uint32 iter_flags; - NPY_BEGIN_THREADS_DEF; - if (wheremask != NULL) { if (nop + 1 > NPY_MAXARGS) { PyErr_SetString(PyExc_ValueError, @@ -1669,6 +1656,7 @@ execute_fancy_ufunc_loop(PyUFuncObject *ufunc, NpyAuxData *innerloopdata; npy_intp fixed_strides[2*NPY_MAXARGS]; PyArray_Descr **iter_dtypes; + NPY_BEGIN_THREADS_DEF; /* Validate that the prepare_ufunc_output didn't mess with pointers */ for (i = nin; i < nop; ++i) { @@ -1708,9 +1696,7 @@ execute_fancy_ufunc_loop(PyUFuncObject *ufunc, strides = NpyIter_GetInnerStrideArray(iter); countptr = NpyIter_GetInnerLoopSizePtr(iter); - if (!needs_api) { - NPY_BEGIN_THREADS; - } + NPY_BEGIN_THREADS_NDITER(iter); NPY_UF_DBG_PRINT("Actual inner loop:\n"); /* Execute the loop */ @@ -1721,9 +1707,7 @@ execute_fancy_ufunc_loop(PyUFuncObject *ufunc, *countptr, innerloopdata); } while (iternext(iter)); - if (!needs_api) { - NPY_END_THREADS; - } + NPY_END_THREADS; NPY_AUXDATA_FREE(innerloopdata); } @@ -2167,6 +2151,11 @@ PyUFunc_GeneralizedFunction(PyUFuncObject *ufunc, */ inner_strides = (npy_intp *)PyArray_malloc( NPY_SIZEOF_INTP * (nop+core_dim_ixs_size)); + if (inner_strides == NULL) { + PyErr_NoMemory(); + retval = -1; + goto fail; + } /* Copy the strides after the first nop */ idim = nop; for (i = 0; i < nop; ++i) { @@ -2273,11 +2262,13 @@ PyUFunc_GeneralizedFunction(PyUFuncObject *ufunc, PyErr_Format(PyExc_ValueError, "ufunc %s ", ufunc_name); + retval = -1; goto fail; default: PyErr_Format(PyExc_ValueError, "ufunc %s has an invalid identity for reduction", ufunc_name); + retval = -1; goto fail; } } @@ -2742,9 +2733,7 @@ reduce_loop(NpyIter *iter, char **dataptrs, npy_intp *strides, return -1; } - if (!needs_api) { - NPY_BEGIN_THREADS_THRESHOLDED(NpyIter_GetIterSize(iter)); - } + NPY_BEGIN_THREADS_NDITER(iter); if (skip_first_count > 0) { do { @@ -2797,9 +2786,7 @@ reduce_loop(NpyIter *iter, char **dataptrs, npy_intp *strides, } while (iternext(iter)); finish_loop: - if (!needs_api) { - NPY_END_THREADS; - } + NPY_END_THREADS; return (needs_api && PyErr_Occurred()) ? -1 : 0; } @@ -3126,9 +3113,7 @@ PyUFunc_Accumulate(PyUFuncObject *ufunc, PyArrayObject *arr, PyArrayObject *out, needs_api = NpyIter_IterationNeedsAPI(iter); - if (!needs_api) { - NPY_BEGIN_THREADS_THRESHOLDED(NpyIter_GetIterSize(iter)); - } + NPY_BEGIN_THREADS_NDITER(iter); do { @@ -3158,9 +3143,7 @@ PyUFunc_Accumulate(PyUFuncObject *ufunc, PyArrayObject *arr, PyArrayObject *out, } } while (iternext(iter)); - if (!needs_api) { - NPY_END_THREADS; - } + NPY_END_THREADS; } else if (iter == NULL) { char *dataptr_copy[3]; @@ -3221,9 +3204,7 @@ PyUFunc_Accumulate(PyUFuncObject *ufunc, PyArrayObject *arr, PyArrayObject *out, innerloop(dataptr_copy, &count, stride_copy, innerloopdata); - if (!needs_api) { - NPY_END_THREADS; - } + NPY_END_THREADS; } } @@ -3274,7 +3255,7 @@ PyUFunc_Reduceat(PyUFuncObject *ufunc, PyArrayObject *arr, PyArrayObject *ind, op_axes_arrays[2]}; npy_uint32 op_flags[3]; int i, idim, ndim, otype_final; - int needs_api, need_outer_iterator; + int need_outer_iterator; NpyIter *iter = NULL; @@ -3519,11 +3500,7 @@ PyUFunc_Reduceat(PyUFuncObject *ufunc, PyArrayObject *arr, PyArrayObject *ind, stride_copy[1] = stride1; stride_copy[2] = stride0; - needs_api = NpyIter_IterationNeedsAPI(iter); - - if (!needs_api) { - NPY_BEGIN_THREADS; - } + NPY_BEGIN_THREADS_NDITER(iter); do { @@ -3560,9 +3537,7 @@ PyUFunc_Reduceat(PyUFuncObject *ufunc, PyArrayObject *arr, PyArrayObject *ind, } } while (iternext(iter)); - if (!needs_api) { - NPY_END_THREADS; - } + NPY_END_THREADS; } else if (iter == NULL) { char *dataptr_copy[3]; @@ -3575,7 +3550,7 @@ PyUFunc_Reduceat(PyUFuncObject *ufunc, PyArrayObject *arr, PyArrayObject *ind, /* Execute the loop with no iterators */ npy_intp stride0 = 0, stride1 = PyArray_STRIDE(op[1], axis); - needs_api = PyDataType_REFCHK(op_dtypes[0]); + int needs_api = PyDataType_REFCHK(op_dtypes[0]); NPY_UF_DBG_PRINT("UFunc: Reduce loop with no iterators\n"); @@ -3619,9 +3594,7 @@ PyUFunc_Reduceat(PyUFuncObject *ufunc, PyArrayObject *arr, PyArrayObject *ind, } } - if (!needs_api) { - NPY_END_THREADS; - } + NPY_END_THREADS; } finish: @@ -5190,9 +5163,7 @@ ufunc_at(PyUFuncObject *ufunc, PyObject *args) i--; } - if (!needs_api) { - NPY_END_THREADS; - } + NPY_END_THREADS; if (err_msg) { PyErr_SetString(PyExc_ValueError, err_msg); diff --git a/numpy/core/tests/test_nditer.py b/numpy/core/tests/test_nditer.py index 3eac5428f..0055c038b 100644 --- a/numpy/core/tests/test_nditer.py +++ b/numpy/core/tests/test_nditer.py @@ -6,7 +6,7 @@ import numpy as np from numpy import array, arange, nditer, all from numpy.compat import asbytes, sixu from numpy.testing import * - +from numpy.core.multiarray_tests import test_nditer_too_large def iter_multi_index(i): @@ -2586,6 +2586,44 @@ def test_iter_too_large(): size = np.iinfo(np.intp).max // 1024 arr = np.lib.stride_tricks.as_strided(np.zeros(1), (size,), (0,)) assert_raises(ValueError, nditer, (arr, arr[:, None])) + # test the same for multiindex. That may get more interesting when + # removing 0 dimensional axis is allowed (since an iterator can grow then) + assert_raises(ValueError, nditer, + (arr, arr[:, None]), flags=['multi_index']) + + +def test_iter_too_large_with_multiindex(): + # When a multi index is being tracked, the error is delayed this + # checks the delayed error messages and getting below that by + # removing an axis. + base_size = 2**10 + num = 1 + while base_size**num < np.iinfo(np.intp).max: + num += 1 + + shape_template = [1, 1] * num + arrays = [] + for i in range(num): + shape = shape_template[:] + shape[i * 2] = 2**10 + arrays.append(np.empty(shape)) + arrays = tuple(arrays) + + # arrays are now too large to be broadcast. The different modes test + # different nditer functionality with or without GIL. + for mode in range(6): + assert_raises(ValueError, test_nditer_too_large, arrays, -1, mode) + # but if we do nothing with the nditer, it can be constructed: + test_nditer_too_large(arrays, -1, 7) + + # When an axis is removed, things should work again (half the time): + for i in range(num): + for mode in range(6): + # an axis with size 1024 is removed: + test_nditer_too_large(arrays, i*2, mode) + # an axis with size 1 is removed: + assert_raises(ValueError, test_nditer_too_large, + arrays, i*2 + 1, mode) if __name__ == "__main__": diff --git a/numpy/core/tests/test_numeric.py b/numpy/core/tests/test_numeric.py index 8f2d36c55..d08fd983e 100644 --- a/numpy/core/tests/test_numeric.py +++ b/numpy/core/tests/test_numeric.py @@ -936,6 +936,20 @@ class TestNonzero(TestCase): assert_equal(np.nonzero(x['a'].T), ([0, 1, 1, 2], [1, 1, 2, 0])) assert_equal(np.nonzero(x['b'].T), ([0, 0, 1, 2, 2], [0, 1, 2, 0, 2])) + def test_sparse(self): + # test special sparse condition boolean code path + for i in range(20): + c = np.zeros(200, dtype=np.bool) + c[i::20] = True + assert_equal(np.nonzero(c)[0], np.arange(i, 200 + i, 20)) + + c = np.zeros(400, dtype=np.bool) + c[10 + i:20 + i] = True + c[20 + i*2] = True + assert_equal(np.nonzero(c)[0], + np.concatenate((np.arange(10 +i, 20 + i), [20 +i*2]))) + + class TestIndex(TestCase): def test_boolean(self): a = rand(3, 5, 8) diff --git a/numpy/lib/function_base.py b/numpy/lib/function_base.py index 62ced0509..7b2d077c3 100644 --- a/numpy/lib/function_base.py +++ b/numpy/lib/function_base.py @@ -1593,6 +1593,7 @@ class vectorize(object): cache=False): self.pyfunc = pyfunc self.cache = cache + self._ufunc = None # Caching to improve default performance if doc is None: self.__doc__ = pyfunc.__doc__ @@ -1616,9 +1617,6 @@ class vectorize(object): excluded = set() self.excluded = set(excluded) - if self.otypes and not self.excluded: - self._ufunc = None # Caching to improve default performance - def __call__(self, *args, **kwargs): """ Return arrays with the results of `pyfunc` broadcast (vectorized) over @@ -1652,7 +1650,8 @@ class vectorize(object): def _get_ufunc_and_otypes(self, func, args): """Return (ufunc, otypes).""" # frompyfunc will fail if args is empty - assert args + if not args: + raise ValueError('args can not be empty') if self.otypes: otypes = self.otypes diff --git a/numpy/lib/src/_compiled_base.c b/numpy/lib/src/_compiled_base.c index 652268f24..f70cd2bab 100644 --- a/numpy/lib/src/_compiled_base.c +++ b/numpy/lib/src/_compiled_base.c @@ -1417,6 +1417,9 @@ _packbits( void *In, npy_intp out_Nm1; int maxi, remain, nonzero, j; char *outptr,*inptr; + NPY_BEGIN_THREADS_DEF; + + NPY_BEGIN_THREADS_THRESHOLDED(out_N); outptr = Out; /* pointer to output buffer */ inptr = In; /* pointer to input buffer */ @@ -1451,6 +1454,8 @@ _packbits( void *In, *outptr = build; outptr += out_stride; } + + NPY_END_THREADS; return; } @@ -1468,6 +1473,9 @@ _unpackbits(void *In, unsigned char mask; int i, index; char *inptr, *outptr; + NPY_BEGIN_THREADS_DEF; + + NPY_BEGIN_THREADS_THRESHOLDED(in_N); outptr = Out; inptr = In; @@ -1480,6 +1488,8 @@ _unpackbits(void *In, } inptr += in_stride; } + + NPY_END_THREADS; return; } diff --git a/numpy/lib/tests/test_function_base.py b/numpy/lib/tests/test_function_base.py index 3e102cf6a..6c11b0385 100644 --- a/numpy/lib/tests/test_function_base.py +++ b/numpy/lib/tests/test_function_base.py @@ -721,6 +721,12 @@ class TestVectorize(TestCase): assert_array_equal(f(x), x*x) assert_equal(_calls[0], len(x)) + def test_otypes(self): + f = np.vectorize(lambda x: x) + f.otypes = 'i' + x = np.arange(5) + assert_array_equal(f(x), x) + class TestDigitize(TestCase): def test_forward(self): diff --git a/numpy/linalg/tests/test_regression.py b/numpy/linalg/tests/test_regression.py index 4ff14a6a5..b0b2b2e30 100644 --- a/numpy/linalg/tests/test_regression.py +++ b/numpy/linalg/tests/test_regression.py @@ -69,5 +69,11 @@ class TestRegression(TestCase): bp = linalg.cholesky(b) assert_array_equal(ap, bp) + def test_large_svd_32bit(self): + # See gh-4442, 64bit would require very large/slow matrices. + x = np.eye(1000, 66) + np.linalg.svd(x) + + if __name__ == '__main__': run_module_suite() diff --git a/numpy/testing/utils.py b/numpy/testing/utils.py index 4905898d2..01c0e769b 100644 --- a/numpy/testing/utils.py +++ b/numpy/testing/utils.py @@ -318,7 +318,9 @@ def assert_equal(actual,desired,err_msg='',verbose=True): # as before except (TypeError, ValueError, NotImplementedError): pass - if desired != actual : + + # Explicitly use __eq__ for comparison, ticket #2552 + if not (desired == actual): raise AssertionError(msg) def print_assert_equal(test_string, actual, desired): |