From 8e363074235b22128ed33e4b9056a5cf337ce18f Mon Sep 17 00:00:00 2001 From: mloubout Date: Thu, 19 Feb 2026 10:04:45 -0500 Subject: [PATCH 1/3] api: fix staggering handling at rebuild --- devito/types/dense.py | 3 +++ devito/types/tensor.py | 15 +++++++++++++-- devito/types/utils.py | 7 +++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/devito/types/dense.py b/devito/types/dense.py index ccee21bffb..0a26762774 100644 --- a/devito/types/dense.py +++ b/devito/types/dense.py @@ -1143,6 +1143,9 @@ def __staggered_setup__(cls, dimensions, staggered=None, **kwargs): * 0 to non-staggered dimensions; * 1 to staggered dimensions. """ + if isinstance(staggered, Staggering): + staggered = staggered._ref + if not staggered: processed = () elif staggered is CELL: diff --git a/devito/types/tensor.py b/devito/types/tensor.py index 432d8a4437..f42d437c38 100644 --- a/devito/types/tensor.py +++ b/devito/types/tensor.py @@ -20,6 +20,18 @@ __all__ = ['TensorFunction', 'TensorTimeFunction', 'VectorFunction', 'VectorTimeFunction'] +def staggering(stagg, i, j, d, dims): + if stagg is None: + # No input + return NODE if i == j else (d, dims[j]) + elif isinstance(stagg, (tuple, list)): + # User input as list or tuple + return stagg[i][j] + elif isinstance(stagg, AbstractTensor): + # From rebuild/tensor property. Indexed as a sympy Matrix + return stagg[i, j] + + class TensorFunction(AbstractTensor): """ Tensor valued Function represented as a Matrix. @@ -128,8 +140,7 @@ def __subfunc_setup__(cls, *args, **kwargs): start = i if (symm or diag) else 0 stop = i + 1 if diag else len(dims) for j in range(start, stop): - staggj = (stagg[i][j] if stagg is not None - else (NODE if i == j else (d, dims[j]))) + staggj = staggering(stagg, i, j, d, dims) sub_kwargs = cls._component_kwargs((i, j), **kwargs) sub_kwargs.update({'name': f"{name}_{d.name}{dims[j].name}", 'staggered': staggj}) diff --git a/devito/types/utils.py b/devito/types/utils.py index fd0dfdb750..51502a8654 100644 --- a/devito/types/utils.py +++ b/devito/types/utils.py @@ -57,6 +57,13 @@ class Staggering(DimensionTuple): def on_node(self): return not self or all(s == 0 for s in self) + @property + def _ref(self): + if self.on_node: + return NODE + else: + return tuple(d for d, s in zip(self.getters, self, strict=True) if s == 1) + class IgnoreDimSort(tuple): """A tuple subclass used to wrap the implicit_dims to indicate From ecc53dd3357f55093400306fb3d1c070df91b9cb Mon Sep 17 00:00:00 2001 From: mloubout Date: Thu, 19 Feb 2026 10:07:30 -0500 Subject: [PATCH 2/3] tests: add test for Staggering as input --- tests/test_staggered_utils.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/test_staggered_utils.py b/tests/test_staggered_utils.py index 9da58d34a4..c009202068 100644 --- a/tests/test_staggered_utils.py +++ b/tests/test_staggered_utils.py @@ -200,3 +200,18 @@ def test_eval_at_different_dim(): eq = Eq(tau.forward, v).evaluate assert grid.time_dim not in eq.rhs.free_symbols + + +def test_new_from_staggering(): + grid = Grid(shape=(31, 17, 25)) + x, _, _ = grid.dimensions + + f = TimeFunction(name="f", grid=grid, staggered=x) + # This used to fail since f.staggered as 4 elements (0, 1, 0, 0) + # but it is processed for Dimension only. + # Now properly converts Staggering to the ref (x,) at init + g = TimeFunction(name="g", grid=grid, staggered=f.staggered) + + assert g.staggered._ref == (x,) + assert g.staggered == (0, 1, 0, 0) + assert g.staggered == f.staggered From a60b12ae5d7c8863c9cdc5673b6e532cfcc3eb3a Mon Sep 17 00:00:00 2001 From: mloubout Date: Thu, 19 Feb 2026 10:08:30 -0500 Subject: [PATCH 3/3] api: silence non-Expr warning for tensor (e.g staggering/name/..) --- devito/data/allocators.py | 2 +- devito/types/basic.py | 16 ++++++++++++++-- devito/types/dense.py | 2 ++ devito/types/utils.py | 4 +++- tests/test_staggered_utils.py | 12 +++++------- 5 files changed, 25 insertions(+), 11 deletions(-) diff --git a/devito/data/allocators.py b/devito/data/allocators.py index 474e426516..95fd8a0cd9 100644 --- a/devito/data/allocators.py +++ b/devito/data/allocators.py @@ -112,7 +112,7 @@ def alloc(self, shape, dtype, padding=0): raise RuntimeError(f"Unable to allocate {size} elements in memory") # Compute the pointer to the user data - padleft_bytes = padleft * ctypes.sizeof(ctype) + padleft_bytes = int(padleft * ctypes.sizeof(ctype)) c_pointer = ctypes.c_void_p(padleft_pointer.value + padleft_bytes) # Cast to 1D array of the specified `datasize` diff --git a/devito/types/basic.py b/devito/types/basic.py index a3b8af4533..29be2acf09 100644 --- a/devito/types/basic.py +++ b/devito/types/basic.py @@ -1,5 +1,6 @@ import abc import inspect +import warnings from contextlib import suppress from ctypes import POINTER, Structure, _Pointer, c_char, c_char_p from functools import cached_property, reduce @@ -9,6 +10,7 @@ import sympy from sympy.core.assumptions import _assume_rules from sympy.core.decorators import call_highest_priority +from sympy.utilities.exceptions import SymPyDeprecationWarning from devito.data import default_allocator from devito.parameters import configuration @@ -1533,10 +1535,20 @@ def _sympify(self, arg): # This is used internally by sympy to process arguments at rebuilt. And since # some of our properties are non-sympyfiable we need to have a fallback try: - return super()._sympify(arg) - except sympy.SympifyError: + # Pure sympy object + return arg._sympy_() + except AttributeError: return arg + @classmethod + def _eval_from_dok(cls, rows, cols, dok): + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", + category=SymPyDeprecationWarning + ) + return super()._eval_from_dok(rows, cols, dok) + @property def grid(self): """ diff --git a/devito/types/dense.py b/devito/types/dense.py index 0a26762774..0de36d3093 100644 --- a/devito/types/dense.py +++ b/devito/types/dense.py @@ -1157,6 +1157,8 @@ def __staggered_setup__(cls, dimensions, staggered=None, **kwargs): assert len(staggered) == len(dimensions) processed = staggered else: + # Staggering is not NODE or CELL or None + # therefore it's a tuple of dimensions processed = [] for d in dimensions: if d in as_tuple(staggered): diff --git a/devito/types/utils.py b/devito/types/utils.py index 51502a8654..93d6b73b62 100644 --- a/devito/types/utils.py +++ b/devito/types/utils.py @@ -59,7 +59,9 @@ def on_node(self): @property def _ref(self): - if self.on_node: + if not self: + return None + elif self.on_node: return NODE else: return tuple(d for d, s in zip(self.getters, self, strict=True) if s == 1) diff --git a/tests/test_staggered_utils.py b/tests/test_staggered_utils.py index c009202068..fab3742638 100644 --- a/tests/test_staggered_utils.py +++ b/tests/test_staggered_utils.py @@ -3,8 +3,7 @@ from sympy import simplify from devito import ( - CELL, NODE, Dimension, Eq, Function, Grid, Operator, TimeFunction, VectorTimeFunction, - div + CELL, NODE, Eq, Function, Grid, Operator, TimeFunction, VectorTimeFunction, div ) from devito.tools import as_tuple, powerset @@ -173,16 +172,15 @@ def test_staggered_rebuild(stagg): f = Function(name='f', grid=grid, space_order=4, staggered=stagg) assert tuple(f.staggered.getters.keys()) == grid.dimensions - new_dims = (Dimension('x1'), Dimension('y1'), Dimension('z1')) - f2 = f.func(dimensions=new_dims) + f2 = f.func(name="f2") - assert f2.dimensions == new_dims + assert f2.dimensions == f.dimensions assert tuple(f2.staggered) == tuple(f.staggered) - assert tuple(f2.staggered.getters.keys()) == new_dims + assert tuple(f2.staggered.getters.keys()) == f.dimensions # Check that rebuild correctly set the staggered indices # with the new dimensions - for (d, nd) in zip(grid.dimensions, new_dims, strict=True): + for (d, nd) in zip(grid.dimensions, f.dimensions, strict=True): if d in as_tuple(stagg) or stagg is CELL: assert f2.indices[nd] == nd + nd.spacing / 2 else: