Skip to content

Commit 805394b

Browse files
authored
Merge pull request #2869 from devitocodes/patch-degenerating-indices
compiler: Patch degenerating_dimensions to catch SteppingDimensions over size 1 buffer
2 parents 7aae8d9 + 4da6f32 commit 805394b

3 files changed

Lines changed: 104 additions & 17 deletions

File tree

devito/ir/support/basic.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -392,7 +392,7 @@ def distance(self, other):
392392
# objects falls back to zero, as any other value would be
393393
# nonsensical
394394
ret.append(S.Zero)
395-
elif degenerating_dimensions(sai, oai):
395+
elif degenerating_indices(self[n], other[n], self.function):
396396
# Special case: `sai` and `oai` may be different symbolic objects
397397
# but they can be proved to systematically generate the same value
398398
ret.append(S.Zero)
@@ -1425,17 +1425,32 @@ def disjoint_test(e0, e1, d, it):
14251425
return not bool(i0.intersect(i1))
14261426

14271427

1428-
def degenerating_dimensions(d0, d1):
1428+
def degenerating_indices(i0, i1, function):
14291429
"""
1430-
True if `d0` and `d1` are Dimensions that are possibly symbolically
1430+
True if `i0` and `i1` are indices that are possibly symbolically
14311431
different, but they can be proved to systematically degenerate to the
14321432
same value, False otherwise.
14331433
"""
14341434
# Case 1: ModuloDimensions of size 1
14351435
try:
1436-
if d0.is_Modulo and d1.is_Modulo and d0.modulo == d1.modulo == 1:
1436+
if i0.is_Modulo and i1.is_Modulo and i0.modulo == i1.modulo == 1:
14371437
return True
14381438
except AttributeError:
14391439
pass
14401440

1441+
# Case 2: SteppingDimension corresponding to buffer of size 1
1442+
# Extract dimension from both IndexAccessFunctions -> d0, d1
1443+
try:
1444+
d0 = i0.d
1445+
except AttributeError:
1446+
d0 = i0
1447+
try:
1448+
d1 = i1.d
1449+
except AttributeError:
1450+
d1 = i1
1451+
1452+
with suppress(AttributeError):
1453+
if d0 is d1 and d0.is_Stepping and function._size_domain[d0] == 1:
1454+
return True
1455+
14411456
return False

tests/test_fission.py

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@
22

33
from conftest import assert_structure
44
from devito import (
5-
Eq, Function, Grid, Inc, Operator, SubDimension, SubDomain, TimeFunction, solve
5+
Buffer, Eq, Function, Grid, Inc, Operator, SubDimension, SubDomain, TimeFunction,
6+
solve
67
)
8+
from devito.ir.iet import retrieve_iteration_tree
9+
from devito.ir.support.properties import PARALLEL
710

811

912
def test_issue_1725():
@@ -126,3 +129,70 @@ def test_issue_1921():
126129
op1.apply(time_m=1, time_M=5, g=g1)
127130

128131
assert np.all(g.data == g1.data)
132+
133+
134+
def test_buffer1_fissioning():
135+
"""
136+
Tests an edge case whereby inability to spot the equivalence of
137+
`f.forward`/`backward` and `f` when using `Buffer(1)` would cause
138+
data dependance analysis to incorrectly determine that `x` and `y`
139+
loops should be sequential.
140+
"""
141+
so = 2
142+
143+
class SD0(SubDomain):
144+
name = 'sd0'
145+
146+
def __init__(self, so, **kwargs):
147+
self.so = so
148+
super().__init__(**kwargs)
149+
150+
def define(self, dimensions):
151+
map_d = {d: d for d in dimensions}
152+
map_d[dimensions[-1]] = ('left', self.so)
153+
return map_d
154+
155+
grid = Grid(shape=(101, 101, 101))
156+
z = grid.dimensions[-1]
157+
158+
sd0 = SD0(so, grid=grid)
159+
iz = sd0.dimensions[-1]
160+
161+
f0 = TimeFunction(name='f0', grid=grid,
162+
space_order=so, save=Buffer(1))
163+
f1 = TimeFunction(name='f1', grid=grid,
164+
space_order=so, save=Buffer(1))
165+
166+
f2 = TimeFunction(name='f2', grid=grid,
167+
space_order=so, save=Buffer(1))
168+
f3 = TimeFunction(name='f3', grid=grid,
169+
space_order=so, save=Buffer(1))
170+
171+
# Free-surface like conditions plus derivative creates a dependence
172+
eq0 = Eq(f0.backward.subs(z, iz-so), -f0.backward.subs(z, so-iz))
173+
eq1 = Eq(f1.backward.subs(z, iz-so), -f1.backward.subs(z, so-iz))
174+
175+
eq2 = Eq(f2, f0.dx + f2)
176+
eq3 = Eq(f3, f1.dy + f3)
177+
178+
eqs = [eq0, eq1, eq2, eq3]
179+
180+
op = Operator(eqs)
181+
182+
trees = retrieve_iteration_tree(op)
183+
184+
# If the equivalence of f0.backward and f0 are not spotted by the
185+
# compiler, data dependence analysis will determine x and y loops
186+
# to be sequential.
187+
for tree in trees:
188+
for i in range(1, 3):
189+
assert PARALLEL in tree[i].properties
190+
191+
# If parallelisation failed, then there will also be no blocking on
192+
# x and y
193+
assert 'x0_blk0_size' in str(op.parameters)
194+
assert 'y0_blk0_size' in str(op.parameters)
195+
196+
# Two loop nests: free-surface-like and update-like
197+
assert_structure(op, ['t,x,y,z', 't,x0_blk0,y0_blk0,x,y,z'],
198+
't,x,y,z,x0_blk0,y0_blk0,x,y,z')

tests/test_mpi.py

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1887,10 +1887,12 @@ def test_enforce_haloupdate_if_unwritten_function(self, mode):
18871887
@pytest.mark.parallel(mode=1)
18881888
def test_haloupdate_buffer1(self, mode):
18891889
grid = Grid(shape=(4, 4))
1890-
x, y = grid.dimensions
18911890

1892-
u = TimeFunction(name='u', grid=grid, time_order=1, save=Buffer(1))
1893-
v = TimeFunction(name='v', grid=grid, time_order=1, save=Buffer(1))
1891+
# With order 2 (1) forward derivatives, the loops can (and will) be fused,
1892+
# removing the need for a halo update so space_order=4 is used to ensure
1893+
# derivatives are centred resulting in parallel loops and halo updates
1894+
u = TimeFunction(name='u', grid=grid, time_order=1, space_order=4, save=Buffer(1))
1895+
v = TimeFunction(name='v', grid=grid, time_order=1, space_order=4, save=Buffer(1))
18941896

18951897
eqns = [Eq(u.forward, div(v) + 1.),
18961898
Eq(v.forward, div(u.forward) + 1.)]
@@ -1909,22 +1911,22 @@ def test_haloupdate_buffer1(self, mode):
19091911
@pytest.mark.parallel(mode=1)
19101912
@pytest.mark.parametrize('sz,fwd,expr,exp0,exp1,args', [
19111913
(1, True, 'rec.interpolate(v2)', 3, 2, ('v1', 'v2')),
1912-
(1, True, 'Eq(v3.forward, v2.laplace + 1)', 1, 1, ('v2',)),
1913-
(1, True, 'Eq(v3.forward, v2.forward.laplace + 1)', 3, 2, ('v1', 'v2',)),
1914-
(2, True, 'Eq(v3.forward, v2.forward.laplace + 1)', 3, 2, ('v1', 'v2',)),
1914+
(1, True, 'Eq(v3.forward, v2.laplace + 1)', 3, 2, ('v1', 'v2')),
1915+
(1, True, 'Eq(v3.forward, v2.forward.laplace + 1)', 3, 2, ('v1', 'v2')),
1916+
(2, True, 'Eq(v3.forward, v2.forward.laplace + 1)', 3, 2, ('v1', 'v2')),
19151917
(1, False, 'rec.interpolate(v2)', 3, 2, ('v1', 'v2')),
1916-
(1, False, 'Eq(v3.backward, v2.laplace + 1)', 1, 1, ('v2',)),
1917-
(1, False, 'Eq(v3.backward, v2.backward.laplace + 1)', 3, 2, ('v1', 'v2',)),
1918-
(2, False, 'Eq(v3.backward, v2.backward.laplace + 1)', 3, 2, ('v1', 'v2',)),
1918+
(1, False, 'Eq(v3.backward, v2.laplace + 1)', 3, 2, ('v1', 'v2')),
1919+
(1, False, 'Eq(v3.backward, v2.backward.laplace + 1)', 3, 3, ('v2', 'v1', 'v2')),
1920+
(2, False, 'Eq(v3.backward, v2.backward.laplace + 1)', 3, 3, ('v2', 'v1', 'v2')),
19191921
])
19201922
def test_haloupdate_buffer_cases(self, sz, fwd, expr, exp0, exp1, args, mode):
19211923
grid = Grid((65, 65, 65), topology=('*', 1, '*'))
19221924

1923-
v1 = TimeFunction(name='v1', grid=grid, space_order=2, time_order=1,
1925+
v1 = TimeFunction(name='v1', grid=grid, space_order=4, time_order=1,
19241926
save=Buffer(1))
1925-
v2 = TimeFunction(name='v2', grid=grid, space_order=2, time_order=1,
1927+
v2 = TimeFunction(name='v2', grid=grid, space_order=4, time_order=1,
19261928
save=Buffer(1))
1927-
v3 = TimeFunction(name='v3', grid=grid, space_order=2, time_order=1, # noqa
1929+
v3 = TimeFunction(name='v3', grid=grid, space_order=4, time_order=1, # noqa
19281930
save=Buffer(1))
19291931

19301932
rec = SparseTimeFunction(name='rec', grid=grid, nt=500, npoint=65) # noqa

0 commit comments

Comments
 (0)