Repository: devitocodes/devito
Issue: cire advanced option does not generate MPI code

the operator in the MFE below does not generate MPI code when run with `DEVITO_MPI=1`

```
from devito import Grid, TimeFunction, Eq, Operator
grid = Grid(shape=(41,41,11))
p0 = TimeFunction(name='p0', grid=grid, time_order=2, space_order=8)
update_p = (p0.dx).dx + (p0.dy).dy + (p0.dz).dz 
op = Operator([stencil_p0, src_term], name='MFE', 
    opt=('advanced', {'cire-repeats-sops': 9, 'cire-rotate': True}))
op.apply(time_M=10)
```

Changed files:
devito/passes/iet/mpi.py, tests/test_mpi.py

diff --git a/devito/passes/iet/mpi.py b/devito/passes/iet/mpi.py
index 3a95b7985..b4115028c 100644
--- a/devito/passes/iet/mpi.py
+++ b/devito/passes/iet/mpi.py
@@ -7,7 +7,7 @@ from devito.ir.iet import (Call, Expression, HaloSpot, Iteration, FindNodes,
 from devito.ir.support import PARALLEL, Scope
 from devito.mpi import HaloExchangeBuilder, HaloScheme
 from devito.passes.iet.engine import iet_pass
-from devito.tools import filter_sorted, generator
+from devito.tools import as_mapper, filter_sorted, generator
 
 __all__ = ['optimize_halospots', 'mpiize']
 
@@ -39,8 +39,11 @@ def _drop_halospots(iet):
 
     # If a HaloSpot Dimension turns out to be SEQUENTIAL, then the HaloSpot is useless
     for hs, iterations in MapNodes(HaloSpot, Iteration).visit(iet).items():
-        if any(i.is_Sequential for i in iterations if i.dim.root in hs.dimensions):
-            mapper[hs].update(set(hs.functions))
+        dmapper = as_mapper(iterations, lambda i: i.dim.root)
+        for d, v in dmapper.items():
+            if d in hs.dimensions and all(i.is_Sequential for i in v):
+                mapper[hs].update(set(hs.functions))
+                break
 
     # If all HaloSpot reads pertain to increments, then the HaloSpot is useless
     for hs, expressions in MapNodes(HaloSpot, Expression).visit(iet).items():
diff --git a/tests/test_mpi.py b/tests/test_mpi.py
index 6c10ae613..6d0b489a6 100644
--- a/tests/test_mpi.py
+++ b/tests/test_mpi.py
@@ -8,7 +8,8 @@ from devito import (Grid, Constant, Function, TimeFunction, SparseFunction,
                     SubDomain, Eq, Ne, Inc, NODE, Operator, norm, inner, configuration,
                     switchconfig, generic_derivative)
 from devito.data import LEFT, RIGHT
-from devito.ir.iet import Call, Conditional, Iteration, FindNodes, retrieve_iteration_tree
+from devito.ir.iet import (Call, Conditional, Iteration, FindNodes, FindSymbols,
+                           retrieve_iteration_tree)
 from devito.mpi import MPI
 from examples.seismic.acoustic import acoustic_setup
 
@@ -1660,7 +1661,7 @@ class TestOperatorAdvanced(object):
         assert (np.isclose(norm(f), 17.24904, atol=1e-4, rtol=0))
 
     @pytest.mark.parallel(mode=[(4, 'basic'), (4, 'overlap2', True)])
-    def test_aliases(self):
+    def test_cire(self):
         """
         Check correctness when the DSE extracts aliases and places them
         into offset-ed loop (nest). For example, the compiler may generate:
@@ -1699,9 +1700,9 @@ class TestOperatorAdvanced(object):
         assert u0_norm == u1_norm
 
     @pytest.mark.parallel(mode=[(4, 'overlap2', True)])
-    def test_aliases_with_shifted_diagonal_halo_touch(self):
+    def test_cire_with_shifted_diagonal_halo_touch(self):
         """
-        Like ``test_aliases`` but now the diagonal halos required to compute
+        Like ``test_cire`` but now the diagonal halos required to compute
         the aliases are shifted due to the iteration space. Basically, this
         is checking that ``TimedAccess.touched_halo`` does the right thing
         using the information stored in ``.intervals``.
@@ -1729,6 +1730,37 @@ class TestOperatorAdvanced(object):
 
         assert u0_norm == u1_norm
 
+    @pytest.mark.parallel(mode=4)
+    def test_cire_w_rotations(self):
+        """
+        MFE for issue #1490.
+        """
+        grid = Grid(shape=(128, 128, 128), dtype=np.float64)
+
+        p = TimeFunction(name='p', grid=grid, time_order=2, space_order=8)
+        p1 = TimeFunction(name='p', grid=grid, time_order=2, space_order=8)
+
+        p.data[0, 40:80, 40:80, 40:80] = 0.12
+        p1.data[0, 40:80, 40:80, 40:80] = 0.12
+
+        eqn = Eq(p.forward, (p.dx).dx + (p.dy).dy + (p.dz).dz)
+
+        op0 = Operator(eqn, opt='noop')
+        op1 = Operator(eqn, opt=('advanced', {'cire-repeats-sops': 9,
+                                              'cire-rotate': True}))
+
+        # Check generated code
+        arrays = [i for i in FindSymbols().visit(op1._func_table['bf0']) if i.is_Array]
+        assert len(arrays) == 3
+        assert 'haloupdate_0' in op1._func_table
+
+        op0.apply(time_M=1)
+        op1.apply(time_M=1, p=p1)
+
+        # TODO: we can tighthen the tolerance, or switch to single precision,
+        # once issue #1438 is fixed
+        assert np.allclose(p.data, p1.data, rtol=10e-11)
+
     @pytest.mark.parallel(mode=[(4, 'full', True)])
     def test_staggering(self):
         """