summaryrefslogtreecommitdiff
path: root/numpy
diff options
context:
space:
mode:
Diffstat (limited to 'numpy')
-rw-r--r--numpy/core/src/umath/ufunc_object.c253
-rw-r--r--numpy/core/tests/test_multiarray.py2
-rw-r--r--numpy/core/tests/test_umath.py27
3 files changed, 116 insertions, 166 deletions
diff --git a/numpy/core/src/umath/ufunc_object.c b/numpy/core/src/umath/ufunc_object.c
index 44973cf09..37e297ed5 100644
--- a/numpy/core/src/umath/ufunc_object.c
+++ b/numpy/core/src/umath/ufunc_object.c
@@ -1523,24 +1523,24 @@ execute_fancy_ufunc_loop(PyUFuncObject *ufunc,
/* Call the __array_prepare__ functions where necessary */
op_it = NpyIter_GetOperandArray(iter);
- for (i = nin; i < nop; ++i) {
- PyArrayObject *op_tmp, *orig_op_tmp;
+ for (i = 0; i < nout; ++i) {
+ PyArrayObject *op_tmp;
/*
* The array can be allocated by the iterator -- it is placed in op[i]
* and returned to the caller, and this needs an extra incref.
*/
- if (op[i] == NULL) {
- op_tmp = op_it[i];
+ if (op[i+nin] == NULL) {
+ op_tmp = op_it[i+nin];
Py_INCREF(op_tmp);
}
else {
- op_tmp = op[i];
+ op_tmp = op[i+nin];
+ op[i+nin] = NULL;
}
/* prepare_ufunc_output may decref & replace the pointer */
- orig_op_tmp = op_tmp;
- Py_INCREF(op_tmp);
+ char *original_data = PyArray_BYTES(op_tmp);
if (prepare_ufunc_output(ufunc, &op_tmp,
arr_prep[i], full_args, i) < 0) {
@@ -1549,7 +1549,7 @@ execute_fancy_ufunc_loop(PyUFuncObject *ufunc,
}
/* Validate that the prepare_ufunc_output didn't mess with pointers */
- if (PyArray_BYTES(op_tmp) != PyArray_BYTES(orig_op_tmp)) {
+ if (PyArray_BYTES(op_tmp) != original_data) {
PyErr_SetString(PyExc_ValueError,
"The __array_prepare__ functions modified the data "
"pointer addresses in an invalid fashion");
@@ -1559,12 +1559,11 @@ execute_fancy_ufunc_loop(PyUFuncObject *ufunc,
}
/*
- * Put the updated operand back and undo the DECREF above. If
- * COPY_IF_OVERLAP made a temporary copy, the output will be copied
- * by UPDATEIFCOPY even if op[i] was changed by prepare_ufunc_output.
+ * Put the updated operand back. If COPY_IF_OVERLAP made a temporary
+ * copy, the output will be copied by WRITEBACKIFCOPY even if op[i]
+ * was changed by prepare_ufunc_output.
*/
- op[i] = op_tmp;
- Py_DECREF(op_tmp);
+ op[i+nin] = op_tmp;
}
/* Only do the loop if the iteration size is non-zero */
@@ -2111,9 +2110,10 @@ _initialize_variable_parts(PyUFuncObject *ufunc,
}
static int
-PyUFunc_GeneralizedFunctionInternal(PyUFuncObject *ufunc, PyArrayObject **op,
- ufunc_full_args full_args, PyObject *type_tup, PyObject *extobj,
- NPY_CASTING casting, NPY_ORDER order, npy_bool subok,
+PyUFunc_GeneralizedFunctionInternal(PyUFuncObject *ufunc,
+ PyArray_Descr *operation_descrs[],
+ PyArrayObject *op[], PyObject *extobj,
+ NPY_ORDER order,
PyObject *axis, PyObject *axes, int keepdims)
{
int nin, nout;
@@ -2122,8 +2122,6 @@ PyUFunc_GeneralizedFunctionInternal(PyUFuncObject *ufunc, PyArrayObject **op,
int retval;
int needs_api = 0;
- PyArray_Descr *dtypes[NPY_MAXARGS];
-
/* Use remapped axes for generalized ufunc */
int broadcast_ndim, iter_ndim;
int op_core_num_dims[NPY_MAXARGS];
@@ -2154,8 +2152,6 @@ PyUFunc_GeneralizedFunctionInternal(PyUFuncObject *ufunc, PyArrayObject **op,
/* swapping around of axes */
int *remap_axis_memory = NULL;
int **remap_axis = NULL;
- /* The __array_prepare__ function to call for each output */
- PyObject *arr_prep[NPY_MAXARGS];
nin = ufunc->nin;
nout = ufunc->nout;
@@ -2165,11 +2161,6 @@ PyUFunc_GeneralizedFunctionInternal(PyUFuncObject *ufunc, PyArrayObject **op,
NPY_UF_DBG_PRINT1("\nEvaluating ufunc %s\n", ufunc_name);
- /* Initialize all dtypes and __array_prepare__ call-backs to NULL */
- for (i = 0; i < nop; ++i) {
- dtypes[i] = NULL;
- arr_prep[i] = NULL;
- }
/* Initialize possibly variable parts to the values from the ufunc */
retval = _initialize_variable_parts(ufunc, op_core_num_dims,
core_dim_sizes, core_dim_flags);
@@ -2375,12 +2366,6 @@ PyUFunc_GeneralizedFunctionInternal(PyUFuncObject *ufunc, PyArrayObject **op,
NPY_UF_DBG_PRINT("Finding inner loop\n");
-
- retval = ufunc->type_resolver(ufunc, casting,
- op, type_tup, dtypes);
- if (retval < 0) {
- goto fail;
- }
/*
* We don't write to all elements, and the iterator may make
* UPDATEIFCOPY temporary copies. The output arrays (unless they are
@@ -2394,34 +2379,12 @@ PyUFunc_GeneralizedFunctionInternal(PyUFuncObject *ufunc, PyArrayObject **op,
NPY_UFUNC_DEFAULT_OUTPUT_FLAGS,
op_flags);
/* For the generalized ufunc, we get the loop right away too */
- retval = ufunc->legacy_inner_loop_selector(ufunc, dtypes,
- &innerloop, &innerloopdata, &needs_api);
+ retval = ufunc->legacy_inner_loop_selector(ufunc,
+ operation_descrs, &innerloop, &innerloopdata, &needs_api);
if (retval < 0) {
goto fail;
}
-#if NPY_UF_DBG_TRACING
- printf("input types:\n");
- for (i = 0; i < nin; ++i) {
- PyObject_Print((PyObject *)dtypes[i], stdout, 0);
- printf(" ");
- }
- printf("\noutput types:\n");
- for (i = nin; i < nop; ++i) {
- PyObject_Print((PyObject *)dtypes[i], stdout, 0);
- printf(" ");
- }
- printf("\n");
-#endif
-
- if (subok) {
- /*
- * Get the appropriate __array_prepare__ function to call
- * for each output
- */
- _find_array_prepare(full_args, arr_prep, nout);
- }
-
/*
* Set up the iterator per-op flags. For generalized ufuncs, we
* can't do buffering, so must COPY or UPDATEIFCOPY.
@@ -2436,7 +2399,7 @@ PyUFunc_GeneralizedFunctionInternal(PyUFuncObject *ufunc, PyArrayObject **op,
/* Create the iterator */
iter = NpyIter_AdvancedNew(nop, op, iter_flags,
order, NPY_UNSAFE_CASTING, op_flags,
- dtypes, iter_ndim,
+ operation_descrs, iter_ndim,
op_axes, iter_shape, 0);
if (iter == NULL) {
retval = -1;
@@ -2595,11 +2558,6 @@ PyUFunc_GeneralizedFunctionInternal(PyUFuncObject *ufunc, PyArrayObject **op,
retval = -1;
}
- /* The caller takes ownership of all the references in op */
- for (i = 0; i < nop; ++i) {
- Py_XDECREF(dtypes[i]);
- Py_XDECREF(arr_prep[i]);
- }
PyArray_free(remap_axis_memory);
PyArray_free(remap_axis);
@@ -2611,10 +2569,6 @@ fail:
NPY_UF_DBG_PRINT1("Returning failure code %d\n", retval);
PyArray_free(inner_strides);
NpyIter_Deallocate(iter);
- for (i = 0; i < nop; ++i) {
- Py_XDECREF(dtypes[i]);
- Py_XDECREF(arr_prep[i]);
- }
PyArray_free(remap_axis_memory);
PyArray_free(remap_axis);
return retval;
@@ -2622,56 +2576,33 @@ fail:
static int
-PyUFunc_GenericFunctionInternal(PyUFuncObject *ufunc, PyArrayObject **op,
- ufunc_full_args full_args, PyObject *type_tup, PyObject *extobj,
- NPY_CASTING casting, NPY_ORDER order, npy_bool subok,
+PyUFunc_GenericFunctionInternal(PyUFuncObject *ufunc,
+ PyArray_Descr *operation_descrs[],
+ PyArrayObject *op[], PyObject *extobj, NPY_ORDER order,
+ PyObject *output_array_prepare[], ufunc_full_args full_args,
PyArrayObject *wheremask)
{
- int nin, nout;
- int i, nop;
- const char *ufunc_name;
+ int nin = ufunc->nin, nout = ufunc->nout, nop = nin + nout;
+
+ const char *ufunc_name = ufunc_name = ufunc_get_name_cstr(ufunc);;
int retval = -1;
npy_uint32 op_flags[NPY_MAXARGS];
npy_intp default_op_out_flags;
- PyArray_Descr *dtypes[NPY_MAXARGS];
-
/* These parameters come from extobj= or from a TLS global */
int buffersize = 0, errormask = 0;
- /* The __array_prepare__ function to call for each output */
- PyObject *arr_prep[NPY_MAXARGS];
-
int trivial_loop_ok = 0;
- nin = ufunc->nin;
- nout = ufunc->nout;
- nop = nin + nout;
-
- ufunc_name = ufunc_get_name_cstr(ufunc);
-
NPY_UF_DBG_PRINT1("\nEvaluating ufunc %s\n", ufunc_name);
- /* Initialize all the dtypes and __array_prepare__ callbacks to NULL */
- for (i = 0; i < nop; ++i) {
- dtypes[i] = NULL;
- arr_prep[i] = NULL;
- }
-
/* Get the buffersize and errormask */
if (_get_bufsize_errmask(extobj, ufunc_name, &buffersize, &errormask) < 0) {
- retval = -1;
- goto fail;
+ return -1;
}
NPY_UF_DBG_PRINT("Finding inner loop\n");
- retval = ufunc->type_resolver(ufunc, casting,
- op, type_tup, dtypes);
- if (retval < 0) {
- goto fail;
- }
-
if (wheremask != NULL) {
/* Set up the flags. */
default_op_out_flags = NPY_ITER_NO_SUBTYPE |
@@ -2688,31 +2619,9 @@ PyUFunc_GenericFunctionInternal(PyUFuncObject *ufunc, PyArrayObject **op,
default_op_out_flags, op_flags);
}
-#if NPY_UF_DBG_TRACING
- printf("input types:\n");
- for (i = 0; i < nin; ++i) {
- PyObject_Print((PyObject *)dtypes[i], stdout, 0);
- printf(" ");
- }
- printf("\noutput types:\n");
- for (i = nin; i < nop; ++i) {
- PyObject_Print((PyObject *)dtypes[i], stdout, 0);
- printf(" ");
- }
- printf("\n");
-#endif
-
- if (subok) {
- /*
- * Get the appropriate __array_prepare__ function to call
- * for each output
- */
- _find_array_prepare(full_args, arr_prep, nout);
- }
-
/* Do the ufunc loop */
if (wheremask != NULL) {
- NPY_UF_DBG_PRINT("Executing fancy inner loop\n");
+ NPY_UF_DBG_PRINT("Executing masked inner loop\n");
if (nop + 1 > NPY_MAXARGS) {
PyErr_SetString(PyExc_ValueError,
@@ -2720,14 +2629,15 @@ PyUFunc_GenericFunctionInternal(PyUFuncObject *ufunc, PyArrayObject **op,
return -1;
}
op[nop] = wheremask;
- dtypes[nop] = NULL;
+ operation_descrs[nop] = NULL;
/* Set up the flags */
npy_clear_floatstatus_barrier((char*)&ufunc);
retval = execute_fancy_ufunc_loop(ufunc, wheremask,
- op, dtypes, order,
- buffersize, arr_prep, full_args, op_flags);
+ op, operation_descrs, order,
+ buffersize, output_array_prepare,
+ full_args, op_flags);
}
else {
NPY_UF_DBG_PRINT("Executing legacy inner loop\n");
@@ -2738,20 +2648,22 @@ PyUFunc_GenericFunctionInternal(PyUFuncObject *ufunc, PyArrayObject **op,
* Since it requires dtypes, it can only be called after
* ufunc->type_resolver
*/
- trivial_loop_ok = check_for_trivial_loop(ufunc, op, dtypes, buffersize);
+ trivial_loop_ok = check_for_trivial_loop(ufunc,
+ op, operation_descrs, buffersize);
if (trivial_loop_ok < 0) {
- goto fail;
+ return -1;
}
/* check_for_trivial_loop on half-floats can overflow */
npy_clear_floatstatus_barrier((char*)&ufunc);
retval = execute_legacy_ufunc_loop(ufunc, trivial_loop_ok,
- op, dtypes, order,
- buffersize, arr_prep, full_args, op_flags);
+ op, operation_descrs, order,
+ buffersize, output_array_prepare,
+ full_args, op_flags);
}
if (retval < 0) {
- goto fail;
+ return -1;
}
/*
@@ -2761,26 +2673,7 @@ PyUFunc_GenericFunctionInternal(PyUFuncObject *ufunc, PyArrayObject **op,
*/
if (PyErr_Occurred() ||
_check_ufunc_fperr(errormask, extobj, ufunc_name) < 0) {
- retval = -1;
- goto fail;
- }
-
-
- /* The caller takes ownership of all the references in op */
- for (i = 0; i < nop; ++i) {
- Py_XDECREF(dtypes[i]);
- Py_XDECREF(arr_prep[i]);
- }
-
- NPY_UF_DBG_PRINT("Returning success code 0\n");
-
- return 0;
-
-fail:
- NPY_UF_DBG_PRINT1("Returning failure code %d\n", retval);
- for (i = 0; i < nop; ++i) {
- Py_XDECREF(dtypes[i]);
- Py_XDECREF(arr_prep[i]);
+ return -1;
}
return retval;
@@ -4759,14 +4652,22 @@ ufunc_generic_fastcall(PyUFuncObject *ufunc,
PyObject *const *args, Py_ssize_t len_args, PyObject *kwnames,
npy_bool outer)
{
- PyArrayObject *operands[NPY_MAXARGS] = {NULL};
- PyObject *override = NULL;
- ufunc_full_args full_args = {NULL, NULL};
- PyObject *typetup = NULL;
-
int errval;
int nin = ufunc->nin, nout = ufunc->nout, nop = ufunc->nargs;
+ /* All following variables are cleared in the `fail` error path */
+ ufunc_full_args full_args;
+ PyArrayObject *wheremask = NULL;
+ PyObject *typetup = NULL;
+
+ PyArrayObject *operands[NPY_MAXARGS];
+ PyArray_Descr *operation_descrs[NPY_MAXARGS];
+ PyObject *output_array_prepare[NPY_MAXARGS];
+ /* Initialize all arrays (we usually only need a small part) */
+ memset(operands, 0, nop * sizeof(*operands));
+ memset(operation_descrs, 0, nop * sizeof(*operation_descrs));
+ memset(output_array_prepare, 0, nout * sizeof(*output_array_prepare));
+
/*
* Note that the input (and possibly output) arguments are passed in as
* positional arguments. We extract these first and check for `out`
@@ -4914,6 +4815,7 @@ ufunc_generic_fastcall(PyUFuncObject *ufunc,
method = "outer";
}
/* We now have all the information required to check for Overrides */
+ PyObject *override = NULL;
errval = PyUFunc_CheckOverride(ufunc, method,
full_args.in, full_args.out,
args, len_args, kwnames, &override);
@@ -4947,7 +4849,6 @@ ufunc_generic_fastcall(PyUFuncObject *ufunc,
NPY_CASTING casting = NPY_DEFAULT_ASSIGN_CASTING;
npy_bool subok = NPY_TRUE;
int keepdims = -1; /* We need to know if it was passed */
- PyArrayObject *wheremask = NULL;
if (convert_ufunc_arguments(ufunc, full_args, operands,
order_obj, &order,
casting_obj, &casting,
@@ -4957,15 +4858,30 @@ ufunc_generic_fastcall(PyUFuncObject *ufunc,
goto fail;
}
+ if (ufunc->type_resolver(ufunc,
+ casting, operands, typetup, operation_descrs) < 0) {
+ goto fail;
+ }
+
+ if (subok) {
+ _find_array_prepare(full_args, output_array_prepare, nout);
+ }
+
+ /*
+ * Do the final preparations and call the inner-loop.
+ */
if (!ufunc->core_enabled) {
- errval = PyUFunc_GenericFunctionInternal(ufunc, operands,
- full_args, typetup, extobj, casting, order, subok,
+ errval = PyUFunc_GenericFunctionInternal(ufunc,
+ operation_descrs, operands,
+ extobj, order,
+ output_array_prepare, full_args, /* for __array_prepare__ */
wheremask);
- Py_XDECREF(wheremask);
}
else {
- errval = PyUFunc_GeneralizedFunctionInternal(ufunc, operands,
- full_args, typetup, extobj, casting, order, subok,
+ errval = PyUFunc_GeneralizedFunctionInternal(ufunc,
+ operation_descrs, operands,
+ extobj, order,
+ /* GUFuncs never (ever) called __array_prepare__! */
axis_obj, axes_obj, keepdims);
}
@@ -4973,9 +4889,19 @@ ufunc_generic_fastcall(PyUFuncObject *ufunc,
goto fail;
}
- /* Free the input references */
- for (int i = 0; i < ufunc->nin; i++) {
- Py_XSETREF(operands[i], NULL);
+ /*
+ * Clear all variables which are not needed any further.
+ * (From here on, we cannot `goto fail` any more.)
+ */
+ Py_XDECREF(wheremask);
+ for (int i = 0; i < nop; i++) {
+ Py_DECREF(operation_descrs[i]);
+ if (i < nin) {
+ Py_DECREF(operands[i]);
+ }
+ else {
+ Py_XDECREF(output_array_prepare[i-nin]);
+ }
}
Py_XDECREF(typetup);
@@ -4990,8 +4916,13 @@ fail:
Py_XDECREF(typetup);
Py_XDECREF(full_args.in);
Py_XDECREF(full_args.out);
+ Py_XDECREF(wheremask);
for (int i = 0; i < ufunc->nargs; i++) {
Py_XDECREF(operands[i]);
+ Py_XDECREF(operation_descrs[i]);
+ if (i < nout) {
+ Py_XDECREF(output_array_prepare[i]);
+ }
}
return NULL;
}
diff --git a/numpy/core/tests/test_multiarray.py b/numpy/core/tests/test_multiarray.py
index 470a45337..7ee7253ef 100644
--- a/numpy/core/tests/test_multiarray.py
+++ b/numpy/core/tests/test_multiarray.py
@@ -6734,7 +6734,7 @@ class TestMatmulOperator(MatmulCommon):
def test_matmul_raises(self):
assert_raises(TypeError, self.matmul, np.int8(5), np.int8(5))
assert_raises(TypeError, self.matmul, np.void(b'abc'), np.void(b'abc'))
- assert_raises(ValueError, self.matmul, np.arange(10), np.void(b'abc'))
+ assert_raises(TypeError, self.matmul, np.arange(10), np.void(b'abc'))
def test_matmul_inplace():
# It would be nice to support in-place matmul eventually, but for now
diff --git a/numpy/core/tests/test_umath.py b/numpy/core/tests/test_umath.py
index 2378b11e9..a2d6b3989 100644
--- a/numpy/core/tests/test_umath.py
+++ b/numpy/core/tests/test_umath.py
@@ -2102,6 +2102,10 @@ class TestSpecialMethods:
do_test(lambda a: np.add(0, 0, out=a), lambda a: (0, 0, a))
do_test(lambda a: np.add(0, 0, out=(a,)), lambda a: (0, 0, a))
+ # Also check the where mask handling:
+ do_test(lambda a: np.add(a, 0, where=False), lambda a: (a, 0))
+ do_test(lambda a: np.add(0, 0, a, where=False), lambda a: (0, 0, a))
+
def test_wrap_with_iterable(self):
# test fix for bug #1026:
@@ -2251,7 +2255,8 @@ class TestSpecialMethods:
assert_equal(x, np.zeros(1))
assert_equal(type(x), np.ndarray)
- def test_prepare(self):
+ @pytest.mark.parametrize("use_where", [True, False])
+ def test_prepare(self, use_where):
class with_prepare(np.ndarray):
__array_priority__ = 10
@@ -2261,11 +2266,18 @@ class TestSpecialMethods:
return np.array(arr).view(type=with_prepare)
a = np.array(1).view(type=with_prepare)
- x = np.add(a, a)
+ if use_where:
+ # Currently raises, due to the array being replaced during prepare
+ with pytest.raises(ValueError):
+ x = np.add(a, a, where=np.array(True))
+ return
+ else:
+ x = np.add(a, a)
assert_equal(x, np.array(2))
assert_equal(type(x), with_prepare)
- def test_prepare_out(self):
+ @pytest.mark.parametrize("use_where", [True, False])
+ def test_prepare_out(self, use_where):
class with_prepare(np.ndarray):
__array_priority__ = 10
@@ -2274,7 +2286,13 @@ class TestSpecialMethods:
return np.array(arr).view(type=with_prepare)
a = np.array([1]).view(type=with_prepare)
- x = np.add(a, a, a)
+ if use_where:
+ # Currently raises, due to the array being replaced during prepare
+ with pytest.raises(ValueError):
+ x = np.add(a, a, a, where=[True])
+ return
+ else:
+ x = np.add(a, a, a)
# Returned array is new, because of the strange
# __array_prepare__ above
assert_(not np.shares_memory(x, a))
@@ -2292,6 +2310,7 @@ class TestSpecialMethods:
a = A()
assert_raises(RuntimeError, ncu.maximum, a, a)
+ assert_raises(RuntimeError, ncu.maximum, a, a, where=False)
def test_array_too_many_args(self):