Repository: numba/numba

Issue:
Incorrect result when numpy broadcasting in parallel
I stumbled into an incorrect behavior with jit parallel=True when writing into a numpy array using broadcasting.
I reduced my code to the following minimal example to demonstrate the incorrect behavior:
```python
import numpy as np
from numba import jit
N=3
nxy=np.zeros((N,1))
@jit(nopython=True,parallel=True)
def example(nxy):
    count=0
    for n in range(nxy.shape[0]):
        #Pointless if statement that does nothing. If I remove it, it works ok.
        if n:
            n
        #writting to array:
        nxy[count,:]=1
        print(nxy[count,:]) #nxy[count,:] should contain [1.]
        count+=1
example(nxy)
```
The expected result would be:
```
[1.]
[1.]
[1.]
```
however the example prints:
```
[1.]
[0.]
[0.]
```
I understand that the example seems weird with having a counter in a for loop, but it is only meant to demonstrate an issue.

If I run it non-jitted or jitted with parallel=False it works ok.
If I remove the pointless if statement, it works ok.
If I replace `nxy[count,:]=1` with `nxy[count,...]=1`  it works ok, because that part then does not seem to get parallelized. I am currently using that as my solution, because I get parallelization elsewhere, however, it makes me uneasy to not know why it behaves incorrectly in the demonstrated case.


Discussion:
Thanks for the report. I suspect that this is using unsupported broadcast semantics with `parallel=True`,  https://numba.readthedocs.io/en/stable/user/parallel.html#supported-operations (see item 5). 

CC @DrTodd13 please could you confirm that is this the case? Thanks.
This issue is marked as stale as it has had no activity in the past 30 days. Please close this issue if no further response or action is needed. Otherwise, please respond with any updates and confirm that this issue still needs to be addressed.
Just responding to prevent a stall. Issue still active.

Resolving patch:
diff --git a/numba/parfors/array_analysis.py b/numba/parfors/array_analysis.py
--- a/numba/parfors/array_analysis.py
+++ b/numba/parfors/array_analysis.py
@@ -2056,6 +2056,7 @@ def _index_to_shape(self, scope, equiv_set, var, ind_var):
         var_shape = equiv_set._get_shape(var)
         if isinstance(ind_typ, types.SliceType):
             seq_typs = (ind_typ,)
+            seq = (ind_var,)
         else:
             require(isinstance(ind_typ, types.BaseTuple))
             seq, op = find_build_sequence(self.func_ir, ind_var)
@@ -2077,7 +2078,10 @@ def to_shape(typ, index, dsize):
         shape_list = []
         index_var_list = []
         replace_index = False
-        for (typ, size, dsize) in zip(seq_typs, ind_shape, var_shape):
+        for (typ, size, dsize, orig_ind) in zip(seq_typs,
+                                                ind_shape,
+                                                var_shape,
+                                                seq):
             # Convert the given dimension of the get/setitem index expr.
             shape_part, index_var_part = to_shape(typ, size, dsize)
             shape_list.append(shape_part)
@@ -2090,7 +2094,7 @@ def to_shape(typ, index, dsize):
                 replace_index = True
                 index_var_list.append(index_var_part)
             else:
-                index_var_list.append(size)
+                index_var_list.append(orig_ind)
 
         # If at least one of the dimensions required a new slice variable
         # then we'll need to replace the build_tuple for this get/setitem.


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
@@ -609,17 +609,23 @@ def _count_non_parfor_array_accesses_inner(f_ir, blocks, typemap, parfor_indices
                     f_ir, parfor_blocks, typemap, parfor_indices)
 
             # getitem
-            if (is_getitem(stmt) and isinstance(typemap[stmt.value.value.name],
+            elif (is_getitem(stmt) and isinstance(typemap[stmt.value.value.name],
                         types.ArrayCompatible) and not _uses_indices(
                         f_ir, index_var_of_get_setitem(stmt), parfor_indices)):
                 ret_count += 1
 
             # setitem
-            if (is_setitem(stmt) and isinstance(typemap[stmt.target.name],
+            elif (is_setitem(stmt) and isinstance(typemap[stmt.target.name],
                     types.ArrayCompatible) and not _uses_indices(
                     f_ir, index_var_of_get_setitem(stmt), parfor_indices)):
                 ret_count += 1
 
+            # find parfor_index aliases
+            elif (isinstance(stmt, ir.Assign) and
+                  isinstance(stmt.value, ir.Var) and
+                  stmt.value.name in parfor_indices):
+                parfor_indices.add(stmt.target.name)
+
     return ret_count
 
 def _uses_indices(f_ir, index, index_set):
@@ -2001,6 +2007,19 @@ def test_impl(arr):
         arr = np.arange(10, dtype=np.int64)
         self.check(test_impl, arr)
 
+    def test_setitem_2d_one_replaced(self):
+        # issue7843
+        def test_impl(x):
+            count = 0
+            for n in range(x.shape[0]):
+                # Useless "if" necessary to trigger bug.
+                if n:
+                    n
+                x[count, :] = 1
+                count += 1
+            return x
+
+        self.check(test_impl, np.zeros((3, 1)))
 
 @skip_parfors_unsupported
 class TestParforsLeaks(MemoryLeakMixin, TestParforsBase):