10000 BUG: Check for warnings in ufunc.reduce · numpy/numpy@d47ca7b · GitHub
[go: up one dir, main page]

Skip to content

Commit d47ca7b

Browse files
committed
BUG: Check for warnings in ufunc.reduce
The two most visible consequences are warnings for: * np.min and np.max when inputs contain nan * np.sum when the summation (of floats) overflows Implementation taken from the code for ufunc.__call__ Fixes #8954
1 parent 1f9ab2c commit d47ca7b

File tree

6 files changed

+46
-9
lines changed

6 files changed

+46
-9
lines changed

numpy/core/src/umath/reduction.c

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
#include "lowlevel_strided_loops.h"
2525
#include "reduction.h"
26+
#include "extobj.h" /* for _check_ufunc_fperr */
2627

2728
/*
2829
* Allocates a result array for a reduction operation, with
@@ -437,6 +438,7 @@ PyArray_InitializeReduceResult(
437438
* data : Data which is passed to assign_identity and the inner loop.
438439
* buffersize : Buffer size for the iterator. For the default, pass in 0.
439440
* funcname : The name of the reduction function, for error messages.
441+
* errormask : forwarded from _get_bufsize_errmask
440442
*
441443
* TODO FIXME: if you squint, this is essentially an second independent
442444
* implementation of generalized ufuncs with signature (i)->(), plus a few
@@ -458,7 +460,8 @@ PyUFunc_ReduceWrapper(PyArrayObject *operand, PyArrayObject *out,
458460
int subok,
459461
PyArray_AssignReduceIdentityFunc *assign_identity,
460462
PyArray_ReduceLoopFunc *loop,
461-
void *data, npy_intp buffersize, const char *funcname)
463+
void *data, npy_intp buffersize, const char *funcname,
464+
int errormask)
462465
{
463466
PyArrayObject *result = NULL, *op_view = NULL;
464467
npy_intp skip_first_count = 0;
@@ -555,6 +558,9 @@ PyUFunc_ReduceWrapper(PyArrayObject *operand, PyArrayObject *out,
555558
goto fail;
556559
}
557560

561+
/* Start with the floating-point exception flags cleared */
562+
PyUFunc_clearfperr();
563+
558564
if (NpyIter_GetIterSize(iter) != 0) {
559565
NpyIter_IterNextFunc *iternext;
560566
char **dataptr;
@@ -586,6 +592,12 @@ PyUFunc_ReduceWrapper(PyArrayObject *operand, PyArrayObject *out,
586592
goto fail;
587593
}
588594
}
595+
596+
/* Check whether any errors occurred during the loop */
597+
if (PyErr_Occurred() ||
598+
_check_ufunc_fperr(errormask, NULL, "reduce") < 0) {
599+
goto fail;
600+
}
589601

590602
NpyIter_Deallocate(iter);
591603
Py_DECREF(op_view);

numpy/core/src/umath/reduction.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ typedef int (PyArray_ReduceLoopFunc)(NpyIter *iter,
137137
* data : Data which is passed to assign_identity and the inner loop.
138138
* buffersize : Buffer size for the iterator. For the default, pass in 0.
139139
* funcname : The name of the reduction function, for error messages.
140+
* errormask : forwarded from _get_bufsize_errmask
140141
*/
141142
NPY_NO_EXPORT PyArrayObject *
142143
PyUFunc_ReduceWrapper(PyArrayObject *operand, PyArrayObject *out,
@@ -149,6 +150,7 @@ PyUFunc_ReduceWrapper(PyArrayObject *operand, PyArrayObject *out,
149150
int subok,
150151
PyArray_AssignReduceIdentityFunc *assign_identity,
151152
PyArray_ReduceLoopFunc *loop,
152-
void *data, npy_intp buffersize, const char *funcname);
153+
void *data, npy_intp buffersize, const char *funcname,
154+
int errormask);
153155

154156
#endif

numpy/core/src/umath/ufunc_object.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2867,7 +2867,7 @@ PyUFunc_Reduce(PyUFuncObject *ufunc, PyArrayObject *arr, PyArrayObject *out,
28672867
keepdims, 0,
28682868
assign_identity,
28692869
reduce_loop,
2870-
ufunc, buffersize, ufunc_name);
2870+
ufunc, buffersize, ufunc_name, errormask);
28712871

28722872
Py_DECREF(dtype);
28732873
return result;

numpy/core/tests/test_multiarray.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3423,8 +3423,13 @@ def test_all(self):
34233423

34243424
def test_combinations(self):
34253425
for arr, pos in self.nan_arr:
3426+
with suppress_warnings() as sup:
3427+
sup.filter(RuntimeWarning,
3428+
"invalid value encountered in reduce")
3429+
max_val = np.max(arr)
3430+
34263431
assert_equal(np.argmax(arr), pos, err_msg="%r" % arr)
3427-
assert_equal(arr[np.argmax(arr)], np.max(arr), err_msg="%r" % arr)
3432+
assert_equal(arr[np.argmax(arr)], max_val, err_msg="%r" % arr)
34283433

34293434
def test_output_shape(self):
34303435
# see also gh-616
@@ -3552,8 +3557,13 @@ def test_all(self):
35523557

35533558
def test_combinations(self):
35543559
for arr, pos in self.nan_arr:
3560+
with suppress_warnings() as sup:
3561+
sup.filter(RuntimeWarning,
3562+
"invalid value encountered in reduce")
3563+
min_val = np.min(arr)
3564+
35553565
assert_equal(np.argmin(arr), pos, err_msg="%r" % arr)
3556-
assert_equal(arr[np.argmin(arr)], np.min(arr), err_msg="%r" % arr)
3566+
assert_equal(arr[np.argmin(arr)], min_val, err_msg="%r" % arr)
35573567

35583568
def test_minimum_signed_integers(self):
35593569

numpy/core/tests/test_ufunc.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
assert_no_warnings
1111
)
1212

13+
import warnings
1314

1415
class TestUfuncKwargs(TestCase):
1516
def test_kwarg_exact(self):
@@ -375,8 +376,17 @@ def test_sum(self):
375376
128, 1024, 1235):
376377
tgt = dt(v * (v + 1) / 2)
377378
d = np< 1070E /span>.arange(1, v + 1, dtype=dt)
378-
assert_almost_equal(np.sum(d), tgt)
379-
assert_almost_equal(np.sum(d[::-1]), tgt)
379+
380+
# warning if sum overflows, which it does in float16
381+
overflow = not np.isfinite(tgt)
382+
383+
with warnings.catch_warnings(record=True) as w:
384+
warnings.simplefilter("always")
385+
assert_almost_equal(np.sum(d), tgt)
386+
assert_equal(len(w), 1 * overflow)
387+
388+
assert_almost_equal(np.sum(d[::-1]), tgt)
389+
assert_equal(len(w), 2 * overflow)
380390

381391
d = np.ones(500, dtype=dt)
382392
assert_almost_equal(np.sum(d[::2]), 250.)

numpy/core/tests/test_umath.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1299,8 +1299,11 @@ def test_minmax_blocked(self):
12991299
inp[:] = np.arange(inp.size, dtype=dt)
13001300
inp[i] = np.nan
13011301
emsg = lambda: '%r\n%s' % (inp, msg)
1302-
assert_(np.isnan(inp.max()), msg=emsg)
1303-
assert_(np.isnan(inp.min()), msg=emsg)
1302+
with suppress_warnings() as sup:
1303+
sup.filter(RuntimeWarning,
1304+
"invalid value encountered in reduce")
1305+
assert_(np.isnan(inp.max()), msg=emsg)
1306+
assert_(np.isnan(inp.min()), msg=emsg)
13041307

13051308
inp[i] = 1e10
13061309
assert_equal(inp.max(), 1e10, err_msg=msg)

0 commit comments

Comments
 (0)
0