Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
565fa75
docs[next]: ADR 0028, dimensions as nominal types
egparedes Sep 18, 2026
823c0dd
feat[next]: add injective tag mangling for generated identifiers
egparedes Sep 18, 2026
7ea399a
wip[next]: dimension classes, resolve() and Staggered[D] in common
egparedes Sep 18, 2026
183488f
wip[next]: migrate the tree to dimension classes; unit tests green
egparedes Sep 21, 2026
05f14f1
docs[next]: drop the implementation plan from the PR
egparedes Sep 21, 2026
03cb2f2
wip[next]: offset-name mangling and DaCe/gtfn fixes; all unit tests g…
egparedes Sep 21, 2026
5e9e542
wip[next]: fingerprint staggered dims; IR text accepts tags; last gtf…
egparedes Sep 21, 2026
2c68d48
wip[next]: notebooks under nominal identity; docs, examples and typing
egparedes Sep 21, 2026
263af8f
wip[next]: migrate src doctests to dimension classes
egparedes Sep 21, 2026
3109d54
test[next]: migrate typing_tests to class-based dimensions
egparedes Sep 21, 2026
ce59507
style[next]: ruff format lines shortened by the tag migration
egparedes Sep 21, 2026
0e55c87
fix[next]: review fixes for dimension classes
egparedes Sep 23, 2026
996a34a
refactor[next]: ConstList replaces _CONST_DIM; AxisLiteral drops kind
egparedes Sep 22, 2026
83c362f
fix[next]: printing IR does not import modules
egparedes Sep 22, 2026
3371a94
test[next]: register doctest dimensions where their tags point
egparedes Sep 22, 2026
dc53f1a
fix[next]: review fixes for ConstList and AxisLiteral
egparedes Sep 23, 2026
c8a69f0
docs[next]: renumber the dimensions-as-nominal-types ADR to 0029
egparedes Oct 1, 2026
47b276b
feat[next]: Cartesian axis levels below DimensionIndex; bound Stagger…
egparedes Oct 1, 2026
ea75ee7
fix[next]: fingerprint a dimension's kind
egparedes Oct 1, 2026
a1cf830
docs[next]: mdformat the Cartesian axis table of ADR 0029
egparedes Oct 1, 2026
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
22 changes: 16 additions & 6 deletions docs/development/ADRs/next/0026-Staggered_Dimensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@ tags: []
- **Status**: valid
- **Authors**: Till Ehrengruber (@tehrengruber)
- **Created**: 2026-07-08
- **Updated**: 2026-07-09
- **Updated**: 2026-10-02

> The *encoding* of this record is superseded by
> [ADR 0029](0029-Dimensions_As_Nominal_Types.md): a staggered dimension is the
> real, interned class `Staggered[D]`, not a name prefix. The semantics below --
> half-integer positions, the shift convention, and the gtfn and DaCe treatment
> -- are unchanged.

A **staggered dimension** is a dimension sitting at the **half-integer**
positions of a base dimension. For example, in a cell-centered 2D Cartesian grid
Expand Down Expand Up @@ -58,10 +64,14 @@ index arithmetic is encoded in `common.connectivity_for_cartesian_shift`.

## Encoding

A staggered dimension is encoded as its base dimension's name with the internal
`_Staggered` prefix (`common._STAGGERED_PREFIX`), rather than as a new attribute
on `Dimension`. The helpers `is_staggered`, `flip_staggered` and
`as_non_staggered` operate purely on that prefix.
A staggered dimension was encoded as its base dimension's name with the internal
`_Staggered` prefix, rather than as a new attribute on `Dimension`. Since
[ADR 0029](0029-Dimensions_As_Nominal_Types.md) it is the class
`common.Staggered[D]`, whose identity is the class itself; the helpers
`is_staggered`, `flip_staggered` and `as_non_staggered` are unchanged in meaning
and now read the class's `base`. Only a declared Cartesian axis
(`CartesianAxisIndex`) can be staggered, which makes a doubly staggered dimension
and a staggered mesh location type errors.

Dimensions are identified by their **name** and appear throughout the toolchain
in more than one form: as a `common.Dimension` instance, but also as an
Expand All @@ -87,7 +97,7 @@ by the `as_non_staggered` name.

In gtfn a staggered dimension is emitted as a C++ `using` alias of its base
dimension's tag (`_add_staggered_aliases` in `itir_to_gtfn_ir.py`): e.g.
`_StaggeredIDim_t` becomes an alias of `IDim_t`, and `visit_AxisLiteral`
the staggered tag's mangled name becomes an alias of `IDim_t`, and `visit_AxisLiteral`
correspondingly emits the base dimension name.

The reason is that gtfn lowers a shift to an integer offset along a SID axis, and
Expand Down
343 changes: 343 additions & 0 deletions docs/development/ADRs/next/0029-Dimensions_As_Nominal_Types.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions docs/development/ADRs/next/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ Writing a new ADR is simple:
- [0021 - Argument Descriptors](0021-Argument-Descriptors.md)
- [0023 - Fingerprinting](0023-Fingerprinting.md)
- [0026 - Staggered Dimensions](0026-Staggered_Dimensions.md)
- [0029 - Dimensions as Nominal Types](0029-Dimensions_As_Nominal_Types.md)

### Frontend and Parsing #frontend

Expand Down
31 changes: 15 additions & 16 deletions docs/user/next/QuickstartGuide.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,11 @@ from gt4py.next import float64, neighbor_sum, where, Dims

#### Fields

Fields store data as a multi-dimensional array, and are defined over a set of named dimensions. The code snippet below defines two named dimensions, _Cell_ and _K_, and creates the fields `a` and `b` over their cartesian product using the `gtx.as_field` helper function. The fields contain the values 2 for `a` and 3 for `b` for all entries.
Fields store data as a multi-dimensional array, and are defined over a set of named dimensions. The code snippet below defines two dimensions, `CellDim` and `KDim` -- a dimension is a class. `CellDim` is a mesh location and subclasses `gtx.DimensionIndex`; `KDim` is a Cartesian axis, which supports index arithmetic such as `KDim + 1` and staggering, and subclasses `gtx.CartesianAxisIndex`. The snippet then creates the fields `a` and `b` over their cartesian product using the `gtx.as_field` helper function. The fields contain the values 2 for `a` and 3 for `b` for all entries.

```{code-cell} ipython3
CellDim = gtx.Dimension("Cell")
KDim = gtx.Dimension("K")
class CellDim(gtx.DimensionIndex): ...
class KDim(gtx.CartesianAxisIndex): ...

num_cells = 5
num_layers = 6
Expand All @@ -70,8 +70,8 @@ b = gtx.as_field([CellDim, KDim], np.full(shape=grid_shape, fill_value=b_value,
Additional numpy-equivalent constructors are available, namely `ones`, `zeros`, `empty`, `full`. These require domain, dtype, and allocator (e.g. a backend) specifications.

```{code-cell} ipython3
I = gtx.Dimension("I")
J = gtx.Dimension("J")
class I(gtx.CartesianAxisIndex): ...
class J(gtx.CartesianAxisIndex): ...

array_of_ones_numpy = np.ones((grid_shape[0], grid_shape[1]))
field_of_ones = gtx.ones(
Expand Down Expand Up @@ -165,11 +165,10 @@ The examples related to unstructured meshes use the mesh below. The edges (in bl

+++

The fields in the subsequent code snippets are 1-dimensional, either over the cells or over the edges. The corresponding named dimensions are thus the following:
The fields in the subsequent code snippets are 1-dimensional, either over the cells or over the edges. They use the `CellDim` declared above, plus a new dimension for the edges. A dimension is identified by its class, so declaring `CellDim` again here would create a *different* dimension from the one the fields above are defined on:

```{code-cell} ipython3
CellDim = gtx.Dimension("Cell")
EdgeDim = gtx.Dimension("Edge")
class EdgeDim(gtx.DimensionIndex): ...
```

You can express connectivity between elements (i.e. cells or edges) of the mesh using connectivity (a.k.a. adjacency or neighborhood) tables. The table below, `edge_to_cell_table`, has one row for every edge where it lists the indices of cells adjacent to that edge. For example, this table says that edge #6 connects to cells #0 and #5. Similarly, `cell_to_edge_table` lists the edges that are neighbors to a particular cell.
Expand Down Expand Up @@ -228,11 +227,11 @@ Another way to look at it is that transform uses the edge-to-cell connectivity t
You can use the field offset `E2C` below to transform a field over cells to a field over edges using the edge-to-cell connectivities:

```{code-cell} ipython3
E2CDim = gtx.Dimension("E2C", kind=gtx.DimensionKind.LOCAL)
E2C = gtx.FieldOffset("E2C", source=CellDim, target=(EdgeDim,E2CDim))
class E2CDim(gtx.DimensionIndex, kind=gtx.DimensionKind.LOCAL): ...
E2C = gtx.FieldOffset(E2CDim.tag, source=CellDim, target=(EdgeDim, E2CDim))
```

Note that the field offset does not contain the actual connectivity table, that's provided through an _offset provider_:
The field offset is named by its local dimension's `tag`, and the offset provider below is keyed by the same `tag`, so all three refer to one connectivity. Note that the field offset does not contain the actual connectivity table, that's provided through an _offset provider_:

```{code-cell} ipython3
E2C_offset_provider = gtx.as_connectivity([EdgeDim, E2CDim], codomain=CellDim, data=edge_to_cell_table, skip_value=-1)
Expand All @@ -251,7 +250,7 @@ def nearest_cell_to_edge(cell_values: gtx.Field[Dims[CellDim], float64]) -> gtx.
def run_nearest_cell_to_edge(cell_values: gtx.Field[Dims[CellDim], float64], out : gtx.Field[Dims[EdgeDim], float64]):
nearest_cell_to_edge(cell_values, out=out)

run_nearest_cell_to_edge(cell_values, edge_values, offset_provider={"E2C": E2C_offset_provider})
run_nearest_cell_to_edge(cell_values, edge_values, offset_provider={E2CDim.tag: E2C_offset_provider})

print("0th adjacent cell's value: {}".format(edge_values.asnumpy()))
```
Expand All @@ -278,7 +277,7 @@ def sum_adjacent_cells(cells : gtx.Field[Dims[CellDim], float64]) -> gtx.Field[D
def run_sum_adjacent_cells(cells : gtx.Field[Dims[CellDim], float64], out : gtx.Field[Dims[EdgeDim], float64]):
sum_adjacent_cells(cells, out=out)

run_sum_adjacent_cells(cell_values, edge_values, offset_provider={"E2C": E2C_offset_provider})
run_sum_adjacent_cells(cell_values, edge_values, offset_provider={E2CDim.tag: E2C_offset_provider})

print("sum of adjacent cells: {}".format(edge_values.asnumpy()))
```
Expand Down Expand Up @@ -380,8 +379,8 @@ print("where nested tuple return: {}".format(((result_1.asnumpy(), result_2.asnu
As explained in the section outline, the pseudo-laplacian needs the cell-to-edge connectivities as well in addition to the edge-to-cell connectivities. Though the connectivity table has been filled in above, you still need to define the local dimension, the field offset, and the offset provider that describe how to use the connectivity table. The procedure is identical to the edge-to-cell connectivity from before:

```{code-cell} ipython3
C2EDim = gtx.Dimension("C2E", kind=gtx.DimensionKind.LOCAL)
C2E = gtx.FieldOffset("C2E", source=EdgeDim, target=(CellDim, C2EDim))
class C2EDim(gtx.DimensionIndex, kind=gtx.DimensionKind.LOCAL): ...
C2E = gtx.FieldOffset(C2EDim.tag, source=EdgeDim, target=(CellDim, C2EDim))

C2E_offset_provider = gtx.as_connectivity([CellDim, C2EDim], codomain=EdgeDim, data=cell_to_edge_table, skip_value=-1)
```
Expand Down Expand Up @@ -442,7 +441,7 @@ result_pseudo_lap = gtx.as_field([CellDim], np.zeros(shape=(6,)))
run_pseudo_laplacian(cell_values,
edge_weight_field,
result_pseudo_lap,
offset_provider={"E2C": E2C_offset_provider, "C2E": C2E_offset_provider})
offset_provider={E2CDim.tag: E2C_offset_provider, C2EDim.tag: C2E_offset_provider})

print("pseudo-laplacian: {}".format(result_pseudo_lap.asnumpy()))
```
Expand Down
8 changes: 6 additions & 2 deletions docs/user/next/workshop/exercises/1_simple_addition.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,12 @@
"metadata": {},
"outputs": [],
"source": [
"I = gtx.Dimension(\"I\")\n",
"J = gtx.Dimension(\"J\")\n",
"class I(gtx.CartesianAxisIndex): ...\n",
"\n",
"\n",
"class J(gtx.CartesianAxisIndex): ...\n",
"\n",
"\n",
"size = 10"
]
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,12 @@
"metadata": {},
"outputs": [],
"source": [
"I = gtx.Dimension(\"I\")\n",
"J = gtx.Dimension(\"J\")\n",
"class I(gtx.CartesianAxisIndex): ...\n",
"\n",
"\n",
"class J(gtx.CartesianAxisIndex): ...\n",
"\n",
"\n",
"size = 10"
]
},
Expand Down
57 changes: 41 additions & 16 deletions docs/user/next/workshop/exercises/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import gt4py.next as gtx
from gt4py.next.iterator.embedded import MutableLocatedField
from gt4py.next import neighbor_sum, where, Dims
from gt4py.next import Dimension, DimensionKind, FieldOffset
from gt4py.next import CartesianAxisIndex, Dimension, DimensionIndex, DimensionKind, FieldOffset
from gt4py.next.program_processors.runners import roundtrip
from gt4py.next.program_processors.runners.gtfn import (
run_gtfn as gtfn_cpu,
Expand Down Expand Up @@ -377,18 +377,43 @@ def ripple_field(domain: gtx.Domain, *, allocator=None) -> MutableLocatedField:
)


C = Dimension("C")
V = Dimension("V")
E = Dimension("E")
K = Dimension("K", kind=gtx.DimensionKind.VERTICAL)

C2EDim = Dimension("C2E", kind=DimensionKind.LOCAL)
C2E = FieldOffset("C2E", source=E, target=(C, C2EDim))
V2EDim = Dimension("V2E", kind=DimensionKind.LOCAL)
V2E = FieldOffset("V2E", source=E, target=(V, V2EDim))
E2VDim = Dimension("E2V", kind=DimensionKind.LOCAL)
E2V = FieldOffset("E2V", source=V, target=(E, E2VDim))
E2CDim = Dimension("E2C", kind=DimensionKind.LOCAL)
E2C = FieldOffset("E2C", source=C, target=(E, E2CDim))
E2C2VDim = Dimension("E2C2V", kind=DimensionKind.LOCAL)
E2C2V = FieldOffset("E2C2V", source=V, target=(E, E2C2VDim))
class C(DimensionIndex): ...


class V(DimensionIndex): ...


class E(DimensionIndex): ...


class K(CartesianAxisIndex, kind=gtx.DimensionKind.VERTICAL): ...


class C2EDim(DimensionIndex, kind=DimensionKind.LOCAL): ...


C2E = FieldOffset(C2EDim.tag, source=E, target=(C, C2EDim))


class V2EDim(DimensionIndex, kind=DimensionKind.LOCAL): ...


V2E = FieldOffset(V2EDim.tag, source=E, target=(V, V2EDim))


class E2VDim(DimensionIndex, kind=DimensionKind.LOCAL): ...


E2V = FieldOffset(E2VDim.tag, source=V, target=(E, E2VDim))


class E2CDim(DimensionIndex, kind=DimensionKind.LOCAL): ...


E2C = FieldOffset(E2CDim.tag, source=C, target=(E, E2CDim))


class E2C2VDim(DimensionIndex, kind=DimensionKind.LOCAL): ...


E2C2V = FieldOffset(E2C2VDim.tag, source=V, target=(E, E2C2VDim))
7 changes: 5 additions & 2 deletions docs/user/next/workshop/slides/slides_1.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -207,8 +207,11 @@
}
],
"source": [
"Cell = gtx.Dimension(\"Cell\")\n",
"K = gtx.Dimension(\"K\", kind=gtx.DimensionKind.VERTICAL)\n",
"class Cell(gtx.DimensionIndex): ...\n",
"\n",
"\n",
"class K(gtx.CartesianAxisIndex, kind=gtx.DimensionKind.VERTICAL): ...\n",
"\n",
"\n",
"domain = gtx.domain({Cell: 5, K: 6})\n",
"\n",
Expand Down
19 changes: 11 additions & 8 deletions docs/user/next/workshop/slides/slides_2.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,10 @@
"metadata": {},
"outputs": [],
"source": [
"Cell = gtx.Dimension(\"Cell\")\n",
"K = gtx.Dimension(\"K\", kind=gtx.DimensionKind.VERTICAL)"
"class Cell(gtx.DimensionIndex): ...\n",
"\n",
"\n",
"class K(gtx.CartesianAxisIndex, kind=gtx.DimensionKind.VERTICAL): ..."
]
},
{
Expand Down Expand Up @@ -152,8 +154,7 @@
"metadata": {},
"outputs": [],
"source": [
"Cell = gtx.Dimension(\"Cell\")\n",
"Edge = gtx.Dimension(\"Edge\")"
"class Edge(gtx.DimensionIndex): ..."
]
},
{
Expand Down Expand Up @@ -272,8 +273,10 @@
"metadata": {},
"outputs": [],
"source": [
"E2CDim = gtx.Dimension(\"E2C\", kind=gtx.DimensionKind.LOCAL)\n",
"E2C = gtx.FieldOffset(\"E2C\", source=Cell, target=(Edge, E2CDim))"
"class E2CDim(gtx.DimensionIndex, kind=gtx.DimensionKind.LOCAL): ...\n",
"\n",
"\n",
"E2C = gtx.FieldOffset(E2CDim.tag, source=Cell, target=(Edge, E2CDim))"
]
},
{
Expand Down Expand Up @@ -317,7 +320,7 @@
" nearest_cell_to_edge(cell_field, out=edge_field)\n",
"\n",
"\n",
"run_nearest_cell_to_edge(cell_field, edge_field, offset_provider={\"E2C\": E2C_offset_provider})\n",
"run_nearest_cell_to_edge(cell_field, edge_field, offset_provider={E2CDim.tag: E2C_offset_provider})\n",
"\n",
"print(\"0th adjacent cell's value: {}\".format(edge_field.asnumpy()))"
]
Expand Down Expand Up @@ -392,7 +395,7 @@
" sum_adjacent_cells(cell_field, out=edge_field)\n",
"\n",
"\n",
"run_sum_adjacent_cells(cell_field, edge_field, offset_provider={\"E2C\": E2C_offset_provider})\n",
"run_sum_adjacent_cells(cell_field, edge_field, offset_provider={E2CDim.tag: E2C_offset_provider})\n",
"\n",
"print(\"sum of adjacent cells: {}\".format(edge_field.asnumpy()))"
]
Expand Down
6 changes: 4 additions & 2 deletions docs/user/next/workshop/slides/slides_3.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,10 @@
"metadata": {},
"outputs": [],
"source": [
"Cell = gtx.Dimension(\"Cell\")\n",
"K = gtx.Dimension(\"K\", kind=gtx.DimensionKind.VERTICAL)"
"class Cell(gtx.DimensionIndex): ...\n",
"\n",
"\n",
"class K(gtx.CartesianAxisIndex, kind=gtx.DimensionKind.VERTICAL): ..."
]
},
{
Expand Down
2 changes: 1 addition & 1 deletion docs/user/next/workshop/slides/slides_4.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
"metadata": {},
"outputs": [],
"source": [
"K = gtx.Dimension(\"K\", kind=gtx.DimensionKind.VERTICAL)"
"class K(gtx.CartesianAxisIndex, kind=gtx.DimensionKind.VERTICAL): ..."
]
},
{
Expand Down
12 changes: 9 additions & 3 deletions examples/lap_cartesian_vs_next.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,16 @@
"# allocator = gtx.gtfn_cpu\n",
"# allocator = gtx.gtfn_gpu\n",
"\n",
"\n",
"# Note: for gt4py.next, names don't matter, for gt4py.cartesian they have to be \"I\", \"J\", \"K\"\n",
"I = gtx.Dimension(\"I\")\n",
"J = gtx.Dimension(\"J\")\n",
"K = gtx.Dimension(\"K\", kind=gtx.DimensionKind.VERTICAL)\n",
"class I(gtx.CartesianAxisIndex): ...\n",
"\n",
"\n",
"class J(gtx.CartesianAxisIndex): ...\n",
"\n",
"\n",
"class K(gtx.CartesianAxisIndex, kind=gtx.DimensionKind.VERTICAL): ...\n",
"\n",
"\n",
"domain = gtx.domain({I: nx, J: ny, K: nz})\n",
"\n",
Expand Down
10 changes: 10 additions & 0 deletions src/gt4py/next/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,24 @@
from .._core.definitions import CUPY_DEVICE_TYPE, Device, DeviceType, is_scalar_type
from . import common, ffront, iterator, program_processors, typing
from .common import (
AnyCartesianAxisIndex,
CartesianAxisIndex,
CartesianConnectivity,
Connectivity,
Dimension,
DimensionIndex,
DimensionKind,
Dims,
Domain,
Field,
GridType,
Staggered,
UnitRange,
as_non_staggered,
domain,
flip_staggered,
is_staggered,
resolve,
unit_range,
)
from .constructors import FieldConstructor, as_connectivity, as_field, empty, full, ones, zeros
Expand Down Expand Up @@ -115,7 +120,12 @@
"is_scalar_type",
# from common
"Dimension",
"DimensionIndex",
"AnyCartesianAxisIndex",
"CartesianAxisIndex",
"DimensionKind",
"Staggered",
"resolve",
"Dims",
"Field",
"CartesianConnectivity",
Expand Down
Loading
Loading