From 58463a00cac849c448154b79977f582b911c3ab6 Mon Sep 17 00:00:00 2001
From: Christopher Whelan <topherwhelan@gmail.com>
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)
 
 
 # ---------------------------------------------------------------------------