Repository: numba/numba

Issue:
Parfor analysis error on 0d array (arrayscalar)
<!--

Thanks for opening an issue! To help the Numba team handle your information
efficiently, please first ensure that there is no other issue present that
already describes the issue you have
(search at https://github.com/numba/numba/issues?&q=is%3Aissue).

For more general "how do I do X?" type questions, please speak to us in real
time on https://gitter.im/numba/numba or post to the Numba mailing list
https://groups.google.com/a/continuum.io/forum/#!forum/numba-users.

-->

## Reporting a bug

<!--

Before submitting a bug report please ensure that you can check off these boxes:

-->

- [x] I am using the latest released version of Numba (most recent is visible in
 the change log (https://github.com/numba/numba/blob/master/CHANGE_LOG).
- [x] I have included below a minimal working reproducer (if you are unsure how
 to write one see http://matthewrocklin.com/blog/work/2018/02/28/minimal-bug-reports).

<!--

Please include details of the bug here, including, if applicable, what you
expected to happen!

-->

```python
@njit(parallel=True)
def foo():
    X = np.array(10)   # arrayscalar
    return np.sum(X) 
```


<details>
<summary>traceback:</summary>

```bash
Traceback (most recent call last):
  File "/Users/siu/miniconda3/envs/dev-numba-py37/lib/python3.7/runpy.py", line 193, in _run_module_as_main
    "__main__", mod_spec)
  File "/Users/siu/miniconda3/envs/dev-numba-py37/lib/python3.7/runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "/Users/siu/dev/numba/chkssa.py", line 270, in <module>
    test_11()
  File "/Users/siu/dev/numba/chkssa.py", line 247, in test_11
    got = foo(*args)
  File "/Users/siu/dev/numba/numba/dispatcher.py", line 395, in _compile_for_args
    raise e
  File "/Users/siu/dev/numba/numba/dispatcher.py", line 352, in _compile_for_args
    return self.compile(tuple(argtypes))
  File "/Users/siu/dev/numba/numba/compiler_lock.py", line 32, in _acquire_compile_lock
    return func(*args, **kwargs)
  File "/Users/siu/dev/numba/numba/dispatcher.py", line 742, in compile
    cres = self._compiler.compile(args, return_type)
  File "/Users/siu/dev/numba/numba/dispatcher.py", line 76, in compile
    status, retval = self._compile_cached(args, return_type)
  File "/Users/siu/dev/numba/numba/dispatcher.py", line 90, in _compile_cached
    retval = self._compile_core(args, return_type)
  File "/Users/siu/dev/numba/numba/dispatcher.py", line 108, in _compile_core
    pipeline_class=self.pipeline_class)
  File "/Users/siu/dev/numba/numba/compiler.py", line 973, in compile_extra
    return pipeline.compile_extra(func)
  File "/Users/siu/dev/numba/numba/compiler.py", line 390, in compile_extra
    return self._compile_bytecode()
  File "/Users/siu/dev/numba/numba/compiler.py", line 904, in _compile_bytecode
    return self._compile_core()
  File "/Users/siu/dev/numba/numba/compiler.py", line 891, in _compile_core
    res = pm.run(self.status)
  File "/Users/siu/dev/numba/numba/compiler_lock.py", line 32, in _acquire_compile_lock
    return func(*args, **kwargs)
  File "/Users/siu/dev/numba/numba/compiler.py", line 266, in run
    raise patched_exception
  File "/Users/siu/dev/numba/numba/compiler.py", line 257, in run
    stage()
  File "/Users/siu/dev/numba/numba/compiler.py", line 580, in stage_parfor_pass
    parfor_pass.run()
  File "/Users/siu/dev/numba/numba/parfor.py", line 1463, in run
    self._convert_loop(self.func_ir.blocks)
  File "/Users/siu/dev/numba/numba/parfor.py", line 1761, in _convert_loop
    assert(equiv_set.has_shape(args[0]))
AssertionError: Failed in nopython mode pipeline (step: convert to parfors)
```
</details>


Discussion:

Resolving patch:
diff --git a/numba/array_analysis.py b/numba/array_analysis.py
--- a/numba/array_analysis.py
+++ b/numba/array_analysis.py
@@ -349,8 +349,9 @@ def _get_names(self, obj):
                     isinstance(typ, types.ArrayCompatible)):
                 ndim = (typ.ndim if isinstance(typ, types.ArrayCompatible)
                         else len(typ))
+                # Treat 0d array as if it were a scalar.
                 if ndim == 0:
-                    return ()
+                    return (name,)
                 else:
                     return tuple("{}#{}".format(name, i) for i in range(ndim))
             else:
@@ -513,6 +514,9 @@ def get_shape_classes(self, name):
                 isinstance(typ, types.SliceType) or
                 isinstance(typ, types.ArrayCompatible)):
             return []
+        # Treat 0d arrays like scalars.
+        if isinstance(typ, types.ArrayCompatible) and typ.ndim == 0:
+            return []
         names = self._get_names(name)
         inds = tuple(self._get_ind(name) for name in names)
         return inds
diff --git a/numba/npyufunc/parfor.py b/numba/npyufunc/parfor.py
--- a/numba/npyufunc/parfor.py
+++ b/numba/npyufunc/parfor.py
@@ -741,6 +741,12 @@ def legalize_names_with_typemap(names, typemap):
             typemap[y] = typemap[x]
     return outdict
 
+def to_scalar_from_0d(x):
+    if isinstance(x, types.ArrayCompatible):
+        if x.ndim == 0:
+            return x.dtype
+    return x
+
 def _create_gufunc_for_parfor_body(
         lowerer,
         parfor,
@@ -857,7 +863,7 @@ def _create_gufunc_for_parfor_body(
             print("pd type = ", typemap[pd], " ", type(typemap[pd]))
 
     # Get the types of each parameter.
-    param_types = [typemap[v] for v in parfor_params]
+    param_types = [to_scalar_from_0d(typemap[v]) for v in parfor_params]
     # Calculate types of args passed to gufunc.
     func_arg_types = [typemap[v] for v in (parfor_inputs + parfor_outputs)] + parfor_red_arg_types
 
diff --git a/numba/parfor.py b/numba/parfor.py
--- a/numba/parfor.py
+++ b/numba/parfor.py
@@ -122,7 +122,10 @@ def __new__(cls, *args):
 def min_parallel_impl(return_type, arg):
     # XXX: use prange for 1D arrays since pndindex returns a 1-tuple instead of
     # integer. This causes type and fusion issues.
-    if arg.ndim == 1:
+    if arg.ndim == 0:
+        def min_1(in_arr):
+            return in_arr[()]
+    elif arg.ndim == 1:
         def min_1(in_arr):
             numba.parfor.init_prange()
             min_checker(len(in_arr))
@@ -141,7 +144,10 @@ def min_1(in_arr):
     return min_1
 
 def max_parallel_impl(return_type, arg):
-    if arg.ndim == 1:
+    if arg.ndim == 0:
+        def max_1(in_arr):
+            return in_arr[()]
+    elif arg.ndim == 1:
         def max_1(in_arr):
             numba.parfor.init_prange()
             max_checker(len(in_arr))
@@ -238,7 +244,10 @@ def dot_parallel_impl(return_type, atyp, btyp):
 def sum_parallel_impl(return_type, arg):
     zero = return_type(0)
 
-    if arg.ndim == 1:
+    if arg.ndim == 0:
+        def sum_1(in_arr):
+            return in_arr[()]
+    elif arg.ndim == 1:
         def sum_1(in_arr):
             numba.parfor.init_prange()
             val = zero
@@ -257,7 +266,10 @@ def sum_1(in_arr):
 def prod_parallel_impl(return_type, arg):
     one = return_type(1)
 
-    if arg.ndim == 1:
+    if arg.ndim == 0:
+        def prod_1(in_arr):
+            return in_arr[()]
+    elif arg.ndim == 1:
         def prod_1(in_arr):
             numba.parfor.init_prange()
             val = one
@@ -278,7 +290,10 @@ def mean_parallel_impl(return_type, arg):
     # can't reuse sum since output type is different
     zero = return_type(0)
 
-    if arg.ndim == 1:
+    if arg.ndim == 0:
+        def mean_1(in_arr):
+            return in_arr[()]
+    elif arg.ndim == 1:
         def mean_1(in_arr):
             numba.parfor.init_prange()
             val = zero
@@ -295,8 +310,10 @@ def mean_1(in_arr):
     return mean_1
 
 def var_parallel_impl(return_type, arg):
-
-    if arg.ndim == 1:
+    if arg.ndim == 0:
+        def var_1(in_arr):
+            return 0
+    elif arg.ndim == 1:
         def var_1(in_arr):
             # Compute the mean
             m = in_arr.mean()


Test patch:
diff --git a/numba/tests/test_parfors.py b/numba/tests/test_parfors.py
--- a/numba/tests/test_parfors.py
+++ b/numba/tests/test_parfors.py
@@ -1470,6 +1470,12 @@ def test_impl(a, b):
         msg = ("The reshape API may only include one negative argument.")
         self.assertIn(msg, str(raised.exception))
 
+    @skip_unsupported
+    def test_0d_array(self):
+        def test_impl(n):
+            return np.sum(n) + np.prod(n) + np.min(n) + np.max(n) + np.var(n)
+        self.check(test_impl, np.array(7), check_scheduling=False)
+
 
 class TestParforsLeaks(MemoryLeakMixin, TestParforsBase):
     def check(self, pyfunc, *args, **kwargs):
@@ -2712,6 +2718,18 @@ def test_impl(x1, x2):
         x2 = np.random.rand(6)
         self.check(test_impl, x1, x2)
 
+    @skip_unsupported
+    def test_parfor_slice22(self):
+        def test_impl(x1, x2):
+            b = np.zeros((10,))
+            for i in prange(1):
+                b += x1[:, x2]
+            return b
+
+        x1 = np.zeros((10,7))
+        x2 = np.array(4)
+        self.check(test_impl, x1, x2)
+
 class TestParforsOptions(TestParforsBase):
 
     def check(self, pyfunc, *args, **kwargs):