Conversation
2881fa4 to
a127841
Compare
|
I like the approach much more than the one in #2844. Maybe we can avoid the biggest downside, the many changes everywhere and the long names, by just defining a common set of dimensions in some module and avoiding the long name for them. |
d38784e to
91e24e2
Compare
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Interactive dimension resolution can retain stale classes, and the fallback scan performs avoidable connectivity-file I/O.
Get a fresh assessment by requesting another Copilot review.
Review effort: Balanced
Findings: 2
Open (3)
What changed in this PR
Makes gt4py.next dimensions nominal Python classes, with qualified-name tags used across IR, code generation, serialization, and backends.
Changes:
- Introduces
DimensionIndex, nominal identity, resolution, mangling, and staggered dimensions. - Migrates frontend, iterator, DaCe, GTFN, embedded execution, docs, and examples.
- Updates tests and adds interactive compilation fallback coverage.
| File | Description |
|---|---|
docs/development/ADRs/next/0026-Staggered_Dimensions.md |
Updates staggered encoding. |
docs/development/ADRs/next/0028-Dimensions_As_Nominal_Types.md |
Records the architecture. |
docs/development/ADRs/next/README.md |
Lists ADR 0028. |
docs/user/next/QuickstartGuide.md |
Migrates dimension examples. |
docs/user/next/workshop/exercises/1_simple_addition.ipynb |
Migrates notebook dimensions. |
docs/user/next/workshop/exercises/1_simple_addition_solution.ipynb |
Migrates solution dimensions. |
docs/user/next/workshop/exercises/helpers.py |
Migrates workshop helpers. |
docs/user/next/workshop/slides/slides_1.ipynb |
Migrates slide examples. |
docs/user/next/workshop/slides/slides_2.ipynb |
Migrates connectivity examples. |
docs/user/next/workshop/slides/slides_3.ipynb |
Migrates slide dimensions. |
docs/user/next/workshop/slides/slides_4.ipynb |
Migrates slide dimensions. |
examples/lap_cartesian_vs_next.ipynb |
Migrates example dimensions. |
src/gt4py/next/__init__.py |
Exports the new API. |
src/gt4py/next/common.py |
Implements nominal dimensions. |
src/gt4py/next/constructors.py |
Accepts dimension classes. |
src/gt4py/next/custom_layout_allocators.py |
Uses typed dimension indices. |
src/gt4py/next/embedded/common.py |
Migrates indexing helpers. |
src/gt4py/next/embedded/nd_array_field.py |
Migrates field indexing. |
src/gt4py/next/embedded/operators.py |
Migrates scan positions. |
src/gt4py/next/ffront/decorator.py |
Updates scan documentation. |
src/gt4py/next/ffront/fbuiltins.py |
Constructs nominal indices. |
src/gt4py/next/ffront/foast_passes/type_deduction.py |
Updates dimension diagnostics. |
src/gt4py/next/ffront/foast_pretty_printer.py |
Updates examples. |
src/gt4py/next/ffront/foast_to_gtir.py |
Emits qualified tags. |
src/gt4py/next/ffront/foast_to_past.py |
Updates examples. |
src/gt4py/next/ffront/func_to_foast.py |
Updates examples. |
src/gt4py/next/ffront/func_to_past.py |
Updates examples. |
src/gt4py/next/ffront/past_to_itir.py |
Lowers qualified dimensions. |
src/gt4py/next/ffront/transform_utils.py |
Detects dimension metaclasses. |
src/gt4py/next/ffront/type_info.py |
Adds unknown-dimension placeholder. |
src/gt4py/next/field_utils.py |
Updates field examples. |
src/gt4py/next/fingerprinting.py |
Fingerprints staggered classes. |
src/gt4py/next/iterator/embedded.py |
Uses shared ConstList. |
src/gt4py/next/iterator/ir.py |
Derives axis dimensions from tags. |
src/gt4py/next/iterator/ir_utils/domain_utils.py |
Resolves axis tags. |
src/gt4py/next/iterator/ir_utils/ir_makers.py |
Emits tagged axes. |
src/gt4py/next/iterator/ir_utils/misc.py |
Resolves axis literals. |
src/gt4py/next/iterator/pretty_parser.py |
Parses qualified tags. |
src/gt4py/next/iterator/pretty_printer.py |
Prints derived axis kinds. |
src/gt4py/next/iterator/tracing.py |
Traces dimension classes. |
src/gt4py/next/iterator/transforms/fuse_as_fieldop.py |
Updates doctests. |
src/gt4py/next/iterator/transforms/inline_fundefs.py |
Updates doctests. |
src/gt4py/next/iterator/transforms/pass_manager.py |
Uses qualified dimension keys. |
src/gt4py/next/iterator/transforms/prune_empty_concat_where.py |
Updates doctests. |
src/gt4py/next/iterator/transforms/remove_broadcast.py |
Updates doctests. |
src/gt4py/next/iterator/transforms/replace_get_domain_range_with_constants.py |
Matches dimensions by tag. |
src/gt4py/next/iterator/transforms/unroll_reduce.py |
Uses local-dimension tags. |
src/gt4py/next/iterator/type_system/inference.py |
Resolves axis dimensions. |
src/gt4py/next/iterator/type_system/type_synthesizer.py |
Migrates inferred dimensions. |
src/gt4py/next/otf/binding/nanobind.py |
Mangles binding dimensions. |
src/gt4py/next/otf/compilation_tasks.py |
Preserves dimension providers. |
src/gt4py/next/otf/runners.py |
Adds interactive fallback. |
src/gt4py/next/program_processors/codegens/gtfn/codegen.py |
Mangles generated tags. |
src/gt4py/next/program_processors/codegens/gtfn/gtfn_module.py |
Migrates connectivity tags. |
src/gt4py/next/program_processors/codegens/gtfn/itir_to_gtfn_ir.py |
Declares qualified tag types. |
src/gt4py/next/program_processors/runners/dace/lowering/gtir_python_codegen.py |
Mangles DaCe symbols. |
src/gt4py/next/program_processors/runners/dace/lowering/gtir_to_sdfg.py |
Uses local tags. |
src/gt4py/next/program_processors/runners/dace/lowering/gtir_to_sdfg_concat_where.py |
Resolves local providers. |
src/gt4py/next/program_processors/runners/dace/lowering/gtir_to_sdfg_lambda.py |
Uses nominal local dimensions. |
src/gt4py/next/program_processors/runners/dace/lowering/gtir_to_sdfg_primitives.py |
Uses local tags. |
src/gt4py/next/program_processors/runners/dace/lowering/gtir_to_sdfg_scan.py |
Uses local tags. |
src/gt4py/next/program_processors/runners/dace/lowering/gtir_to_sdfg_utils.py |
Mangles map variables. |
src/gt4py/next/program_processors/runners/dace/sdfg_args.py |
Mangles SDFG arguments. |
src/gt4py/next/program_processors/runners/dace/transformations/loop_blocking.py |
Accepts dimension classes. |
src/gt4py/next/program_processors/runners/dace/transformations/map_orderer.py |
Accepts dimension classes. |
src/gt4py/next/program_processors/runners/dace/workflow/bindings.py |
Unmangles provider keys. |
src/gt4py/next/program_processors/runners/roundtrip.py |
Resolves generated dimensions. |
src/gt4py/next/type_system/mypy_plugin.py |
Removes dimension hooks. |
src/gt4py/next/type_system/type_info.py |
Updates dimension diagnostics. |
src/gt4py/next/type_system/type_specifications.py |
Displays class dimensions. |
src/gt4py/next/type_system/type_translation.py |
Recognizes dimension classes. |
tests/next_tests/artifacts/custom_named_collections.py |
Migrates fixture dimensions. |
tests/next_tests/benchmarks/benchmark_program_call.py |
Migrates benchmark dimensions. |
tests/next_tests/fixtures/past_common.py |
Reuses canonical dimensions. |
tests/next_tests/integration_tests/cases_utils.py |
Centralizes test dimensions. |
tests/next_tests/integration_tests/feature_tests/dace_tests/test_orchestration.py |
Updates mangled connectivity arguments. |
tests/next_tests/integration_tests/feature_tests/dace_tests/test_write_back_buffer_elimination_lowering.py |
Migrates connectivity declarations. |
tests/next_tests/integration_tests/feature_tests/ffront_tests/test_concat_where.py |
Uses qualified provider keys. |
tests/next_tests/integration_tests/feature_tests/ffront_tests/test_external_local_field.py |
Uses qualified provider keys. |
tests/next_tests/integration_tests/feature_tests/ffront_tests/test_foast_pretty_printer.py |
Migrates dimensions. |
tests/next_tests/integration_tests/feature_tests/ffront_tests/test_import_from_mod.py |
Uses qualified provider keys. |
tests/next_tests/integration_tests/feature_tests/ffront_tests/test_named_collections.py |
Uses qualified provider keys. |
tests/next_tests/integration_tests/feature_tests/ffront_tests/test_reductions.py |
Migrates reduction coverage. |
tests/next_tests/integration_tests/feature_tests/ffront_tests/test_staggered.py |
Updates staggered coverage. |
tests/next_tests/integration_tests/feature_tests/ffront_tests/test_temporaries_with_sizes.py |
Uses qualified symbolic sizes. |
tests/next_tests/integration_tests/feature_tests/ffront_tests/test_tuples.py |
Uses qualified provider keys. |
tests/next_tests/integration_tests/feature_tests/ffront_tests/test_type_conversion.py |
Uses qualified provider keys. |
tests/next_tests/integration_tests/feature_tests/instrumentation_tests/test_hooks.py |
Migrates dimensions. |
tests/next_tests/integration_tests/feature_tests/iterator_tests/test_builtins.py |
Migrates local dimensions. |
tests/next_tests/integration_tests/feature_tests/iterator_tests/test_conditional.py |
Migrates dimensions. |
tests/next_tests/integration_tests/feature_tests/iterator_tests/test_implicit_fencil.py |
Migrates dimensions. |
tests/next_tests/integration_tests/feature_tests/iterator_tests/test_program.py |
Migrates dimensions. |
tests/next_tests/integration_tests/feature_tests/iterator_tests/test_strided_offset_provider.py |
Migrates connectivity dimensions. |
tests/next_tests/integration_tests/feature_tests/iterator_tests/test_tuple.py |
Migrates dimensions. |
tests/next_tests/integration_tests/multi_feature_tests/ffront_tests/test_ffront_fvm_nabla.py |
Uses qualified provider keys. |
tests/next_tests/integration_tests/multi_feature_tests/ffront_tests/test_icon_like_scan.py |
Reuses canonical dimensions. |
tests/next_tests/integration_tests/multi_feature_tests/ffront_tests/test_multiple_output_domains.py |
Uses qualified provider keys. |
tests/next_tests/integration_tests/multi_feature_tests/fvm_nabla_setup.py |
Reuses canonical dimensions. |
tests/next_tests/integration_tests/multi_feature_tests/iterator_tests/test_anton_toy.py |
Migrates dimensions. |
tests/next_tests/integration_tests/multi_feature_tests/iterator_tests/test_fvm_nabla.py |
Reuses canonical dimensions. |
tests/next_tests/integration_tests/multi_feature_tests/iterator_tests/test_if_stmt.py |
Migrates dimensions. |
tests/next_tests/integration_tests/multi_feature_tests/iterator_tests/test_temporaries.py |
Migrates dimensions. |
tests/next_tests/integration_tests/multi_feature_tests/iterator_tests/test_with_toy_connectivity.py |
Uses qualified provider keys. |
tests/next_tests/regression_tests/embedded_tests/test_domain_pickle.py |
Migrates pickle dimensions. |
tests/next_tests/regression_tests/ffront_tests/test_offset_dimensions_names.py |
Updates tag regressions. |
tests/next_tests/toy_connectivity.py |
Defines canonical connectivity dimensions. |
tests/next_tests/unit_tests/conftest.py |
Migrates dummy dimensions. |
tests/next_tests/unit_tests/embedded_tests/test_basic_program.py |
Migrates dimensions. |
tests/next_tests/unit_tests/embedded_tests/test_common.py |
Migrates index tests. |
tests/next_tests/unit_tests/embedded_tests/test_context.py |
Migrates range dimensions. |
tests/next_tests/unit_tests/embedded_tests/test_nd_array_field.py |
Tests nominal field behavior. |
tests/next_tests/unit_tests/ffront_tests/test_decorator_domain_deduction.py |
Migrates grid deduction tests. |
tests/next_tests/unit_tests/ffront_tests/test_diagnostic_messages.py |
Migrates diagnostic fixtures. |
tests/next_tests/unit_tests/ffront_tests/test_fbuiltins.py |
Migrates builtin fixtures. |
tests/next_tests/unit_tests/ffront_tests/test_foast_to_gtir.py |
Verifies qualified lowering. |
tests/next_tests/unit_tests/ffront_tests/test_func_to_foast.py |
Migrates parser fixtures. |
tests/next_tests/unit_tests/ffront_tests/test_func_to_foast_error_line_number.py |
Migrates line-sensitive fixture. |
tests/next_tests/unit_tests/ffront_tests/test_past_to_gtir.py |
Expects qualified axes. |
tests/next_tests/unit_tests/ffront_tests/test_source_utils.py |
Migrates closure fixtures. |
tests/next_tests/unit_tests/ffront_tests/test_stages.py |
Migrates stage fixtures. |
tests/next_tests/unit_tests/ffront_tests/test_type_deduction.py |
Tests nominal deduction. |
tests/next_tests/unit_tests/iterator_tests/ir_utils_tests/test_domain_utils.py |
Tests tagged domains. |
tests/next_tests/unit_tests/iterator_tests/test_embedded_field_with_list.py |
Tests shared ConstList. |
tests/next_tests/unit_tests/iterator_tests/test_embedded_internals.py |
Migrates dimensions. |
tests/next_tests/unit_tests/iterator_tests/test_inline_dynamic_shifts.py |
Migrates dimensions. |
tests/next_tests/unit_tests/iterator_tests/test_pretty_parser.py |
Tests ignored kind suffixes. |
tests/next_tests/unit_tests/iterator_tests/test_pretty_printer.py |
Tests derived kind suffixes. |
tests/next_tests/unit_tests/iterator_tests/test_pretty_roundtrip.py |
Updates axis round trips. |
tests/next_tests/unit_tests/iterator_tests/test_runtime_domain.py |
Migrates connectivity types. |
tests/next_tests/unit_tests/iterator_tests/test_type_inference.py |
Tests resolved dimensions. |
tests/next_tests/unit_tests/iterator_tests/transforms_tests/test_collapse_tuple.py |
Migrates dimensions. |
tests/next_tests/unit_tests/iterator_tests/transforms_tests/test_concat_where_canonicalize_domain_args.py |
Migrates dimensions. |
tests/next_tests/unit_tests/iterator_tests/transforms_tests/test_concat_where_expand_tuple_args.py |
Migrates dimensions. |
tests/next_tests/unit_tests/iterator_tests/transforms_tests/test_concat_where_transform_to_as_fieldop.py |
Migrates dimensions. |
tests/next_tests/unit_tests/iterator_tests/transforms_tests/test_cse.py |
Migrates provider types. |
tests/next_tests/unit_tests/iterator_tests/transforms_tests/test_dead_code_elimination.py |
Migrates dimensions. |
tests/next_tests/unit_tests/iterator_tests/transforms_tests/test_domain_inference.py |
Uses qualified axis tags. |
tests/next_tests/unit_tests/iterator_tests/transforms_tests/test_expand_tuple_maps.py |
Migrates dimensions. |
tests/next_tests/unit_tests/iterator_tests/transforms_tests/test_fuse_as_fieldop.py |
Migrates local list dimensions. |
tests/next_tests/unit_tests/iterator_tests/transforms_tests/test_global_tmps.py |
Migrates dimensions. |
tests/next_tests/unit_tests/iterator_tests/transforms_tests/test_inline_scalar.py |
Migrates dimensions. |
tests/next_tests/unit_tests/iterator_tests/transforms_tests/test_prune_casts.py |
Migrates dimensions. |
tests/next_tests/unit_tests/iterator_tests/transforms_tests/test_prune_empty_concat_where.py |
Uses qualified provider keys. |
tests/next_tests/unit_tests/iterator_tests/transforms_tests/test_unroll_reduce.py |
Tests qualified local tags. |
tests/next_tests/unit_tests/otf_tests/binding_tests/test_cpp_interface.py |
Migrates binding dimensions. |
tests/next_tests/unit_tests/otf_tests/compilation_tests/build_systems_tests/conftest.py |
Generates mangled fixture tags. |
tests/next_tests/unit_tests/otf_tests/test_compiled_program.py |
Migrates dimensions. |
tests/next_tests/unit_tests/otf_tests/test_runners.py |
Tests interactive fallback. |
tests/next_tests/unit_tests/program_processor_tests/codegens_tests/gtfn_tests/test_gtfn_module.py |
Uses qualified axis tags. |
tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/test_dace.py |
Uses qualified provider keys. |
tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/test_dace_bindings.py |
Tests mangled bindings. |
tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/test_dace_translation.py |
Builds symbolic names through helpers. |
tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/test_gtir_to_sdfg.py |
Tests nominal DaCe lowering. |
tests/next_tests/unit_tests/program_processor_tests/runners_tests/dace_tests/transformation_tests/test_map_promoter.py |
Migrates map dimensions. |
tests/next_tests/unit_tests/test_common.py |
Tests nominal dimension semantics. |
tests/next_tests/unit_tests/test_constructors.py |
Uses typed indices. |
tests/next_tests/unit_tests/test_custom_layout_allocators.py |
Tests typed alignment indices. |
tests/next_tests/unit_tests/test_field_utils.py |
Migrates dimensions. |
tests/next_tests/unit_tests/test_utils.py |
Tests dimension-key fingerprints. |
tests/next_tests/unit_tests/type_system_tests/test_type_info.py |
Migrates type fixtures. |
tests/next_tests/unit_tests/type_system_tests/test_type_translation.py |
Tests class-based translation. |
typing_tests/test_next.yaml |
Verifies PEP 484 dimensions. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| @functools.cache | ||
| def resolve(tag: Tag) -> Dimension: |
| compilable = task.construct_compilable(True) | ||
| if ( | ||
| reason is None | ||
| and (name := _interactive_main_reference((task.executor, compilable))) is not None |
| f"{', '.join([dim.value for dim in target_dims])}" | ||
| f"{', '.join([dim.__qualname__ for dim in field_type.dims])} from " | ||
| f"{source_dim.__qualname__} to target dim(s): " | ||
| f"{', '.join([dim.tag for dim in target_dims])}" |
Records the design this stack implements, ahead of the code, because it reverses
decisions that are easier to argue about as prose than as a 150-file diff.
A concrete dimension becomes a class and an index an instance of it, so
`Field[Dims[IDim], float64]` type-checks with no gt4py mypy plugin. A
dimension's identity is the Python type and its tag is the qualified Python
name, which is also a unique, valid IR spelling.
The decisions worth reviewing first, each with the reason it is not the obvious
choice:
* Nominal identity, not `(name, kind)` value equality with an interning
registry. The registry decouples the Python type's identity from the IR's
and needs `copyreg` plus a custom fingerprint deconstructor to bridge the
gap. Under value equality the `typing` subscription cache already aliases
`Field[Dims[I]]` and `Field[Dims[I2]]` for two distinct same-named classes,
so the static and runtime views disagree exactly there.
* `resolve(tag)` is an import, so types reaching the IR must be declared at
module level. Interactive `__main__` is a documented limitation; `spawn`
workers re-execute the main script, so file-based `__main__` resolves.
* Generated identifiers need a *prefix* escape (`_` -> `_u`, `.` -> `_d`).
The obvious `_` -> `__` then `.` -> `_` is not injective: a dot becomes a
single underscore, so `".."` collides with an escaped `"_"`.
* `Staggered[D]` supersedes ADR 0026's `_Staggered` name prefix, which cannot
survive type identity. It cannot be a PEP 695 generic either -- that yields
a `_GenericAlias`, not a class -- so it is an interning metaclass paired
with a `TYPE_CHECKING` declaration.
* `DimensionMeta` must declare `__hash__` explicitly, since `__eq__` stays for
the `I == 5` overload and Python would otherwise make every dimension class
unhashable.
Consequence recorded for ADR 0023: a dimension is fingerprinted by qualified
name, so moving a declaration between modules invalidates compiled artifacts.
A dimension tag becomes a qualified Python name in this stack, so it contains
dots -- illegal in a C++ identifier, in a DaCe symbol, and in `eve`'s
`SymbolName` (`^[a-zA-Z_]\w*$`). `codegen_name` mangles a tag into a valid
identifier and `from_codegen_name` recovers it, for the backends that parse a
generated name back into the dimension it refers to.
The escape is a *prefix* escape (`_` -> `_u`, `.` -> `_d`). The obvious
alternative -- double every underscore, then turn dots into single underscores --
is **not injective**: a dot becomes a single underscore, so `'..'` and `'_'` both
map to `'__'`. Exhaustively: 686 collisions in 1092 inputs over `{a, ., _}` up to
length 6. A collision here would mean two distinct dimensions silently sharing
one generated symbol, i.e. wrong results rather than a crash, so the test is
exhaustive rather than example-based: every string over `{a, ., _, u, d}` up to
length 5 mangles uniquely and round-trips, and the adversarial cases that look
like the escape sequences themselves (`_u`, `_d`, `a_ud.b`) are pinned
separately.
No caller yet; the sites that need it arrive with the dimension classes.
The core of ADR 0028. NOT green: `Dimension("X")` now raises, so every
declaration in the tree has to become a class statement. That sweep is the next
commit; this one is the mechanism it depends on.
* `DimensionMeta` / `DimensionIndex`: a dimension is a class, an index an
instance. Identity is the type; `tag` is the qualified Python name and is a
metaclass *property*, so it cannot drift from what it names. `__hash__` is
declared explicitly because `__eq__` stays for the `I == 5` overload.
Dimension-vs-dimension `__eq__` is deliberately *not* overridden -- identity
is the correct answer, and overriding it is what the ADR rejects.
* `type Dimension = type[DimensionIndex]`, a PEP 695 alias so the removed
`Dimension("I")` raises instead of silently evaluating to `str`.
* Display uses `__qualname__`, not `tag`: 148 tests assert on message text like
`Field[[IDim], float64]`, and a qualified name there is noise. `repr` carries
the module.
* `resolve(tag)` imports and walks the qualname, memoized, with a grammar for
the parametrized `<owner>[<base>]` form.
* `Staggered[D]` replaces ADR 0026's `_Staggered` name prefix, which cannot
survive type identity. An interning metaclass builds a *real* class, paired
with a `TYPE_CHECKING` declaration -- a PEP 695 generic yields a
`_GenericAlias`, which is not a class and fails eve's `type[...]` validation.
Bases are `(Staggered,)` and deliberately not `(Staggered, base)`, so a
staggered field is not accepted where its base is required. Verified: real
class, interning stable, kind inherited, tag resolvable, instances work,
`copyreg` round-trips both the parametrized class and the bare base, and all
three escape routes (subclassing either level, double subscript) are blocked.
* `ConstListDim` is declared **once** in `common`. It used to be built
independently in `iterator/embedded.py` and in the DaCe lowering, which was
harmless while dimensions compared by `(name, kind)`. Under nominal identity
those would be two different dimensions and the `offset_type == _CONST_DIM`
checks in the DaCe lowering would stop matching `ListType`s built by embedded
execution.
Continues the dimension-classes core. `tests/next_tests/unit_tests` is green (1752 passed); integration and DaCe suites not yet run. Source: * `.value` -> `.tag` on dimensions, replayed from #2844 by exact line match (88 sites) plus the lines that had drifted since its base. * `isinstance(x, Dimension)` and `case Dimension()` -> `DimensionMeta`, since `Dimension` is a PEP 695 alias that neither accepts. * `resolve()` at the IR boundaries that rebuild a dimension from a tag. * `NamedIndex` removed: an index is an instance of its dimension. Four sites tuple-unpacked an index and now read `.dim` / `.value`. * Injective mangling applied to generated identifiers in gtfn (the declared `TagDefinition`s and the references to them), nanobind, DaCe and the roundtrip emitter -- declarations and references must mangle identically, or the bindings name a C++ type that was never declared. * DaCe no longer synthesizes a local dimension from the offset tag; it uses the connectivity's own `neighbor_dim`. Display vs identity, now applied uniformly: diagnostics print `__qualname__`, IR and codegen use `tag`. `Staggered` overrides `tag` so its `__qualname__` can stay short. `order_dimensions` sorts by `__qualname__` too: sorting by the qualified tag made a field's canonical dimension order depend on *which module* declared each dimension (recorded in ADR 0028). Tests: * Every `Dimension(...)` declaration became a class (codemod); the 132 function-local ones were hoisted. No hoist changed behaviour: same-variable locals merge into one class, and every same-name/different-variable pair already differed in kind. * Inline `Dimension("X")` expressions map to one shared class per (name, kind) per file, because two such calls used to be equal. * One string per connectivity: a local dimension's tag is now qualified, so the `V2EDim = Dimension("V2E")` convention that made the offset tag, the local dimension and the provider key one string is gone. Restored symbolically -- `FieldOffset(V2EDim.tag, ...)`, `{V2EDim.tag: table}` -- so PR 4 changes only the declaration. * Cross-module duplicates: dimensions that were value-equal across modules are distinct now. `cases_utils` imports the six unstructured dimensions it shared with `toy_connectivity` instead of redeclaring them, and four tests import rather than redeclare. A same-*variable* check was not enough: the original *strings* decide equality, and `test_gtfn_module`'s `IDim` was `"I"` while `cases_utils`' was `"IDim"`, so those stay distinct.
It was committed by accident, via a broad `git add` of `docs/`. It is a working artifact for planning this stack -- with four rounds of review notes folded in -- not documentation for the repository, and ADR 0028 already records the decisions that need to live here. It stays available outside the PR.
…reen
`tests/next_tests/unit_tests` green on every backend: 1760 passed without DaCe,
451 with. Integration suite not yet re-run.
The main fix is a consequence of the previous commit that it did not follow
through. Pulling the one-string-per-connectivity invariant into this PR makes
the *offset* keys qualified here too, so the offset-name mangling the plan
scheduled for PR 4 is needed now:
* gtfn: connectivity parameter names and `generated::<name>_t` keys, the
`SymRef` to each connectivity, and `axis_literal` literals.
* DaCe: `connectivity_identifier` mangles, and all three places that parse an
identifier back (`is_connectivity_identifier`, `_field_symbol`, and the
generated binding code) unmangle -- the binding uses the mangled name as a
Python variable but must look the table up by the real tag.
* DaCe `visit_AxisLiteral`: a dotted tag in a symbol name gets re-parsed as an
attribute access, leaving bare sympy symbols (no `dtype`) in an array's free
symbols.
* `IndexConnectorFmt` was mangled at one of its three uses, so a connector was
declared under one name and referenced under another.
`codegen_name` also escapes `[` and `]`: `Staggered[pkg.K]` has them and they
survived into identifiers (Python read the emitted name as a subscript). The
exhaustive injectivity test now covers `{a . _ [ ] u d l r}`.
Tests: the DaCe tests that hard-coded symbol names -- 190 in one file -- now
compute them from `sdfg_args`, so they stop depending on a naming scheme they
are not testing. The binding golden test formats both sides, since the longer
names make lines wrap. More dimensions that were value-equal across library
modules are unified: `past_common` and `fvm_nabla_setup` import theirs, and
`test_dace_bindings` takes the `cases` dimensions its fixture is sized on.
…n names * Strict fingerprinter: `Staggered[K]` has no importable qualified name, so the by-reference `type` deconstruction rejected it. Registered on `StaggeredMeta`, it is fingerprinted by its base dimension (importable), the same reduction its `copyreg` hook uses; the bare `Staggered` stays by reference. * IR text format: the pretty parser read axis and offset literals as `CNAME`, so a qualified tag (dots, and brackets for `Staggered[pkg.K]`) did not round-trip. A `TAG` terminal describes a qualified name; unambiguous, since a tag starts with a letter and the literal's suffix terminates it. * gtfn: the scan column axis and sparse-argument tuple-like dimensions still named `generated::<tag>_t` with the raw tag. * The sparse-argument path read `.tag` off a legacy `FieldOffset`, which only has `.value` -- a leftover of the #2844 `.value -> .tag` replay.
Everything now passes: unit 1764 + 451 (DaCe), integration and regression
2745 + DaCe, and every notebook `test_examples` runs.
* Notebooks. Compilation runs in `spawn` workers by default, and a class declared
in an interactive `__main__` (a notebook, the REPL, `python -c`) pickles in the
parent but cannot be unpickled in a worker, which has no such `__main__` to
import. Reproduced with CI's own mechanism (`nbmake`):
"Can't get attribute 'IDim' on <module '__main__'>". The project's own
Quickstart and workshop do exactly this, so a documented limitation was not
enough. The process runner now falls back to the calling thread, with a
warning, when a job references a class from an interactive `__main__` -- the
same fallback it already takes for an unpicklable executor. Scripts keep
parallel compilation: spawn re-imports a script's `__main__`. ADR 0028 updated.
* `__gt_dims__` is the interop protocol with `gt4py.cartesian`, which names axes
by bare name ("I", "J", "K"), so it returns `__qualname__`, not the qualified
tag -- otherwise cartesian transposes the array wrongly. No test covered it;
one does now, and fails with the fix reverted.
* Docs, workshop notebooks and `examples/` migrated; notebook code cells only,
stored outputs untouched. Both the Quickstart and `slides_2` declared the cell
dimension twice, harmless while declarations compared equal; the second one is
dropped, since the Quickstart later combined fields built on each.
* mypy clean on `src/`. `_is_field_axis` called `isinstance` on a PEP 695 alias,
which raises at runtime; only an `assert` reaches it. `TYPE_BUILTINS` keeps the
`common.Dimension` alias: its `__name__` is "Dimension", the DSL builtin's
name. (#2844 swapped in `DimensionIndex`, which would rename the builtin.)
* DaCe orchestration test and two diagnostics that joined tags into messages.
The `src/` doctests were the one suite not covered locally: `pytest tests/` does
not collect them, and nox runs them last, after the main suite. The Python 3.13
and 3.14 nox sessions caught 40 failures there -- all `Dimension("I")` calls on
what is now a non-callable alias. They pass now on 3.12, 3.13 and 3.14 (116).
* 60 doctest declarations become `class X(DimensionIndex): ...`, and 8 inline
`Dimension("I")` expressions get a declaration hoisted in front of them.
* Expected outputs showing the old `Dimension(value='I', ...)` repr are updated to
the actual output. Applied only where the difference is purely a dimension's
spelling, checked mechanically; one update the check refused was a false
positive and was verified by eye.
* A class declared in a doctest runs in a *copy* of the module's globals, so it
is not an attribute of the real module and `resolve()` cannot import it. Four
doctests run an IR pass that resolves a dimension; they bind the class onto the
module explicitly, with a comment -- the same consequence of nominal identity
as interactive `__main__`, confined to doctests.
* Those bindings are separate `>>>` statements: written as `import sys; ...` on
one line, ruff's docstring formatter split them into a continuation that
doctest compiles as a single statement.
With the dimension half of the mypy plugin gone, a dimension bound to a variable
(`IDim = gtx.Dimension("I")`) is no longer valid in an annotation: only a class
is. `test_typing_exports` runs these snippets in CI and was not covered by any
local suite.
Taken from #2844 unchanged. Static typing depends only on dimensions being
classes, which both designs share; nothing in these snippets touches identity
semantics, and main has not changed the file since #2844's base. 16 passed.
`pre-commit run --all-files` (what CI runs) reformats four lines that now fit on one line after `.value` -> `.tag`; the per-commit runs only covered staged files.
- a dimension cannot be staggered twice: the check reads the *argument* - intern staggered dimensions with 'setdefault', since compilation runs in threads - 'resolve' rejects a bracketed tag whose owner is not a parametrized dimension - the 'Dim + offset' hint quotes the dimension's display name, not '.value' - ADR 0026: the name-prefix encoding is superseded by ADR 0028 - drop the FieldOffset tag/name analysis: the knowledge base keeps that record - 'runtimes': say where an unpicklable job is actually reported
- common.ConstListDim becomes common.ConstList, the local dimension of make_const_list, used directly by iterator embedded and the DaCe lowering (identity checks) - AxisLiteral stores only the tag; kind and dim are resolved from it. The pretty printer derives the suffix (now with ₗ for local dimensions, which used to be printed as vertical), and the parser ignores it.
The pretty printer takes an axis literal's kind from its inferred type, or from a dimension that is already loaded (common.resolve_loaded), and never imports. gtfn's domain canonicalization resolves through dim_from_axis_literal.
- ADR 0028's date, which this PR's edits had outrun - say why 'ListType.offset_type' can be 'None' while embedded uses 'ConstList' - test 'resolve_loaded', which is what keeps printing IR import-free
ADR 0028 on main is now 'Plain Builders Instead of factory-boy Factories' (#2808), so the two ADRs of this stack move up by one.
…ed on a declared axis Add `AnyCartesianAxisIndex` (either cell class of a Cartesian axis) and `CartesianAxisIndex` (a declared axis) below `DimensionIndex`, both exported as `gtx.*`. `Staggered[D]` derives from `AnyCartesianAxisIndex` and its parameter is bounded on `CartesianAxisIndex`, so a doubly staggered dimension, a staggered mesh location and a staggered local dimension are `[type-var]` errors for mypy and pyright and `TypeError`s at runtime. `DimensionMeta.__add__` / `__sub__` carry the self-type `type[AnyCartesianAxisIndex]`, so `Cell + 1` on a mesh location is an `[operator]` error, a `TypeError` at runtime and a `DSLError` in a field operator. Comparisons (`D == n`, `D < n`) stay available on every dimension. The tree's Cartesian dimensions (`IDim`, `KDim`, ...) are declared as `CartesianAxisIndex`; mesh locations stay `DimensionIndex`. ADR 0029 records the levels and the equality decision (dimension-vs-dimension `==` is identity).
A dimension's `kind` decides a field's layout order and the scan axis, so a dimension redefined under the same name with another kind must not reuse compiled artifacts. Staggered dimensions follow through their base.
92a3e89 to
a1cf830
Compare


A concrete dimension becomes a class, and an index along it an instance of that class, so
Field[Dims[IDim], float64]type-checks under any PEP 484 checker with no gt4py mypy plugin (#2503). A dimension's identity is its Python type, and itstag-- the spelling in the IR and generated code -- is its qualified Python name.Part 1 of 4 of the connectivities as types stack (see Stack). Review the ADR first (
docs/development/ADRs/next/0029-Dimensions_As_Nominal_Types.md; numbered 0029 because main's #2808 took 0028): the rest of the diff is largely mechanical once its decisions are accepted.Cartesian axes are a level below the root
A dimension is one of three things, and the class says which:
AnyCartesianAxisIndex(either cell class of an axis) andCartesianAxisIndex(a declared axis) sit belowDimensionIndex, so notype[DimensionIndex]annotation in the tree widens.Staggered[D]is bounded on the declared level and derives fromAnyCartesianAxisIndex, andDimensionMeta.__add__/__sub__takecls: type[AnyCartesianAxisIndex]. As a result these are errors for mypy and pyright, and at runtime as well:Staggered[Staggered[K]][type-var]TypeErrorStaggered[Cell], staggering a local dimension[type-var]TypeErrorCell + 1,Cell - 1[operator]TypeError;DSLErrorin a field operatorComparisons (
Cell < n, which build aDomainforconcat_where) stay available on every dimension. Whether a dimension is an axis or a mesh location is a decision per declaration (IDimandCellare bothHORIZONTAL), so the tree's Cartesian dimensions are now declared asCartesianAxisIndex.Where this diverges from #2844: identity
#2844 chose
(name, kind)value equality plus an interning registry, so independently declared same-named dimensions stay interchangeable. This PR chooses nominal identity. That removes the registry, the blanketcopyreghook and the custom fingerprint deconstructor that bridged the gap between the Python type's identity and the IR's. One argument in its favour that the shared proposal itself raises: under(name, kind)equality thetypingsubscription cache aliasesField[Dims[I]]andField[Dims[I2]]for two distinct same-named classes, so the static and runtime views disagree exactly there.The pieces of #2844 that are independent of identity are reused. Each was checked against this tree rather than copied: three were wrong here, the sharpest being its
TYPE_BUILTINSchange, which would have silently renamed the DSL'sDimensionbuiltin (the alias's__name__is"Dimension", not"type"as its comment says).Consequences worth knowing, all recorded in the ADR
resolve(tag)is an import, so dimensions reaching the IR must be declared at module level. Printing IR never imports: it goes throughcommon.resolve_loaded, which only looks at loaded modules.__main__. Compilation runs inspawnworkers by default, and a class declared in a notebook pickles in the parent but cannot be unpickled in a worker. The repository's own Quickstart and workshop do exactly this, so this was not left as a documented limitation: the process runner now compiles such a job in the calling thread, with a warning -- the fallback it already takes for an unpicklable executor. Scripts keep parallel compilation.V2EDim = Dimension("V2E")convention that made the offset tag, the local dimension's name and the provider key equal is gone. It is restored symbolically --FieldOffset(V2EDim.tag, ...),{V2EDim.tag: table}-- so the follow-up that makes connectivities classes changes only the declaration. Until then the Quickstart's connectivity section is more verbose than before.eveidentifiers.codegen_nameis a prefix escape: the obvious_ -> __,. -> _is not injective (".."collides with an escaped"_"). Tested exhaustively.__qualname__, identity istag-- in diagnostics, inorder_dimensions(keying it on the tag would make a field's dimension order depend on the module a dimension is declared in), and in__gt_dims__, the interop protocol withgt4py.cartesian, which names axes by bare name.Staggered[D]replaces ADR 0026's_Staggeredname prefix, which cannot survive type identity. It cannot be a PEP 695 generic (that yields a_GenericAlias, not a class): it is an interning metaclass plus aTYPE_CHECKINGdeclaration, with a narrowcopyregand a fingerprint deconstructor registered on its metaclass.DimensionMeta.__eq__stays forI == 5(aDomain), but returnsNotImplementedfor a dimension operand, soI == Jfalls back toisand is always abool.Cleanups the class-based dimensions make possible
common.ConstListis the one local dimension ofmake_const_listresults. It replaces the two_CONST_DIMaliases in iterator embedded and the DaCe lowering, which only worked while dimensions compared by(name, kind); their checks now use identity (is).AxisLiteralstores only the tag.AxisLiteral.kindand the newAxisLiteral.dimare derived from it, so the kind can no longer disagree with the dimension's own. It already did in one case: the pretty printer printed a local axis as vertical (ᵥ), so a local axis didn't round-trip through the textual IR. The printer now derives the suffix (ₕ,ᵥ, and the newₗ), and the parser ignores it.Test-tree changes that are findings, not churn
Dimensions that were value-equal across modules are now distinct.
cases_utils,fvm_nabla_setup,past_commonand several tests declared their own copies of the same dimensions and mixed objects built on each; they now import one canonical declaration. The Quickstart and a workshop notebook each declared their cell dimension twice, and the Quickstart later combined fields built on both. What decided equality was the original string, not the variable:test_gtfn_module'sIDimwas"I"whilecases_utils' was"IDim", so those correctly stay distinct.Not included
Local.Verification
Run locally (CPU) on ea75ee7 (the head adds only an mdformat fix of an ADR table); 0 failures in every row that ran:
test_next(internal, cpu, nomesh): embedded, roundtrip, gtfntest_next(dace, cpu, nomesh)src/gt4py/nextdoctests (in each session)test_typing_exports(mypy cases)pre-commit run --all-filesGitHub CI on the pushed head passes every check:
test_next(internal and dace; nomesh and atlas) on 3.12 and 3.14,test_cartesian,test_eve,test_storage, notebooks, package,typing-exportsand code quality. Locally, on this head, 3.13 (4917 internal / 1488 dace), 3.14 (4918 / 1488), atlas (45 / 5) and the notebooks (19) also passed.Stack
Staggered[D: CartesianAxisIndex],ConstList,AxisLiteralwithoutkind.NeighborConnectivity[Domain, Codomain]declarations, shared local dimensions on every backend,NeighborTableType,ts.ShiftType,DimensionKind.LOCALremoved (ADR 0030).FieldOffsetis removed,as_offsettakes a Cartesian axis,offset_provider_typebecomestable_types; migration script.MultiDimensionIndexand typed iterator-embedded positions.The stack was shortened from 7 PRs to 4: this PR absorbs #2911 (its commits sit on top of this PR's own), #2907 absorbs #2909, and #2910 absorbs #2908.
Implements
egparedes/connectivities-as-types, with the Cartesian axis levels specified in gt4py_knowledge#36; supersedes #2844 (closed). Subsumes #2845's test migration, since without adimension(tag, kind)factory there is no minimal migration form and every declaration takes class form immediately. #2898 (the offset's own tag in shift lowering) is already merged.