Repository: numba/numba

Issue:
Error in detecting nans
Hello,
I'm encountering a strange issue when trying to jit the following function : 

Unjitted version:

``` python
def bab( tsbeta, rtn, weight ):
    n = len(tsbeta)
    vidx = ~(isfinite(tsbeta) & isfinite(rtn) & isfinite(weight) )
    btr = float64(0);
    btb = float64(0);
    for i in range(n):
        if vidx[i]: continue;            
        omb = tsbeta[i] - 1.0;        
        btr += rtn[i]
        btb += pow(omb,2)*weight[i];    
    return btr/btb , btr, btb

x = ones(100)
x[85:90] = nan
bab(   0.5*ones(100),   x,    ones(100))
```

gives output
`(4.0, 95.0, 23.75)`

However, 

``` python
@jit
def bab( tsbeta, rtn, weight ):
    n = len(tsbeta)
    vidx = ~(isfinite(tsbeta) & isfinite(rtn) & isfinite(weight) )
    btr = float64(0);
    btb = float64(0);
    for i in range(n):
        if vidx[i]: continue;            
        omb = tsbeta[i] - 1.0;        
        btr += rtn[i]
        btb += pow(omb,2)*weight[i];    
    return btr/btb , btr, btb
```

for the same input throws a ZeroDivisionError, because for some reason btb evaluates to 0.

So then, I try this version:

``` python
@jit
def bab( tsbeta, rtn, weight ):
    n = len(tsbeta)
    vidx = ~(isfinite(tsbeta) & isfinite(rtn) & isfinite(weight) )
    btr = float64(0);
    btb = float64(0);
    for i in range(n):
        if vidx[i]: continue;            
        omb = tsbeta[i] - 1.0;        
        btr += rtn[i]
        btb += pow(omb,2)*weight[i];    
    return btr/btb if btb > 0 else nan , btr, btb
```

And the output is:
`(nan, 0.0, 0.0)`.

Any ideas of what's going on ?



Discussion:
Hi and thanks for the report.

As for the ZeroDivisionError: you can use `np.divide()` (i.e. return `inf` or `nan` instead of raising).

As for the bogus results: it seems this is a bug in Numba. We will try to fix this soon.


Resolving patch:
diff --git a/numba/targets/builtins.py b/numba/targets/builtins.py
--- a/numba/targets/builtins.py
+++ b/numba/targets/builtins.py
@@ -368,13 +368,6 @@ def int_invert_impl(context, builder, sig, args):
     return impl_ret_untracked(context, builder, sig.return_type, res)
 
 
-def bool_invert_impl(context, builder, sig, args):
-    [typ] = sig.args
-    [val] = args
-    res = builder.sub(Constant.int(val.type, 1), val)
-    return impl_ret_untracked(context, builder, sig.return_type, res)
-
-
 def int_sign_impl(context, builder, sig, args):
     """
     np.sign(int)
@@ -423,7 +416,6 @@ def int_sign_impl(context, builder, sig, args):
 lower_builtin('<=', types.boolean, types.boolean)(int_ule_impl)
 lower_builtin('>', types.boolean, types.boolean)(int_ugt_impl)
 lower_builtin('>=', types.boolean, types.boolean)(int_uge_impl)
-lower_builtin('~', types.boolean)(bool_invert_impl)
 
 
 def _implement_integer_operators():
@@ -438,13 +430,8 @@ def _implement_integer_operators():
     lower_builtin('<<', ty, ty)(int_shl_impl)
     lower_builtin('>>', ty, ty)(int_shr_impl)
 
-    lower_builtin('&', ty, ty)(int_and_impl)
-    lower_builtin('|', ty, ty)(int_or_impl)
-    lower_builtin('^', ty, ty)(int_xor_impl)
-
     lower_builtin('-', ty)(int_negate_impl)
     lower_builtin('+', ty)(int_positive_impl)
-    lower_builtin('~', ty)(int_invert_impl)
 
     lower_builtin('**', ty, ty)(int_power_impl)
     lower_builtin(pow, ty, ty)(int_power_impl)
@@ -467,8 +454,18 @@ def _implement_integer_operators():
         lower_builtin(pow, types.float64, ty)(int_power_impl)
         lower_builtin(abs, ty)(int_abs_impl)
 
+def _implement_bitwise_operators():
+    for ty in (types.Boolean, types.Integer):
+        lower_builtin('&', ty, ty)(int_and_impl)
+        lower_builtin('|', ty, ty)(int_or_impl)
+        lower_builtin('^', ty, ty)(int_xor_impl)
+
+        lower_builtin('~', ty)(int_invert_impl)
+
 _implement_integer_operators()
 
+_implement_bitwise_operators()
+
 
 def optional_is_none(context, builder, sig, args):
     """Check if an Optional value is invalid
diff --git a/numba/targets/ufunc_db.py b/numba/targets/ufunc_db.py
--- a/numba/targets/ufunc_db.py
+++ b/numba/targets/ufunc_db.py
@@ -53,7 +53,7 @@ def _fill_ufunc_db(ufunc_db):
     v = numpy_support.version
 
     ufunc_db[np.negative] = {
-        '?->?': builtins.bool_invert_impl,
+        '?->?': builtins.int_invert_impl,
         'b->b': builtins.int_negate_impl,
         'B->B': builtins.int_negate_impl,
         'h->h': builtins.int_negate_impl,
@@ -866,7 +866,7 @@ def _fill_ufunc_db(ufunc_db):
     }
 
     ufunc_db[np.invert] = { # aka np.bitwise_not
-        '?->?': builtins.bool_invert_impl,
+        '?->?': builtins.int_invert_impl,
         'b->b': builtins.int_invert_impl,
         'B->B': builtins.int_invert_impl,
         'h->h': builtins.int_invert_impl,
diff --git a/numba/typing/builtins.py b/numba/typing/builtins.py
--- a/numba/typing/builtins.py
+++ b/numba/typing/builtins.py
@@ -246,7 +246,8 @@ class BitwiseRightShift(BitwiseShiftOperation):
 
 
 class BitwiseLogicOperation(BinOp):
-    cases = list(integer_binop_cases)
+    cases = [signature(types.boolean, types.boolean, types.boolean)]
+    cases += list(integer_binop_cases)
 
 
 @infer
@@ -272,7 +273,10 @@ class BitwiseXor(BitwiseLogicOperation):
 class BitwiseInvert(ConcreteTemplate):
     key = "~"
 
-    cases = [signature(types.int8, types.boolean)]
+    # Note Numba follows the Numpy semantics of returning a bool,
+    # while Python returns an int.  This makes it consistent with
+    # np.invert() and makes array expressions correct.
+    cases = [signature(types.boolean, types.boolean)]
     cases += [signature(choose_result_int(op), op) for op in types.unsigned_domain]
     cases += [signature(choose_result_int(op), op) for op in types.signed_domain]
 


Test patch:
diff --git a/numba/tests/support.py b/numba/tests/support.py
--- a/numba/tests/support.py
+++ b/numba/tests/support.py
@@ -132,7 +132,8 @@ def assertNoNRTLeak(self):
                          "number of meminfo allocs != number of meminfo frees")
 
 
-    _exact_typesets = [(bool, np.bool_), utils.INT_TYPES, (str,), (np.integer,), (utils.text_type), ]
+    _bool_types = (bool, np.bool_)
+    _exact_typesets = [_bool_types, utils.INT_TYPES, (str,), (np.integer,), (utils.text_type), ]
     _approx_typesets = [(float,), (complex,), (np.inexact)]
     _sequence_typesets = [(tuple, list)]
     _float_types = (float, np.floating)
@@ -326,6 +327,15 @@ def _assertNumberEqual(first, second, delta=None):
         if hasattr(first, 'dtype') and hasattr(second, 'dtype'):
             self.assertEqual(first.dtype, second.dtype)
 
+        # Mixing bools and non-bools should always fail
+        if (isinstance(first, self._bool_types) !=
+            isinstance(second, self._bool_types)):
+            assertion_message = ("Mismatching return types (%s vs. %s)"
+                                 % (first.__class__, second.__class__))
+            if msg:
+                assertion_message += ': %s' % (msg,)
+            self.fail(assertion_message)
+
         try:
             if cmath.isnan(first) and cmath.isnan(second):
                 # The NaNs will compare unequal, skip regular comparison
diff --git a/numba/tests/test_array_exprs.py b/numba/tests/test_array_exprs.py
--- a/numba/tests/test_array_exprs.py
+++ b/numba/tests/test_array_exprs.py
@@ -8,7 +8,7 @@
 from numba import compiler, typing, typeof, ir
 from numba.compiler import Pipeline, _PipelineManager, Flags
 from numba.targets import cpu
-from .support import MemoryLeakMixin
+from .support import MemoryLeakMixin, TestCase
 
 
 class Namespace(dict):
@@ -84,7 +84,7 @@ def mk_no_rw_pipeline(cls, args, return_type=None, flags=None, locals={},
         return cls.mk_pipeline(args, return_type, flags, locals, library, **kws)
 
 
-class TestArrayExpressions(MemoryLeakMixin, unittest.TestCase):
+class TestArrayExpressions(MemoryLeakMixin, TestCase):
 
     def test_simple_expr(self):
         '''
@@ -265,7 +265,7 @@ def test_cmp_op(self):
                                    ns.test_pipeline.interp.blocks)
 
 
-class TestRewriteIssues(MemoryLeakMixin, unittest.TestCase):
+class TestRewriteIssues(MemoryLeakMixin, TestCase):
 
     def test_issue_1184(self):
         from numba import jit
@@ -321,9 +321,23 @@ def foo(a, b):
 
         expect = foo.py_func(a, b)
         got = foo(a, b)
+        self.assertPreciseEqual(got, expect)
 
-        self.assertEqual(got.dtype, np.float64)
-        np.testing.assert_allclose(got, expect)
+    def test_bitwise_arrayexpr(self):
+        """
+        Typing of bitwise boolean array expression can be incorrect
+        (issue #1813).
+        """
+        @njit
+        def foo(a, b):
+            return ~(a & (~b))
+
+        a = np.array([True, True, False, False])
+        b = np.array([False, True, False, True])
+
+        expect = foo.py_func(a, b)
+        got = foo(a, b)
+        self.assertPreciseEqual(got, expect)
 
 
 class TestSemantics(MemoryLeakMixin, unittest.TestCase):
diff --git a/numba/tests/test_operators.py b/numba/tests/test_operators.py
--- a/numba/tests/test_operators.py
+++ b/numba/tests/test_operators.py
@@ -18,9 +18,6 @@
 
 Noflags = Flags()
 
-force_pyobj_flags = Flags()
-force_pyobj_flags.set("enable_pyobject")
-
 force_pyobj_flags = Flags()
 force_pyobj_flags.set("force_pyobject")
 
@@ -388,12 +385,14 @@ def run_test_ints(self, pyfunc, x_operands, y_operands, types_list,
                 x_expected = copy.copy(x)
                 got = cfunc(x_got, y)
                 expected = pyfunc(x_expected, y)
-                self.assertTrue(np.all(got == expected),
-                                "mismatch for (%r, %r) with types %s: %r != %r"
-                                % (x, y, arg_types, got, expected))
-                self.assertTrue(np.all(x_got == x_expected),
-                                "mismatch for (%r, %r) with types %s: %r != %r"
-                                % (x, y, arg_types, x_got, x_expected))
+                self.assertPreciseEqual(
+                    got, expected,
+                    msg="mismatch for (%r, %r) with types %s: %r != %r"
+                        % (x, y, arg_types, got, expected))
+                self.assertPreciseEqual(
+                    x_got, x_expected,
+                    msg="mismatch for (%r, %r) with types %s: %r != %r"
+                        % (x, y, arg_types, x_got, x_expected))
 
     def run_test_floats(self, pyfunc, x_operands, y_operands, types_list,
                         flags=force_pyobj_flags):
@@ -502,6 +501,15 @@ def test_ne_scalar_npm(self):
     # Arithmetic operators
     #
 
+    def run_binop_bools(self, pyfunc, flags=force_pyobj_flags):
+        x_operands = [False, False, True, True]
+        y_operands = [False, True, False, True]
+
+        types_list = [(types.boolean, types.boolean)]
+
+        self.run_test_ints(pyfunc, x_operands, y_operands, types_list,
+                           flags=flags)
+
     def run_binop_ints(self, pyfunc, flags=force_pyobj_flags):
         x_operands = [-5, 0, 1, 2]
         y_operands = [-3, -1, 1, 3]
@@ -945,6 +953,7 @@ def run_logical(self, pyfunc, flags=force_pyobj_flags):
                           'bitwise_or', 'bitwise_ior',
                           'bitwise_xor', 'bitwise_ixor'),
                          {'ints': 'run_logical',
+                          'bools': 'run_binop_bools',
                           })
 
     #
@@ -952,7 +961,6 @@ def run_logical(self, pyfunc, flags=force_pyobj_flags):
     #
 
     def test_bitwise_not(self, flags=force_pyobj_flags):
-
         pyfunc = self.op.bitwise_not_usecase_binary
 
         x_operands = list(range(0, 8)) + [2**32 - 1]
@@ -989,6 +997,17 @@ def test_bitwise_not(self, flags=force_pyobj_flags):
         self.run_test_ints(pyfunc, x_operands, y_operands, types_list,
                            flags=flags)
 
+        # For booleans, we follow Numpy semantics (i.e. ~True == False,
+        # not ~True == -2)
+        values = [False, False, True, True]
+        values = list(map(np.bool_, values))
+
+        pyfunc = self.op.bitwise_not_usecase
+        cres = compile_isolated(pyfunc, (types.boolean,), flags=flags)
+        cfunc = cres.entry_point
+        for val in values:
+            self.assertPreciseEqual(pyfunc(val), cfunc(val))
+
     @tag('important')
     def test_bitwise_not_npm(self):
         self.test_bitwise_not(flags=Noflags)
diff --git a/numba/tests/test_support.py b/numba/tests/test_support.py
--- a/numba/tests/test_support.py
+++ b/numba/tests/test_support.py
@@ -27,6 +27,7 @@ class TestAssertPreciseEqual(TestCase):
     float_types = [float] + np_float_types
     np_complex_types = [np.complex64, np.complex128]
     complex_types = [complex] + np_complex_types
+    bool_types = [bool, np.bool_]
 
     def eq(self, left, right, **kwargs):
         def assert_succeed(left, right):
@@ -62,6 +63,9 @@ def test_types(self):
         # int and long are compatible between each other
         for u, v in itertools.product(self.int_types, self.int_types):
             self.eq(u(1), v(1))
+        # int and bool are not compatible between each other
+        for u, v in itertools.product(self.int_types, self.bool_types):
+            self.ne(u(1), v(1))
         # NumPy float types are not compatible between each other
         for u, v in itertools.product(self.np_float_types, self.np_float_types):
             if u is v:
@@ -83,6 +87,12 @@ def test_int_values(self):
                 self.ne(tp(-1), tp(1), prec=prec)
                 self.ne(tp(2**80), tp(1+2**80), prec=prec)
 
+    def test_bool_values(self):
+        for tpa, tpb in itertools.product(self.bool_types, self.bool_types):
+            self.eq(tpa(True), tpb(True))
+            self.eq(tpa(False), tpb(False))
+            self.ne(tpa(True), tpb(False))
+
     def test_abs_tol_parse(self):
         # check invalid values in abs_tol kwarg raises
         with self.assertRaises(ValueError):
@@ -166,7 +176,6 @@ def test_float32_values_inexact(self):
         self.eq(tp(1e-7), tp(1e-8), prec='single', abs_tol=1e-7)
         self.ne(tp(1e-7), tp(3e-7), prec='single', abs_tol=1e-7)
 
-
     def test_complex_values(self):
         # Complex literals with signed zeros are confusing, better use
         # the explicit constructor.
diff --git a/numba/tests/test_ufuncs.py b/numba/tests/test_ufuncs.py
--- a/numba/tests/test_ufuncs.py
+++ b/numba/tests/test_ufuncs.py
@@ -938,6 +938,10 @@ def pyfunc(a0):
 
 class TestArrayOperators(BaseUFuncTest, TestCase):
 
+    def _check_results(self, expected, got):
+        self.assertEqual(expected.dtype.kind, got.dtype.kind)
+        np.testing.assert_array_almost_equal(expected, got)
+
     def unary_op_test(self, operator, flags=enable_nrt_flags,
                       skip_inputs=[], additional_inputs=[],
                       int_output_type=None, float_output_type=None):
@@ -956,8 +960,8 @@ def unary_op_test(self, operator, flags=enable_nrt_flags,
                                     flags=flags)
             cfunc = cr.entry_point
             expected = pyfunc(input_operand)
-            result = cfunc(input_operand)
-            np.testing.assert_array_almost_equal(expected, result)
+            got = cfunc(input_operand)
+            self._check_results(expected, got)
 
     def binary_op_test(self, operator, flags=enable_nrt_flags,
                        skip_inputs=[], additional_inputs=[],
@@ -996,22 +1000,31 @@ def binary_op_test(self, operator, flags=enable_nrt_flags,
                                     flags=flags)
             cfunc = cr.entry_point
             expected = pyfunc(input_operand0, input_operand1)
-            result = cfunc(input_operand0, input_operand1)
-            np.testing.assert_array_almost_equal(expected, result)
+            got = cfunc(input_operand0, input_operand1)
+            self._check_results(expected, got)
+
+    def bitwise_additional_inputs(self):
+        # For bitwise operators, we want to check the results for boolean
+        # arrays (see #1813).
+        return [
+            (True, types.boolean),
+            (False, types.boolean),
+            (np.array([True, False]), types.Array(types.boolean, 1, 'C')),
+            ]
 
     def binary_int_op_test(self, *args, **kws):
-        if 'skip_inputs' not in kws:
-            kws['skip_inputs'] = []
-        kws['skip_inputs'].extend([
+        skip_inputs = kws.setdefault('skip_inputs', [])
+        skip_inputs += [
             types.float32, types.float64,
             types.Array(types.float32, 1, 'C'),
-            types.Array(types.float64, 1, 'C')])
+            types.Array(types.float64, 1, 'C'),
+            ]
         return self.binary_op_test(*args, **kws)
 
-    def _make_arrays(self, dtypes):
-        for dtype in dtypes:
-            yield np.linspace(0, 5, 3).astype(dtype)
-            yield np.linspace(1, 6, 3).astype(dtype)
+    def binary_bitwise_op_test(self, *args, **kws):
+        additional_inputs = kws.setdefault('additional_inputs', [])
+        additional_inputs += self.bitwise_additional_inputs()
+        return self.binary_int_op_test(*args, **kws)
 
     def inplace_op_test(self, operator, lhs_values, rhs_values,
                         lhs_dtypes, rhs_dtypes):
@@ -1048,9 +1061,14 @@ def inplace_float_op_test(self, operator, lhs_values, rhs_values):
                                     (np.float32, np.float64, np.int64))
 
     def inplace_int_op_test(self, operator, lhs_values, rhs_values):
-        return self.inplace_op_test(operator, lhs_values, rhs_values,
-                                    (np.int16, np.int32, np.int64),
-                                    (np.int16, np.uint32))
+        self.inplace_op_test(operator, lhs_values, rhs_values,
+                             (np.int16, np.int32, np.int64),
+                             (np.int16, np.uint32))
+
+    def inplace_bitwise_op_test(self, operator, lhs_values, rhs_values):
+        self.inplace_int_op_test(operator, lhs_values, rhs_values)
+        self.inplace_op_test(operator, lhs_values, rhs_values,
+                             (np.bool_,), (np.bool_, np.bool_))
 
     # ____________________________________________________________
     # Unary operators
@@ -1063,10 +1081,11 @@ def test_unary_negative_array_op(self):
         self.unary_op_test('-')
 
     def test_unary_invert_array_op(self):
-        self.unary_op_test('~', skip_inputs=[
-            types.float32, types.float64,
-            types.Array(types.float32, 1, 'C'),
-            types.Array(types.float64, 1, 'C')])
+        self.unary_op_test('~',
+                           skip_inputs=[types.float32, types.float64,
+                                        types.Array(types.float32, 1, 'C'),
+                                        types.Array(types.float64, 1, 'C')],
+                           additional_inputs=self.bitwise_additional_inputs())
 
     # ____________________________________________________________
     # Inplace operators
@@ -1097,13 +1116,13 @@ def test_inplace_pow(self):
         self.inplace_float_op_test('**=', [-1, 1.5, 3], [-5, 2, 2.5])
 
     def test_inplace_and(self):
-        self.inplace_int_op_test('&=', [0, 1, 2, 3, 51], [0, 13, 16, 42, 255])
+        self.inplace_bitwise_op_test('&=', [0, 1, 2, 3, 51], [0, 13, 16, 42, 255])
 
     def test_inplace_or(self):
-        self.inplace_int_op_test('|=', [0, 1, 2, 3, 51], [0, 13, 16, 42, 255])
+        self.inplace_bitwise_op_test('|=', [0, 1, 2, 3, 51], [0, 13, 16, 42, 255])
 
     def test_inplace_xor(self):
-        self.inplace_int_op_test('^=', [0, 1, 2, 3, 51], [0, 13, 16, 42, 255])
+        self.inplace_bitwise_op_test('^=', [0, 1, 2, 3, 51], [0, 13, 16, 42, 255])
 
     @tag('important')
     def test_inplace_lshift(self):
@@ -1178,15 +1197,15 @@ def test_right_shift_array_op(self):
 
     @tag('important')
     def test_bitwise_and_array_op(self):
-        self.binary_int_op_test('&')
+        self.binary_bitwise_op_test('&')
 
     @tag('important')
     def test_bitwise_or_array_op(self):
-        self.binary_int_op_test('|')
+        self.binary_bitwise_op_test('|')
 
     @tag('important')
     def test_bitwise_xor_array_op(self):
-        self.binary_int_op_test('^')
+        self.binary_bitwise_op_test('^')
 
     @tag('important')
     def test_equal_array_op(self):