Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions devito/ir/clusters/algorithms.py
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,9 @@ def guard(clusters):
else:
guards = {d: mode(*v, evaluate=False) for d, v in guards.items()}

# A condition folded to `true` (e.g., `time >= 0`) imposes no guard
guards = {d: v for d, v in guards.items() if v is not sympy.true}

# Construct a guarded Cluster
processed.append(c.rebuild(exprs=exprs, guards=guards))

Expand Down
9 changes: 6 additions & 3 deletions devito/ir/support/guards.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def negated(self):
# *** GuardFactor


class GuardFactor(Guard, CondEq, Pickable):
class GuardFactor(Guard):

"""
A guard for factor-based ConditionalDimensions.
Expand Down Expand Up @@ -86,11 +86,14 @@ def _args_rebuild(self):
return (self.d,)


class GuardFactorEq(GuardFactor, CondEq):
# The relational bases are mixed in by the leaves only, or `CondEq` would
# precede `CondNe` in the MRO of `GuardFactorNe`, making it an equality. E.g.,
# `Not(GuardFactorEq(d))` would then be printed as `time % k == 0`
class GuardFactorEq(GuardFactor, CondEq, Pickable):
pass


class GuardFactorNe(GuardFactor, CondNe):
class GuardFactorNe(GuardFactor, CondNe, Pickable):
pass


Expand Down
15 changes: 13 additions & 2 deletions devito/passes/clusters/buffering.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
)
from devito.logger import warning
from devito.passes.clusters.utils import is_memcpy
from devito.symbolics import CondEq, IntDiv, retrieve_functions, uxreplace
from devito.symbolics import (
CondEq, IntDiv, retrieve_functions, retrieve_terminals, uxreplace
)
from devito.tools import (
Stamp, as_mapper, as_tuple, filter_ordered, flatten, frozendict, is_integer,
timed_pass
Expand Down Expand Up @@ -1064,7 +1066,16 @@ def _overwritten(v, c, idxf, descriptors, order, d):

# The later copy-back fires under all of its guards, which may be
# keyed on Dimensions derived from `d` (e.g., a layer Dimension)
conditions.append(And(*c1.guards.values(), coincide))
condition = And(*c1.guards.values(), coincide)

# The condition is evaluated at the earlier copy-back, so nothing
# it reads, indices included, may be written in between
reads = {i.function for i in retrieve_terminals(condition, deep=True)}
between = [c2 for c2, n in order.items() if order[c] < n <= order[c1]]
if any(not reads.isdisjoint(c2.scope.writes) for c2 in between):
continue

conditions.append(condition)

if not conditions:
return None
Expand Down
7 changes: 7 additions & 0 deletions devito/passes/iet/languages/C.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,13 @@ class CPrinter(BasePrinter, C99CodePrinter):
def _print_ImaginaryUnit(self, expr):
return '_Complex_I'

# C99 has no `true`/`false` without `stdbool.h`, which is never included
def _print_BooleanTrue(self, expr):
return '1'

def _print_BooleanFalse(self, expr):
return '0'

def _print_ListInitializer(self, expr):
li = super()._print_ListInitializer(expr)
if expr.dtype:
Expand Down
70 changes: 69 additions & 1 deletion tests/test_buffering.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import numpy as np
import pytest
from sympy import Not, Or
from sympy import And, Not, Or

from conftest import skipif
from devito import (
Expand Down Expand Up @@ -1179,3 +1179,71 @@ def test_buffering_multi_cond(factor):
# The last sample is on the subsampled grid, hence no extra slot
expected[-2:] = [ntmod - 1, 0]
assert np.allclose(f_all.data[:, 11, 11], expected)


def test_true_condition_single_buffer():
"""
A condition folded to `true` (`time` is nonnegative, so `time >= 0` is
`true`) imposes no guard. Reads of `f` under it and unguarded reads of `f`
must thus share one buffer.
"""
grid = Grid(shape=(4, 4))
time = grid.time_dim
nt = 4

f = TimeFunction(name='f', grid=grid, time_order=0, space_order=0, save=nt)
g = TimeFunction(name='g', grid=grid, time_order=0, space_order=0)
h = TimeFunction(name='h', grid=grid, time_order=0, space_order=0)
for i in range(nt):
f.data[i] = i

ctrue = ConditionalDimension(name='ctrue', parent=time, condition=time >= 0,
relation='strict')

eqs = [Eq(g, g + f, implicit_dims=ctrue), Eq(h, h + 2*f)]

op = Operator(eqs, opt='buffering', name='TrueCondition')

buffers = [i for i in FindSymbols().visit(op.body) if i.is_Array and i._mem_heap]
assert len(buffers) == 1

op.apply(time_m=0, time_M=nt-1)
assert np.allclose(g.data[0], 6.)
assert np.allclose(h.data[0], 12.)


@pytest.mark.parametrize('case', ['mutable', 'indirect'])
def test_buffering_same_slot_skip(case):
"""
A later guard reads data updated between two potential writes. The update
makes that guard false, so the only firing write must still be copied back.
Every Function has at most one firing write per step, slot and point.
"""
grid = Grid(shape=(4, 4))
time = grid.time_dim
x, _ = grid.dimensions

f = TimeFunction(name='f', grid=grid, time_order=0, space_order=0, save=4)
T = TimeFunction(name='T', grid=grid, time_order=0, space_order=0)

if case == 'mutable':
cm = ConditionalDimension(name='cm', parent=time, relation='strict',
condition=And(CondEq(time, 3), T[0, 0, 0] < 3.5))
eqs = [Eq(f, T), Eq(T, T + 1), Eq(f, T + 1, implicit_dims=cm)]
expected = [0, 1, 2, 3]
else:
I = TimeFunction(name='I', grid=grid, time_order=0, space_order=0,
dtype=np.int32)
ci = ConditionalDimension(name='ci', parent=time, condition=CondEq(time, 3),
relation='strict')
cm = ConditionalDimension(name='cm', parent=time, relation='strict',
condition=And(CondEq(time, 3),
T[0, I[0, 0, 0], 0] < 3.5))
eqs = [Eq(T, time + 2*x), Eq(f, T), Eq(I, 1, implicit_dims=ci),
Eq(f, T + 1, implicit_dims=cm)]
expected = [2, 3, 4, 5]

op = Operator(eqs, opt='buffering', name='SameSlotSkip')
op.apply(time_m=0, time_M=3)

assert np.all(f.data[:, 1, 1] == expected)
44 changes: 42 additions & 2 deletions tests/test_dimension.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import numpy as np
import pytest
from sympy import And, Or
from sympy import And, Not, Or, false

from conftest import assert_blocking, assert_structure, opts_tiling, skipif
from devito import ( # noqa
Expand All @@ -13,7 +13,7 @@
configuration, dimensions, floor, norm, sin, sum, switchconfig
)
from devito.exceptions import InvalidArgument
from devito.ir import SymbolRegistry
from devito.ir import GuardFactor, SymbolRegistry
from devito.ir.equations.algorithms import concretize_subdims
from devito.ir.iet import (
Conditional, Expression, FindNodes, FindSymbols, Iteration, retrieve_iteration_tree
Expand Down Expand Up @@ -2128,6 +2128,46 @@ def test_blocking_w_guard(self):
't,x0_blk0,y0_blk0,x,y,z'],
't,x0_blk0,y0_blk0,x,y,z,z')

def test_false_condition(self):
"""
A condition that SymPy folds to `false` (`time` is nonnegative) must
still be printed as valid C.
"""
grid = Grid(shape=(4, 4))
time = grid.time_dim

condition = time < 0
assert condition is false
ct = ConditionalDimension(name='ct', parent=time, condition=condition)

T = TimeFunction(name='T', grid=grid, time_order=0)
f = TimeFunction(name='f', grid=grid, time_order=0, save=2)

op = Operator([Eq(f, T + 1, implicit_dims=ct), Eq(T, T + 1)],
name='FalseCondition')
op.apply(time_M=1)

assert np.all(f.data[:, 1, 1] == [0, 0])

def test_negated_factor_condition(self):
"""
Negating a subsampling guard must select the timesteps off the
subsampled grid.
"""
grid = Grid(shape=(4, 4))
time = grid.time_dim

ct = ConditionalDimension(name='ct', parent=time, factor=2)
cn = ConditionalDimension(name='cn', parent=time,
condition=Not(GuardFactor(ct)), relation='strict')

f = TimeFunction(name='f', grid=grid, time_order=0, save=4)

op = Operator(Eq(f, time + 1, implicit_dims=cn), name='NegatedFactor')
op.apply(time_M=3, ctf=2)

assert np.all(f.data[:, 1, 1] == [0, 2, 0, 4])


class TestCustomDimension:

Expand Down