Repository: devitocodes/devito
Issue: A function that appears in a ConditionalDimension, but not in the Operator input is not always being added to the parameters of the Operator.

A function that appears in a ConditionalDimension, but not in the Operator input is not always beeing added to the parameters of the Operator. May need to run a few times to reproduce.

**MFE**:
```
import numpy as np
from devito import ConditionalDimension, Function, Eq, Operator, Dimension
from devito.types import Lt

shape = (8, 8)
x = Dimension(name='x')
y = Dimension(name='y')
g = Function(name='g', shape=shape, dimensions=(x, y), dtype=np.float32)
cond = Lt(g, 2)
ci = ConditionalDimension(name='ci', parent=y, condition=cond)
f = Function(name='f', shape=shape, dimensions=(x, ci))
eq1 = Eq(f, 5)
op = Operator([eq1])
print(op.ccode)
import pdb; pdb.set_trace()
op.apply()
```
**Error log:**
```
Traceback (most recent call last):
  File "tests/mfe_args.py", line 23, in <module>
    op.apply()
  File "/home/gbisbas/workspace/devito/devito/operator/operator.py", line 655, in apply
    args = self.arguments(**kwargs)
  File "/home/gbisbas/workspace/devito/devito/operator/operator.py", line 537, in arguments
    args = self._prepare_arguments(**kwargs)
  File "/home/gbisbas/workspace/devito/devito/operator/operator.py", line 482, in _prepare_arguments
    p._arg_check(args, self._dspace[p])
AttributeError: 'Symbol' object has no attribute '_arg_check'
```

**Hook from @FabioLuporini :**

I think I understand the issue above
we’re lacking a small piece of code that searches cd.condition
https://github.com/devitocodes/devito/blob/master/devito/ir/support/utils.py#L144
```
devito/ir/support/utils.py:144
        for j in candidates:
```
among candidates there could a ConditionalDimension. That must be searched inside its condition
actually we can make it even simpler a (lowered) expression knows its conditionals: https://github.com/devitocodes/devito/blob/master/devito/ir/equations/equation.py#L159
```
devito/ir/equations/equation.py:159
        expr._conditionals = tuple(conditionals)
```
so I think we just need to extend the roots here https://github.com/devitocodes/devito/blob/master/devito/ir/support/utils.py#L131 with the conditionals (if any, clearly; as you know, very often there are none)
```
devito/ir/support/utils.py:131
            roots.extend(list(i.lhs.indices))
```
try that, in a separate, self-contained PR, which includes patch + MFE. Thanks!

Changed files:
devito/ir/support/utils.py, devito/ir/clusters/algorithms.py, tests/test_dimension.py, devito/ir/equations/equation.py, devito/ir/support/basic.py

diff --git a/devito/ir/clusters/algorithms.py b/devito/ir/clusters/algorithms.py
index 308fee8e1..8beb1bfec 100644
--- a/devito/ir/clusters/algorithms.py
+++ b/devito/ir/clusters/algorithms.py
@@ -6,8 +6,6 @@ from devito.ir.support import Any, Backward, Forward, IterationSpace
 from devito.ir.clusters.analysis import analyze
 from devito.ir.clusters.cluster import Cluster, ClusterGroup
 from devito.ir.clusters.queue import QueueStateful
-from devito.ir.equations.algorithms import lower_exprs
-from devito.symbolics import CondEq
 from devito.tools import timed_pass
 
 __all__ = ['clusterize']
@@ -165,23 +163,26 @@ def guard(clusters):
     processed = []
     for c in clusters:
         # Group together consecutive expressions with same ConditionalDimensions
-        for cds, g in groupby(c.exprs, key=lambda e: e.conditionals):
+        for cds, g in groupby(c.exprs, key=lambda e: tuple(e.conditionals)):
             exprs = list(g)
 
             if not cds:
                 processed.append(c.rebuild(exprs=exprs))
                 continue
 
-            # Create a guarded Cluster
+            # Chain together all conditions from all expressions in `c`
             guards = {}
             for cd in cds:
                 condition = guards.setdefault(cd.parent, [])
-                if cd.condition is None:
-                    condition.append(CondEq(cd.parent % cd.factor, 0))
-                else:
-                    condition.append(lower_exprs(cd.condition))
-            guards = {k: sympy.And(*v, evaluate=False) for k, v in guards.items()}
-            exprs = [e.func(*e.args, conditionals=dict(guards)) for e in exprs]
+                for e in exprs:
+                    try:
+                        condition.append(e.conditionals[cd])
+                        break
+                    except KeyError:
+                        pass
+            guards = {d: sympy.And(*v, evaluate=False) for d, v in guards.items()}
+
+            # Construct a guarded Cluster
             processed.append(c.rebuild(exprs=exprs, guards=guards))
 
     return ClusterGroup(processed)
diff --git a/devito/ir/equations/equation.py b/devito/ir/equations/equation.py
index e5cc15755..2f68b243f 100644
--- a/devito/ir/equations/equation.py
+++ b/devito/ir/equations/equation.py
@@ -1,11 +1,12 @@
 from cached_property import cached_property
 import sympy
 
-from devito.ir.equations.algorithms import dimension_sort
+from devito.ir.equations.algorithms import dimension_sort, lower_exprs
 from devito.finite_differences.differentiable import diff2sympy
 from devito.ir.support import (IterationSpace, DataSpace, Interval, IntervalGroup,
                                Stencil, detect_accesses, detect_oobs, detect_io,
                                build_intervals, build_iterators)
+from devito.symbolics import CondEq
 from devito.tools import Pickable, frozendict
 from devito.types import Eq
 
@@ -122,7 +123,7 @@ class LoweredEq(sympy.Eq, IREq):
         # Analyze the expression
         mapper = detect_accesses(expr)
         oobs = detect_oobs(mapper)
-        conditionals = [i for i in ordering if i.is_Conditional]
+        conditional_dimensions = [i for i in ordering if i.is_Conditional]
 
         # Construct Intervals for IterationSpace and DataSpace
         intervals = build_intervals(Stencil.union(*mapper.values()))
@@ -144,11 +145,20 @@ class LoweredEq(sympy.Eq, IREq):
 
         # Construct the DataSpace
         dintervals.extend([Interval(i, 0, 0) for i in ordering
-                           if i not in ispace.dimensions + conditionals])
+                           if i not in ispace.dimensions + conditional_dimensions])
         parts = {k: IntervalGroup(build_intervals(v)).add(iintervals)
                  for k, v in mapper.items() if k}
         dspace = DataSpace(dintervals, parts)
 
+        # Construct the conditionals
+        conditionals = {}
+        for d in conditional_dimensions:
+            if d.condition is None:
+                conditionals[d] = CondEq(d.parent % d.factor, 0)
+            else:
+                conditionals[d] = lower_exprs(d.condition)
+        conditionals = frozendict(conditionals)
+
         # Lower all Differentiable operations into SymPy operations
         rhs = diff2sympy(expr.rhs)
 
@@ -157,7 +167,7 @@ class LoweredEq(sympy.Eq, IREq):
 
         expr._dspace = dspace
         expr._ispace = ispace
-        expr._conditionals = frozendict([(d, ()) for d in conditionals])
+        expr._conditionals = conditionals
         expr._reads, expr._writes = detect_io(expr)
 
         expr._is_Increment = input_expr.is_Increment
diff --git a/devito/ir/support/basic.py b/devito/ir/support/basic.py
index f90cda631..51863889c 100644
--- a/devito/ir/support/basic.py
+++ b/devito/ir/support/basic.py
@@ -697,9 +697,8 @@ class Scope(object):
                 self.initialized.add(e.lhs.function)
 
             # Look up ConditionalDimensions
-            for d, v in e.conditionals.items():
-                symbols = d.free_symbols | set(retrieve_terminals(v))
-                for j in symbols:
+            for v in e.conditionals.values():
+                for j in retrieve_terminals(v):
                     v = self.reads.setdefault(j.function, [])
                     v.append(TimedAccess(j, 'R', -1, e.ispace))
 
diff --git a/devito/ir/support/utils.py b/devito/ir/support/utils.py
index 5b3b41f67..579206987 100644
--- a/devito/ir/support/utils.py
+++ b/devito/ir/support/utils.py
@@ -121,7 +121,7 @@ def detect_io(exprs, relax=False):
     else:
         rule = lambda i: i.is_Scalar or i.is_Tensor
 
-    # Don't forget this nasty case, with indirections on the LHS:
+    # Don't forget the nasty case with indirections on the LHS:
     # >>> u[t, a[x]] = f[x]  -> (reads={a, f}, writes={u})
 
     roots = []
@@ -129,6 +129,7 @@ def detect_io(exprs, relax=False):
         try:
             roots.append(i.rhs)
             roots.extend(list(i.lhs.indices))
+            roots.extend(list(i.conditionals.values()))
         except AttributeError:
             # E.g., FunctionFromPointer
             roots.append(i)
diff --git a/tests/test_dimension.py b/tests/test_dimension.py
index a08883a63..0efda3e1d 100644
--- a/tests/test_dimension.py
+++ b/tests/test_dimension.py
@@ -951,6 +951,24 @@ class TestConditionalDimension(object):
         assert np.all(f.data[2:6, c1:c2] == 5.)
         assert np.all(f.data[:, c3:c4] < 5.)
 
+    def test_from_cond_to_param(self):
+        """
+        Test that Functions appearing in the condition of a ConditionalDimension
+        but not explicitly in an Eq are actually part of the Operator input
+        (stems from issue #1298).
+        """
+        grid = Grid(shape=(8, 8))
+        x, y = grid.dimensions
+
+        g = Function(name='g', grid=grid)
+        h = Function(name='h', grid=grid)
+        ci = ConditionalDimension(name='ci', parent=y, condition=Lt(g, 2 + h))
+        f = Function(name='f', shape=grid.shape, dimensions=(x, ci))
+
+        for _ in range(5):
+            # issue #1298 was non deterministic
+            Operator(Eq(f, 5)).apply()
+
     @skipif('device')
     def test_no_fusion_simple(self):
         """