diff --git a/devito/deprecations.py b/devito/deprecations.py index dbba9a7665..95d9b02698 100644 --- a/devito/deprecations.py +++ b/devito/deprecations.py @@ -6,15 +6,15 @@ class DevitoDeprecation: @cached_property def coeff_warn(self): - warn("The Coefficient API is deprecated and will be removed, coefficients should" - " be passed directly to the derivative object `u.dx(weights=...)", + 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" - " be passed directly to the derivative object `u.dx(weights=...)", + 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 cbf206b3ff..60050bd79f 100644 --- a/devito/ir/clusters/cluster.py +++ b/devito/ir/clusters/cluster.py @@ -470,6 +470,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 1c760128b5..6448e921ea 100644 --- a/devito/ir/support/space.py +++ b/devito/ir/support/space.py @@ -271,6 +271,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) @@ -516,6 +519,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 d8776e25eb..b767e73555 100644 --- a/examples/userapi/02_apply.ipynb +++ b/examples/userapi/02_apply.ipynb @@ -143,7 +143,11 @@ " 'y_m': 0,\n", " 'y_size': 4,\n", " 'y_M': 3,\n", - " 'timers': }" + " 'h_x': np.float32(0.33333334),\n", + " 'h_y': np.float32(0.33333334),\n", + " 'o_x': np.float32(0.0),\n", + " 'o_y': np.float32(0.0),\n", + " 'timers': }" ] }, "execution_count": 5, @@ -420,8 +424,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, @@ -450,14 +454,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, @@ -690,7 +694,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 98f6f1603c..a96e6408f1 100644 --- a/tests/test_checkpointing.py +++ b/tests/test_checkpointing.py @@ -12,7 +12,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 aae7e4e0cb..492306726a 100644 --- a/tests/test_dimension.py +++ b/tests/test_dimension.py @@ -256,6 +256,25 @@ def test_buffer1_direction(self, direction): for tree in trees: assert tree[0].direction == direction + 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 d5c9827836..86d9f5d1c4 100644 --- a/tests/test_operator.py +++ b/tests/test_operator.py @@ -2134,8 +2134,11 @@ def test_pseudo_time_dep(self): 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 @@ -2143,7 +2146,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 = [ @@ -2154,13 +2157,13 @@ 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)) class TestEstimateMemory: