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:

Patch:
diff --git a/numba/core/types/iterators.py b/numba/core/types/iterators.py
index c66a706fd..2baf1d42a 100644
--- a/numba/core/types/iterators.py
+++ b/numba/core/types/iterators.py
@@ -103,5 +103,6 @@ class ArrayIterator(SimpleIteratorType):
         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
index 82259b5bc..3e4fab769 100644
--- 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
index 59303775b..041ff29d1 100644
--- a/numba/tests/test_array_iterators.py
+++ b/numba/tests/test_array_iterators.py
@@ -174,7 +174,7 @@ class TestArrayIterators(MemoryLeakMixin, TestCase):
         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 @@ class TestArrayIterators(MemoryLeakMixin, TestCase):
         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
index 0164beced..25da59e28 100644
--- a/numba/tests/test_typedlist.py
+++ b/numba/tests/test_typedlist.py
@@ -1016,6 +1016,7 @@ class TestListRefctTypes(MemoryLeakMixin, TestCase):
 
     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 @@ class TestListRefctTypes(MemoryLeakMixin, TestCase):
 
     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 @@ class TestImmutable(MemoryLeakMixin, TestCase):
 
     def test_append_fails(self):
         self.disable_leak_check()
+
         @njit
         def foo():
             l = List()
@@ -1470,21 +1473,27 @@ class TestListFromIter(MemoryLeakMixin, TestCase):
     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