diff --git a/devito/deprecations.py b/devito/deprecations.py index f13c145de5..7484d25a3a 100644 --- a/devito/deprecations.py +++ b/devito/deprecations.py @@ -6,14 +6,14 @@ class DevitoDeprecation(): @cached_property def coeff_warn(self): - warn("The Coefficient API is deprecated and will be removed, coefficients should" + warn("The Coefficient API is deprecated and will be removed, coefficients should " "be passed directly to the derivative object `u.dx(weights=...)", DeprecationWarning, stacklevel=2) return @cached_property def symbolic_warn(self): - warn("coefficients='symbolic' is deprecated, coefficients should" + warn("coefficients='symbolic' is deprecated, coefficients should " "be passed directly to the derivative object `u.dx(weights=...)", DeprecationWarning, stacklevel=2) return diff --git a/devito/ir/clusters/cluster.py b/devito/ir/clusters/cluster.py index 629ebdde4a..562512c252 100644 --- a/devito/ir/clusters/cluster.py +++ b/devito/ir/clusters/cluster.py @@ -387,6 +387,19 @@ def dspace(self): # Dimension-centric view of the data space intervals = IntervalGroup.generate('union', *parts.values()) + # 'union' may consume intervals (values) from keys that have dimensions + # not mapped to intervals e.g. issue #2235, resulting in reduced + # iteration size. Here, we relax this mapped upper interval, by + # intersecting intervals with matching only dimensions + for f, v in parts.items(): + for i in v: + if i.dim in self.ispace and i.dim in f.dimensions: + # oobs check is not required but helps reduce + # interval reconstruction + ii = intervals[i.dim].intersection(v[i.dim]) + if not ii.is_Null: + intervals = intervals.set_upper(i.dim, ii.upper) + # E.g., `db0 -> time`, but `xi NOT-> x` intervals = intervals.promote(lambda d: not d.is_Sub) intervals = intervals.zero(set(intervals.dimensions) - oobs) diff --git a/devito/ir/support/space.py b/devito/ir/support/space.py index 6a11acdc28..3f11ec0bd2 100644 --- a/devito/ir/support/space.py +++ b/devito/ir/support/space.py @@ -259,6 +259,9 @@ def negate(self): def zero(self): return Interval(self.dim, 0, 0, self.stamp) + def set_upper(self, v=0): + return Interval(self.dim, self.lower, v, self.stamp) + def flip(self): return Interval(self.dim, self.upper, self.lower, self.stamp) @@ -496,6 +499,11 @@ def zero(self, d=None): return IntervalGroup(intervals, relations=self.relations, mode=self.mode) + def set_upper(self, d, v=0): + dims = as_tuple(d) + return IntervalGroup([i.set_upper(v) if i.dim in dims else i for i in self], + relations=self.relations, mode=self.mode) + def lift(self, d=None, v=None): d = set(self.dimensions if d is None else as_tuple(d)) intervals = [i.lift(v) if i.dim._defines & d else i for i in self] diff --git a/examples/userapi/02_apply.ipynb b/examples/userapi/02_apply.ipynb index 693c4eed08..f8f730dd52 100644 --- a/examples/userapi/02_apply.ipynb +++ b/examples/userapi/02_apply.ipynb @@ -142,7 +142,11 @@ " 'y_m': 0,\n", " 'y_size': 4,\n", " 'y_M': 3,\n", - " 'timers': }" + " 'h_x': 0.33333334,\n", + " 'h_y': 0.33333334,\n", + " 'o_x': 0.0,\n", + " 'o_y': 0.0,\n", + " 'timers': }" ] }, "execution_count": 5, @@ -419,8 +423,8 @@ { "data": { "text/plain": [ - "PerformanceSummary([('section0',\n", - " PerfEntry(time=3e-06, gflopss=0.0, gpointss=0.0, oi=0.0, ops=0, itershapes=[]))])" + "PerformanceSummary([(PerfKey(name='section0', rank=None),\n", + " PerfEntry(time=1e-06, gflopss=0.0, gpointss=0.0, oi=0.0, ops=0, itershapes=[]))])" ] }, "execution_count": 14, @@ -449,14 +453,14 @@ "name": "stderr", "output_type": "stream", "text": [ - "Operator `Kernel` run in 0.00 s\n" + "Operator `Kernel` ran in 0.01 s\n" ] }, { "data": { "text/plain": [ - "PerformanceSummary([('section0',\n", - " PerfEntry(time=3e-06, gflopss=0.021333333333333333, gpointss=0.010666666666666666, oi=0.16666666666666666, ops=2, itershapes=[(2, 4, 4)]))])" + "PerformanceSummary([(PerfKey(name='section0', rank=None),\n", + " PerfEntry(time=1e-06, gflopss=0.064, gpointss=0.032, oi=0.16666666666666666, ops=2, itershapes=((2, 4, 4),)))])" ] }, "execution_count": 15, @@ -525,7 +529,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.8" + "version": "3.10.12" } }, "nbformat": 4, diff --git a/tests/test_checkpointing.py b/tests/test_checkpointing.py index 75cca861cc..0217f46d52 100644 --- a/tests/test_checkpointing.py +++ b/tests/test_checkpointing.py @@ -10,7 +10,7 @@ @switchconfig(log_level='WARNING') -def test_segmented_incremment(): +def test_segmented_increment(): """ Test for segmented operator execution of a one-sided first order function (increment). The corresponding set of stencil offsets in diff --git a/tests/test_dimension.py b/tests/test_dimension.py index 84df27ecef..feab3fc5f0 100644 --- a/tests/test_dimension.py +++ b/tests/test_dimension.py @@ -231,6 +231,25 @@ def test_degenerate_to_zero(self): assert np.all(u.data == 10) + def test_default_timeM(self): + """ + MFE for issue #2235 + """ + grid = Grid(shape=(4, 4)) + + u = TimeFunction(name='u', grid=grid) + usave = TimeFunction(name='usave', grid=grid, save=5) + + eqns = [Eq(u.forward, u + 1), + Eq(usave, u)] + + op = Operator(eqns) + + assert op.arguments()['time_M'] == 4 + op.apply() + + assert all(np.all(usave.data[i] == i) for i in range(4)) + class TestSubDimension: diff --git a/tests/test_operator.py b/tests/test_operator.py index d5759c1c92..9d24566468 100644 --- a/tests/test_operator.py +++ b/tests/test_operator.py @@ -1990,8 +1990,11 @@ def test_2194_v2(self, eqns, expected, exp_trees, exp_iters): class TestInternals: - def test_indirection(self): - nt = 10 + @pytest.mark.parametrize('nt, offset, epass', + ([1, 1, True], [1, 2, False], + [5, 3, True], [3, 5, False], + [4, 1, True], [5, 10, False])) + def test_indirection(self, nt, offset, epass): grid = Grid(shape=(4, 4)) time = grid.time_dim x, y = grid.dimensions @@ -1999,7 +2002,7 @@ def test_indirection(self): f = TimeFunction(name='f', grid=grid, save=nt) g = TimeFunction(name='g', grid=grid) - idx = time + 1 + idx = time + offset s = Indirection(name='ofs0', mapped=idx) eqns = [ @@ -2010,10 +2013,10 @@ def test_indirection(self): op = Operator(eqns) assert op._dspace[time].lower == 0 - assert op._dspace[time].upper == 1 - assert op.arguments()['time_M'] == nt - 2 + assert op._dspace[time].upper == offset - op() - - assert np.all(f.data[0] == 0.) - assert np.all(f.data[i] == 3. for i in range(1, 10)) + if epass: + assert op.arguments()['time_M'] == nt - offset - 1 + op() + assert np.all(f.data[0] == 0.) + assert np.all(f.data[i] == 3. for i in range(1, nt))