Repository: devitocodes/devito
Issue: Collapse incorrect depth

```
    nx, ny, nchi, ncho = 12, 12, 1, 1
    dt = np.float32
    x, y, ci, co = SpaceDimension("x"), SpaceDimension("y"), Dimension("ci"), Dimension("co")
    grid = Grid((nx, ny), dtype=dt, dimensions=(x, y))
    stride = 2
    # Image
    X = Function(name="xin", dimensions=(ci, x, y),
                 shape=(nchi, nx, ny), grid=grid, space_order=n//2)
    # Output
    dy = Function(name="dy", dimensions=(co, x, y),
                  shape=(ncho, nx, ny), grid=grid, space_order=n//2)
    # Weights
    i, j = Dimension("i"), Dimension("j")
    dW = Function(name="dW", dimensions=(co, ci, i, j), shape=(ncho, nchi, n, m), grid=grid)
    # Convolution
    eq = [Eq(dW[co, ci, i, j], dW[co, ci, i, j] + dy[co, x, y]*X[ci, x+i-n//2, y+j-m//2])
               for i in range(n) for j in range(m)]
    op = Operator(eq)
```

produces incorrect code with a `colapse(3)` than can lead to shared  `co/ci` 



Changed files:
devito/ir/clusters/analysis.py, tests/test_dle.py, devito/archinfo.py, devito/ir/equations/equation.py, devito/ir/support/basic.py

diff --git a/devito/archinfo.py b/devito/archinfo.py
index 3a04d06f8..babd72f28 100644
--- a/devito/archinfo.py
+++ b/devito/archinfo.py
@@ -89,8 +89,6 @@ def get_cpu_info():
     cpu_info['logical'] = logical
 
     # Detect number of physical cores
-    # TODO: on multi-socket systems + unix, can't use psutil due to
-    # `https://github.com/giampaolo/psutil/issues/1558`
     # Special case: in some ARM processors psutils fails to detect physical cores
     # correctly so we use lscpu()
     try:
@@ -99,7 +97,8 @@ def get_cpu_info():
             return cpu_info
     except:
         pass
-
+    # TODO: on multi-socket systems + unix, can't use psutil due to
+    # `https://github.com/giampaolo/psutil/issues/1558`
     mapper = {}
     if lines:
         # Copied and readapted from psutil
diff --git a/devito/ir/clusters/analysis.py b/devito/ir/clusters/analysis.py
index 856070f06..1a37ab538 100644
--- a/devito/ir/clusters/analysis.py
+++ b/devito/ir/clusters/analysis.py
@@ -97,12 +97,12 @@ class Parallelism(Detector):
                 is_parallel_indep &= (dep.distance_mapper.get(d.root) == 0)
                 continue
 
+            if dep.function in scope.initialized:
+                # False alarm, the dependence is over a locally-defined symbol
+                continue
+
             if dep.is_increment:
-                if dep.function in scope.initialized:
-                    # False alarm, the increment is over a locally-defined symbol
-                    pass
-                else:
-                    is_parallel_atomic = True
+                is_parallel_atomic = True
                 continue
 
             return SEQUENTIAL
diff --git a/devito/ir/equations/equation.py b/devito/ir/equations/equation.py
index 97a2c12f3..470752f7d 100644
--- a/devito/ir/equations/equation.py
+++ b/devito/ir/equations/equation.py
@@ -13,11 +13,8 @@ from devito.types import Eq
 __all__ = ['LoweredEq', 'ClusterizedEq', 'DummyEq']
 
 
-class IREq(object):
+class IREq(sympy.Eq):
 
-    """
-    A mixin providing operations common to all :mod:`ir` equation types.
-    """
     _state = ('is_Increment', 'ispace', 'dspace', 'conditionals', 'implicit_dims')
 
     @property
@@ -83,7 +80,7 @@ class IREq(object):
         return {i: getattr(self, i) for i in self._state}
 
 
-class LoweredEq(sympy.Eq, IREq):
+class LoweredEq(IREq):
 
     """
     LoweredEq(devito.Eq)
@@ -198,7 +195,7 @@ class LoweredEq(sympy.Eq, IREq):
         return super(LoweredEq, self).func(*args, **self.state, evaluate=False)
 
 
-class ClusterizedEq(sympy.Eq, IREq, Pickable):
+class ClusterizedEq(IREq, Pickable):
 
     """
     ClusterizedEq(devito.IREq, **kwargs)
diff --git a/devito/ir/support/basic.py b/devito/ir/support/basic.py
index 91ef7c7ce..90febec1e 100644
--- a/devito/ir/support/basic.py
+++ b/devito/ir/support/basic.py
@@ -496,7 +496,7 @@ class Dependence(object):
 
     @cached_property
     def is_increment(self):
-        return self.source.is_increment and self.sink.is_increment
+        return self.source.is_increment or self.sink.is_increment
 
     @cached_property
     def is_regular(self):
@@ -563,10 +563,9 @@ class Dependence(object):
         Return True if ``dim`` may represent a reduction dimension for
         ``self``, False otherwise.
         """
-        test0 = self.is_increment
-        test1 = self.is_regular
-        test2 = all(i not in self._defined_findices for i in dim._defines)
-        return test0 and test1 and test2
+        return (self.is_increment and
+                self.is_regular and
+                not (dim._defines & self._defined_findices))
 
     @memoized_meth
     def is_reduce_atmost(self, dim=None):
@@ -575,7 +574,8 @@ class Dependence(object):
         represent a reduction dimension for ``self`` or if `self`` is definitely
         independent of ``dim``, False otherwise.
         """
-        return self.is_reduce(dim) or self.is_indep(dim)
+        return (not (dim._defines & self._defined_findices)
+                or self.is_indep(dim))
 
     @memoized_meth
     def is_indep(self, dim=None):
@@ -591,23 +591,19 @@ class Dependence(object):
                 # `dim` *and* that the distance along `dim` is 0 (e.g., dim=x and
                 # f[x, g[x, y]] -> f[x, h[x, y]])
                 try:
-                    test0 = self.source.affine(dim)
-                    test1 = self.sink.affine(dim)
-                    test2 = self.distance_mapper[dim] == 0
-                    return test0 and test1 and test2
+                    return (self.source.affine(dim) and
+                            self.sink.affine(dim) and
+                            self.distance_mapper[dim] == 0)
                 except KeyError:
                     # `dim is None` or anything not in `self._defined_findices`
                     return False
             if dim is None:
                 return self.distance == 0
             else:
-                # Note: below, `i in self._defined_findices` is to check whether `i`
-                # is actually (one of) the reduction dimension(s), in which case
-                # `self` would indeed be a dimension-dependent dependence
-                test0 = (not self.is_increment or
-                         any(i in self._defined_findices for i in dim._defines))
-                test1 = len(self.cause & dim._defines) == 0
-                return test0 and test1
+                # The only hope at this point is that `dim` appears in the findices
+                # with distance 0 (that is, it's not the cause of the dependence)
+                return (any(i in self._defined_findices for i in dim._defines) and
+                        len(self.cause & dim._defines) == 0)
         except TypeError:
             # Conservatively assume this is not dimension-independent
             return False
diff --git a/tests/test_dle.py b/tests/test_dle.py
index c8d7f1dec..6f0c26f49 100644
--- a/tests/test_dle.py
+++ b/tests/test_dle.py
@@ -4,8 +4,8 @@ from operator import mul
 import numpy as np
 import pytest
 
-from devito import (Grid, Function, TimeFunction, SparseTimeFunction, SubDimension,
-                    Eq, Inc, Operator, info)
+from devito import (Grid, Function, TimeFunction, SparseTimeFunction, SpaceDimension,
+                    Dimension, SubDimension, Eq, Inc, Operator, info)
 from devito.exceptions import InvalidArgument
 from devito.ir.iet import Call, Iteration, Conditional, FindNodes, retrieve_iteration_tree
 from devito.passes import NThreads, NThreadsNonaffine
@@ -499,6 +499,38 @@ class TestNodeParallelism(object):
                 for k in i.pragmas:
                     assert 'omp for collapse' not in k.value
 
+    def test_collapsing_v2(self):
+        """
+        MFE from issue #1478.
+        """
+        n = 8
+        m = 8
+        nx, ny, nchi, ncho = 12, 12, 1, 1
+        x, y = SpaceDimension("x"), SpaceDimension("y")
+        ci, co = Dimension("ci"), Dimension("co")
+        i, j = Dimension("i"), Dimension("j")
+        grid = Grid((nx, ny), dtype=np.float32, dimensions=(x, y))
+
+        X = Function(name="xin", dimensions=(ci, x, y),
+                     shape=(nchi, nx, ny), grid=grid, space_order=n//2)
+        dy = Function(name="dy", dimensions=(co, x, y),
+                      shape=(ncho, nx, ny), grid=grid, space_order=n//2)
+        dW = Function(name="dW", dimensions=(co, ci, i, j), shape=(ncho, nchi, n, m),
+                      grid=grid)
+
+        eq = [Eq(dW[co, ci, i, j],
+                 dW[co, ci, i, j] + dy[co, x, y]*X[ci, x+i-n//2, y+j-m//2])
+              for i in range(n) for j in range(m)]
+
+        op = Operator(eq, opt=('advanced', {'openmp': True}))
+
+        iterations = FindNodes(Iteration).visit(op)
+        assert len(iterations) == 4
+        assert iterations[0].ncollapse == 1
+        assert iterations[1].is_Vectorized
+        assert iterations[2].is_Sequential
+        assert iterations[3].is_Sequential
+
     def test_scheduling(self):
         """
         Affine iterations -> #pragma omp ... schedule(dynamic,1) ...