summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSebastian Berg <sebastian@sipsolutions.net>2021-10-21 16:31:18 -0500
committerSebastian Berg <sebastian@sipsolutions.net>2021-10-21 16:31:18 -0500
commitfb6a4a69526c69d1f995cfa50105a3b6b2b38a9d (patch)
treec116bae3166d4d238ecd0a730b3f235b032065ff
parentba3664766d1f8c6b2521b82edd717a2f2be974f2 (diff)
downloadnumpy-fb6a4a69526c69d1f995cfa50105a3b6b2b38a9d.tar.gz
BUG: Relax homogeneous signature fallback in type resolution
Relaxing the fallback means that reductions, which pass `(dtype, None, dtype)` as signature (`type_tup` in the old resolver) can resolve to the homogeneous loop if `dtype=dtype` is passed. This change is important, because the resolution changed from using `(None, None, dtype)` to `(dtype, None, dtype)` since reductions normally are expected to have the first input match the output. Specifying the signature more precisly without also relaxing the "homogeneous" fallback, however, lead to a regression noticed by Pandas. Closes gh-20151
-rw-r--r--numpy/core/src/umath/ufunc_type_resolution.c11
-rw-r--r--numpy/core/tests/test_ufunc.py5
2 files changed, 10 insertions, 6 deletions
diff --git a/numpy/core/src/umath/ufunc_type_resolution.c b/numpy/core/src/umath/ufunc_type_resolution.c
index aa8d7b982..9ed923cf5 100644
--- a/numpy/core/src/umath/ufunc_type_resolution.c
+++ b/numpy/core/src/umath/ufunc_type_resolution.c
@@ -2164,6 +2164,10 @@ type_tuple_type_resolver(PyUFuncObject *self,
* `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)`.
+ * Since reduction pass in `(dtype, None, dtype)` we broaden this to
+ * replacing all unspecified dtypes with the homogeneous output one.
+ * Note that this can (and often will) lead to unsafe casting. This is
+ * normally rejected (but not currently for reductions!).
* 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
@@ -2182,13 +2186,12 @@ type_tuple_type_resolver(PyUFuncObject *self,
if (homogeneous_type != NPY_NOTYPE) {
for (int i = 0; i < nin; i++) {
if (specified_types[i] != NPY_NOTYPE) {
- homogeneous_type = NPY_NOTYPE;
- break;
+ /* Never replace a specified type! */
+ continue;
}
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,
diff --git a/numpy/core/tests/test_ufunc.py b/numpy/core/tests/test_ufunc.py
index 78833a33c..4b06c8668 100644
--- a/numpy/core/tests/test_ufunc.py
+++ b/numpy/core/tests/test_ufunc.py
@@ -2383,8 +2383,9 @@ def test_reduce_casterrors(offset):
out = np.array(-1, dtype=np.intp)
count = sys.getrefcount(value)
- with pytest.raises(TypeError):
- # This is an unsafe cast, but we currently always allow that:
+ with pytest.raises(ValueError, match="invalid literal"):
+ # This is an unsafe cast, but we currently always allow that.
+ # Note that the double loop is picked, but the cast fails.
np.add.reduce(arr, dtype=np.intp, out=out)
assert count == sys.getrefcount(value)
# If an error occurred during casting, the operation is done at most until