From b0cad26599cab995522263b052f814c1dff7a7a0 Mon Sep 17 00:00:00 2001 From: Roman Cattaneo <1116746+romanc@users.noreply.github.com> Date: Tue, 11 Nov 2025 17:34:06 +0100 Subject: [PATCH 1/9] unrelated changes --- src/gt4py/cartesian/frontend/defir_to_gtir.py | 2 +- src/gt4py/cartesian/frontend/gtscript_frontend.py | 5 ++--- src/gt4py/eve/exceptions.py | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/gt4py/cartesian/frontend/defir_to_gtir.py b/src/gt4py/cartesian/frontend/defir_to_gtir.py index 28a6a7b490..33e0427235 100644 --- a/src/gt4py/cartesian/frontend/defir_to_gtir.py +++ b/src/gt4py/cartesian/frontend/defir_to_gtir.py @@ -529,7 +529,7 @@ def visit_If(self, node: If) -> Union[gtir.FieldIfStmt, gtir.ScalarIfStmt]: loc=location_to_source_location(node.loc), ) - def visit_HorizontalIf(self, node: HorizontalIf) -> gtir.FieldIfStmt: + def visit_HorizontalIf(self, node: HorizontalIf) -> gtir.HorizontalRestriction: def make_bound_or_level(bound: AxisBound, level) -> Optional[common.AxisBound]: if (level == LevelMarker.START and bound.offset <= -10000) or ( level == LevelMarker.END and bound.offset >= 10000 diff --git a/src/gt4py/cartesian/frontend/gtscript_frontend.py b/src/gt4py/cartesian/frontend/gtscript_frontend.py index 65bbd77f06..df313b2f29 100644 --- a/src/gt4py/cartesian/frontend/gtscript_frontend.py +++ b/src/gt4py/cartesian/frontend/gtscript_frontend.py @@ -1140,8 +1140,7 @@ def visit_Name(self, node: ast.Name) -> nodes.Ref: raise AssertionError(f"Missing '{symbol}' symbol definition") def visit_Index(self, node: ast.Index): - index = self.visit(node.value) - return index + return self.visit(node.value) def _eval_new_spatial_index( self, index_nodes: Sequence[nodes.Expr], field_axes: Optional[Set[Literal["I", "J", "K"]]] @@ -2364,7 +2363,7 @@ def run(self, backend_name: str): parameters=[ parameter_decls[item.name] for item in api_signature if item.name in parameter_decls ], - computations=init_computations + computations if init_computations else computations, + computations=init_computations + computations, externals=self.resolved_externals, docstring=inspect.getdoc(self.definition) or "", loc=nodes.Location.from_ast_node(self.ast_root.body[0]), diff --git a/src/gt4py/eve/exceptions.py b/src/gt4py/eve/exceptions.py index dd16f45035..d240f2ffe8 100644 --- a/src/gt4py/eve/exceptions.py +++ b/src/gt4py/eve/exceptions.py @@ -20,7 +20,7 @@ class EveError: This base class has to be always inherited together with a standard exception, and thus it should not be used as direct superclass for custom exceptions. Inherit directly from :class:`EveTypeError`, - :class:`EveTypeError`, etc. instead. + :class:`EveValueError`, etc. instead. """ From c2c9d81efecafaa9df5236bee880e2178528d13a Mon Sep 17 00:00:00 2001 From: Roman Cattaneo <1116746+romanc@users.noreply.github.com> Date: Tue, 11 Nov 2025 17:42:37 +0100 Subject: [PATCH 2/9] catch read and write with non-zero offset in parallel vertical loops --- src/gt4py/cartesian/gtc/gtir.py | 76 ++++++++++--------- .../test_code_generation.py | 68 +++++++++++++++++ 2 files changed, 109 insertions(+), 35 deletions(-) diff --git a/src/gt4py/cartesian/gtc/gtir.py b/src/gt4py/cartesian/gtc/gtir.py index e236e4e1b8..b1d3d4413d 100644 --- a/src/gt4py/cartesian/gtc/gtir.py +++ b/src/gt4py/cartesian/gtc/gtir.py @@ -99,25 +99,11 @@ def no_write_and_read_with_offset_of_same_field( ) -> None: if isinstance(instance.left, FieldAccess): offset_reads = ( - ( - eve.walk_values(instance.right) - .filter(_cartesian_fieldaccess) - .filter(lambda acc: acc.offset.i != 0 or acc.offset.j != 0) - .getattr("name") - .to_set() - ) - | ( - eve.walk_values(instance.right) - .filter(_absolutekindex_fieldaccess) - .getattr("name") - .to_set() - ) - | ( - eve.walk_values(instance.right) - .filter(_variablek_fieldaccess) - .getattr("name") - .to_set() - ) + eve.walk_values(instance.right) + .filter(_cartesian_fieldaccess) + .filter(lambda acc: acc.offset.i != 0 or acc.offset.j != 0) + .getattr("name") + .to_set() ) if instance.left.name in offset_reads: raise ValueError("Self-assignment with offset is illegal.") @@ -250,6 +236,42 @@ def _no_write_and_read_with_horizontal_offset( f"Illegal write and read with horizontal offset detected for {non_tmp_fields}." ) + @datamodels.root_validator + @classmethod + def _vertical_offset_in_parallel(cls: type[VerticalLoop], instance: VerticalLoop) -> None: + """ + Read and write of the same field in a parallel loop with non-zero offsets is not allowed in parallel. + """ + if instance.loop_order != common.LoopOrder.PARALLEL: + return + + # gather all writes as a mapping of id(node) -> node + writes: dict[int, FieldAccess] = dict() + for left in eve.walk_values(instance.body).if_isinstance(ParAssignStmt).getattr("left"): + if isinstance(left, FieldAccess): + writes[id(left)] = left + + # check that we don't have read/write of the same field with non-zero offsets + for node in eve.walk_values(instance.body).if_isinstance(FieldAccess): + if id(node) in writes: + # this is the write access - skip it + continue + + for write_access in writes.values(): + if node.name == write_access.name: + if isinstance(node.offset, (VariableKOffset, AbsoluteKIndex)) or isinstance( + write_access.offset, (VariableKOffset, AbsoluteKIndex) + ): + raise ValueError( + "Not allowed to read and write with `VariableKOffset` and/or `AbsoluteKIndex` in PARALLEL loops." + ) + + # For cartesian offsets, we allow it if both are equal (e.g. 0) + if node.offset.k != write_access.offset.k: + raise ValueError( + "Not allowed to read and write with k-offsets in PARALLEL loops." + ) + class Argument(eve.Node): name: str @@ -281,22 +303,6 @@ def _cartesian_fieldaccess(node) -> bool: ) -def _variablek_fieldaccess(node) -> bool: - return ( - isinstance(node, FieldAccess) - and isinstance(node.offset, VariableKOffset) - and not isinstance(node.offset, AbsoluteKIndex) - ) - - -def _absolutekindex_fieldaccess(node) -> bool: - return ( - isinstance(node, FieldAccess) - and isinstance(node.offset, AbsoluteKIndex) - and not isinstance(node.offset, VariableKOffset) - ) - - # TODO(havogt): either move to eve or will be removed in the attr-based eve if a List[Node] is represented as a CollectionNode def _written_and_read_with_offset(stmts: List[Stmt]) -> Set[str]: """Return a list of names that are written to and read with offset.""" diff --git a/tests/cartesian_tests/integration_tests/multi_feature_tests/test_code_generation.py b/tests/cartesian_tests/integration_tests/multi_feature_tests/test_code_generation.py index d22fca7765..75386e2406 100644 --- a/tests/cartesian_tests/integration_tests/multi_feature_tests/test_code_generation.py +++ b/tests/cartesian_tests/integration_tests/multi_feature_tests/test_code_generation.py @@ -1472,3 +1472,71 @@ def test_upcasting_stencil( test_upcasting_stencil(input, index_array, output) assert (input == output).all() + + +def test_k_offsets_in_parallel_loops() -> None: + with pytest.raises(ValueError, match="read and write with k-offsets in PARALLEL"): + + @gtscript.stencil(backend="debug") + def self_assign_offset_parallel(field: Field[np.int32]) -> None: + with computation(PARALLEL), interval(1, None): + field = field[0, 0, -1] * 2 + + with pytest.raises(ValueError, match="read and write with k-offsets in PARALLEL"): + + @gtscript.stencil(backend="debug") + def self_assign_offset_parallel_temp(field: Field[np.int32]) -> None: + with computation(PARALLEL), interval(1, None): + tmp = field[0, 0, -1] + field = tmp * 2 + + with pytest.raises( + ValueError, match="read and write with `VariableKOffset` and/or `AbsoluteKIndex`" + ): + + @gtscript.stencil(backend="debug") + def mixed_read_write(field: Field[np.int32]): + with computation(PARALLEL), interval(...): + level = field.at(K=1) + field = 2 * level + + with pytest.raises( + ValueError, match="read and write with `VariableKOffset` and/or `AbsoluteKIndex`" + ): + + @gtscript.stencil(backend="debug") + def mixed_read_write(field: Field[np.int32], offset: int = -1): + with computation(PARALLEL), interval(1, None): + bottom = field[0, 0, offset] + field = field + 2 * bottom + + # center reads and writes are allowed + @gtscript.stencil(backend="debug") + def self_assignment_center_read_parallel(field: Field[np.int32]) -> None: + with computation(PARALLEL), interval(...): + field = field[0, 0, 0] * 2 + + @gtscript.stencil(backend="debug") + def self_assignment_center_write_parallel(field: Field[np.int32]) -> None: + with computation(PARALLEL), interval(...): + field[0, 0, 0] = field * 2 + + # not mixing reads and writes are allowed too (e.g. index fields) + @gtscript.stencil(backend="debug") + def self_assignment_center_parallel(field: Field[np.float32], index: Field[np.int32]) -> None: + with computation(PARALLEL), interval(1, None): + field = index + index[0, 0, -1] * 2 + + +@pytest.mark.parametrize("backend", ALL_BACKENDS) +def test_self_assignment_in_forward(backend: str) -> None: + @gtscript.stencil(backend=backend) + def self_assignment_parallel(field: Field[np.int32]) -> None: + with computation(FORWARD), interval(1, None): + field = field[0, 0, -1] * 2 + + @gtscript.stencil(backend=backend) + def self_assignment_2_parallel(field: Field[np.int32]) -> None: + with computation(FORWARD), interval(1, None): + tmp = field[0, 0, -1] + field = tmp * 2 From b7a935d0289c03cd51c2b8c6908e0a99c8d07160 Mon Sep 17 00:00:00 2001 From: Roman Cattaneo <1116746+romanc@users.noreply.github.com> Date: Wed, 12 Nov 2025 10:39:39 +0100 Subject: [PATCH 3/9] add integration tests for I/J dimensions --- .../multi_feature_tests/test_code_generation.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/cartesian_tests/integration_tests/multi_feature_tests/test_code_generation.py b/tests/cartesian_tests/integration_tests/multi_feature_tests/test_code_generation.py index 75386e2406..e6dacdabc3 100644 --- a/tests/cartesian_tests/integration_tests/multi_feature_tests/test_code_generation.py +++ b/tests/cartesian_tests/integration_tests/multi_feature_tests/test_code_generation.py @@ -1474,6 +1474,23 @@ def test_upcasting_stencil( assert (input == output).all() +def test_no_read_write_with_horizontal_offset() -> None: + with pytest.raises(ValueError, match="Self-assignment with offset is illegal."): + + @gtscript.stencil(backend="debug") + def self_assign_offset(field: Field[np.float64]) -> None: + with computation(PARALLEL), interval(...): + field = (field[I - 1] + field[I + 1]) / 2 + + with pytest.raises(ValueError, match="Illegal write and read with horizontal offset"): + + @gtscript.stencil(backend="debug") + def self_assign_offset(field: Field[np.float64]) -> None: + with computation(PARALLEL), interval(...): + tmp = (field[J - 1] + field[J + 1]) / 2 + field = tmp * 2 + + def test_k_offsets_in_parallel_loops() -> None: with pytest.raises(ValueError, match="read and write with k-offsets in PARALLEL"): From 14410f62a14eb5cd1662be7f4ec2b3f65b2cefaa Mon Sep 17 00:00:00 2001 From: Roman Cattaneo <1116746+romanc@users.noreply.github.com> Date: Wed, 12 Nov 2025 11:05:33 +0100 Subject: [PATCH 4/9] add name of offending field to error message --- src/gt4py/cartesian/gtc/gtir.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/gt4py/cartesian/gtc/gtir.py b/src/gt4py/cartesian/gtc/gtir.py index b1d3d4413d..24bda65297 100644 --- a/src/gt4py/cartesian/gtc/gtir.py +++ b/src/gt4py/cartesian/gtc/gtir.py @@ -263,13 +263,15 @@ def _vertical_offset_in_parallel(cls: type[VerticalLoop], instance: VerticalLoop write_access.offset, (VariableKOffset, AbsoluteKIndex) ): raise ValueError( - "Not allowed to read and write with `VariableKOffset` and/or `AbsoluteKIndex` in PARALLEL loops." + "Not allowed to read and write with `VariableKOffset` and/or " + f"`AbsoluteKIndex` in PARALLEL loops: `{node.name}`" ) # For cartesian offsets, we allow it if both are equal (e.g. 0) if node.offset.k != write_access.offset.k: raise ValueError( - "Not allowed to read and write with k-offsets in PARALLEL loops." + "Not allowed to read and write with k-offsets in PARALLEL " + f"loops: `{node.name}`" ) From 9f88e77924cc0db3b90978f8b98b9b884b1a484f Mon Sep 17 00:00:00 2001 From: Roman Cattaneo <1116746+romanc@users.noreply.github.com> Date: Wed, 12 Nov 2025 11:22:49 +0100 Subject: [PATCH 5/9] allow in PARALLEL loops of size one (e.g. interval(0, 1)) --- src/gt4py/cartesian/gtc/gtir.py | 10 +++++++++- .../multi_feature_tests/test_code_generation.py | 9 +++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/gt4py/cartesian/gtc/gtir.py b/src/gt4py/cartesian/gtc/gtir.py index 24bda65297..b9738858ed 100644 --- a/src/gt4py/cartesian/gtc/gtir.py +++ b/src/gt4py/cartesian/gtc/gtir.py @@ -242,7 +242,15 @@ def _vertical_offset_in_parallel(cls: type[VerticalLoop], instance: VerticalLoop """ Read and write of the same field in a parallel loop with non-zero offsets is not allowed in parallel. """ - if instance.loop_order != common.LoopOrder.PARALLEL: + + def _size_one(interval: Interval) -> bool: + if interval.start.level != interval.end.level: + # if the levels (start/end) aren't the same, we don't know at this stage + return False + + return abs(interval.end.offset - interval.start.offset) == 1 + + if instance.loop_order != common.LoopOrder.PARALLEL or _size_one(instance.interval): return # gather all writes as a mapping of id(node) -> node diff --git a/tests/cartesian_tests/integration_tests/multi_feature_tests/test_code_generation.py b/tests/cartesian_tests/integration_tests/multi_feature_tests/test_code_generation.py index e6dacdabc3..056e4b73a7 100644 --- a/tests/cartesian_tests/integration_tests/multi_feature_tests/test_code_generation.py +++ b/tests/cartesian_tests/integration_tests/multi_feature_tests/test_code_generation.py @@ -1544,6 +1544,15 @@ def self_assignment_center_parallel(field: Field[np.float32], index: Field[np.in with computation(PARALLEL), interval(1, None): field = index + index[0, 0, -1] * 2 + # parallel intervals of static size 1 are allowed too + @gtscript.stencil(backend="debug") + def the_stencil(field: Field[np.bool_]) -> None: + with computation(PARALLEL): + with interval(0, 1): + field = field[K + 1] + with interval(-1, None): + field = field[K - 1] + @pytest.mark.parametrize("backend", ALL_BACKENDS) def test_self_assignment_in_forward(backend: str) -> None: From 992afcff02cb7610f30479790534b43a96fe59eb Mon Sep 17 00:00:00 2001 From: Roman Cattaneo <1116746+romanc@users.noreply.github.com> Date: Thu, 13 Nov 2025 15:02:17 +0100 Subject: [PATCH 6/9] change error messages --- src/gt4py/cartesian/gtc/gtir.py | 4 ++-- .../multi_feature_tests/test_code_generation.py | 8 ++++---- .../test_gtc/test_passes/test_min_k_interval.py | 4 +++- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/gt4py/cartesian/gtc/gtir.py b/src/gt4py/cartesian/gtc/gtir.py index b9738858ed..e5d4e1296f 100644 --- a/src/gt4py/cartesian/gtc/gtir.py +++ b/src/gt4py/cartesian/gtc/gtir.py @@ -271,14 +271,14 @@ def _size_one(interval: Interval) -> bool: write_access.offset, (VariableKOffset, AbsoluteKIndex) ): raise ValueError( - "Not allowed to read and write with `VariableKOffset` and/or " + "Not allowed to write and read with `VariableKOffset` and/or " f"`AbsoluteKIndex` in PARALLEL loops: `{node.name}`" ) # For cartesian offsets, we allow it if both are equal (e.g. 0) if node.offset.k != write_access.offset.k: raise ValueError( - "Not allowed to read and write with k-offsets in PARALLEL " + "Not allowed to write and read with k-offsets in PARALLEL " f"loops: `{node.name}`" ) diff --git a/tests/cartesian_tests/integration_tests/multi_feature_tests/test_code_generation.py b/tests/cartesian_tests/integration_tests/multi_feature_tests/test_code_generation.py index 056e4b73a7..77a3220c7f 100644 --- a/tests/cartesian_tests/integration_tests/multi_feature_tests/test_code_generation.py +++ b/tests/cartesian_tests/integration_tests/multi_feature_tests/test_code_generation.py @@ -1492,14 +1492,14 @@ def self_assign_offset(field: Field[np.float64]) -> None: def test_k_offsets_in_parallel_loops() -> None: - with pytest.raises(ValueError, match="read and write with k-offsets in PARALLEL"): + with pytest.raises(ValueError, match="write and read with k-offsets in PARALLEL"): @gtscript.stencil(backend="debug") def self_assign_offset_parallel(field: Field[np.int32]) -> None: with computation(PARALLEL), interval(1, None): field = field[0, 0, -1] * 2 - with pytest.raises(ValueError, match="read and write with k-offsets in PARALLEL"): + with pytest.raises(ValueError, match="write and read with k-offsets in PARALLEL"): @gtscript.stencil(backend="debug") def self_assign_offset_parallel_temp(field: Field[np.int32]) -> None: @@ -1508,7 +1508,7 @@ def self_assign_offset_parallel_temp(field: Field[np.int32]) -> None: field = tmp * 2 with pytest.raises( - ValueError, match="read and write with `VariableKOffset` and/or `AbsoluteKIndex`" + ValueError, match="write and read with `VariableKOffset` and/or `AbsoluteKIndex`" ): @gtscript.stencil(backend="debug") @@ -1518,7 +1518,7 @@ def mixed_read_write(field: Field[np.int32]): field = 2 * level with pytest.raises( - ValueError, match="read and write with `VariableKOffset` and/or `AbsoluteKIndex`" + ValueError, match="write and read with `VariableKOffset` and/or `AbsoluteKIndex`" ): @gtscript.stencil(backend="debug") diff --git a/tests/cartesian_tests/unit_tests/test_gtc/test_passes/test_min_k_interval.py b/tests/cartesian_tests/unit_tests/test_gtc/test_passes/test_min_k_interval.py index 6bb4ec63f6..4c4fed3bf8 100644 --- a/tests/cartesian_tests/unit_tests/test_gtc/test_passes/test_min_k_interval.py +++ b/tests/cartesian_tests/unit_tests/test_gtc/test_passes/test_min_k_interval.py @@ -254,5 +254,7 @@ def stencil_with_invalid_temporary_access_end(field_a: gs.Field[float], field_b: ) def test_invalid_temporary_access(definition): builder = StencilBuilder(definition, backend=from_name("numpy")) - with pytest.raises(TypeError, match="Invalid access with offset in k to temporary field tmp."): + with pytest.raises( + ValueError, match="Not allowed to write and read with k-offsets in PARALLEL loops: `tmp`" + ): k_boundary = compute_k_boundary(builder.gtir_pipeline.full(skip=[prune_unused_parameters])) From 8e6698da25dbe5726d4d1405ee8f0e6c6e56652a Mon Sep 17 00:00:00 2001 From: Roman Cattaneo <1116746+romanc@users.noreply.github.com> Date: Thu, 13 Nov 2025 16:00:01 +0100 Subject: [PATCH 7/9] a bit of cleanup to make it look nice --- src/gt4py/cartesian/gtc/gtir.py | 9 ++++++--- .../multi_feature_tests/test_code_generation.py | 16 ++++++++-------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/gt4py/cartesian/gtc/gtir.py b/src/gt4py/cartesian/gtc/gtir.py index e5d4e1296f..d0eeeca9c3 100644 --- a/src/gt4py/cartesian/gtc/gtir.py +++ b/src/gt4py/cartesian/gtc/gtir.py @@ -240,7 +240,10 @@ def _no_write_and_read_with_horizontal_offset( @classmethod def _vertical_offset_in_parallel(cls: type[VerticalLoop], instance: VerticalLoop) -> None: """ - Read and write of the same field in a parallel loop with non-zero offsets is not allowed in parallel. + In a parallel vertical loop we disallow writing and reading the same field with a non-zero offset. + + To write and read with non-zero offset creates a race condition in parallel. There's an + exception for vertical loops with size one, e.g. `interval(0, 1)`. """ def _size_one(interval: Interval) -> bool: @@ -259,7 +262,7 @@ def _size_one(interval: Interval) -> bool: if isinstance(left, FieldAccess): writes[id(left)] = left - # check that we don't have read/write of the same field with non-zero offsets + # check that we don't have a write and reads of the same field with non-zero offsets for node in eve.walk_values(instance.body).if_isinstance(FieldAccess): if id(node) in writes: # this is the write access - skip it @@ -275,7 +278,7 @@ def _size_one(interval: Interval) -> bool: f"`AbsoluteKIndex` in PARALLEL loops: `{node.name}`" ) - # For cartesian offsets, we allow it if both are equal (e.g. 0) + # For cartesian offsets, we allow it if both offsets are equal (e.g. 0) if node.offset.k != write_access.offset.k: raise ValueError( "Not allowed to write and read with k-offsets in PARALLEL " diff --git a/tests/cartesian_tests/integration_tests/multi_feature_tests/test_code_generation.py b/tests/cartesian_tests/integration_tests/multi_feature_tests/test_code_generation.py index 77a3220c7f..dae9b29bc9 100644 --- a/tests/cartesian_tests/integration_tests/multi_feature_tests/test_code_generation.py +++ b/tests/cartesian_tests/integration_tests/multi_feature_tests/test_code_generation.py @@ -1474,7 +1474,7 @@ def test_upcasting_stencil( assert (input == output).all() -def test_no_read_write_with_horizontal_offset() -> None: +def test_no_write_and_read_with_horizontal_offset() -> None: with pytest.raises(ValueError, match="Self-assignment with offset is illegal."): @gtscript.stencil(backend="debug") @@ -1497,14 +1497,14 @@ def test_k_offsets_in_parallel_loops() -> None: @gtscript.stencil(backend="debug") def self_assign_offset_parallel(field: Field[np.int32]) -> None: with computation(PARALLEL), interval(1, None): - field = field[0, 0, -1] * 2 + field = field[K - 1] * 2 with pytest.raises(ValueError, match="write and read with k-offsets in PARALLEL"): @gtscript.stencil(backend="debug") def self_assign_offset_parallel_temp(field: Field[np.int32]) -> None: with computation(PARALLEL), interval(1, None): - tmp = field[0, 0, -1] + tmp = field[K - 1] field = tmp * 2 with pytest.raises( @@ -1538,13 +1538,13 @@ def self_assignment_center_write_parallel(field: Field[np.int32]) -> None: with computation(PARALLEL), interval(...): field[0, 0, 0] = field * 2 - # not mixing reads and writes are allowed too (e.g. index fields) + # not mixing reads and writes are allowed (e.g. index fields) @gtscript.stencil(backend="debug") def self_assignment_center_parallel(field: Field[np.float32], index: Field[np.int32]) -> None: with computation(PARALLEL), interval(1, None): - field = index + index[0, 0, -1] * 2 + field = index + index[K - 1] * 2 - # parallel intervals of static size 1 are allowed too + # parallel intervals of static size 1 are allowed @gtscript.stencil(backend="debug") def the_stencil(field: Field[np.bool_]) -> None: with computation(PARALLEL): @@ -1559,10 +1559,10 @@ def test_self_assignment_in_forward(backend: str) -> None: @gtscript.stencil(backend=backend) def self_assignment_parallel(field: Field[np.int32]) -> None: with computation(FORWARD), interval(1, None): - field = field[0, 0, -1] * 2 + field = field[K - 1] * 2 @gtscript.stencil(backend=backend) def self_assignment_2_parallel(field: Field[np.int32]) -> None: with computation(FORWARD), interval(1, None): - tmp = field[0, 0, -1] + tmp = field[K - 1] field = tmp * 2 From 47b8656e34821ee233706666f3d8bfbf2ca5e377 Mon Sep 17 00:00:00 2001 From: Roman Cattaneo Date: Thu, 13 Nov 2025 16:37:10 +0100 Subject: [PATCH 8/9] tailor message to I/J offsets Co-authored-by: Florian Deconinck --- src/gt4py/cartesian/gtc/gtir.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gt4py/cartesian/gtc/gtir.py b/src/gt4py/cartesian/gtc/gtir.py index d0eeeca9c3..caae3cd80e 100644 --- a/src/gt4py/cartesian/gtc/gtir.py +++ b/src/gt4py/cartesian/gtc/gtir.py @@ -106,7 +106,7 @@ def no_write_and_read_with_offset_of_same_field( .to_set() ) if instance.left.name in offset_reads: - raise ValueError("Self-assignment with offset is illegal.") + raise ValueError("Self-assignment with offset in I or J is illegal.") _dtype_validation = common.assign_stmt_dtype_validation(strict=False) From f0b29231fa97ee0f56003fc6729c9ca7cf680e3f Mon Sep 17 00:00:00 2001 From: Roman Cattaneo <1116746+romanc@users.noreply.github.com> Date: Thu, 13 Nov 2025 17:15:22 +0100 Subject: [PATCH 9/9] fixup: change error message in test --- .../multi_feature_tests/test_code_generation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/cartesian_tests/integration_tests/multi_feature_tests/test_code_generation.py b/tests/cartesian_tests/integration_tests/multi_feature_tests/test_code_generation.py index dae9b29bc9..3da7aa5d28 100644 --- a/tests/cartesian_tests/integration_tests/multi_feature_tests/test_code_generation.py +++ b/tests/cartesian_tests/integration_tests/multi_feature_tests/test_code_generation.py @@ -1475,7 +1475,7 @@ def test_upcasting_stencil( def test_no_write_and_read_with_horizontal_offset() -> None: - with pytest.raises(ValueError, match="Self-assignment with offset is illegal."): + with pytest.raises(ValueError, match="Self-assignment with offset in I or J is illegal."): @gtscript.stencil(backend="debug") def self_assign_offset(field: Field[np.float64]) -> None: