Skip to content

Commit edc4805

Browse files
committed
ENH: iter: Improve broadcasting error message
1 parent a6651a1 commit edc4805

File tree

2 files changed

+155
-25
lines changed

2 files changed

+155
-25
lines changed

numpy/core/include/numpy/ufuncobject.h

+25-12
Original file line numberDiff line numberDiff line change
@@ -44,19 +44,32 @@ typedef struct {
4444
PyObject *obj;
4545
PyObject *userloops;
4646

47-
/* generalized ufunc */
48-
int core_enabled; /* 0 for scalar ufunc; 1 for generalized ufunc */
49-
int core_num_dim_ix; /* number of distinct dimension names in
50-
signature */
47+
/* generalized ufunc parameters */
48+
49+
/* 0 for scalar ufunc; 1 for generalized ufunc */
50+
int core_enabled;
51+
/* number of distinct dimension names in signature */
52+
int core_num_dim_ix;
5153

52-
/* dimension indices of input/output argument k are stored in
53-
core_dim_ixs[core_offsets[k]..core_offsets[k]+core_num_dims[k]-1] */
54-
int *core_num_dims; /* numbers of core dimensions of each argument */
55-
int *core_dim_ixs; /* dimension indices in a flatted form; indices
56-
are in the range of [0,core_num_dim_ix) */
57-
int *core_offsets; /* positions of 1st core dimensions of each
58-
argument in core_dim_ixs */
59-
char *core_signature; /* signature string for printing purpose */
54+
/*
55+
* dimension indices of input/output argument k are stored in
56+
* core_dim_ixs[core_offsets[k]..core_offsets[k]+core_num_dims[k]-1]
57+
*/
58+
59+
/* numbers of core dimensions of each argument */
60+
int *core_num_dims;
61+
/*
62+
* dimension indices in a flatted form; indices
63+
* are in the range of [0,core_num_dim_ix)
64+
*/
65+
int *core_dim_ixs;
66+
/*
67+
* positions of 1st core dimensions of each
68+
* argument in core_dim_ixs
69+
*/
70+
int *core_offsets;
71+
/* signature string for printing purpose */
72+
char *core_signature;
6073
} PyUFuncObject;
6174

6275
#include "arrayobject.h"

numpy/core/src/multiarray/new_iterator.c.src

+130-13
Original file line numberDiff line numberDiff line change
@@ -2696,6 +2696,53 @@ npyiter_check_casting(npy_intp niter, PyArrayObject **op,
26962696
return 1;
26972697
}
26982698

2699+
static PyObject *
2700+
npyiter_shape_string(npy_intp n, npy_intp *vals, char *ending)
2701+
{
2702+
npy_intp i;
2703+
PyObject *ret, *tmp;
2704+
2705+
if (n <= 0) {
2706+
return PyString_FromString("()");
2707+
}
2708+
2709+
switch (n) {
2710+
case 1:
2711+
return PyString_FromFormat("(%zd)%s",
2712+
vals[0], ending);
2713+
case 2:
2714+
return PyString_FromFormat("(%zd,%zd)%s",
2715+
vals[0], vals[1], ending);
2716+
case 3:
2717+
return PyString_FromFormat("(%zd,%zd,%zd)%s",
2718+
vals[0], vals[1], vals[2], ending);
2719+
case 4:
2720+
return PyString_FromFormat("(%zd,%zd,%zd,%zd)%s",
2721+
vals[0], vals[1], vals[2], vals[3], ending);
2722+
}
2723+
2724+
ret = PyString_FromFormat("(%zd,%zd,%zd,%zd,%zd",
2725+
vals[0], vals[1], vals[2], vals[3], vals[4]);
2726+
if (ret == NULL) {
2727+
return NULL;
2728+
}
2729+
for (i = 5; i < n; ++i) {
2730+
tmp = PyString_FromFormat(",%zd", vals[i]);
2731+
if (tmp == NULL) {
2732+
Py_DECREF(ret);
2733+
return NULL;
2734+
}
2735+
2736+
PyString_ConcatAndDel(&ret, tmp);
2737+
if (ret == NULL) {
2738+
return NULL;
2739+
}
2740+
}
2741+
tmp = PyString_FromFormat(")%s", ending);
2742+
PyString_ConcatAndDel(&ret, tmp);
2743+
return ret;
2744+
}
2745+
26992746
/*
27002747
* Fills in the AXISDATA for the 'niter' operands, broadcasting
27012748
* the dimensionas as necessary. Also fills
@@ -2840,10 +2887,7 @@ npyiter_fill_axisdata(NpyIter *iter, char **op_dataptr,
28402887
NAD_SHAPE(axisdata) = shape;
28412888
}
28422889
else if (NAD_SHAPE(axisdata) != shape) {
2843-
PyErr_SetString(PyExc_ValueError,
2844-
"operands cannot be broadcast "
2845-
"to a single shape");
2846-
return 0;
2890+
goto broadcast_error;
28472891
}
28482892
NAD_STRIDES(axisdata)[iiter] = PyArray_STRIDE(
28492893
op[iiter], ondim-idim-1);
@@ -2890,10 +2934,7 @@ npyiter_fill_axisdata(NpyIter *iter, char **op_dataptr,
28902934
NAD_SHAPE(axisdata) = shape;
28912935
}
28922936
else if (NAD_SHAPE(axisdata) != shape) {
2893-
PyErr_SetString(PyExc_ValueError,
2894-
"operands cannot be broadcast "
2895-
"to a single shape");
2896-
return 0;
2937+
goto broadcast_error;
28972938
}
28982939
NAD_STRIDES(axisdata)[iiter] =
28992940
PyArray_STRIDE(op[iiter], i);
@@ -2929,14 +2970,16 @@ npyiter_fill_axisdata(NpyIter *iter, char **op_dataptr,
29292970
else {
29302971
i = ndim-idim-1;
29312972
}
2932-
if (i < PyArray_NDIM(op[iiter])) {
2973+
if (i >= 0 && i < PyArray_NDIM(op[iiter])) {
29332974
if (PyArray_DIM(op[iiter], i) != NAD_SHAPE(axisdata)) {
29342975
if (op_flags[iiter]&NPY_ITER_WRITEONLY) {
29352976
PyErr_SetString(PyExc_ValueError,
2936-
"output operand cannot be broadcasted");
2977+
"output operand is smaller "
2978+
"than the broadcast dimensions");
29372979
} else {
29382980
PyErr_SetString(PyExc_ValueError,
2939-
"operand cannot be broadcasted");
2981+
"operand is smaller "
2982+
"than the broadcast dimensions");
29402983
}
29412984
return 0;
29422985
}
@@ -2956,10 +2999,12 @@ npyiter_fill_axisdata(NpyIter *iter, char **op_dataptr,
29562999
else { /*if (NAD_SHAPE(axisdata) != 1) {*/
29573000
if (op_flags[iiter]&NPY_ITER_WRITEONLY) {
29583001
PyErr_SetString(PyExc_ValueError,
2959-
"output operand cannot be broadcasted");
3002+
"output operand is smaller "
3003+
"than the broadcast dimensions");
29603004
} else {
29613005
PyErr_SetString(PyExc_ValueError,
2962-
"operand cannot be broadcasted");
3006+
"operand is smaller "
3007+
"than the broadcast dimensions");
29633008
}
29643009
return 0;
29653010
}
@@ -2982,6 +3027,78 @@ npyiter_fill_axisdata(NpyIter *iter, char **op_dataptr,
29823027
NIT_ITEREND(iter) = NIT_ITERSIZE(iter);
29833028

29843029
return 1;
3030+
3031+
broadcast_error: {
3032+
PyObject *errmsg, *tmp;
3033+
npy_intp remdims[NPY_MAXDIMS];
3034+
char *tmpstr;
3035+
3036+
if (op_axes == NULL) {
3037+
errmsg = PyString_FromString("operands could not be broadcast "
3038+
"together with shapes ");
3039+
for (iiter = 0; iiter < niter; ++iiter) {
3040+
if (op[iiter] != NULL) {
3041+
tmp = npyiter_shape_string(PyArray_NDIM(op[iiter]),
3042+
PyArray_DIMS(op[iiter]),
3043+
" ");
3044+
if (tmp == NULL) {
3045+
return 0;
3046+
}
3047+
PyString_ConcatAndDel(&errmsg, tmp);
3048+
if (errmsg == NULL) {
3049+
return 0;
3050+
}
3051+
}
3052+
}
3053+
PyErr_SetObject(PyExc_ValueError, errmsg);
3054+
}
3055+
else {
3056+
errmsg = PyString_FromString("operands could not be broadcast "
3057+
"together with remapped shapes "
3058+
"[original->remapped]: ");
3059+
for (iiter = 0; iiter < niter; ++iiter) {
3060+
if (op[iiter] != NULL) {
3061+
npy_intp *axes = op_axes[iiter];
3062+
3063+
tmpstr = (axes == NULL) ? " " : "->";
3064+
tmp = npyiter_shape_string(PyArray_NDIM(op[iiter]),
3065+
PyArray_DIMS(op[iiter]),
3066+
tmpstr);
3067+
if (tmp == NULL) {
3068+
return 0;
3069+
}
3070+
PyString_ConcatAndDel(&errmsg, tmp);
3071+
if (errmsg == NULL) {
3072+
return 0;
3073+
}
3074+
3075+
if (axes != NULL) {
3076+
for (idim = 0; idim < ndim; ++idim) {
3077+
npy_intp i = axes[ndim-idim-1];
3078+
3079+
if (i >= 0 && i < PyArray_NDIM(op[iiter])) {
3080+
remdims[idim] = PyArray_DIM(op[iiter], i);
3081+
}
3082+
else {
3083+
remdims[idim] = 1;
3084+
}
3085+
}
3086+
tmp = npyiter_shape_string(ndim, remdims, " ");
3087+
if (tmp == NULL) {
3088+
return 0;
3089+
}
3090+
PyString_ConcatAndDel(&errmsg, tmp);
3091+
if (errmsg == NULL) {
3092+
return 0;
3093+
}
3094+
}
3095+
}
3096+
}
3097+
PyErr_SetObject(PyExc_ValueError, errmsg);
3098+
}
3099+
3100+
return 0;
3101+
}
29853102
}
29863103

29873104
/*

0 commit comments

Comments
 (0)