From 58463a00cac849c448154b79977f582b911c3ab6 Mon Sep 17 00:00:00 2001 From: Christopher Whelan Date: Fri, 3 Apr 2020 23:16:34 -0700 Subject: [PATCH] CLN: Add float and double versions of BN_NAN --- bottleneck/include/bottleneck.h | 10 +++-- bottleneck/src/move_template.c | 42 +++++++++--------- bottleneck/src/nonreduce_axis_template.c | 2 +- bottleneck/src/reduce_template.c | 48 +++++++++++---------- bottleneck/tests/list_input_test.py | 54 +++++++++++++++++------- bottleneck/tests/nonreduce_axis_test.py | 18 +++++--- bottleneck/tests/nonreduce_test.py | 12 ++++-- 7 files changed, 110 insertions(+), 76 deletions(-) diff --git a/bottleneck/include/bottleneck.h b/bottleneck/include/bottleneck.h index adf93da41c..9dc679894c 100644 --- a/bottleneck/include/bottleneck.h +++ b/bottleneck/include/bottleneck.h @@ -87,10 +87,12 @@ static inline float __bn_nanf(void) { return __bint.__f; } -#define BN_INFINITYF __bn_inff() -#define BN_NANF __bn_nanf() -#define BN_INFINITY ((npy_double)BN_INFINITYF) -#define BN_NAN ((npy_double)BN_NANF) +#define BN_INFINITYF __bn_inff() +#define BN_NANF __bn_nanf() +#define BN_INFINITY ((npy_double)BN_INFINITYF) +#define BN_NAN ((npy_double)BN_NANF) +#define BN_NAN_float32 BN_NANF +#define BN_NAN_float64 BN_NAN /* WIRTH ----------------------------------------------------------------- */ diff --git a/bottleneck/src/move_template.c b/bottleneck/src/move_template.c index cb4caf9ce6..7d2cc425d7 100644 --- a/bottleneck/src/move_template.c +++ b/bottleneck/src/move_template.c @@ -83,7 +83,7 @@ MOVE(move_sum, DTYPE0) { asum += ai; count += 1; } - YI(DTYPE0) = BN_NAN; + YI(DTYPE0) = BN_NAN_DTYPE0; } WHILE1 { const npy_DTYPE0 ai = AI(DTYPE0); @@ -91,7 +91,7 @@ MOVE(move_sum, DTYPE0) { asum += ai; count += 1; } - YI(DTYPE0) = count >= min_count ? asum : BN_NAN; + YI(DTYPE0) = count >= min_count ? asum : BN_NAN_DTYPE0; } WHILE2 { const npy_DTYPE0 ai = AI(DTYPE0); @@ -109,7 +109,7 @@ MOVE(move_sum, DTYPE0) { count--; } } - YI(DTYPE0) = count >= min_count ? asum : BN_NAN; + YI(DTYPE0) = count >= min_count ? asum : BN_NAN_DTYPE0; } NEXT2 } @@ -127,7 +127,7 @@ MOVE(move_sum, DTYPE0) { asum = 0; WHILE0 { asum += AI(DTYPE0); - YI(DTYPE1) = BN_NAN; + YI(DTYPE1) = BN_NAN_DTYPE1; } WHILE1 { asum += AI(DTYPE0); @@ -162,7 +162,7 @@ MOVE(move_mean, DTYPE0) { asum += ai; count += 1; } - YI(DTYPE0) = BN_NAN; + YI(DTYPE0) = BN_NAN_DTYPE0; } WHILE1 { const npy_DTYPE0 ai = AI(DTYPE0); @@ -170,7 +170,7 @@ MOVE(move_mean, DTYPE0) { asum += ai; count += 1; } - YI(DTYPE0) = count >= min_count ? asum / count : BN_NAN; + YI(DTYPE0) = count >= min_count ? asum / count : BN_NAN_DTYPE0; } count_inv = 1.0 / count; WHILE2 { @@ -191,7 +191,7 @@ MOVE(move_mean, DTYPE0) { count_inv = 1.0 / count; } } - YI(DTYPE0) = count >= min_count ? asum * count_inv : BN_NAN; + YI(DTYPE0) = count >= min_count ? asum * count_inv : BN_NAN_DTYPE0; } NEXT2 } @@ -209,7 +209,7 @@ MOVE(move_mean, DTYPE0) { asum = 0; WHILE0 { asum += AI(DTYPE0); - YI(DTYPE1) = BN_NAN; + YI(DTYPE1) = BN_NAN_DTYPE1; } WHILE1 { asum += AI(DTYPE0); @@ -249,7 +249,7 @@ MOVE(NAME, DTYPE0) { amean += delta / count; assqdm += delta * (ai - amean); } - YI(DTYPE0) = BN_NAN; + YI(DTYPE0) = BN_NAN_DTYPE0; } WHILE1 { const npy_DTYPE0 ai = AI(DTYPE0); @@ -265,7 +265,7 @@ MOVE(NAME, DTYPE0) { } yi = FUNC(assqdm / (count - ddof)); } else { - yi = BN_NAN; + yi = BN_NAN_DTYPE0; } YI(DTYPE0) = yi; } @@ -310,7 +310,7 @@ MOVE(NAME, DTYPE0) { } yi = FUNC(assqdm * ddof_inv); } else { - yi = BN_NAN; + yi = BN_NAN_DTYPE0; } YI(DTYPE0) = yi; } @@ -335,7 +335,7 @@ MOVE(NAME, DTYPE0) { const npy_DTYPE1 delta = ai - amean; amean += delta / (INDEX + 1); assqdm += delta * (ai - amean); - YI(DTYPE1) = BN_NAN; + YI(DTYPE1) = BN_NAN_DTYPE1; } WHILE1 { const npy_DTYPE1 ai = AI(DTYPE0); @@ -457,16 +457,16 @@ MOVE(NAME, DTYPE0) { extreme_pair->death = window; WHILE0 { MACRO_FLOAT(DTYPE0, - BN_NAN, ) + BN_NAN_DTYPE0, ) } WHILE1 { MACRO_FLOAT(DTYPE0, - count >= min_count ? VALUE : BN_NAN, ) + count >= min_count ? VALUE : BN_NAN_DTYPE0, ) } WHILE2 { MACRO_FLOAT( DTYPE0, - count >= min_count ? VALUE : BN_NAN, + count >= min_count ? VALUE : BN_NAN_DTYPE0, const npy_DTYPE0 aold = AOLD(DTYPE0); if (!bn_isnan(aold)) count--; if (extreme_pair->death == INDEX) { @@ -501,7 +501,7 @@ MOVE(NAME, DTYPE0) { WHILE0 { MACRO_INT(DTYPE0, DTYPE1, - BN_NAN, ) + BN_NAN_DTYPE1, ) } WHILE1 { MACRO_INT(DTYPE0, @@ -625,7 +625,7 @@ MOVE_MAIN(move_median, 0) } \ } \ if (n < min_count) { \ - r = BN_NAN; \ + r = BN_NAN_##dtype1; \ } else if (n == 1) { \ r = 0.0; \ } else { \ @@ -634,7 +634,7 @@ MOVE_MAIN(move_median, 0) r = 2.0 * (r - 0.5); \ } \ } else { \ - r = BN_NAN; \ + r = BN_NAN_##dtype1; \ } /* dtype = [['float64', 'float64'], ['float32', 'float32']] */ @@ -643,7 +643,7 @@ MOVE(move_rank, DTYPE0) { BN_BEGIN_ALLOW_THREADS WHILE { WHILE0 { - YI(DTYPE1) = BN_NAN; + YI(DTYPE1) = BN_NAN_DTYPE1; } WHILE1 { MOVE_RANK(DTYPE0, DTYPE1, 0) @@ -668,7 +668,7 @@ MOVE(move_rank, DTYPE0) { BN_BEGIN_ALLOW_THREADS WHILE { WHILE0 { - YI(DTYPE1) = BN_NAN; + YI(DTYPE1) = BN_NAN_DTYPE1; } WHILE1 { const npy_DTYPE0 ai = AI(DTYPE0); @@ -683,7 +683,7 @@ MOVE(move_rank, DTYPE0) { } } if (INDEX < min_count - 1) { - r = BN_NAN; + r = BN_NAN_DTYPE1; } else if (INDEX == 0) { r = 0.0; } else { diff --git a/bottleneck/src/nonreduce_axis_template.c b/bottleneck/src/nonreduce_axis_template.c index 142945b7af..d1b5a065ca 100644 --- a/bottleneck/src/nonreduce_axis_template.c +++ b/bottleneck/src/nonreduce_axis_template.c @@ -363,7 +363,7 @@ NRA(push, DTYPE0) { BN_BEGIN_ALLOW_THREADS WHILE { index = 0; - ai_last = BN_NAN; + ai_last = BN_NAN_DTYPE0; FOR { ai = AI(DTYPE0); if (!bn_isnan(ai)) { diff --git a/bottleneck/src/reduce_template.c b/bottleneck/src/reduce_template.c index f80d96ab03..5a19b2f11d 100644 --- a/bottleneck/src/reduce_template.c +++ b/bottleneck/src/reduce_template.c @@ -228,7 +228,7 @@ REDUCE_ALL(nanmean, DTYPE0) { if (count > 0) { return PyFloat_FromDouble(asum / count); } else { - return PyFloat_FromDouble(BN_NAN); + return PyFloat_FromDouble(BN_NAN_DTYPE0); } } @@ -236,7 +236,7 @@ REDUCE_ONE(nanmean, DTYPE0) { INIT_ONE(DTYPE0, DTYPE0) BN_BEGIN_ALLOW_THREADS if (LENGTH == 0) { - FILL_Y(BN_NAN) + FILL_Y(BN_NAN_DTYPE0) } else { WHILE { Py_ssize_t count = 0; @@ -251,7 +251,7 @@ REDUCE_ONE(nanmean, DTYPE0) { if (count > 0) { asum /= count; } else { - asum = BN_NAN; + asum = BN_NAN_DTYPE0; } YPP = asum; NEXT @@ -279,7 +279,7 @@ REDUCE_ALL(nanmean, DTYPE0) { if (total_length > 0) { return PyFloat_FromDouble(asum / total_length); } else { - return PyFloat_FromDouble(BN_NAN); + return PyFloat_FromDouble(BN_NAN_DTYPE1); } } @@ -287,7 +287,7 @@ REDUCE_ONE(nanmean, DTYPE0) { INIT_ONE(DTYPE1, DTYPE1) BN_BEGIN_ALLOW_THREADS if (LENGTH == 0) { - FILL_Y(BN_NAN) + FILL_Y(BN_NAN_DTYPE1) } else { WHILE { npy_DTYPE1 asum = 0; @@ -297,7 +297,7 @@ REDUCE_ONE(nanmean, DTYPE0) { if (LENGTH > 0) { asum /= LENGTH; } else { - asum = BN_NAN; + asum = BN_NAN_DTYPE1; } YPP = asum; NEXT @@ -346,7 +346,7 @@ REDUCE_ALL(NAME, DTYPE0) { } out = FUNC(asum / (count - ddof)); } else { - out = BN_NAN; + out = BN_NAN_DTYPE0; } BN_END_ALLOW_THREADS return PyFloat_FromDouble(out); @@ -356,7 +356,7 @@ REDUCE_ONE(NAME, DTYPE0) { INIT_ONE(DTYPE0, DTYPE0) BN_BEGIN_ALLOW_THREADS if (LENGTH == 0) { - FILL_Y(BN_NAN) + FILL_Y(BN_NAN_DTYPE0) } else { WHILE { Py_ssize_t count = 0; @@ -380,7 +380,7 @@ REDUCE_ONE(NAME, DTYPE0) { } asum = FUNC(asum / (count - ddof)); } else { - asum = BN_NAN; + asum = BN_NAN_DTYPE0; } YPP = asum; NEXT @@ -418,7 +418,7 @@ REDUCE_ALL(NAME, DTYPE0) { } out = FUNC(asum / (size - ddof)); } else { - out = BN_NAN; + out = BN_NAN_DTYPE1; } BN_END_ALLOW_THREADS return PyFloat_FromDouble(out); @@ -430,7 +430,7 @@ REDUCE_ONE(NAME, DTYPE0) { const npy_DTYPE1 length_inv = 1.0 / LENGTH; const npy_DTYPE1 length_ddof_inv = 1.0 / (LENGTH - ddof); if (LENGTH == 0) { - FILL_Y(BN_NAN) + FILL_Y(BN_NAN_DTYPE1) } else { WHILE { npy_DTYPE1 asum = 0; @@ -446,7 +446,7 @@ REDUCE_ONE(NAME, DTYPE0) { } asum = FUNC(asum * length_ddof_inv); } else { - asum = BN_NAN; + asum = BN_NAN_DTYPE1; } YPP = asum; NEXT @@ -537,7 +537,7 @@ REDUCE_ALL(NAME, DTYPE0) { } } if (is_allnan) { - extreme = BN_NAN; + extreme = BN_NAN_DTYPE0; } BN_END_ALLOW_THREADS return PyFloat_FromDouble(extreme); @@ -591,9 +591,9 @@ REDUCE_ONE(NAME, DTYPE0) { } for (npy_intp k = 0; k < LOOP_SIZE; k++) { if (allnans[k]) { - py[k] = BN_NAN; + py[k] = BN_NAN_DTYPE0; } else { - py[k] = extremes[k]; + py[k] = extremes[i]; } } free(extremes); @@ -642,7 +642,7 @@ REDUCE_ONE(NAME, DTYPE0) { } if (is_allnan) { - extreme = BN_NAN; + extreme = BN_NAN_DTYPE0; } YPP = extreme; NEXT @@ -658,7 +658,9 @@ REDUCE_ONE(NAME, DTYPE0) { is_allnan = 0; } } - if (is_allnan) extreme = BN_NAN; + if (is_allnan) { + extreme = BN_NAN_DTYPE0; + } YPP = extreme; NEXT } @@ -1012,7 +1014,7 @@ REDUCE_MAIN(ss, 0) } \ } \ if (l != LENGTH) { \ - med = BN_NAN; \ + med = BN_NAN_##dtype; \ goto done; \ } \ k = LENGTH >> 1; \ @@ -1055,7 +1057,7 @@ REDUCE_MAIN(ss, 0) l = 0; \ r = n - 1; \ if (n == 0) { \ - med = BN_NAN; \ + med = BN_NAN_##dtype; \ goto done; \ } \ PARTITION(dtype) \ @@ -1077,7 +1079,7 @@ REDUCE_ALL(NAME, DTYPE0) { INIT_ALL_RAVEL_ANY_ORDER BN_BEGIN_ALLOW_THREADS if (LENGTH == 0) { - med = BN_NAN; + med = BN_NAN_DTYPE1; } else { BUFFER_NEW(DTYPE0, LENGTH) FUNC(DTYPE0) @@ -1093,7 +1095,7 @@ REDUCE_ONE(NAME, DTYPE0) { INIT_ONE(DTYPE1, DTYPE1) BN_BEGIN_ALLOW_THREADS if (LENGTH == 0) { - FILL_Y(BN_NAN) + FILL_Y(BN_NAN_DTYPE1) } else { npy_DTYPE1 med = BN_NAN; BUFFER_NEW(DTYPE0, LENGTH) @@ -1118,7 +1120,7 @@ REDUCE_ALL(median, DTYPE0) { INIT_ALL_RAVEL_ANY_ORDER BN_BEGIN_ALLOW_THREADS if (LENGTH == 0) { - med = BN_NAN; + med = BN_NAN_DTYPE1; } else { BUFFER_NEW(DTYPE0, LENGTH) MEDIAN_INT(DTYPE0) @@ -1134,7 +1136,7 @@ REDUCE_ONE(median, DTYPE0) { INIT_ONE(DTYPE1, DTYPE1) BN_BEGIN_ALLOW_THREADS if (LENGTH == 0) { - FILL_Y(BN_NAN) + FILL_Y(BN_NAN_DTYPE1) } else { npy_DTYPE1 med; BUFFER_NEW(DTYPE0, LENGTH) diff --git a/bottleneck/tests/list_input_test.py b/bottleneck/tests/list_input_test.py index 0dd943ae91..11bec90a08 100644 --- a/bottleneck/tests/list_input_test.py +++ b/bottleneck/tests/list_input_test.py @@ -2,12 +2,13 @@ import warnings +import hypothesis import numpy as np import pytest from numpy.testing import assert_array_almost_equal import bottleneck as bn -from .util import DTYPES +from .util import DTYPES, get_functions, hy_lists def lists(dtypes=DTYPES): @@ -27,8 +28,11 @@ def lists(dtypes=DTYPES): yield a.astype(dtype).tolist() -@pytest.mark.parametrize("func", bn.get_functions("all"), ids=lambda x: x.__name__) -def test_list_input(func): +@hypothesis.given(input_list=hy_lists()) +@pytest.mark.parametrize( + "func", get_functions("all"), ids=lambda x: x.__name__, +) +def test_list_input(func, input_list) -> None: """Test that bn.xxx gives the same output as bn.slow.xxx for list input.""" msg = "\nfunc %s | input %s (%s) | shape %s\n" msg += "\nInput array:\n%s\n" @@ -36,16 +40,34 @@ def test_list_input(func): if name == "replace": return func0 = eval("bn.slow.%s" % name) - for i, a in enumerate(lists()): - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - try: - actual = func(a) - desired = func0(a) - except TypeError: - actual = func(a, 2) - desired = func0(a, 2) - a = np.array(a) - tup = (name, "a" + str(i), str(a.dtype), str(a.shape), a) - err_msg = msg % tup - assert_array_almost_equal(actual, desired, err_msg=err_msg) + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + + actual_raised = False + desired_raised = False + + try: + if any(x in func.__name__ for x in ["move", "partition"]): + actual = func(input_list, 2) + else: + actual = func(input_list) + except ValueError: + actual_raised = True + + try: + if any(x in func.__name__ for x in ["move", "partition"]): + desired = func0(input_list, 2) + else: + desired = func0(input_list) + except ValueError: + desired_raised = True + + if actual_raised and desired_raised: + return + + assert not (actual_raised or desired_raised) + + a = np.array(input_list) + tup = (name, "a", str(a.dtype), str(a.shape), a) + err_msg = msg % tup + assert_array_almost_equal(actual, desired, err_msg=err_msg) diff --git a/bottleneck/tests/nonreduce_axis_test.py b/bottleneck/tests/nonreduce_axis_test.py index 831ce15b19..f503347eec 100644 --- a/bottleneck/tests/nonreduce_axis_test.py +++ b/bottleneck/tests/nonreduce_axis_test.py @@ -1,5 +1,6 @@ import numpy as np import pytest +import hypothesis from numpy.testing import ( assert_array_almost_equal, assert_array_equal, @@ -10,7 +11,7 @@ import bottleneck as bn from .reduce_test import unit_maker as reduce_unit_maker from .reduce_test import unit_maker_argparse as unit_maker_parse_rankdata -from .util import DTYPES, array_order, arrays +from .util import DTYPES, array_order, arrays, hy_array_gen # --------------------------------------------------------------------------- # partition, argpartition @@ -62,8 +63,8 @@ def test_partition_and_argpartition(func) -> None: assert_array_equal(s1, s0, err_msg) -def complete_the_partition(a, n, axis): - def func1d(a, n): +def complete_the_partition(a: np.ndarray, n: int, axis: int) -> np.ndarray: + def func1d(a: np.ndarray, n: int) -> np.ndarray: a[:n] = np.sort(a[:n]) a[n + 1 :] = np.sort(a[n + 1 :]) return a @@ -127,11 +128,14 @@ def complete_the_argpartition(index, a, n, axis): return a -def test_transpose() -> None: +@hypothesis.given(array=hy_array_gen) +def test_transpose(array) -> None: """partition transpose test""" - a = np.arange(12).reshape(4, 3) - actual = bn.partition(a.T, 2, -1).T - desired = bn.slow.partition(a.T, 2, -1).T + if min(array.shape) < 3: + return + + actual = bn.partition(array.T, 2, -1).T + desired = bn.slow.partition(array.T, 2, -1).T assert_equal(actual, desired, "partition transpose test") diff --git a/bottleneck/tests/nonreduce_test.py b/bottleneck/tests/nonreduce_test.py index b1081b2ff7..d62d871e1a 100644 --- a/bottleneck/tests/nonreduce_test.py +++ b/bottleneck/tests/nonreduce_test.py @@ -80,10 +80,14 @@ def test_replace_unsafe_cast() -> None: def test_non_array() -> None: """Test that non-array input raises""" - a = [1, 2, 3] - assert_raises(TypeError, bn.replace, a, 0, 1) - a = (1, 2, 3) - assert_raises(TypeError, bn.replace, a, 0, 1) + list_input = [1, 2, 3] + assert_raises(TypeError, bn.replace, list_input, 0, 1) + + tuple_input = (1, 2, 3) + assert_raises(TypeError, bn.replace, tuple_input, 0, 1) + + integer_input = 1 + assert_raises(TypeError, bn.replace, integer_input, 0, 1) # ---------------------------------------------------------------------------