Repository: numba/numba

Issue:
issue with >1d array iteration
numba 0.49.0
numpy 1.18.2
Python 3.8.2 (default, May  7 2020, 20:00:49) [GCC 7.3.0] on linux

```python
import numba as nb
import numpy as np


@nb.njit()
def outer_product_sum(x: np.ndarray, y: np.ndarray) -> np.ndarray:
    assert x.ndim == 2, "dimensions of both arrays must be 2"
    assert y.ndim == 2, "dimensions of both arrays must be 2"
    assert x.shape[1] == y.shape[1], "The second dimension of both arrays must be equal"

    o = np.zeros((x.shape[0], y.shape[0]))
    for v1, v2 in zip(x.T, y.T):
        o += np.outer(v1, v2)
    return o

x = np.array([[1, 8, 9, 1], [4, 64, 27, 1]])
y = np.array([[1, 1/2, 1/3, 1],  [1/2, 1/4, 1/9, 1], [1/2, 1/4, 1/9, 1]])
expected = np.outer(x[:,0], y[:,0]) + np.outer(x[:,1], y[:,1]) + np.outer(x[:,2], y[:,2]) + np.outer(x[:,3], y[:,3])
actual = outer_product_sum(x, y)
assert np.isclose(expected, actual).all()
```

Without the decorator, this test passes, but with the decorator, it fails. On inspection, it is clear that the issue is not with the sum, but with the inner product. In this context, `np.outer` is not producing correct results. `v1`, `v2` are as expected, but `np.outer` no longer performs the outer product.



Discussion:
Thanks for the report, I can reproduce. I don't think it's a problem with `outer`, but instead a problem with iteration over >1d arrays (which, IIRC, was added in 0.49). For example, this seems to work as expected:
```python
import numba as nb
import numpy as np


@nb.njit()
def outer_product_sum(x: np.ndarray, y: np.ndarray) -> np.ndarray:
    assert x.ndim == 2, "dimensions of both arrays must be 2"
    assert y.ndim == 2, "dimensions of both arrays must be 2"
    assert x.shape[1] == y.shape[1], "The second dimension of both arrays must be equal"

    o = np.zeros((x.shape[0], y.shape[0]))
    for i in range(x.shape[0]):
        v1 = x[:, i]
        v2 = y[:, i]
        o += np.outer(v1, v2)
    return o

x = np.array([[1, 8, 9, 1], [4, 64, 27, 1]])
y = np.array([[1, 1/2, 1/3, 1],  [1/2, 1/4, 1/9, 1], [1/2, 1/4, 1/9, 1]])

actual = outer_product_sum(x, y)
expected = outer_product_sum.py_func(x, y)
assert np.isclose(expected, actual).all()
```
Thanks, that fixed the issue for me too. Looks like it was an issue with iteration.
Narrowed it down to:
```python
import numba as nb
import numpy as np

x = np.array([[1, 2,], [3, 4]])

@nb.njit
def foo(x):
    z = zip(x.T, x.T)
    y1, _ = next(z)
    print(y1.ravel())

foo(x)
foo.py_func(x)
```
probably iteration, F order and `ravel()` related.

Resolving patch:
diff --git a/numba/core/types/iterators.py b/numba/core/types/iterators.py
--- a/numba/core/types/iterators.py
+++ b/numba/core/types/iterators.py
@@ -103,5 +103,6 @@ def __init__(self, array_type):
         elif nd == 1:
             yield_type = array_type.dtype
         else:
-            yield_type = array_type.copy(ndim=array_type.ndim - 1)
+            # iteration semantics leads to A order layout
+            yield_type = array_type.copy(ndim=array_type.ndim - 1, layout='A')
         super(ArrayIterator, self).__init__(name, yield_type)
diff --git a/numba/typed/typedlist.py b/numba/typed/typedlist.py
--- a/numba/typed/typedlist.py
+++ b/numba/typed/typedlist.py
@@ -482,7 +482,7 @@ def _guess_dtype(iterable):
             "List() argument must be iterable")
     # Special case for nested NumPy arrays.
     elif isinstance(iterable, types.Array) and iterable.ndim > 1:
-        return iterable.copy(ndim=iterable.ndim - 1)
+        return iterable.copy(ndim=iterable.ndim - 1, layout='A')
     elif hasattr(iterable, "dtype"):
         return iterable.dtype
     elif hasattr(iterable, "yield_type"):


Test patch:
diff --git a/numba/tests/test_array_iterators.py b/numba/tests/test_array_iterators.py
--- a/numba/tests/test_array_iterators.py
+++ b/numba/tests/test_array_iterators.py
@@ -174,7 +174,7 @@ def check_array_ndenumerate_sum(self, arr, arrty):
         self.check_array_unary(arr, arrty, array_ndenumerate_sum)
 
     def test_array_iter(self):
-        # Test iterating over a 1d array
+        # Test iterating over arrays
         arr = np.arange(6)
         self.check_array_iter_1d(arr)
         self.check_array_iter_items(arr)
@@ -190,6 +190,59 @@ def test_array_iter(self):
         self.check_array_iter_items(arr)
         self.check_array_iter_items(arr.T)
 
+    def test_array_iter_yielded_order(self):
+        # See issue #5692
+        @jit(nopython=True)
+        def foo(arr):
+            t = []
+            for y1 in arr:
+                for y2 in y1:
+                    t.append(y2.ravel())
+            return t
+
+        # 'F' ordered
+        arr = np.arange(24).reshape((2, 3, 4), order='F')
+        expected = foo.py_func(arr)
+        got = foo(arr)
+        self.assertPreciseEqual(expected, got)
+
+        # 'A' ordered, outer strided
+        arr = np.arange(64).reshape((4, 8, 2), order='F')[::2, :, :]
+        expected = foo.py_func(arr)
+        got = foo(arr)
+        self.assertPreciseEqual(expected, got)
+
+        # 'A' ordered, middle strided
+        arr = np.arange(64).reshape((4, 8, 2), order='F')[:, ::2, :]
+        expected = foo.py_func(arr)
+        got = foo(arr)
+        self.assertPreciseEqual(expected, got)
+
+        # 'A' ordered, inner strided
+        arr = np.arange(64).reshape((4, 8, 2), order='F')[:, :, ::2]
+        expected = foo.py_func(arr)
+        got = foo(arr)
+        self.assertPreciseEqual(expected, got)
+
+        @jit(nopython=True)
+        def flag_check(arr):
+            out = []
+            for sub in arr:
+                out.append((sub, sub.flags.c_contiguous,
+                            sub.flags.f_contiguous))
+            return out
+
+        arr = np.arange(10).reshape((2, 5), order='F')
+        expected = flag_check.py_func(arr)
+        got = flag_check(arr)
+
+        self.assertEqual(len(expected), len(got))
+        ex_arr, e_flag_c, e_flag_f = expected[0]
+        go_arr, g_flag_c, g_flag_f = got[0]
+        np.testing.assert_allclose(ex_arr, go_arr)
+        self.assertEqual(e_flag_c, g_flag_c)
+        self.assertEqual(e_flag_f, g_flag_f)
+
     def test_array_view_iter(self):
         # Test iterating over a 1d view over a 2d array
         arr = np.arange(12).reshape((3, 4))
diff --git a/numba/tests/test_typedlist.py b/numba/tests/test_typedlist.py
--- a/numba/tests/test_typedlist.py
+++ b/numba/tests/test_typedlist.py
@@ -1016,6 +1016,7 @@ def foo():
 
     def test_list_as_item_in_list(self):
         nested_type = types.ListType(types.int32)
+
         @njit
         def foo():
             la = List.empty_list(nested_type)
@@ -1030,6 +1031,7 @@ def foo():
 
     def test_array_as_item_in_list(self):
         nested_type = types.Array(types.float64, 1, 'C')
+
         @njit
         def foo():
             l = List.empty_list(nested_type)
@@ -1321,6 +1323,7 @@ def foo():
 
     def test_append_fails(self):
         self.disable_leak_check()
+
         @njit
         def foo():
             l = List()
@@ -1470,21 +1473,27 @@ def foo():
     def test_ndarray_twod(self):
 
         @njit
-        def foo():
-            return List(np.array([[1,2],[3,4]]))
+        def foo(x):
+            return List(x)
 
-        expected = List()
-        expected.append(np.array([1,2]))
-        expected.append(np.array([3,4]))
-        received = foo()
+        carr = np.array([[1, 2], [3, 4]])
+        farr = np.asfortranarray(carr)
+        aarr = np.arange(8).reshape((2, 4))[:, ::2]
+
+        for layout, arr in zip('CFA', (carr, farr, aarr)):
+            self.assertEqual(typeof(arr).layout, layout)
+            expected = List()
+            expected.append(arr[0, :])
+            expected.append(arr[1, :])
+            received = foo(arr)
 
-        np.testing.assert_equal(expected[0], received[0])
-        np.testing.assert_equal(expected[1], received[1])
+            np.testing.assert_equal(expected[0], received[0])
+            np.testing.assert_equal(expected[1], received[1])
 
-        pyreceived = foo.py_func()
+            pyreceived = foo.py_func(arr)
 
-        np.testing.assert_equal(expected[0], pyreceived[0])
-        np.testing.assert_equal(expected[1], pyreceived[1])
+            np.testing.assert_equal(expected[0], pyreceived[0])
+            np.testing.assert_equal(expected[1], pyreceived[1])
 
     def test_exception_on_plain_int(self):
         @njit