Skip to content

idea[havogt]: align staggering with the shipped shift convention - #28

Merged
havogt merged 1 commit into
mainfrom
staggering-naming-inversion
Aug 5, 2026
Merged

idea[havogt]: align staggering with the shipped shift convention#28
havogt merged 1 commit into
mainfrom
staggering-naming-inversion

Conversation

@havogt

@havogt havogt commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Part II of dimension-generic-fields spelled a staggering shift as a(I + 1/2)
for a: Field[Dims[I]] — naming the dimension the field is on. gt4py as
shipped (#2667, ADR 0026) does the opposite, and the proposal's spelling is not
a different result there but a hard error:

I  + 0.5   -> domain(result)=I            codomain(field)=_StaggeredI   offset=1
IH + 0.5   -> domain(result)=_StaggeredI  codomain(field)=I             offset=0

a on I:  a(IH + 0.5) -> Domain(_StaggeredI=(0:10))   OK
         a(I  + 0.5) -> ValueError: Incompatible dimensions in the connectivity codomain(s)

The implementation is right and the proposal moves.

Why: a shift is precomposition

A field is a map f: I → value, a connectivity a map φ: D → I; applying one
is f ∘ φ, a field on D. The connectivity's domain is therefore the
result's dimension and its codomain the field's — forced by composition, not
chosen. The index expression is written in the result's index space and must
evaluate to an index of the field's own dimension, which is exactly what the
in-tree test comment states: a(IHalfDim + 0.5) # always pass an I-index to an IField.

The unstructured connectivities already encode this in their names — V2E is
"for each Vertex, its Edges", domain Vertex, codomain Edge, so it consumes
an edge field and produces a vertex field. Staggering is merely the first place
the convention becomes observable, because integer Cartesian shifts have
domain == codomain.

§4.2 records the discarded alternative (source-naming) with its one real
advantage and the reason it loses: it gives no rule for a(E2C[0]), so
unstructured shifts would have to become the exception.

What changed

  • §1, §4.1–§4.3: the spelling flips (a(Staggered[I] + 1/2)), the position
    convention becomes the shipped one (staggered point i at i - 1/2), and the
    index rule is restated as ceil when the shift names an unstaggered dimension
    / floor when it names a staggered one — keyed on the result's dimension,
    which is what connectivity_for_cartesian_shift already implements.
  • Prototype (typed_dimensions.py, static_checks.py, static_errors.py,
    test_typed_dimensions.py): overloads, runtime rule and every expectation
    flipped. 19/19 pass, mypy clean, the error oracle fires on exactly the
    marked lines.
  • dual is now a typed function. This is the part that cost more than
    swapping two type arguments: because the shift names the result grid, a
    dual-generic body cannot shift on its own dimension and needs dual(dim)
    the type-level counterpart of gt4py's value-level flip_staggered. It gets
    the same overload pair, so it stays precise under a dimension variable
    (to_staggered in static_checks.py).

Three corrections found while verifying

  • §4.4 "backends are unaffected" was wrong. gtfn needs
    _add_staggered_aliases (itir_to_gtfn_ir.py), emitting the staggered tag as
    a C++ using alias of its base tag, because a gtfn shift can offset a SID but
    cannot rename its axis. The FOAST bullet also had source/target
    inverted; it now matches the shipped source=conn.codomain, target=(conn.domain_dim,).
  • The I + 0.7 gap is mypy-only, not runtime-only: the DSL already rejects
    it at FOAST time with a DSLError and a hint.
  • check_dims is not statically expressible — a dimension and its staggered
    counterpart may not co-occur, but Field[Dims[I, Staggered[I]]] stays
    writable for mypy. Added as a known gap.

Incidental

The prototype's mypy.ini pinned python_version = 3.10, which numpy's stubs
now reject outright, so both mypy-oracle tests were failing on main for
reasons unrelated to any of this. Bumped to 3.12 (gt4py has dropped 3.10/3.11).
With that fixed, mypy 2.3 reproduces the metaclass quirk and its suppression
exactly as documented
— §10 risk 1's "could break on a mypy upgrade" has not
materialised; pyright remains untested. .gitignore gained __pycache__/,
since the vendored prototypes are runnable.

Note for merge order

Touches dimension-generic-fields.md alongside #27 (which stubs out §3). The
edits are in disjoint sections; the only adjacency is the > **Prototypes**:
block near the top, where #27 inserts a note and this branch edits the mypy
version on the following line. Whichever lands second may need a trivial rebase.

A shift names the dimension the *result* lives on (the rule unstructured
shifts already follow), not the field's own; staggered point i sits at
i - 1/2. Flips the Staggered[D] overloads, the runtime index rule, the
prototype and its static/runtime checks.

Also corrects three claims in Part II: gtfn is not unaffected (it needs
_add_staggered_aliases), the I + 0.7 gap is mypy-only, and check_dims
(a dim and its counterpart may not co-occur) is not statically expressible.
@havogt
havogt merged commit cc353d2 into main Aug 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant