diff options
author | Charles Harris <charlesr.harris@gmail.com> | 2013-03-01 17:53:09 -0800 |
---|---|---|
committer | Charles Harris <charlesr.harris@gmail.com> | 2013-03-01 17:53:09 -0800 |
commit | 985b2674760176d7ec8d53ecc6a1a4f835a94b15 (patch) | |
tree | 01d48eaeab17a0f4654c63aaecd332b4db6590c9 /numpy | |
parent | d21281a6f4070afc706da90e40802b42ceda7e99 (diff) | |
parent | f2c81b485fa18e7ab918b470403c52751648dd3a (diff) | |
download | numpy-985b2674760176d7ec8d53ecc6a1a4f835a94b15.tar.gz |
Merge pull request #2953 from ContinuumIO/gufunc-fix
generalized ufunc signature problem fix
Diffstat (limited to 'numpy')
-rw-r--r-- | numpy/core/src/umath/ufunc_object.c | 240 | ||||
-rw-r--r-- | numpy/core/tests/test_ufunc.py | 9 |
2 files changed, 199 insertions, 50 deletions
diff --git a/numpy/core/src/umath/ufunc_object.c b/numpy/core/src/umath/ufunc_object.c index 2712f0474..124185bfd 100644 --- a/numpy/core/src/umath/ufunc_object.c +++ b/numpy/core/src/umath/ufunc_object.c @@ -71,6 +71,12 @@ static int _does_loop_use_arrays(void *data); +static int +assign_reduce_identity_zero(PyArrayObject *result); + +static int +assign_reduce_identity_one(PyArrayObject *result); + /* * fpstatus is the ufunc_formatted hardware status * errmask is the handling mask specified by the user. @@ -1643,7 +1649,7 @@ PyUFunc_GeneralizedFunction(PyUFuncObject *ufunc, PyArrayObject **op) { int nin, nout; - int i, idim, nop; + int i, j, idim, nop; char *ufunc_name; int retval = -1, subok = 1; int needs_api = 0; @@ -1651,11 +1657,12 @@ PyUFunc_GeneralizedFunction(PyUFuncObject *ufunc, PyArray_Descr *dtypes[NPY_MAXARGS]; /* Use remapped axes for generalized ufunc */ - int broadcast_ndim, op_ndim; + int broadcast_ndim, iter_ndim; int op_axes_arrays[NPY_MAXARGS][NPY_MAXDIMS]; int *op_axes[NPY_MAXARGS]; npy_uint32 op_flags[NPY_MAXARGS]; + npy_intp iter_shape[NPY_MAXARGS]; NpyIter *iter = NULL; @@ -1673,7 +1680,9 @@ PyUFunc_GeneralizedFunction(PyUFuncObject *ufunc, npy_intp *inner_strides = NULL; npy_intp *inner_strides_tmp, *ax_strides_tmp[NPY_MAXDIMS]; - int core_dim_ixs_size, *core_dim_ixs; + /* The sizes of the core dimensions (# entries is ufunc->core_num_dim_ix) */ + npy_intp *core_dim_sizes = inner_dimensions + 1; + int core_dim_ixs_size; /* The __array_prepare__ function to call for each output */ PyObject *arr_prep[NPY_MAXARGS]; @@ -1719,7 +1728,11 @@ PyUFunc_GeneralizedFunction(PyUFuncObject *ufunc, goto fail; } - /* Figure out the number of dimensions needed by the iterator */ + /* + * Figure out the number of iteration dimensions, which + * is the broadcast result of all the input non-core + * dimensions. + */ broadcast_ndim = 0; for (i = 0; i < nin; ++i) { int n = PyArray_NDIM(op[i]) - ufunc->core_num_dims[i]; @@ -1727,8 +1740,18 @@ PyUFunc_GeneralizedFunction(PyUFuncObject *ufunc, broadcast_ndim = n; } } - op_ndim = broadcast_ndim + ufunc->core_num_dim_ix; - if (op_ndim > NPY_MAXDIMS) { + + /* + * Figure out the number of iterator creation dimensions, + * which is the broadcast dimensions + all the core dimensions of + * the outputs, so that the iterator can allocate those output + * dimensions following the rules of order='F', for example. + */ + iter_ndim = broadcast_ndim; + for (i = nin; i < nop; ++i) { + iter_ndim += ufunc->core_num_dims[i]; + } + if (iter_ndim > NPY_MAXDIMS) { PyErr_Format(PyExc_ValueError, "too many dimensions for generalized ufunc %s", ufunc_name); @@ -1736,9 +1759,76 @@ PyUFunc_GeneralizedFunction(PyUFuncObject *ufunc, goto fail; } + /* + * Validate the core dimensions of all the operands, + * and collect all of the labeled core dimension sizes + * into the array 'core_dim_sizes'. Initialize them to + * 1, for example in the case where the operand broadcasts + * to a core dimension, it won't be visited. + */ + 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; + /* Make sure any output operand has enough dimensions */ + if (i >= nin && core_start_dim < 0) { + PyErr_Format(PyExc_ValueError, + "%s: Output operand %d does not have enough dimensions " + "(has %d, gufunc core with signature %s " + "requires %d)", + ufunc_name, i - nin, PyArray_NDIM(op[i]), + ufunc->core_signature, num_dims); + retval = -1; + goto fail; + } + + /* + * Make sure each core dimension matches all other core + * dimensions with the same label + * + * NOTE: For input operands, core_start_dim may be negative. + * In that case, the operand is being broadcast onto + * core dimensions. For example, a scalar will broadcast + * to fit any core signature. + */ + if (core_start_dim >= 0) { + idim = 0; + } else { + idim = -core_start_dim; + } + for (; idim < num_dims; ++idim) { + int core_dim_index = ufunc->core_dim_ixs[dim_offset + idim]; + npy_intp op_dim_size = + PyArray_SHAPE(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 ((i >= nin || op_dim_size != 1) && + core_dim_sizes[core_dim_index] != op_dim_size) { + PyErr_Format(PyExc_ValueError, + "%s: Operand %d has a mismatch in its core " + "dimension %d, with gufunc signature %s " + "(size %d is different from %d)", + ufunc_name, i, idim, ufunc->core_signature, + op_dim_size, core_dim_sizes[core_dim_index]); + retval = -1; + goto fail; + } + } + } + } + + /* Fill in the initial part of 'iter_shape' */ + for (idim = 0; idim < broadcast_ndim; ++idim) { + iter_shape[idim] = -1; + } + /* Fill in op_axes for all the operands */ + j = broadcast_ndim; core_dim_ixs_size = 0; - core_dim_ixs = ufunc->core_dim_ixs; for (i = 0; i < nop; ++i) { int n; if (op[i]) { @@ -1760,22 +1850,27 @@ PyUFunc_GeneralizedFunction(PyUFuncObject *ufunc, op_axes_arrays[i][idim] = -1; } } - /* Use the signature information for the rest */ - for (idim = broadcast_ndim; idim < op_ndim; ++idim) { + + /* Any output core dimensions shape should be ignored */ + for (idim = broadcast_ndim; idim < iter_ndim; ++idim) { op_axes_arrays[i][idim] = -1; } - for (idim = 0; idim < ufunc->core_num_dims[i]; ++idim) { - if (n + idim >= 0) { - op_axes_arrays[i][broadcast_ndim + core_dim_ixs[idim]] = - n + idim; - } - else { - op_axes_arrays[i][broadcast_ndim + core_dim_ixs[idim]] = -1; + + /* Except for when it belongs to this output */ + if (i >= nin) { + int dim_offset = ufunc->core_offsets[i]; + int num_dims = ufunc->core_num_dims[i]; + /* Fill in 'iter_shape' and 'op_axes' for this output */ + for (idim = 0; idim < num_dims; ++idim) { + iter_shape[j] = core_dim_sizes[ + ufunc->core_dim_ixs[dim_offset + idim]]; + op_axes_arrays[i][j] = n + idim; + ++j; } } - core_dim_ixs_size += ufunc->core_num_dims[i]; - core_dim_ixs += ufunc->core_num_dims[i]; + op_axes[i] = op_axes_arrays[i]; + core_dim_ixs_size += ufunc->core_num_dims[i]; } /* Get the buffersize, errormask, and error object globals */ @@ -1881,12 +1976,26 @@ PyUFunc_GeneralizedFunction(PyUFuncObject *ufunc, NPY_ITER_NO_BROADCAST; } + /* + * If there are no iteration dimensions, create a fake one + * so that the scalar edge case works right. + */ + if (iter_ndim == 0) { + iter_ndim = 1; + iter_shape[0] = 1; + for (i = 0; i < nop; ++i) { + op_axes[i][0] = -1; + } + } + /* Create the iterator */ iter = NpyIter_AdvancedNew(nop, op, NPY_ITER_MULTI_INDEX| NPY_ITER_REFS_OK| - NPY_ITER_REDUCE_OK, + NPY_ITER_REDUCE_OK| + NPY_ITER_ZEROSIZE_OK, order, NPY_UNSAFE_CASTING, op_flags, - dtypes, op_ndim, op_axes, NULL, 0); + dtypes, iter_ndim, + op_axes, iter_shape, 0); if (iter == NULL) { retval = -1; goto fail; @@ -1906,37 +2015,38 @@ PyUFunc_GeneralizedFunction(PyUFuncObject *ufunc, */ inner_strides = (npy_intp *)PyArray_malloc( NPY_SIZEOF_INTP * (nop+core_dim_ixs_size)); - /* The strides after the first nop match core_dim_ixs */ - core_dim_ixs = ufunc->core_dim_ixs; - inner_strides_tmp = inner_strides + nop; - for (idim = 0; idim < ufunc->core_num_dim_ix; ++idim) { - ax_strides_tmp[idim] = NpyIter_GetAxisStrideArray(iter, - broadcast_ndim+idim); - if (ax_strides_tmp[idim] == NULL) { - retval = -1; - goto fail; - } - } + /* Copy the strides after the first nop */ + idim = nop; for (i = 0; i < nop; ++i) { - for (idim = 0; idim < ufunc->core_num_dims[i]; ++idim) { - inner_strides_tmp[idim] = ax_strides_tmp[core_dim_ixs[idim]][i]; + 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; + /* + * Need to use the arrays in the iterator, not op, because + * a copy with a different-sized type may have been made. + */ + PyArrayObject *arr = NpyIter_GetOperandArray(iter)[i]; + npy_intp *shape = PyArray_SHAPE(arr); + npy_intp *strides = PyArray_STRIDES(arr); + for (j = 0; j < num_dims; ++j) { + if (core_start_dim + j >= 0) { + /* + * Force the stride to zero when the shape is 1, sot + * that the broadcasting works right. + */ + if (shape[core_start_dim + j] != 1) { + inner_strides[idim++] = strides[core_start_dim + j]; + } else { + inner_strides[idim++] = 0; + } + } else { + inner_strides[idim++] = 0; + } } - - core_dim_ixs += ufunc->core_num_dims[i]; - inner_strides_tmp += ufunc->core_num_dims[i]; - } - - /* Set up the inner dimensions array */ - if (NpyIter_GetShape(iter, inner_dimensions) != NPY_SUCCEED) { - retval = -1; - goto fail; } - /* Move the core dimensions to start at the second element */ - memmove(&inner_dimensions[1], &inner_dimensions[broadcast_ndim], - NPY_SIZEOF_INTP * ufunc->core_num_dim_ix); - /* Remove all the core dimensions from the iterator */ - for (i = 0; i < ufunc->core_num_dim_ix; ++i) { + /* Remove all the core output dimensions from the iterator */ + for (i = broadcast_ndim; i < iter_ndim; ++i) { if (NpyIter_RemoveAxis(iter, broadcast_ndim) != NPY_SUCCEED) { retval = -1; goto fail; @@ -1971,8 +2081,8 @@ PyUFunc_GeneralizedFunction(PyUFuncObject *ufunc, NPY_UF_DBG_PRINT("Executing inner loop\n"); - /* Do the ufunc loop */ if (NpyIter_GetIterSize(iter) != 0) { + /* Do the ufunc loop */ NpyIter_IterNextFunc *iternext; char **dataptr; npy_intp *count_ptr; @@ -1991,6 +2101,36 @@ PyUFunc_GeneralizedFunction(PyUFuncObject *ufunc, inner_dimensions[0] = *count_ptr; innerloop(dataptr, inner_dimensions, inner_strides, innerloopdata); } while (iternext(iter)); + } else { + /** + * For each output operand, check if it has non-zero size, + * and assign the identity if it does. For example, a dot + * product of two zero-length arrays will be a scalar, + * which has size one. + */ + for (i = nin; i < nop; ++i) { + if (PyArray_SIZE(op[i]) != 0) { + switch (ufunc->identity) { + case PyUFunc_Zero: + assign_reduce_identity_zero(op[i]); + break; + case PyUFunc_One: + assign_reduce_identity_one(op[i]); + break; + case PyUFunc_None: + case PyUFunc_ReorderableNone: + PyErr_Format(PyExc_ValueError, + "ufunc %s ", + ufunc_name); + goto fail; + default: + PyErr_Format(PyExc_ValueError, + "ufunc %s has an invalid identity for reduction", + ufunc_name); + goto fail; + } + } + } } /* Check whether any errors occurred during the loop */ @@ -2433,13 +2573,13 @@ reduce_type_resolver(PyUFuncObject *ufunc, PyArrayObject *arr, } static int -assign_reduce_identity_zero(PyArrayObject *result, void *data) +assign_reduce_identity_zero(PyArrayObject *result) { return PyArray_FillWithScalar(result, PyArrayScalar_False); } static int -assign_reduce_identity_one(PyArrayObject *result, void *data) +assign_reduce_identity_one(PyArrayObject *result) { return PyArray_FillWithScalar(result, PyArrayScalar_True); } diff --git a/numpy/core/tests/test_ufunc.py b/numpy/core/tests/test_ufunc.py index 1de7337c5..a0fbb4bba 100644 --- a/numpy/core/tests/test_ufunc.py +++ b/numpy/core/tests/test_ufunc.py @@ -316,6 +316,8 @@ class TestUfunc(TestCase): def test_inner1d(self): a = np.arange(6).reshape((2,3)) assert_array_equal(umt.inner1d(a,a), np.sum(a*a,axis=-1)) + a = np.arange(6) + assert_array_equal(umt.inner1d(a,a), np.sum(a*a)) def test_broadcast(self): msg = "broadcast" @@ -425,6 +427,13 @@ class TestUfunc(TestCase): w = np.arange(300,324).reshape((2,3,4)) assert_array_equal(umt.innerwt(a,b,w), np.sum(a*b*w,axis=-1)) + def test_innerwt_empty(self): + """Test generalized ufunc with zero-sized operands""" + a = np.array([], dtype='f8') + b = np.array([], dtype='f8') + w = np.array([], dtype='f8') + assert_array_equal(umt.innerwt(a,b,w), np.sum(a*b*w,axis=-1)) + def test_matrix_multiply(self): self.compare_matrix_multiply_results(np.long) self.compare_matrix_multiply_results(np.double) |