8000 Merge pull request #8876 from eric-wieser/ufunc-refactor · numpy/numpy@2187003 · GitHub
[go: up one dir, main page]

Skip to content

Commit 2187003

Browse files
authored
Merge pull request #8876 from eric-wieser/ufunc-refactor
MAINT: Minor ufunc cleanup
2 parents d46df62 + 19ae9fb commit 2187003

File tree

1 file changed

+141
-116
lines changed

1 file changed

+141
-116
lines changed

numpy/core/src/umath/ufunc_object.c

Lines changed: 141 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -756,6 +756,15 @@ _set_out_array(PyObject *obj, PyArrayObject **store)
756756

757757
/********* GENERIC UFUNC USING ITERATOR *********/
758758

759+
/*
760+
* Produce a name for the ufunc, if one is not already set
761+
* This is used in the PyUFunc_handlefperr machinery, and in error messages
762+
*/
763+
static const char*
764+
_get_ufunc_name(PyUFuncObject *ufunc) {
765+
return ufunc->name ? ufunc->name : "<unnamed ufunc>";
766+
}
767+
759768
/*
760769
* Parses the positional and keyword arguments for a generic ufunc call.
761770
*
@@ -779,14 +788,12 @@ get_ufunc_arguments(PyUFuncObject *ufunc,
779788
int nout = ufunc->nout;
780789
PyObject *obj, *context;
781790
PyObject *str_key_obj = NULL;
782-
const char *ufunc_name;
791+
const char *ufunc_name = _get_ufunc_name(ufunc);
783792
int type_num;
784793

785794
int any_flexible = 0, any_object = 0, any_flexible_userloops = 0;
786795
int has_sig = 0;
787796

788-
ufunc_name = ufunc->name ? ufunc->name : "<unnamed ufunc>";
789-
790797
*out_extobj = NULL;
791798
*out_typetup = NULL;
792799
if (out_wheremask != NULL) {
@@ -1983,6 +1990,123 @@ _check_ufunc_fperr(int errmask, PyObject *extobj, const char *ufunc_name) {
19831990
return ret;
19841991
}
19851992

1993+
/*
1994+
* Validate the core dimensions of all the operands, and collect all of
1995+
* the labelled core dimensions into 'core_dim_sizes'.
1996+
*
1997+
* Returns 0 on success, and -1 on failure
1998+
*
1999+
* The behavior has been changed in NumPy 1.10.0, and the following
2000+
* requirements must be fulfilled or an error will be raised:
2001+
* * Arguments, both input and output, must have at least as many
2002+
* dimensions as the corresponding number of core dimensions. In
2003+
* previous versions, 1's were prepended to the shape as needed.
2004+
* * Core dimensions with same labels must have exactly matching sizes.
2005+
* In previous versions, core dimensions of size 1 would broadcast
2006+
* against other core dimensions with the same label.
2007+
* * All core dimensions must have their size specified by a passed in
2008+
* input or output argument. In previous versions, core dimensions in
2009+
* an output argument that were not specified in an input argument,
2010+
* and whose size could not be inferred from a passed in output
2011+
* argument, would have their size set to 1.
2012+
*/
2013+
static int
2014+
_get_coredim_sizes(PyUFuncObject *ufunc, PyArrayObject **op,
2015+
npy_intp* core_dim_sizes) {
2016+
int i;
2017+
int nin = ufunc->nin;
2018+
int nout = ufunc->nout;
2019+
int nop = nin + nout;
2020+
2021+
for (i = 0; i < ufunc->core_num_dim_ix; ++i) {
2022+
core_dim_sizes[i] = -1;
2023+
}
2024+
for (i = 0; i < nop; ++i) {
2025+
if (op[i] != NULL) {
2026+
int idim;
2027+
int dim_offset = ufunc->core_offsets[i];
2028+
int num_dims = ufunc->core_num_dims[i];
2029+
int core_start_dim = PyArray_NDIM(op[i]) - num_dims;
2030+
2031+
/* Check if operands have enough dimensions */
2032+
if (core_start_dim < 0) {
2033+
PyErr_Format(PyExc_ValueError,
2034+
"%s: %s operand %d does not have enough "
2035+
"dimensions (has %d, gufunc core with "
2036+
"signature %s requires %d)",
2037+
_get_ufunc_name(ufunc), i < nin ? "Input" : "Output",
2038+
i < nin ? i : i - nin, PyArray_NDIM(op[i]),
2039+
ufunc->core_signature, num_dims);
2040+
return -1;
2041+
}
2042+
2043+
/*
2044+
* Make sure every core dimension exactly matches all other core
2045+
* dimensions with the same label.
2046+
*/
2047+
for (idim = 0; idim < num_dims; ++idim) {
2048+
int core_dim_index = ufunc->core_dim_ixs[dim_offset+idim];
2049+
npy_intp op_dim_size =
2050+
PyArray_DIM(op[i], core_start_dim+idim);
2051+
2052+
if (core_dim_sizes[core_dim_index] == -1) {
2053+
core_dim_sizes[core_dim_index] = op_dim_size;
2054+
}
2055+
else if (op_dim_size != core_dim_sizes[core_dim_index]) {
2056+
PyErr_Format(PyExc_ValueError,
2057+
"%s: %s operand %d has a mismatch in its "
2058+
"core dimension %d, with gufunc "
2059+
"signature %s (size %zd is different "
2060+
"from %zd)",
2061+
_get_ufunc_name(ufunc), i < nin ? "Input" : "Output",
2062+
i < nin ? i : i - nin, idim,
2063+
ufunc->core_signature, op_dim_size,
2064+
core_dim_sizes[core_dim_index]);
2065+
return -1;
2066+
}
2067+
}
2068+
}
2069+
}
2070+
2071+
/*
2072+
* Make sure no core dimension is unspecified.
2073+
*/
2074+
for (i = 0; i < ufunc->core_num_dim_ix; ++i) {
2075+
if (core_dim_sizes[i] == -1) {
2076+
break;
2077+
}
2078+
}
2079+
if (i != ufunc->core_num_dim_ix) {
2080+
/*
2081+
* There is at least one core dimension missing, find in which
2082+
* operand it comes up first (it has to be an output operand).
2083+
*/
2084+
const int missing_core_dim = i;
2085+
int out_op;
2086+
for (out_op = nin; out_op < nop; ++out_op) {
2087+
int first_idx = ufunc->core_offsets[out_op];
2088+
int last_idx = first_idx + ufunc->core_num_dims[out_op];
2089+
for (i = first_idx; i < last_idx; ++i) {
2090+
if (ufunc->core_dim_ixs[i] == missing_core_dim) {
2091+
break;
2092+
}
2093+
}
2094+
if (i < last_idx) {
2095+
/* Change index offsets for error message */
2096+
out_op -= nin;
2097+
i -= first_idx;
2098+
break;
2099+
}
2100+
}
2101+
PyErr_Format(PyExc_ValueError,
2102+
"%s: Output operand %d has core dimension %d "
2103+
"unspecified, with gufunc signature %s",
2104+
_get_ufunc_name(ufunc), out_op, i, ufunc->core_signature);
2105+
return -1;
2106+
}
2107+
return 0;
2108+
}
2109+
19862110

19872111
static int
19882112
PyUFunc_GeneralizedFunction(PyUFuncObject *ufunc,
@@ -1992,7 +2116,7 @@ PyUFunc_GeneralizedFunction(PyUFuncObject *ufunc,
19922116
int nin, nout;
19932117
int i, j, idim, nop;
19942118
const char *ufunc_name;
1995-
int retval = -1, subok = 1;
2119+
int retval = 0, subok = 1;
19962120
int needs_api = 0;
19972121

19982122
PyArray_Descr *dtypes[NPY_MAXARGS];
@@ -2046,7 +2170,7 @@ PyUFunc_GeneralizedFunction(PyUFuncObject *ufunc,
20462170
nout = ufunc->nout;
20472171
nop = nin + nout;
20482172

2049-
ufunc_name = ufunc->name ? ufunc->name : "<unnamed ufunc>";
2173+
ufunc_name = _get_ufunc_name(ufunc);
20502174

20512175
NPY_UF_DBG_PRINT1("\nEvaluating ufunc %s\n", ufunc_name);
20522176

@@ -2098,110 +2222,9 @@ PyUFunc_GeneralizedFunction(PyUFuncObject *ufunc,
20982222
goto fail;
20992223
}
21002224

2101-
/*
2102-
* Validate the core dimensions of all the operands, and collect all of
2103-
* the labelled core dimensions into 'core_dim_sizes'.
2104-
*
2105-
* The behavior has been changed in NumPy 1.10.0, and the following
2106-
* requirements must be fulfilled or an error will be raised:
2107-
* * Arguments, both input and output, must have at least as many
2108-
* dimensions as the corresponding number of core dimensions. In
2109-
* previous versions, 1's were prepended to the shape as needed.
2110-
* * Core dimensions with same labels must have exactly matching sizes.
2111-
* In previous versions, core dimensions of size 1 would broadcast
2112-
* against other core dimensions with the same label.
2113-
* * All core dimensions must have their size specified by a passed in
2114-
* input or output argument. In previous versions, core dimensions in
2115-
* an output argument that were not specified in an input argument,
2116-
* and whose size could not be inferred from a passed in output
2117-
* argument, would have their size set to 1.
2118-
*/
2119-
for (i = 0; i < ufunc->core_num_dim_ix; ++i) {
2120-
core_dim_sizes[i] = -1;
2121-
}
2122-
for (i = 0; i < nop; ++i) {
2123-
if (op[i] != NULL) {
2124-
int dim_offset = ufunc->core_offsets[i];
2125-
int num_dims = ufunc->core_num_dims[i];
2126-
int core_start_dim = PyArray_NDIM(op[i]) - num_dims;
2127-
2128-
/* Check if operands have enough dimensions */
2129-
if (core_start_dim < 0) {
2130-
PyErr_Format(PyExc_ValueError,
2131-
"%s: %s operand %d does not have enough "
2132-
"dimensions (has %d, gufunc core with "
2133-
"signature %s requires %d)",
2134-
ufunc_name, i < nin ? "Input" : "Output",
2135-
i < nin ? i : i - nin, PyArray_NDIM(op[i]),
2136-
ufunc->core_signature, num_dims);
2137-
retval = -1;
2138-
goto fail;
2139-
}
2140-
2141-
/*
2142-
* Make sure every core dimension exactly matches all other core
2143-
* dimensions with the same label.
2144-
*/
2145-
for (idim = 0; idim < num_dims; ++idim) {
2146-
int core_dim_index = ufunc->core_dim_ixs[dim_offset+idim];
2147-
npy_intp op_dim_size =
2148-
PyArray_DIM(op[i], core_start_dim+idim);
2149-
2150-
if (core_dim_sizes[core_dim_index] == -1) {
2151-
core_dim_sizes[core_dim_index] = op_dim_size;
2152-
}
2153-
else if (op_dim_size != core_dim_sizes[core_dim_index]) {
2154-
PyErr_Format(PyExc_ValueError,
2155-
"%s: %s operand %d has a mismatch in its "
2156-
"core dimension %d, with gufunc "
2157-
"signature %s (size %zd is different "
2158-
"from %zd)",
2159-
ufunc_name, i < nin ? "Input" : "Output",
2160-
i < nin ? i : i - nin, idim,
2161-
ufunc->core_signature, op_dim_size,
2162-
core_dim_sizes[core_dim_index]);
2163-
retval = -1;
2164-
goto fail;
2165-
}
2166-
}
2167-
}
2168-
}
2169-
2170-
/*
2171-
* Make sure no core dimension is unspecified.
2172-
*/
2173-
for (i = 0; i < ufunc->core_num_dim_ix; ++i) {
2174-
if (core_dim_sizes[i] == -1) {
2175-
break;
2176-
}
2177-
}
2178-
if (i != ufunc->core_num_dim_ix) {
2179-
/*
2180-
* There is at least one core dimension missing, find in which
2181-
* operand it comes up first (it has to be an output operand).
2182-
*/
2183-
const int missing_core_dim = i;
2184-
int out_op;
2185-
for (out_op = nin; out_op < nop; ++out_op) {
2186-
int first_idx = ufunc->core_offsets[out_op];
2187-
int last_idx = first_idx + ufunc->core_num_dims[out_op];
2188-
for (i = first_idx; i < last_idx; ++i) {
2189-
if (ufunc->core_dim_ixs[i] == missing_core_dim) {
2190-
break;
2191-
}
2192-
}
2193-
if (i < last_idx) {
2194-
/* Change index offsets for error message */
2195-
out_op -= nin;
2196-
i -= first_idx;
2197-
break;
2198-
}
2199-
}
2200-
PyErr_Format(PyExc_ValueError,
2201-
"%s: Output operand %d has core dimension %d "
2202-
"unspecified, with gufunc signature %s",
2203-
ufunc_name, out_op, i, ufunc->core_signature);
2204-
retval = -1;
2225+
/* Collect the lengths of the labelled core dimensions */
2226+
retval = _get_coredim_sizes(ufunc, op, core_dim_sizes);
2227+
if(retval < 0) {
22052228
goto fail;
22062229
}
22072230

@@ -2212,7 +2235,6 @@ PyUFunc_GeneralizedFunction(PyUFuncObject *ufunc,
22122235

22132236
/* Fill in op_axes for all the operands */
22142237
j = broadcast_ndim;
2215-
core_dim_ixs_size = 0;
22162238
for (i = 0; i < nop; ++i) {
22172239
int n;
22182240
if (op[i]) {
@@ -2254,7 +2276,6 @@ PyUFunc_GeneralizedFunction(PyUFuncObject *ufunc,
22542276
}
22552277

22562278
op_axes[i] = op_axes_arrays[i];
2257-
core_dim_ixs_size += ufunc->core_num_dims[i];
22582279
}
22592280

22602281
/* Get the buffersize and errormask */
@@ -2369,6 +2390,10 @@ PyUFunc_GeneralizedFunction(PyUFuncObject *ufunc,
23692390
* Set up the inner strides array. Because we're not doing
23702391
* buffering, the strides are fixed throughout the looping.
23712392
*/
2393+
core_dim_ixs_size = 0;
2394+
for (i = 0; i < nop; ++i) {
2395+
core_dim_ixs_size += ufunc->core_num_dims[i];
2396+
}
23722397
inner_strides = (npy_intp *)PyArray_malloc(
23732398
NPY_SIZEOF_INTP * (nop+core_dim_ixs_size));
23742399
if (inner_strides == NULL) {
@@ -2607,7 +2632,7 @@ PyUFunc_GenericFunction(PyUFuncObject *ufunc,
26072632
nout = ufunc->nout;
26082633
nop = nin + nout;
26092634

2610-
ufunc_name = ufunc->name ? ufunc->name : "<unnamed ufunc>";
2635+
ufunc_name = _get_ufunc_name(ufunc);
26112636

26122637
NPY_UF_DBG_PRINT1("\nEvaluating ufunc %s\n", ufunc_name);
26132638

@@ -2847,7 +2872,7 @@ reduce_type_resolver(PyUFuncObject *ufunc, PyArrayObject *arr,
28472872
int i, retcode;
28482873
PyArrayObject *op[3] = {arr, arr, NULL};
28492874
PyArray_Descr *dtypes[3] = {NULL, NULL, NULL};
2850-
const char *ufunc_name = ufunc->name ? ufunc->name : "(unknown)";
2875+
const char *ufunc_name = _get_ufunc_name(ufunc);
28512876
PyObject *type_tup = NULL;
28522877

28532878
*out_dtype = NULL;
@@ -3036,7 +3061,7 @@ PyUFunc_Reduce(PyUFuncObject *ufunc, PyArrayObject *arr, PyArrayObject *out,
30363061
PyArray_Descr *dtype;
30373062
PyArrayObject *result;
30383063
PyArray_AssignReduceIdentityFunc *assign_identity = NULL;
3039-
const char *ufunc_name = ufunc->name ? ufunc->name : "(unknown)";
3064+
const char *ufunc_name = _get_ufunc_name(ufunc);
30403065
/* These parameters come from a TLS global */
30413066
int buffersize = 0, errormask = 0;
30423067

@@ -3144,7 +3169,7 @@ PyUFunc_Accumulate(PyUFuncObject *ufunc, PyArrayObject *arr, PyArrayObject *out,
31443169
PyUFuncGenericFunction innerloop = NULL;
31453170
void *innerloopdata = NULL;
31463171

3147-
const char *ufunc_name = ufunc->name ? ufunc->name : "(unknown)";
3172+
const char *ufunc_name = _get_ufunc_name(ufunc);
31483173

31493174
/* These parameters come from extobj= or from a TLS global */
31503175
int buffersize = 0, errormask = 0;
@@ -3511,7 +3536,7 @@ PyUFunc_Reduceat(PyUFuncObject *ufunc, PyArrayObject *arr, PyArrayObject *ind,
35113536
PyUFuncGenericFunction innerloop = NULL;
35123537
void *innerloopdata = NULL;
35133538

3514-
const char *ufunc_name = ufunc->name ? ufunc->name : "(unknown)";
3539+
const char *ufunc_name = _get_ufunc_name(ufunc);
35153540
char *opname = "reduceat";
35163541

35173542
/* These parameters come from extobj= or from a TLS global */

0 commit comments

Comments
 (0)
0