From de0f5dcf501b29b173911b72c6e1488c4e573935 Mon Sep 17 00:00:00 2001 From: Eric Wieser Date: Thu, 30 Mar 2017 20:04:41 +0100 Subject: [PATCH 1/3] BUG: Use the same default ufunc name in all places Previously the ufunc methods would use "(unknown)", but the basic __call__ would use "" --- numpy/core/src/umath/ufunc_object.c | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/numpy/core/src/umath/ufunc_object.c b/numpy/core/src/umath/ufunc_object.c index 22a73e6ba919..33d65b5bccc7 100644 --- a/numpy/core/src/umath/ufunc_object.c +++ b/numpy/core/src/umath/ufunc_object.c @@ -756,6 +756,15 @@ _set_out_array(PyObject *obj, PyArrayObject **store) /********* GENERIC UFUNC USING ITERATOR *********/ +/* + * Produce a name for the ufunc, if one is not already set + * This is used in the PyUFunc_handlefperr machinery, and in error messages + */ +static const char* +_get_ufunc_name(PyUFuncObject *ufunc) { + return ufunc->name ? ufunc->name : ""; +} + /* * Parses the positional and keyword arguments for a generic ufunc call. * @@ -779,14 +788,12 @@ get_ufunc_arguments(PyUFuncObject *ufunc, int nout = ufunc->nout; PyObject *obj, *context; PyObject *str_key_obj = NULL; - const char *ufunc_name; + const char *ufunc_name = _get_ufunc_name(ufunc); int type_num; int any_flexible = 0, any_object = 0, any_flexible_userloops = 0; int has_sig = 0; - ufunc_name = ufunc->name ? ufunc->name : ""; - *out_extobj = NULL; *out_typetup = NULL; if (out_wheremask != NULL) { @@ -2037,7 +2044,7 @@ PyUFunc_GeneralizedFunction(PyUFuncObject *ufunc, nout = ufunc->nout; nop = nin + nout; - ufunc_name = ufunc->name ? ufunc->name : ""; + ufunc_name = _get_ufunc_name(ufunc); NPY_UF_DBG_PRINT1("\nEvaluating ufunc %s\n", ufunc_name); @@ -2598,7 +2605,7 @@ PyUFunc_GenericFunction(PyUFuncObject *ufunc, nout = ufunc->nout; nop = nin + nout; - ufunc_name = ufunc->name ? ufunc->name : ""; + ufunc_name = _get_ufunc_name(ufunc); NPY_UF_DBG_PRINT1("\nEvaluating ufunc %s\n", ufunc_name); @@ -2838,7 +2845,7 @@ reduce_type_resolver(PyUFuncObject *ufunc, PyArrayObject *arr, int i, retcode; PyArrayObject *op[3] = {arr, arr, NULL}; PyArray_Descr *dtypes[3] = {NULL, NULL, NULL}; - const char *ufunc_name = ufunc->name ? ufunc->name : "(unknown)"; + const char *ufunc_name = _get_ufunc_name(ufunc); PyObject *type_tup = NULL; *out_dtype = NULL; @@ -3027,7 +3034,7 @@ PyUFunc_Reduce(PyUFuncObject *ufunc, PyArrayObject *arr, PyArrayObject *out, PyArray_Descr *dtype; PyArrayObject *result; PyArray_AssignReduceIdentityFunc *assign_identity = NULL; - const char *ufunc_name = ufunc->name ? ufunc->name : "(unknown)"; + const char *ufunc_name = _get_ufunc_name(ufunc); /* These parameters come from a TLS global */ int buffersize = 0, errormask = 0; @@ -3135,7 +3142,7 @@ PyUFunc_Accumulate(PyUFuncObject *ufunc, PyArrayObject *arr, PyArrayObject *out, PyUFuncGenericFunction innerloop = NULL; void *innerloopdata = NULL; - const char *ufunc_name = ufunc->name ? ufunc->name : "(unknown)"; + const char *ufunc_name = _get_ufunc_name(ufunc); /* These parameters come from extobj= or from a TLS global */ int buffersize = 0, errormask = 0; @@ -3510,7 +3517,7 @@ PyUFunc_Reduceat(PyUFuncObject *ufunc, PyArrayObject *arr, PyArrayObject *ind, PyUFuncGenericFunction innerloop = NULL; void *innerloopdata = NULL; - const char *ufunc_name = ufunc->name ? ufunc->name : "(unknown)"; + const char *ufunc_name = _get_ufunc_name(ufunc); char *opname = "reduceat"; /* These parameters come from extobj= or from a TLS global */ From e9fd6d447db636e97c5d522241e40f57082ee978 Mon Sep 17 00:00:00 2001 From: Eric Wieser Date: Thu, 30 Mar 2017 20:18:51 +0100 Subject: [PATCH 2/3] MAINT: Pull out large function from PyUFunc_GeneralizedFunction --- numpy/core/src/umath/ufunc_object.c | 226 +++++++++++++++------------- 1 file changed, 121 insertions(+), 105 deletions(-) diff --git a/numpy/core/src/umath/ufunc_object.c b/numpy/core/src/umath/ufunc_object.c index 33d65b5bccc7..23b59a54184a 100644 --- a/numpy/core/src/umath/ufunc_object.c +++ b/numpy/core/src/umath/ufunc_object.c @@ -1981,6 +1981,123 @@ _check_ufunc_fperr(int errmask, PyObject *extobj, const char *ufunc_name) { return ret; } +/* + * Validate the core dimensions of all the operands, and collect all of + * the labelled core dimensions into 'core_dim_sizes'. + * + * Returns 0 on success, and -1 on failure + * + * The behavior has been changed in NumPy 1.10.0, and the following + * requirements must be fulfilled or an error will be raised: + * * Arguments, both input and output, must have at least as many + * dimensions as the corresponding number of core dimensions. In + * previous versions, 1's were prepended to the shape as needed. + * * Core dimensions with same labels must have exactly matching sizes. + * In previous versions, core dimensions of size 1 would broadcast + * against other core dimensions with the same label. + * * All core dimensions must have their size specified by a passed in + * input or output argument. In previous versions, core dimensions in + * an output argument that were not specified in an input argument, + * and whose size could not be inferred from a passed in output + * argument, would have their size set to 1. + */ +static int +_get_coredim_sizes(PyUFuncObject *ufunc, PyArrayObject **op, + npy_intp* core_dim_sizes) { + int i; + int nin = ufunc->nin; + int nout = ufunc->nout; + int nop = nin + nout; + + for (i = 0; i < ufunc->core_num_dim_ix; ++i) { + core_dim_sizes[i] = -1; + } + for (i = 0; i < nop; ++i) { + if (op[i] != NULL) { + int idim; + int dim_offset = ufunc->core_offsets[i]; + int num_dims = ufunc->core_num_dims[i]; + int core_start_dim = PyArray_NDIM(op[i]) - num_dims; + + /* Check if operands have enough dimensions */ + if (core_start_dim < 0) { + PyErr_Format(PyExc_ValueError, + "%s: %s operand %d does not have enough " + "dimensions (has %d, gufunc core with " + "signature %s requires %d)", + _get_ufunc_name(ufunc), i < nin ? "Input" : "Output", + i < nin ? i : i - nin, PyArray_NDIM(op[i]), + ufunc->core_signature, num_dims); + return -1; + } + + /* + * Make sure every core dimension exactly matches all other core + * dimensions with the same label. + */ + for (idim = 0; idim < num_dims; ++idim) { + int core_dim_index = ufunc->core_dim_ixs[dim_offset+idim]; + npy_intp op_dim_size = + PyArray_DIM(op[i], core_start_dim+idim); + + if (core_dim_sizes[core_dim_index] == -1) { + core_dim_sizes[core_dim_index] = op_dim_size; + } + else if (op_dim_size != core_dim_sizes[core_dim_index]) { + PyErr_Format(PyExc_ValueError, + "%s: %s operand %d has a mismatch in its " + "core dimension %d, with gufunc " + "signature %s (size %zd is different " + "from %zd)", + _get_ufunc_name(ufunc), i < nin ? "Input" : "Output", + i < nin ? i : i - nin, idim, + ufunc->core_signature, op_dim_size, + core_dim_sizes[core_dim_index]); + return -1; + } + } + } + } + + /* + * Make sure no core dimension is unspecified. + */ + for (i = 0; i < ufunc->core_num_dim_ix; ++i) { + if (core_dim_sizes[i] == -1) { + break; + } + } + if (i != ufunc->core_num_dim_ix) { + /* + * There is at least one core dimension missing, find in which + * operand it comes up first (it has to be an output operand). + */ + const int missing_core_dim = i; + int out_op; + for (out_op = nin; out_op < nop; ++out_op) { + int first_idx = ufunc->core_offsets[out_op]; + int last_idx = first_idx + ufunc->core_num_dims[out_op]; + for (i = first_idx; i < last_idx; ++i) { + if (ufunc->core_dim_ixs[i] == missing_core_dim) { + break; + } + } + if (i < last_idx) { + /* Change index offsets for error message */ + out_op -= nin; + i -= first_idx; + break; + } + } + PyErr_Format(PyExc_ValueError, + "%s: Output operand %d has core dimension %d " + "unspecified, with gufunc signature %s", + _get_ufunc_name(ufunc), out_op, i, ufunc->core_signature); + return -1; + } + return 0; +} + static int PyUFunc_GeneralizedFunction(PyUFuncObject *ufunc, @@ -1990,7 +2107,7 @@ PyUFunc_GeneralizedFunction(PyUFuncObject *ufunc, int nin, nout; int i, j, idim, nop; const char *ufunc_name; - int retval = -1, subok = 1; + int retval = 0, subok = 1; int needs_api = 0; PyArray_Descr *dtypes[NPY_MAXARGS]; @@ -2096,110 +2213,9 @@ PyUFunc_GeneralizedFunction(PyUFuncObject *ufunc, goto fail; } - /* - * Validate the core dimensions of all the operands, and collect all of - * the labelled core dimensions into 'core_dim_sizes'. - * - * The behavior has been changed in NumPy 1.10.0, and the following - * requirements must be fulfilled or an error will be raised: - * * Arguments, both input and output, must have at least as many - * dimensions as the corresponding number of core dimensions. In - * previous versions, 1's were prepended to the shape as needed. - * * Core dimensions with same labels must have exactly matching sizes. - * In previous versions, core dimensions of size 1 would broadcast - * against other core dimensions with the same label. - * * All core dimensions must have their size specified by a passed in - * input or output argument. In previous versions, core dimensions in - * an output argument that were not specified in an input argument, - * and whose size could not be inferred from a passed in output - * argument, would have their size set to 1. - */ - for (i = 0; i < ufunc->core_num_dim_ix; ++i) { - core_dim_sizes[i] = -1; - } - for (i = 0; i < nop; ++i) { - if (op[i] != NULL) { - int dim_offset = ufunc->core_offsets[i]; - int num_dims = ufunc->core_num_dims[i]; - int core_start_dim = PyArray_NDIM(op[i]) - num_dims; - - /* Check if operands have enough dimensions */ - if (core_start_dim < 0) { - PyErr_Format(PyExc_ValueError, - "%s: %s operand %d does not have enough " - "dimensions (has %d, gufunc core with " - "signature %s requires %d)", - ufunc_name, i < nin ? "Input" : "Output", - i < nin ? i : i - nin, PyArray_NDIM(op[i]), - ufunc->core_signature, num_dims); - retval = -1; - goto fail; - } - - /* - * Make sure every core dimension exactly matches all other core - * dimensions with the same label. - */ - for (idim = 0; idim < num_dims; ++idim) { - int core_dim_index = ufunc->core_dim_ixs[dim_offset+idim]; - npy_intp op_dim_size = - PyArray_DIM(op[i], core_start_dim+idim); - - if (core_dim_sizes[core_dim_index] == -1) { - core_dim_sizes[core_dim_index] = op_dim_size; - } - else if (op_dim_size != core_dim_sizes[core_dim_index]) { - PyErr_Format(PyExc_ValueError, - "%s: %s operand %d has a mismatch in its " - "core dimension %d, with gufunc " - "signature %s (size %zd is different " - "from %zd)", - ufunc_name, i < nin ? "Input" : "Output", - i < nin ? i : i - nin, idim, - ufunc->core_signature, op_dim_size, - core_dim_sizes[core_dim_index]); - retval = -1; - goto fail; - } - } - } - } - - /* - * Make sure no core dimension is unspecified. - */ - for (i = 0; i < ufunc->core_num_dim_ix; ++i) { - if (core_dim_sizes[i] == -1) { - break; - } - } - if (i != ufunc->core_num_dim_ix) { - /* - * There is at least one core dimension missing, find in which - * operand it comes up first (it has to be an output operand). - */ - const int missing_core_dim = i; - int out_op; - for (out_op = nin; out_op < nop; ++out_op) { - int first_idx = ufunc->core_offsets[out_op]; - int last_idx = first_idx + ufunc->core_num_dims[out_op]; - for (i = first_idx; i < last_idx; ++i) { - if (ufunc->core_dim_ixs[i] == missing_core_dim) { - break; - } - } - if (i < last_idx) { - /* Change index offsets for error message */ - out_op -= nin; - i -= first_idx; - break; - } - } - PyErr_Format(PyExc_ValueError, - "%s: Output operand %d has core dimension %d " - "unspecified, with gufunc signature %s", - ufunc_name, out_op, i, ufunc->core_signature); - retval = -1; + /* Collect the lengths of the labelled core dimensions */ + retval = _get_coredim_sizes(ufunc, op, core_dim_sizes); + if(retval < 0) { goto fail; } From 19ae9fb4f03d0d8379c64bbd5589dad961436044 Mon Sep 17 00:00:00 2001 From: Eric Wieser Date: Thu, 30 Mar 2017 20:39:45 +0100 Subject: [PATCH 3/3] MAINT: Compute core_dim_ixs_size near where it is used --- numpy/core/src/umath/ufunc_object.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/numpy/core/src/umath/ufunc_object.c b/numpy/core/src/umath/ufunc_object.c index 23b59a54184a..850d2176c65d 100644 --- a/numpy/core/src/umath/ufunc_object.c +++ b/numpy/core/src/umath/ufunc_object.c @@ -2226,7 +2226,6 @@ PyUFunc_GeneralizedFunction(PyUFuncObject *ufunc, /* Fill in op_axes for all the operands */ j = broadcast_ndim; - core_dim_ixs_size = 0; for (i = 0; i < nop; ++i) { int n; if (op[i]) { @@ -2268,7 +2267,6 @@ PyUFunc_GeneralizedFunction(PyUFuncObject *ufunc, } op_axes[i] = op_axes_arrays[i]; - core_dim_ixs_size += ufunc->core_num_dims[i]; } /* Get the buffersize and errormask */ @@ -2383,6 +2381,10 @@ PyUFunc_GeneralizedFunction(PyUFuncObject *ufunc, * Set up the inner strides array. Because we're not doing * buffering, the strides are fixed throughout the looping. */ + core_dim_ixs_size = 0; + for (i = 0; i < nop; ++i) { + core_dim_ixs_size += ufunc->core_num_dims[i]; + } inner_strides = (npy_intp *)PyArray_malloc( NPY_SIZEOF_INTP * (nop+core_dim_ixs_size)); if (inner_strides == NULL) {