diff options
Diffstat (limited to 'numpy')
| -rw-r--r-- | numpy/core/code_generators/generate_umath.py | 2 | ||||
| -rw-r--r-- | numpy/core/code_generators/ufunc_docstrings.py | 4 | ||||
| -rw-r--r-- | numpy/core/src/umath/ufunc_type_resolution.c | 205 | ||||
| -rw-r--r-- | numpy/core/tests/test_scalarmath.py | 3 | ||||
| -rw-r--r-- | numpy/core/tests/test_ufunc.py | 4 |
5 files changed, 135 insertions, 83 deletions
diff --git a/numpy/core/code_generators/generate_umath.py b/numpy/core/code_generators/generate_umath.py index ea74c107f..6b6a0fe64 100644 --- a/numpy/core/code_generators/generate_umath.py +++ b/numpy/core/code_generators/generate_umath.py @@ -902,7 +902,7 @@ defdict = { 'nextafter': Ufunc(2, 1, None, docstrings.get('numpy.core.umath.nextafter'), - "PyUFunc_SimpleUniformOperationTypeResolver", + None, TD(flts), ), 'spacing': diff --git a/numpy/core/code_generators/ufunc_docstrings.py b/numpy/core/code_generators/ufunc_docstrings.py index e33d71ab3..f19946be4 100644 --- a/numpy/core/code_generators/ufunc_docstrings.py +++ b/numpy/core/code_generators/ufunc_docstrings.py @@ -3628,9 +3628,9 @@ add_newdoc('numpy.core.umath', 'nextafter', Examples -------- >>> eps = np.finfo(np.float64).eps - >>> np.nextafter(1., 2.) == eps + 1 + >>> np.nextafter(1, 2) == eps + 1 True - >>> np.nextafter([1., 2.], [2., 1.]) == [eps + 1, 2 - eps] + >>> np.nextafter([1, 2], [2, 1]) == [eps + 1, 2 - eps] array([ True, True]) """) diff --git a/numpy/core/src/umath/ufunc_type_resolution.c b/numpy/core/src/umath/ufunc_type_resolution.c index a747c0e02..ac92b62df 100644 --- a/numpy/core/src/umath/ufunc_type_resolution.c +++ b/numpy/core/src/umath/ufunc_type_resolution.c @@ -1201,20 +1201,8 @@ PyUFunc_DivisionTypeResolver(PyUFuncObject *ufunc, /* Use the default when datetime and timedelta are not involved */ if (!PyTypeNum_ISDATETIME(type_num1) && !PyTypeNum_ISDATETIME(type_num2)) { - int res = PyUFunc_SimpleUniformOperationTypeResolver(ufunc, - casting, operands, type_tup, out_dtypes); - if (res < 0 || out_dtypes[0]->type_num != NPY_BOOL) { - return res; - } - /* - * Hardcode that boolean division is handled by casting to int8, - * we could consider deprecating this (this is safe so no need to - * "validate casting" again. - */ - Py_SETREF(out_dtypes[0], PyArray_DescrFromType(NPY_BYTE)); - Py_SETREF(out_dtypes[1], PyArray_DescrFromType(NPY_BYTE)); - Py_SETREF(out_dtypes[2], PyArray_DescrFromType(NPY_BYTE)); - return res; + return PyUFunc_DefaultTypeResolver(ufunc, casting, operands, + type_tup, out_dtypes); } if (type_num1 == NPY_TIMEDELTA) { @@ -2101,6 +2089,91 @@ linear_search_type_resolver(PyUFuncObject *self, return -1; } + +static int +type_tuple_type_resolver_core(PyUFuncObject *self, + PyArrayObject **op, + NPY_CASTING input_casting, NPY_CASTING casting, + int specified_types[], + int any_object, + int no_castable_output, int use_min_scalar, + PyArray_Descr **out_dtype) +{ + int i, j; + int nop = self->nargs; + int types[NPY_MAXARGS]; + + /* For making a better error message on coercion error */ + char err_dst_typecode = '-', err_src_typecode = '-'; + + /* If the ufunc has userloops, search for them. */ + if (self->userloops) { + switch (type_tuple_userloop_type_resolver(self, + nop, specified_types, + op, input_casting, casting, + any_object, use_min_scalar, + out_dtype)) { + /* Error */ + case -1: + return -1; + /* Found matching loop */ + case 1: + return 0; + } + } + + for (i = 0; i < self->ntypes; ++i) { + char *orig_types = self->types + i*self->nargs; + + /* Check specified types and copy into an int array for matching */ + for (j = 0; j < nop; ++j) { + if (specified_types[j] == NPY_NOTYPE) { + types[j] = orig_types[j]; + continue; + } + if (orig_types[j] != specified_types[j]) { + break; + } + /* indicate that we do not have to check this type anymore. */ + types[j] = NPY_NOTYPE; + } + if (j < nop) { + /* no match */ + continue; + } + + switch (ufunc_loop_matches(self, op, + input_casting, casting, + any_object, use_min_scalar, + types, NULL, + &no_castable_output, &err_src_typecode, + &err_dst_typecode)) { + case -1: + /* Error */ + return -1; + case 0: + /* Cannot cast inputs */ + continue; + case 1: + /* Success, fill also the NPY_NOTYPE (cast from char to int) */ + for (j = 0; j < nop; j++) { + types[j] = orig_types[j]; + } + set_ufunc_loop_data_types(self, op, out_dtype, types, NULL); + /* In principle, we only need to validate the NPY_NOTYPE ones */ + if (PyUFunc_ValidateCasting(self, casting, op, out_dtype) < 0) { + for (j = 0; j < self->nargs; j++) { + Py_DECREF(out_dtype[j]); + out_dtype[j] = NULL; + } + return -1; + } + return 0; + } + } + return -2; +} + /* * Does a linear search for the inner loop of the ufunc specified by type_tup. * @@ -2116,14 +2189,11 @@ type_tuple_type_resolver(PyUFuncObject *self, int any_object, PyArray_Descr **out_dtype) { - int i, j, nin = self->nin, nop = nin + self->nout; - int specified_types[NPY_MAXARGS], types[NPY_MAXARGS]; + int nin = self->nin, nop = nin + self->nout; + int specified_types[NPY_MAXARGS]; const char *ufunc_name; int no_castable_output = 0, use_min_scalar; - /* For making a better error message on coercion error */ - char err_dst_typecode = '-', err_src_typecode = '-'; - ufunc_name = ufunc_get_name_cstr(self); use_min_scalar = should_use_min_scalar(nin, op, 0, NULL); @@ -2145,7 +2215,7 @@ type_tuple_type_resolver(PyUFuncObject *self, PyErr_SetString(PyExc_RuntimeError, bad_type_tup_msg); return -1; } - for (i = 0; i < nop; ++i) { + for (int i = 0; i < nop; ++i) { PyObject *item = PyTuple_GET_ITEM(type_tup, i); if (item == Py_None) { specified_types[i] = NPY_NOTYPE; @@ -2164,70 +2234,51 @@ type_tuple_type_resolver(PyUFuncObject *self, return -1; } - /* If the ufunc has userloops, search for them. */ - if (self->userloops) { - switch (type_tuple_userloop_type_resolver(self, - nop, specified_types, - op, input_casting, casting, - any_object, use_min_scalar, - out_dtype)) { - /* Error */ - case -1: - return -1; - /* Found matching loop */ - case 1: - return 0; - } - } + int res = type_tuple_type_resolver_core(self, + op, input_casting, casting, specified_types, any_object, + no_castable_output, use_min_scalar, out_dtype); - for (i = 0; i < self->ntypes; ++i) { - char *orig_types = self->types + i*self->nargs; + if (res != -2) { + return res; + } - /* Check specified types and copy into an int array for matching */ - for (j = 0; j < nop; ++j) { - if (specified_types[j] == NPY_NOTYPE) { - types[j] = orig_types[j]; - continue; - } - if (orig_types[j] != specified_types[j]) { + /* + * When the user passed `dtype=dtype`, it gets translated to + * `signature=(None,)*nin + (dtype,)*nout`. If the signature matches that + * exactly (could be relaxed but that is not necessary for backcompat), + * we also try `signature=(dtype,)*(nin+nout)`. + * This used to be the main meaning for `dtype=dtype`, but some calls broke + * the expectation, and changing it, allows for `dtype=dtype` to be useful + * for ufuncs like `np.ldexp` in the future while also normalizing it to + * a `signature` early on. + */ + int homogeneous_type = NPY_NOTYPE; + if (self->nout > 0) { + homogeneous_type = specified_types[nin]; + for (int i = nin+1; i < nop; i++) { + if (specified_types[i] != homogeneous_type) { + homogeneous_type = NPY_NOTYPE; break; } - /* indicate that we do not have to check this type anymore. */ - types[j] = NPY_NOTYPE; } - if (j < nop) { - /* no match */ - continue; + } + if (homogeneous_type != NPY_NOTYPE) { + for (int i = 0; i < nin; i++) { + if (specified_types[i] != NPY_NOTYPE) { + homogeneous_type = NPY_NOTYPE; + break; + } + specified_types[i] = homogeneous_type; } + } + if (homogeneous_type != NPY_NOTYPE) { + /* Try again with the homogeneous specified types. */ + res = type_tuple_type_resolver_core(self, + op, input_casting, casting, specified_types, any_object, + no_castable_output, use_min_scalar, out_dtype); - switch (ufunc_loop_matches(self, op, - input_casting, casting, - any_object, use_min_scalar, - types, NULL, - &no_castable_output, &err_src_typecode, - &err_dst_typecode)) { - case -1: - /* Error */ - return -1; - case 0: - /* Cannot cast inputs */ - continue; - case 1: - /* Success, fill also the NPY_NOTYPE (cast from char to int) */ - for (j = 0; j < nop; j++) { - types[j] = orig_types[j]; - } - set_ufunc_loop_data_types(self, op, out_dtype, types, NULL); - /* In principle, we only need to validate the NPY_NOTYPE ones */ - if (PyUFunc_ValidateCasting(self, casting, op, out_dtype) < 0) { - for (j = 0; j < self->nargs; j++) { - Py_DECREF(out_dtype[j]); - out_dtype[j] = NULL; - } - return -1; - } - - return 0; + if (res != -2) { + return res; } } diff --git a/numpy/core/tests/test_scalarmath.py b/numpy/core/tests/test_scalarmath.py index 0718f365e..d91b4a391 100644 --- a/numpy/core/tests/test_scalarmath.py +++ b/numpy/core/tests/test_scalarmath.py @@ -307,7 +307,8 @@ class TestModulus: # promotes to float which does not fit a = np.array([1, 2], np.int64) b = np.array([1, 2], np.uint64) - with pytest.raises(TypeError, match=r"Cannot cast ufunc"): + pattern = 'could not be coerced to provided output parameter' + with assert_raises_regex(TypeError, pattern): a //= b diff --git a/numpy/core/tests/test_ufunc.py b/numpy/core/tests/test_ufunc.py index 47606876e..c13865ce4 100644 --- a/numpy/core/tests/test_ufunc.py +++ b/numpy/core/tests/test_ufunc.py @@ -2201,8 +2201,8 @@ def test_ufunc_casterrors(): # was aborted (this is not necessarily defined behaviour) assert out[-1] == 1 - with pytest.raises(TypeError): - # Input "casting" failure (there is no intp out loop for object inputs) + with pytest.raises(ValueError): + # Input casting failure: np.add(arr, arr, out=out, dtype=np.intp, casting="unsafe") assert count == sys.getrefcount(value) |
