Repository: devitocodes/devito
Issue: min-storage advanced option does not compile with MPI

the operator in the MFE below does not compile 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(Eq(p0.forward, update_p), name='MFE', opt=('advanced', {'min-storage': 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 b4115028c..ff432383d 100644
--- a/devito/passes/iet/mpi.py
+++ b/devito/passes/iet/mpi.py
@@ -70,11 +70,11 @@ def _hoist_halospots(iet):
     # a stopper to halo hoisting
 
     def rule0(dep, candidates):
-        # E.g., `dep=W<f,[x]> -> R<f,[x-1]>` and `candidates=(time, x)` => False
+        # E.g., `dep=W<f,[x]> -> R<f,[x-1]>` and `candidates=({time}, {x})` => False
         # E.g., `dep=W<f,[t1, x, y]> -> R<f,[t0, x-1, y+1]>`, `dep.cause={t,time}` and
-        #       `candidates=(x,)` => True
-        return (all(d in dep.distance_mapper for d in candidates) and
-                not dep.cause & candidates)
+        #       `candidates=({x},)` => True
+        return (all(i & set(dep.distance_mapper) for i in candidates) and
+                not any(i & dep.cause for i in candidates))
 
     def rule1(dep, candidates):
         # An increment isn't a stopper to hoisting
@@ -94,7 +94,7 @@ def _hoist_halospots(iet):
 
             for f in hs.fmapper:
                 for n, i in enumerate(iters):
-                    candidates = set().union(*[i.dim._defines for i in iters[n:]])
+                    candidates = [i.dim._defines for i in iters[n:]]
 
                     test = True
                     for dep in scopes[i].d_flow.project(f):
diff --git a/tests/test_mpi.py b/tests/test_mpi.py
index 6d0b489a6..c2f39c83a 100644
--- a/tests/test_mpi.py
+++ b/tests/test_mpi.py
@@ -1731,9 +1731,13 @@ class TestOperatorAdvanced(object):
         assert u0_norm == u1_norm
 
     @pytest.mark.parallel(mode=4)
-    def test_cire_w_rotations(self):
+    @pytest.mark.parametrize('opt_options', [
+        {'cire-repeats-sops': 9, 'cire-rotate': True},  # Issue #1490 (rotating registers)
+        {'min-storage': True},                          # Issue #1491 (min-storage option)
+    ])
+    def test_cire_options(self, opt_options):
         """
-        MFE for issue #1490.
+        MFEs for several issues tracked on GitHub.
         """
         grid = Grid(shape=(128, 128, 128), dtype=np.float64)
 
@@ -1746,19 +1750,23 @@ class TestOperatorAdvanced(object):
         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}))
+        op1 = Operator(eqn, opt=('advanced', opt_options))
 
         # 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
+        # We expect exactly one halo exchange
+        calls = FindNodes(Call).visit(op1)
+        assert len(calls) == 5
+        assert calls[0].name == 'haloupdate_0'
+        assert all(i.name == 'bf0' for i in calls[1:])
 
         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
+        # TODO: we will tighten the tolerance, or switch to single precision,
+        # or both, once issue #1438 is fixed
         assert np.allclose(p.data, p1.data, rtol=10e-11)
 
     @pytest.mark.parallel(mode=[(4, 'full', True)])