1717 from conftest import assert_structure , skipif
1818
1919from devito import ( # noqa
20- CELL , NODE , Buffer , Constant , Dimension , Eq , Function , Ge , Grid , Gt , Inc , Le , Lt ,
21- Operator , SpaceDimension , SparseFunction , SparseTimeFunction , TensorFunction ,
20+ CELL , NODE , Buffer , CondEq , Constant , Dimension , Eq , Function , Ge , Grid , Gt , Inc , Le ,
21+ Lt , Operator , SpaceDimension , SparseFunction , SparseTimeFunction , TensorFunction ,
2222 TensorTimeFunction , TimeFunction , VectorFunction , VectorTimeFunction , configuration ,
2323 dimensions , div , error , exp , grad , sin , switchconfig
2424)
@@ -1975,10 +1975,11 @@ def test_scheduling_sparse_functions(self):
19751975 op = Operator ([eqn1 ] + eqn2 + [eqn3 ] + eqn4 , opt = ('noop' , {'openmp' : False }))
19761976 trees = retrieve_iteration_tree (op )
19771977 assert len (trees ) == 5
1978- # Time loop not shared due to the WAR
1979- assert trees [0 ][0 ].dim is time and trees [0 ][0 ] is trees [1 ][0 ] # this IS shared
1980- assert trees [1 ][0 ] is not trees [3 ][0 ]
1981- assert trees [3 ][0 ].dim is time and trees [3 ][0 ] is trees [4 ][0 ] # this IS shared
1978+ # Single, shared time loop despite the WAR: splitting it would run the
1979+ # injection into `u1[time]` to completion before eqn3 reads
1980+ # `u1[time + 1]`, reversing the very dependence it stems from
1981+ assert trees [0 ][0 ].dim is time
1982+ assert all (trees [0 ][0 ] is i [0 ] for i in trees )
19821983
19831984 # Now single, shared time loop expected
19841985 eqn2 = sf1 .inject (u1 .forward , expr = sf1 )
@@ -1987,6 +1988,72 @@ def test_scheduling_sparse_functions(self):
19871988 assert len (trees ) == 5
19881989 assert all (trees [0 ][0 ] is i [0 ] for i in trees )
19891990
1991+ def test_no_time_fission_reversing_war (self ):
1992+ """
1993+ The time loop must not be split to disambiguate iteration directions
1994+ when doing so reverses a WAR carried in time.
1995+ """
1996+ nt = 6
1997+ grid = Grid ((3 , 3 ))
1998+ u1 = TimeFunction (name = 'u1' , grid = grid , save = nt + 2 , time_order = 2 )
1999+ u2 = TimeFunction (name = 'u2' , grid = grid , time_order = 2 )
2000+
2001+ # `Inc(u1, ...)` writes the current time slot, which eqn3 reads one
2002+ # step ahead as `u1[time + 1]`
2003+ eqns = [Eq (u1 .forward , u1 + 2.0 - u1 .backward ),
2004+ Inc (u1 , 100.0 ),
2005+ Eq (u2 .forward , u2 + 2 * u2 .backward - u1 .dt2 )]
2006+
2007+ op = Operator (eqns , opt = ('noop' , {'openmp' : False }))
2008+ assert len ({i [0 ] for i in retrieve_iteration_tree (op )}) == 1
2009+
2010+ op .apply (time_m = 1 , time_M = nt , dt = 1.0 )
2011+
2012+ # Reference: the same equations, in order, one time step at a time
2013+ r1 = np .zeros ((nt + 2 , 3 , 3 ), dtype = grid .dtype )
2014+ r2 = np .zeros ((3 , 3 , 3 ), dtype = grid .dtype )
2015+ for t in range (1 , nt + 1 ):
2016+ r1 [t + 1 ] = r1 [t ] + 2.0 - r1 [t - 1 ]
2017+ r1 [t ] += 100.0
2018+ tm1 , t0 , tp1 = (t - 1 ) % 3 , t % 3 , (t + 1 ) % 3
2019+ r2 [tp1 ] = r2 [t0 ] + 2 * r2 [tm1 ] - (r1 [t + 1 ] - 2 * r1 [t ] + r1 [t - 1 ])
2020+
2021+ assert np .allclose (u1 .data , r1 )
2022+ assert np .allclose (u2 .data , r2 )
2023+
2024+ def test_no_time_fission_splitting_temporaries (self ):
2025+ """
2026+ The time loop must not be split between a sparse function's position
2027+ temporaries and the accesses that use them.
2028+ """
2029+ grid = Grid ((11 , 11 ))
2030+ time = grid .time_dim
2031+
2032+ u = TimeFunction (name = 'u' , grid = grid , space_order = 2 , save = Buffer (2 ))
2033+ v = TimeFunction (name = 'v' , grid = grid , space_order = 2 , save = Buffer (2 ))
2034+ sf = SparseTimeFunction (name = 'sf' , grid = grid , npoint = 1 , nt = 10 )
2035+ sf .coordinates .data [:] = 0.5
2036+
2037+ # `u` is stepped for `n2` steps, then `v` for `n1`, and so on, with the
2038+ # two live time slots handed over at each phase boundary
2039+ n1 , n2 = 2 , 3
2040+ mod = time % (n1 + n2 )
2041+ cv = ConditionalDimension (name = 'cv' , parent = time , condition = Lt (mod , n1 ))
2042+ cu = ConditionalDimension (name = 'cu' , parent = time , condition = Ge (mod , n1 ))
2043+ cuv = ConditionalDimension (name = 'cuv' , parent = time , condition = CondEq (mod , 0 ))
2044+ cvu = ConditionalDimension (name = 'cvu' , parent = time , condition = CondEq (mod , n1 ))
2045+
2046+ eqns = ([Eq (u .forward , u .laplace + u , implicit_dims = cu )] +
2047+ sf .inject (u .forward , expr = sf , implicit_dims = cu ) +
2048+ [Eq (v .forward , v , implicit_dims = cuv ), Eq (v , u , implicit_dims = cuv )] +
2049+ [Eq (v .forward , v .laplace + v , implicit_dims = cv )] +
2050+ sf .inject (v .forward , expr = sf , implicit_dims = cv ) +
2051+ [Eq (u .forward , v .forward , implicit_dims = cvu ),
2052+ Eq (u , v , implicit_dims = cvu )])
2053+
2054+ # Would raise if a position temporary were left behind in another loop
2055+ Operator (eqns ).cfunction # noqa B018
2056+
19902057 def test_scheduling_with_free_dims (self ):
19912058 """Tests loop scheduling in presence of free dimensions."""
19922059 grid = Grid ((4 , 4 ))
0 commit comments