summaryrefslogtreecommitdiff
path: root/numpy
diff options
context:
space:
mode:
Diffstat (limited to 'numpy')
-rw-r--r--numpy/core/code_generators/generate_umath.py2
-rw-r--r--numpy/core/code_generators/ufunc_docstrings.py4
-rw-r--r--numpy/core/src/umath/ufunc_type_resolution.c205
-rw-r--r--numpy/core/tests/test_scalarmath.py3
-rw-r--r--numpy/core/tests/test_ufunc.py4
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)