Repository: numba/numba

Issue:
NameError in stencil when cval is np.nan or np.inf
I was playing around with the stencil decorator and tried supplying `np.nan` to the `cval` argument. This results in a NameError. It looks like the type inference is not able to infer a type for np.nan. The docs state that any value can be supplied to `cval` so I assume this is a bug. The same thing happens for `np.inf`. The below code triggers the error on my machine with version 0.53.1. If this is intended, perhaps the documentation could be updated to indicate that NaNs and infs are not valid.

```python
import numpy as np
from numba import stencil


@stencil(neighborhood=((-1, 1), (-1, 1)), func_or_mode="constant", cval=np.nan)
def kern(a):
    return (
        a[-1, -1]
        + a[-1, 0]
        + a[-1, 1]
        + a[0, -1]
        + a[0, 0]
        + a[0, 1]
        + a[1, -1]
        + a[1, 0]
        + a[1, 1]
    )


x = np.random.randint(100, size=(100, 100)).astype(float)
y = kern(x)
```

Here is the stack trace:
```text
Traceback (most recent call last):
  File "stencil.py", line 21, in <module>
    y = kern(x)
  File "/home/$USER/anaconda3/envs/main/lib/python3.8/site-packages/numba/stencils/stencil.py", line 763, in __call__
    new_func = self._stencil_wrapper(result, None, real_ret, typemap,
  File "/home/$USER/anaconda3/envs/main/lib/python3.8/site-packages/numba/stencils/stencil.py", line 728, in _stencil_wrapper
    new_func = compiler.compile_ir(
  File "/home/$USER/anaconda3/envs/main/lib/python3.8/site-packages/numba/core/compiler.py", line 669, in compile_ir
    return pipeline.compile_ir(func_ir=func_ir, lifted=lifted,
  File "/home/$USER/anaconda3/envs/main/lib/python3.8/site-packages/numba/core/compiler.py", line 363, in compile_ir
    return self._compile_ir()
  File "/home/$USER/anaconda3/envs/main/lib/python3.8/site-packages/numba/core/compiler.py", line 422, in _compile_ir
    return self._compile_core()
  File "/home/$USER/anaconda3/envs/main/lib/python3.8/site-packages/numba/core/compiler.py", line 395, in _compile_core
    raise e
  File "/home/$USER/anaconda3/envs/main/lib/python3.8/site-packages/numba/core/compiler.py", line 386, in _compile_core
    pm.run(self.state)
  File "/home/$USER/anaconda3/envs/main/lib/python3.8/site-packages/numba/core/compiler_machinery.py", line 339, in run
    raise patched_exception
  File "/home/$USER/anaconda3/envs/main/lib/python3.8/site-packages/numba/core/compiler_machinery.py", line 330, in run
    self._runPass(idx, pass_inst, state)
  File "/home/$USER/anaconda3/envs/main/lib/python3.8/site-packages/numba/core/compiler_lock.py", line 35, in _acquire_compile_lock
    return func(*args, **kwargs)
  File "/home/$USER/anaconda3/envs/main/lib/python3.8/site-packages/numba/core/compiler_machinery.py", line 289, in _runPass
    mutated |= check(pss.run_pass, internal_state)
  File "/home/$USER/anaconda3/envs/main/lib/python3.8/site-packages/numba/core/compiler_machinery.py", line 262, in check
    mangled = func(compiler_state)
  File "/home/$USER/anaconda3/envs/main/lib/python3.8/site-packages/numba/core/typed_passes.py", line 104, in run_pass
    typemap, return_type, calltypes, errs = type_inference_stage(
  File "/home/$USER/anaconda3/envs/main/lib/python3.8/site-packages/numba/core/typed_passes.py", line 80, in type_inference_stage
    infer.build_constraint()
  File "/home/$USER/anaconda3/envs/main/lib/python3.8/site-packages/numba/core/typeinfer.py", line 1024, in build_constraint
    self.constrain_statement(inst)
  File "/home/$USER/anaconda3/envs/main/lib/python3.8/site-packages/numba/core/typeinfer.py", line 1371, in constrain_statement
    self.typeof_assign(inst)
  File "/home/$USER/anaconda3/envs/main/lib/python3.8/site-packages/numba/core/typeinfer.py", line 1442, in typeof_assign
    self.typeof_global(inst, inst.target, value)
  File "/home/$USER/anaconda3/envs/main/lib/python3.8/site-packages/numba/core/typeinfer.py", line 1542, in typeof_global
    typ = self.resolve_value_type(inst, gvar.value)
  File "/home/$USER/anaconda3/envs/main/lib/python3.8/site-packages/numba/core/typeinfer.py", line 1463, in resolve_value_type
    raise TypingError(msg, loc=inst.loc)
numba.core.errors.TypingError: Failed in nopython mode pipeline (step: nopython frontend)
NameError: name 'nan' is not defined
```



Discussion:
@fbunt Thanks for the report, I can reproduce this on mainline. The issue appears to be in the rendering of the constant `np.nan` in the stencil output template string, it's writing `str(np.nan)` in as the constant, which is `"nan"`, but it actually needs the `np` module in the string for it to resolve.

Resolving patch:
diff --git a/numba/stencils/stencil.py b/numba/stencils/stencil.py
--- a/numba/stencils/stencil.py
+++ b/numba/stencils/stencil.py
@@ -581,6 +581,19 @@ def _stencil_wrapper(self, result, sigret, return_type, typemap, calltypes, *arg
         shape_name = ir_utils.get_unused_var_name("full_shape", name_var_table)
         func_text += "    {} = {}.shape\n".format(shape_name, first_arg)
 
+        # Converts cval to a string constant
+        def cval_as_str(cval):
+            if not np.isfinite(cval):
+                # See if this is a string-repr numerical const, issue #7286
+                if np.isnan(cval):
+                    return "np.nan"
+                elif np.isinf(cval):
+                    if cval < 0:
+                        return "-np.inf"
+                    else:
+                        return "np.inf"
+            else:
+                return str(cval)
 
         # If we have to allocate the output array (the out argument was not used)
         # then us numpy.full if the user specified a cval stencil decorator option
@@ -594,7 +607,8 @@ def _stencil_wrapper(self, result, sigret, return_type, typemap, calltypes, *arg
                     raise ValueError(
                         "cval type does not match stencil return type.")
                 out_init ="{} = np.full({}, {}, dtype=np.{})\n".format(
-                            out_name, shape_name, cval, return_type_name)
+                            out_name, shape_name, cval_as_str(cval),
+                            return_type_name)
             else:
                 out_init ="{} = np.zeros({}, dtype=np.{})\n".format(
                             out_name, shape_name, return_type_name)
@@ -606,7 +620,7 @@ def _stencil_wrapper(self, result, sigret, return_type, typemap, calltypes, *arg
                 if not self._typingctx.can_convert(cval_ty, return_type.dtype):
                     msg = "cval type does not match stencil return type."
                     raise ValueError(msg)
-                out_init = "{}[:] = {}\n".format(out_name, cval)
+                out_init = "{}[:] = {}\n".format(out_name, cval_as_str(cval))
                 func_text += "    " + out_init
 
         offset = 1


Test patch:
diff --git a/numba/tests/test_stencils.py b/numba/tests/test_stencils.py
--- a/numba/tests/test_stencils.py
+++ b/numba/tests/test_stencils.py
@@ -611,6 +611,36 @@ def wrapped():
             else:
                 raise AssertionError("Expected error was not raised")
 
+    @skip_unsupported
+    def test_out_kwarg_w_cval_np_attr(self):
+        """ Test issue #7286 where the cval is a np attr/string-based numerical
+        constant"""
+        for cval in (np.nan, np.inf, -np.inf, float('inf'), -float('inf')):
+            def kernel(a):
+                return (a[0, 0] - a[1, 0])
+
+            stencil_fn = numba.stencil(kernel, cval=cval)
+
+            def wrapped():
+                A = np.arange(12.).reshape((3, 4))
+                ret = np.ones_like(A)
+                stencil_fn(A, out=ret)
+                return ret
+
+            # stencil function case
+            A = np.arange(12.).reshape((3, 4))
+            expected = np.full_like(A, -4)
+            expected[-1, :] = cval
+            ret = np.ones_like(A)
+            stencil_fn(A, out=ret)
+            np.testing.assert_almost_equal(ret, expected)
+
+            # wrapped function case, check njit, then njit(parallel=True)
+            impls = self.compile_all(wrapped,)
+            for impl in impls:
+                got = impl.entry_point()
+                np.testing.assert_almost_equal(got, expected)
+
 
 class pyStencilGenerator:
     """
@@ -2795,5 +2825,16 @@ def kernel(a):
         a = np.arange(20, dtype=np.uint32).reshape(4, 5)
         self.check(kernel, a)
 
+    def test_basic98(self):
+        """ Test issue #7286 where the cval is a np attr/string-based numerical
+        constant"""
+        for cval in (np.nan, np.inf, -np.inf, float('inf'), -float('inf')):
+            def kernel(a):
+                return a[0, 0]
+            a = np.arange(6.).reshape((2, 3))
+            self.check(kernel, a, options={'neighborhood': ((-1, 1), (-1, 1),),
+                       'cval':cval})
+
+
 if __name__ == "__main__":
     unittest.main()