77from sympy import Expr , S
88
99from devito .ir .support .space import Backward , null_ispace
10- from devito .ir .support .utils import AccessMode , extrema
10+ from devito .ir .support .utils import AccessMode , erange , extrema
1111from devito .ir .support .vector import LabeledVector , Vector
1212from devito .symbolics import (
1313 compare_ops , q_affine , q_comp_acc , q_constant , retrieve_indexed , search
@@ -358,6 +358,9 @@ def distance(self, other, logical=False):
358358 # E.g., `uv(x).x` and `uv(x).y` -- not a real dependence!
359359 return Vector (S .ImaginaryUnit )
360360
361+ if disjoint_subdims (self , other ):
362+ return Vector (S .ImaginaryUnit )
363+
361364 ret = []
362365 for sit , oit in zip (self .itintervals , other .itintervals , strict = False ):
363366 n = len (ret )
@@ -369,20 +372,14 @@ def distance(self, other, logical=False):
369372 # E.g., `self=R<f,[x]>` and `self.itintervals=(x, i)`
370373 break
371374
372- # If over SubDimensions, check disjointness
373- test = disjoint_subdims (self [n ], other [n ], sai , oai , sit , oit )
374- if test == DISJOINT :
375- return Vector (S .ImaginaryUnit )
376- elif test == MAYBE_OVERLAP :
377- ret .append (S .Infinity )
378- continue
379-
380375 try :
381376 if not (sit == oit and sai .root is oai .root ):
382377 # E.g., `self=R<f,[x + 2]>` and `other=W<f,[i + 1]>`
383378 # E.g., `self=R<f,[x]>`, `other=W<f,[x + 1]>`,
384379 # `self.itintervals=(x<0>,)`, `other.itintervals=(x<1>,)`
385- return vinf (ret )
380+ # Keep looking: a later axis may prove disjointness
381+ ret .append (S .Infinity )
382+ continue
386383 except AttributeError :
387384 # E.g., `self=R<f,[cy]>` and `self.itintervals=(y,)` => `sai=None`
388385 pass
@@ -1152,7 +1149,29 @@ def reads_smart_gen(self, f):
11521149 """
11531150 Generate all read accesses to a given function.
11541151
1155- StencilDimensions, if any, are replaced with their extrema.
1152+ StencilDimensions, if any, are replaced with:
1153+
1154+ * in presence of SubDimensions: the range of points they span;
1155+ * in all other cases: just their extrema, since it suffices to
1156+ capture all possible dependencies.
1157+
1158+ The reason SubDimensions must be treated specially -- with a full set
1159+ of TimedAccess objects getting generated -- is to handle the special
1160+ case of slabs thinner than the stencil’s reach. For example, consider
1161+ the following scenario:
1162+
1163+ * A SubDimension with just two points, 10 and 11;
1164+ * One equation writes `F[10]` and `F[11]`;
1165+ * Another equation runs over the same SubDimension reading the stencil
1166+ `F[x-4] ... F[x+4]`.
1167+
1168+ If we examine only the two extreme stencil offsets:
1169+
1170+ * `F[x-4]` reads points 6–7: no overlap.
1171+ * `F[x+4]` reads points 14–15: no overlap.
1172+
1173+ But interior offsets certainly overlap -- for instance, `F[x-1]` reads
1174+ 9–10, which includes the producer’s point 10.
11561175
11571176 Notes
11581177 -----
@@ -1163,9 +1182,13 @@ def reads_smart_gen(self, f):
11631182 be found. For example, a DiscreteFunction would never appear among
11641183 the iteration symbols.
11651184 """
1185+ uses_subdims = lambda i : any (d .is_Sub for d in i .ispace .dimensions )
1186+
11661187 if isinstance (f , (Function , Temp , TempArray , TBArray )):
11671188 for i in self .getreads (f ):
1168- for j in extrema (i .access ):
1189+ expand = erange if uses_subdims (i ) else extrema
1190+
1191+ for j in expand (i .access ):
11691192 yield TimedAccess (j , i .mode , i .timestamp , i .ispace )
11701193
11711194 else :
@@ -1581,90 +1604,95 @@ def skippable_interval(d, ispace, it):
15811604 return d is None or (d in ispace and not d ._defines & it .dim ._defines )
15821605
15831606
1584- # Possible return values for `disjoint_subdims`
1585- INAPPLICABLE = 0
1586- DISJOINT = 1
1587- MAYBE_OVERLAP = 2
1588-
1589-
1590- def disjoint_subdims (e0 , e1 , d0 , d1 , it0 , it1 ):
1607+ def disjoint_subdims (a0 , a1 ):
15911608 """
1592- Determine whether two accesses span distinct pieces of the same
1593- SubDimension decomposition.
1594-
1595- Consider a root Dimension `x` with bounds `x_m` and `x_M`. A valid
1596- left/middle/right decomposition with thicknesses `L` and `R` is::
1597-
1598- xl = [x_m, x_m + L - 1]
1599- xm = [x_m + L, x_M - R]
1600- xr = [x_M - R + 1, x_M]
1601-
1602- These intervals are pairwise disjoint. Replacing `xl`, `xm`, or `xr`
1603- with `x` in an affine access removes the choice of partition piece while
1604- retaining the relative access. If two such normalized accesses have zero
1605- distance, they apply the same affine map to disjoint intervals and therefore
1606- cannot refer to the same data point. The apparent dependence is imaginary.
1607-
1608- For example, `f[xl]` and `f[xm]` normalize to `f[x]` and `f[x]`;
1609- they are independent. The same holds for `f[xl + 1]` and `f[xm + 1]`
1610- when their iteration intervals have equal offsets. By contrast, `f[xl]`
1611- and `f[xm - 1]` normalize to different accesses, and the latter may reach
1612- into the left piece, so they must be treated conservatively.
1613-
1614- This proof requires distinct pieces of the same root, compatible declared
1615- thicknesses, affine accesses, and iteration intervals with equal offsets and
1616- directions. Runtime bounds are assumed to preserve the declared partition.
1617- Return DISJOINT if disjointness is proven, and MAYBE_OVERLAP if the
1618- intervals are aligned SubDimensions but are not proven disjoint. In
1619- particular, two declarations of the same left, right, or middle piece
1620- overlap along this Dimension. MAYBE_OVERLAP lets the caller record an
1621- infinite distance and inspect later Dimensions, which may still prove the
1622- multidimensional accesses disjoint. Return INAPPLICABLE if this test does not
1623- apply, so that the general distance analysis can classify the dependence.
1609+ Determine whether two TimedAccesses touch disjoint SubDimension regions
1610+ of the same Function.
1611+
1612+ Compare symbolic accessed bounds, including shifts and stencil points.
1613+ Block intervals are promoted to their logical SubDimensions. Bounds and
1614+ thicknesses remain symbolic: MPI decomposition and runtime overrides can
1615+ change their values independently of the defaults.
1616+
1617+ For example, `xl = [m, m + L - 1]` and `xm = [m + L, M - R]` are
1618+ disjoint when they share the symbol `L`, whatever its runtime value.
1619+ Equal default thicknesses alone do not establish that relationship.
1620+
1621+ Opposite left/right slabs are assumed to form a valid partition: their
1622+ thicknesses satisfy `L + R <= N`. For translated stencil accesses, the
1623+ interior must also accommodate their combined inward reach. For example,
1624+ a pointwise left write and a right read at offset -4 require four interior
1625+ points. Runtime space_order checks cover explicit middle SubDimensions,
1626+ not arbitrary left/right pairs; no concrete domain size or thickness is
1627+ used here.
1628+
1629+ Match data axes independently of the iteration nests. Return True if any
1630+ axis proves separation, False otherwise. Accesses over the same interval
1631+ use the general distance analysis.
16241632 """
1625- try :
1626- # E.g., `f[xl]` over `(xl,)` and `f[xm]` over `(xm,)` need this
1627- # special test, while accesses over the same `(xl,)` should use general
1628- # distance analysis, so we can return immediately in such a case
1629- if not (d0 .is_Sub and
1630- d1 .is_Sub and
1631- d0 .root is d1 .root and
1632- it0 .dim .root is d0 .root and
1633- it1 .dim .root is d1 .root and
1633+ for e0 , e1 , d0 , d1 in zip (a0 , a1 , a0 .aindices , a1 .aindices , strict = False ):
1634+ it0 = a0 .intervals [d0 ]
1635+ it1 = a1 .intervals [d1 ]
1636+ if it0 .is_Null or it1 .is_Null :
1637+ continue
1638+
1639+ it0 = it0 .promote (lambda d : d .is_Incr )
1640+ it1 = it1 .promote (lambda d : d .is_Incr )
1641+ if not (it0 .dim .is_Sub and
1642+ it1 .dim .is_Sub and
1643+ it0 .dim .root is it1 .dim .root and
16341644 it0 != it1 ):
1635- return INAPPLICABLE
1636- except AttributeError :
1637- return INAPPLICABLE
1638-
1639- if (d0 .is_left and d1 .is_middle ) or \
1640- (d0 .is_middle and d1 .is_left ):
1641- is_partition = d0 .ltkn .value == d1 .ltkn .value
1642- elif (d0 .is_middle and d1 .is_right ) or \
1643- (d0 .is_right and d1 .is_middle ):
1644- is_partition = d0 .rtkn .value == d1 .rtkn .value
1645- elif d0 .is_left and d1 .is_right :
1646- is_partition = d0 .ltkn .value is not None and d1 .rtkn .value is not None
1647- elif d0 .is_right and d1 .is_left :
1648- is_partition = d0 .rtkn .value is not None and d1 .ltkn .value is not None
1649- else :
1650- is_partition = False
1651-
1652- if not is_partition :
1653- return MAYBE_OVERLAP
1654-
1655- if not q_affine (e0 , d0 ) or not q_affine (e1 , d1 ):
1656- return MAYBE_OVERLAP
1645+ continue
16571646
1658- if it0 .offsets != it1 .offsets or it0 .direction is not it1 .direction :
1659- return MAYBE_OVERLAP
1647+ bounds = []
1648+ for e , d , it in ((e0 , d0 , it0 ), (e1 , d1 , it1 )):
1649+ if not q_affine (e , d ):
1650+ break
16601651
1661- e0 = e0 ._subs (d0 , d0 .root )
1662- e1 = e1 ._subs (d1 , d1 .root )
1652+ lower , upper = [], []
1653+ for v in erange (e ):
1654+ slope = v .diff (d )
1655+ if slope .is_nonnegative :
1656+ m , M = it .symbolic_min , it .symbolic_max
1657+ elif slope .is_nonpositive :
1658+ M , m = it .symbolic_min , it .symbolic_max
1659+ else :
1660+ break
1661+ lower .append (v ._subs (d , m ))
1662+ upper .append (v ._subs (d , M ))
1663+ else :
1664+ bounds .append ((sympy .Min (* lower ), sympy .Max (* upper )))
1665+
1666+ if len (bounds ) == 2 :
1667+ (m0 , M0 ), (m1 , M1 ) = bounds
1668+ mapper = {}
1669+
1670+ dl , dr = (it0 .dim , it1 .dim ) if it0 .dim .is_left else (it1 .dim , it0 .dim )
1671+ dlp , drp = dl .parent , dr .parent
1672+
1673+ if dl .is_left and dr .is_right and dlp is drp :
1674+ # A valid partition satisfies L + R <= N, where N is the parent
1675+ # extent; an explicit middle SubDomain checks this at construction.
1676+ # Further, for stencils, we require that:
1677+ # `N - L - R >= the combined inward reach`
1678+ # so accesses from opposite slabs cannot meet. Explicit middle
1679+ # SubDimensions check for at least space_order interior points
1680+ # at *op.apply time*, accounting for runtime overrides. Without
1681+ # an explicit middle, the gap assumption is unchecked
1682+ gap = sympy .Dummy (nonnegative = True )
1683+ if e0 .diff (d0 ) == e1 .diff (d1 ) == 1 :
1684+ M , m = (M0 , m1 ) if it0 .dim .is_left else (M1 , m0 )
1685+ reach = (M - dl .symbolic_max - m + dr .symbolic_min ).expand ()
1686+ if is_integer (reach ):
1687+ gap += max (0 , reach )
1688+
1689+ mapper [dlp .symbolic_max ] = dlp .symbolic_min + dl .ltkn + dr .rtkn + gap - 1
1690+
1691+ if (M0 - m1 ).subs (mapper ).is_negative or \
1692+ (M1 - m0 ).subs (mapper ).is_negative :
1693+ return True
16631694
1664- if e0 - e1 == 0 :
1665- return DISJOINT
1666- else :
1667- return MAYBE_OVERLAP
1695+ return False
16681696
16691697
16701698def disjoint_test (e0 , e1 , d , it ):
0 commit comments