From d723f3679a1bebaf2e3e2645a9984d242d33e0a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrique=20Gonz=C3=A1lez=20Paredes?= Date: Thu, 17 Sep 2026 14:17:25 +0200 Subject: [PATCH 1/3] personal/egparedes: connectivities as types A neighbor connectivity is today spread over three user-authored objects that must agree by string equality (FieldOffset tag, local Dimension name, offset_provider key) plus the Python variable name the FieldOffset is bound to. Propose a connectivity class that contains its local dimension, is its own provider key, and whose identity is its qualified Python name (also the IR tag), building on shared/dimensions-as-types and gt4py#2844. Includes a research appendix with the full constraint catalogue and concept inventory derived from an audit of gt4py at b3c53fa7e (v1.2.2). --- content/index.md | 1 + .../connectivities-as-types.md | 473 +++++++++++++++ .../connectivities-as-types_research.md | 560 ++++++++++++++++++ 3 files changed, 1034 insertions(+) create mode 100644 content/personal/egparedes/connectivities-as-types/connectivities-as-types.md create mode 100644 content/personal/egparedes/connectivities-as-types/connectivities-as-types_research.md diff --git a/content/index.md b/content/index.md index 450efbe..61ddbc5 100644 --- a/content/index.md +++ b/content/index.md @@ -44,6 +44,7 @@ Work-in-progress proposals, organized by contributor. - [[personal/egparedes/layered-architecture|A layered architecture for gt4py with enforced public/internal decoupling]] — keywords: architecture, layering, tach, modularity, public-api, semi-public, internal, packaging, dependencies, refactoring, infrastructure, over-engineering, otf, workflow, dsl, config, allocators, caching, fingerprint, compiled-program, process-pool, factory-boy, instrumentation, adr, jax, decoupling, migration, tech-debt - [[personal/egparedes/otf-toolchain-split|Dissolving gt4py.next.otf into a toolchain core and DSL-agnostic build infrastructure]] — keywords: otf, toolchain, workflow, pipeline, refactoring, architecture, layering, modularity, backend, bindings, build-system, caching, runners, compiled-program, factory-boy, naming, observability, stage-inspection, adr, tach, tech-debt +- [[personal/egparedes/connectivities-as-types/connectivities-as-types|Connectivities as types: one declaration for offset, local dimension and provider key]] — keywords: type-system, dimensions, local-dimensions, connectivities, offset-provider, unstructured, neighbor-sum, reduction, frontend, foast, gtir, embedded, gtfn, dace, nominal-types, metaclass, serialization, fingerprint, staggering, migration, adr, tech-debt - [[personal/egparedes/discretization-independent-fd-syntax|A discretization-independent surface syntax for finite-difference computations]] — keywords: finite-difference, finite-volume, mesh-invariant, structured, unstructured, exterior-calculus, dec, mimetic, de-rham, location-typing, stencil, weight-generation, rbf-fd, gfdm, moment-matching, conservation, field-operators, icon, pace, pmap, fv3, ifs-fvm, arakawa-staggering, c-grid, d-grid, a-grid, dynamical-core, hevi, vertical-solve, elliptic-solve, mpdata, semi-implicit, miura, atlas, prior-art, dsl-design EshiftE["embedded shift
fbuiltins.py:494"] + N3 --> EredE["embedded reduce
nd_array_field.py:983"] + N2 --> LOW["FOAST to GTIR
foast_to_gtir.py:305, 331"] + LOW --> N5["N5 - itir.OffsetLiteral"] + N5 --> CshiftC["compiled shift
type_synthesizer.py:748"] + N3 --> CredC["compiled reduce
unroll_reduce.py:47"] + N3 --> SPARSE["sparse field argument
gtfn_module.py:95
gtir_to_sdfg.py:581"] + + EshiftE --> GET + EredE --> GET + CshiftC --> GET + CredC --> GET + SPARSE --> GET + N4 -.->|"must equal the string that arrives"| GET + + GET["get_offset(offset_provider, string)
common.py:1200"] + GET --> DATA["NeighborTable / NeighborConnectivityType"] +``` + +### 2.4 The contrast that suggests the fix + +Cartesian shifts carry **dimensions** in the IR node; unstructured shifts carry +a **string** that must be resolved against a dict. Every constraint A1-A5 exists +only on the right-hand side. + +```text + CARTESIAN (already clean) UNSTRUCTURED (entangled) + ───────────────────────── ──────────────────────── + field(IDim + 1) field(V2E) + │ │ + v v + CartesianConnectivity itir.OffsetLiteral("V2E") <- a string + (common.py:1241) │ + │ v + v get_offset(provider, "V2E") + itir.CartesianOffset │ + domain: AxisLiteral v + codomain: AxisLiteral NeighborTable + (iterator/ir.py:99) + │ + v + NO tag. NO provider entry. NO lookup. +``` + +This is the concrete precedent behind any consolidation proposal: the Cartesian +path already eliminated the string indirection, and the unstructured path +retains it only because the neighbor table data must be supplied at runtime. + +## 3. Master table — cross-name-space identity constraints + +| # | Constraint | Embedded (field) | Embedded (iterator) | IR / type system | GTFN | DaCe | Enforced? | Source | +| ------- | ---------------------------------------------------------------------------- | ---------------- | ------------------- | ---------------- | ---------------- | ------------ | ----------------------------- | -------------------------------------------------------------------------------------------- | +| **A1** | `FieldOffset.value` (N1) == provider key (N4) | required | required | — | — | — | `KeyError` | `fbuiltins.py:494, 508`; `common.py:1207-1208` | +| **A2** | Python var name (N2) == provider key (N4) | — | — | required | required | required | silent; `KeyError` at runtime | `foast_to_gtir.py:305, 331` | +| **A3** | local dim `.value` (N3) == provider key (N4), **reductions** | required | required | required | required | required | `KeyError` | `nd_array_field.py:981-985`; `embedded.py:953, 1517, 1776`; `unroll_reduce.py:43-50, 61-65` | +| **A4** | local dim `.value` (N3) == provider key (N4), **sparse field args** | — | — | — | required | required | `assert` / `ValueError` | `gtfn_module.py:88-98`; `gtir_to_sdfg.py:572-585, 838-842`; `gtir_to_sdfg_lambda.py:766-770` | +| **A5** | `FieldOffset.value` (N1) == local dim `.value` (N3), **shift path** | n/a (A1 governs) | n/a | **not** required | **not** required | inconsistent | codegen branch handles it | `itir_to_gtfn_ir.py:181-190`; regression test | +| **A6** | `target[-1]` == connectivity `neighbor_dim` (full `Dimension` equality) | required | required | required | required | required | no eager check; index error | `fbuiltins.py:496`; `common.py:984-986` | +| **A7** | `FieldOffset.source` == connectivity `codomain` | required | required | required | required | required | `assert` only | `embedded.py:596-614`; `type_synthesizer.py:748-758` | +| **A8** | `FieldOffset.target[0]` == connectivity `domain[0]` (`source_dim`) | required | required | required | required | required | `assert found` | `type_synthesizer.py:752-758`; `embedded.py:597-599` | +| **A9** | `Dimension.value` (N3) is the key of the embedded **iterator position dict** | — | required | — | — | — | `assert ... in pos` | `embedded.py:574-576, 597-616, 941-950` | +| **A10** | `Dimension.value` (N3) round-trips through `AxisLiteral.value` (N5) | — | — | required | required | required | structural | `iterator/ir.py:92-96`; `ir_utils/misc.py:234-235`; `inference.py:463-464` | + +### Notes on A5 + +A5 is the only row with history. PR #1789 (`fix[next]: gtfn with offset name != local dimension name`) lifted it for shifts and added the +`if offset_name != connectivity_type.neighbor_dim.value` branch at +`itir_to_gtfn_ir.py:185-190`. Its regression test is +`tests/next_tests/regression_tests/ffront_tests/test_offset_dimensions_names.py`, +whose docstring gives the motivation: + +> If the value of the `NeighborConnectivityType.neighbor_dim` did not match the +> `FieldOffset` value, gtfn would silently ignore the neighbor index, see +> . + +That test covers **only** `a(Off[1])` on `GTFN_CPU`. It does not cover +`neighbor_sum`, embedded execution, or DaCe. A3 and A4 were never lifted, so a +mismatch still breaks reductions and sparse arguments. + +The DaCe "inconsistent" entry: `gtir_to_sdfg_lambda.py:1155` builds +`Dimension(offset, LOCAL)` — a local dim named after the *tag* — while +`type_synthesizer.py:327-329` builds the same `ListType` from +`conn_type.neighbor_dim`. The two agree only when A5 holds. + +## 4. Per-context detail + +### 4.1 Embedded — field level (`nd_array_field`, `fbuiltins`) + +| Site | Key used | Constraint | +| ------------------------------------------------ | ----------------- | ------------------------------------------------------------------------------ | +| `fbuiltins.py:491-498` `FieldOffset.__getitem__` | `self.value` (N1) | A1; then `NamedIndex(self.target[-1], offset)` gives A6 | +| `fbuiltins.py:502-520` `as_connectivity_field` | `self.value` (N1) | A1 | +| `nd_array_field.py:981-985` reductions | `axis.value` (N3) | A3 — carries the comment `# assumes offset and local dimension have same name` | +| `nd_array_field.py:972-979` | — | `axis.kind == LOCAL`; at most one local dim per field | +| `nd_array_field.py:317-320` `premap` | — | `FieldOffset` to `Connectivity` via A1 | + +### 4.2 Embedded — iterator level (`iterator/embedded.py`) + +| Site | Key used | Constraint | +| --------------------------------------- | --------------------------------------------------------- | --------------------------------- | +| `:596-616` `execute_shift` | tag (N4), then `source_dim.value` / `codomain.value` (N3) | A7, A8, A9 | +| `:566-576` sparse shift | tag (N4) | A3 | +| `:941-953` `make_in_iterator` | `sparse_dimensions[0].value` (N3) used as tag | A3 | +| `:1517-1519` `SparseListIterator.deref` | `self.list_offset` (N3-derived) | A3 | +| `:1005` `field_setitem` | `value.offset.value` used as a **field dim name** | A3 (tag to N3, reverse direction) | +| `:1410-1416` `_List.__gt_type__` | tag, then `neighbor_dim` | correct direction, no assumption | +| `:1436-1451` `neighbors` | `offset.value` (N1) | A1 | +| `:1776` `_fieldspec_list_to_value` | `offset_type.value` (N3) | A3 | + +### 4.3 IR / type system + +| Site | Key used | Constraint | +| ------------------------------------------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------ | +| `type_synthesizer.py:326-329` `neighbors` | `OffsetLiteral.value` (N5), then `neighbor_dim` | A2; local dim taken from provider, **not** from the tag | +| `type_synthesizer.py:740-758` `shift` | N5, then `domain[0]`/`codomain` | A2, A7, A8 (`assert found`, `assert not found`) | +| `type_synthesizer.py:433-447` `_canonicalize_nb_fields` | field's LOCAL dim to `ListType.offset_type` | where N3 enters `ListType` and becomes an A3 key downstream | +| `type_synthesizer.py:546-556` `_resolve_dimensions` | N5, then `get_offset_type` | A2 | +| `unroll_reduce.py:43-50, 61-65` | `arg.type.offset_type.value` (N3) | **A3** | +| `domain_utils.py:205-223` | `off.value` (N5) | A2 | +| `pass_manager.py:55-63` | `source_dim.value`/`codomain.value` (N3) | domain sizes keyed by dimension name | +| `past_to_itir.py:409-410` | — | `ValueError: "common.Dimension '{dim.value}' must not be local."` in program domains | +| `type_deduction.py:459-464` | — | `"Second dimension in offset must be a local dimension."` | +| `type_info.py:637-650, 848-878` | — | shift typing via `source`/`target` only; the tag is never consulted | + +### 4.4 GTFN backend + +| Site | Name used | Constraint | +| ------------------------------------- | -------------------------------------------------- | ---------------------------------------------------------- | +| `itir_to_gtfn_ir.py:181-190` | provider key **and** `neighbor_dim.value` | the only site that anticipates A5 failing; emits both tags | +| `itir_to_gtfn_ir.py:191-196` | `source_dim.value`, `codomain.value` | must be `HORIZONTAL`, else `NotImplementedError` | +| `itir_to_gtfn_ir.py:197-200` | — | provider entries must be `NeighborConnectivityType` | +| `itir_to_gtfn_ir.py:485-492` | N5 tags | `o in self.offset_provider_type` | +| `itir_to_gtfn_ir.py:139-148, 166-180` | `dim.value` (N3) | every field dim name becomes a C++ tag | +| `gtfn_module.py:88-98` | `dim.value` (N3) | **A4** | +| `gtfn_module.py:126-136` | `domain[0].value`, `domain[1].value`, provider key | all three become `generated::_t` | + +### 4.5 DaCe backend + +| Site | Name used | Constraint | +| ---------------------------------------------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | +| `gtir_to_sdfg.py:572-585` | `local_dim.value` (N3) | **A4**, explicit: `ValueError("The provided local dimension {local_dim} does not match any offset provider type.")` | +| `gtir_to_sdfg.py:838-842` | `dim.value` (N3) | A4 — array shape from `max_neighbors` | +| `gtir_to_sdfg_lambda.py:766-770` | `local_dim.value` (N3) | A4 | +| `gtir_to_sdfg_lambda.py:1312-1319, 1371, 1443, 1455` | `offset_type.value` (N3) | A3, plus connectivity array name | +| `gtir_to_sdfg_lambda.py:1155` | tag (N5) to `Dimension(offset, LOCAL)` | reverse of A5; conflicts with `type_synthesizer.py:329` | +| `gtir_to_sdfg_lambda.py:1718-1727` | `offset_provider_arg.value` (N5) | genuine tag lookup — correct | +| `gtir_to_sdfg_primitives.py:324-331` | `offset_type.value` (N3) | A3 | +| `gtir_to_sdfg_scan.py:385-389` | `offset_type.value` (N3) | A3 | +| `sdfg_args.py:73-93` | field name plus `dim.value` | dim matched against `source_dim`/`neighbor_dim`, else `ValueError` | + +## 5. Constraints on the *format* of names + +| # | Constraint | Source | +| ------ | --------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | +| **F1** | `_Staggered` is a **reserved prefix**: any `Dimension` whose `value` starts with it is treated as staggered | `common.py:1444-1464` (`_STAGGERED_PREFIX = "_Staggered"`) | +| **F2** | GTFN aliases every staggered tag to its base tag by string surgery | `itir_to_gtfn_ir.py:703`, `_add_staggered_aliases:204-215` | +| **F3** | `_CONST_DIM` is a reserved LOCAL dimension name, deliberately *absent* from the provider and special-cased at every lookup | `embedded.py:220, 572, 1513, 1768`; `gtir_to_sdfg_lambda.py:62, 1314, 1355` | +| **F4** | **Dimension names determine memory layout** — `order_dimensions` sorts by `(kind, as_non_staggered(dim).value)` | `common.py:1334-1344` | +| **F5** | GTFN: every dim name and provider key becomes a C++ type `generated::_t`, so it must be a valid C++ identifier and collision-free | `gtfn_module.py:97, 130-136` | +| **F6** | GTFN connectivity params: `gt_conn_`, so keys must not collide **case-insensitively** | `gtfn_module.py:32, 120, 133` | +| **F7** | DaCe connectivity arrays: `gt_conn_`, recovered by regex `^gt_conn_(\S+)$` | `sdfg_args.py:24-25, 56-70` | +| **F8** | DaCe map variables: `i__gtx_[dim]`; map fusion/splitting transformations **rely on these strings matching** | `gtir_to_sdfg_utils.py:44-54` | +| **F9** | DaCe field symbols: `____size/stride`, `_range_symbol_name(field, dim.value)` | `sdfg_args.py:73-82, 119-122` | + +## 6. Structural (kind / arity) constraints + +| # | Constraint | Enforced | Source | +| --- | ------------------------------------------------------------------------- | ------------------------------------ | ----------------------------------------------------------------------------- | +| S1 | `len(target) == 2` implies `target[1].kind == LOCAL` | eager `ValueError` | `fbuiltins.py:480-482`; also `type_deduction.py:459-462` | +| S2 | A neighbor table's domain is exactly `(HORIZONTAL, LOCAL)` | `is_neighbor_table` guard | `common.py:1160-1168` | +| S3 | At most one LOCAL dim per field | `ValueError` / `NotImplementedError` | `common.py:1334-1337`; `nd_array_field.py:976-979`; `gtir_to_sdfg.py:586-589` | +| S4 | Cartesian offset iff `len(target)==1 and source==target[0]` and not LOCAL | predicate | `fbuiltins.py:524-529` | +| S5 | Non-Cartesian offset or LOCAL dim implies grid type `UNSTRUCTURED` | `ValueError` | `transform_utils.py:60-77` | +| S6 | `as_offset` is Cartesian-only | `DSLError` | `type_deduction.py:955-965` | +| S7 | Program domains must not contain LOCAL dims | `ValueError` | `past_to_itir.py:409-410` | + +## 7. Observed behaviour + +Two properties above were confirmed by running them, not only by reading. + +### 7.1 A1 vs A2 — embedded and compiled key on different strings + +```python +MyOff = gtx.FieldOffset("TAGNAME", source=E, target=(V, Neigh)) # tag != variable name + + +@gtx.field_operator +def foo(a: Field[Dims[E], float]) -> Field[Dims[V], float]: + return a(MyOff[1]) +``` + +```text +embedded: offset_provider={"TAGNAME": conn} -> OK ; {"MyOff": conn} -> KeyError 'TAGNAME' +roundtrip: offset_provider={"MyOff": conn} -> OK ; {"TAGNAME": conn} -> KeyError 'MyOff' +``` + +The compiled path uses the Python variable name because `foast_to_gtir.py:302-306` +and `:325-331` emit `im.shift(offset_name.id, ...)` / `im.as_fieldop_neighbors(str(offset_name), ...)` +from the FOAST `Name.id` — never from `FieldOffset.value`. Lowering the operator +above yields: + +```text +foo = λ(a) → (⇑(λ(__it) → ·⟪MyOffₒ, 1ₒ⟫(__it)))(a); +``` + +### 7.2 A3 — reductions still require tag == local dim name + +Reusing the deliberately mismatched declaration from the #1789 regression test +(`Off` tagged `"Off"`, local dim named `"Neigh"`): + +```python +Off = gtx.FieldOffset("Off", source=E, target=(V, Neigh)) + + +@gtx.field_operator +def bar(a: Field[Dims[E], float]) -> Field[Dims[V], float]: + return neighbor_sum(a(Off), axis=Neigh) +``` + +```text +embedded: FAILED: KeyError: "Offset 'Neigh' not found in offset provider." +roundtrip: OK -> [30. 50. 40.] +``` + +`unroll_reduce.py:43-50` has the same assumption for the compiled pipeline +(established by reading; the roundtrip backend above does not exercise that pass). + +## 8. Practical consequence + +To be safe across **all** contexts, four strings must be identical: + +```text +FieldOffset.value == == offset_provider key == target[-1].value +``` + +plus `target[0] == conn.domain[0]` and `source == conn.codomain` as `Dimension` +objects (A6-A8). This is exactly what `tests/next_tests/toy_connectivity.py:18-26` +encodes: + +```python +V2EDim = gtx.Dimension("V2E", kind=gtx.DimensionKind.LOCAL) # value is "V2E", not "V2EDim" +V2E = gtx.FieldOffset("V2E", source=Edge, target=(Vertex, V2EDim)) +``` + +Relaxing any one of the four is currently supported only in the narrow slice +PR #1789 covered: shift-only, GTFN, no sparse arguments. Nothing validates the +full set up front — a violation surfaces as a `KeyError` from `common.py:1208`, +a bare `assert`, or, per the #1789 test docstring, silently wrong results. + +Two existing `TODO`s point at this tangle: + +- `common.py:976-977` — `NeighborConnectivityType`: *"refactor towards encoding + this information in the local dimensions of the `ConnectivityType.domain`"*. +- `fbuiltins.py:467-470` — *"`FieldOffset` and `runtime.Offset` are not an exact + conceptual match. Revisit if we want to continue subclassing here."* From 8f992214cc17ae4ebb36c60070da2f9959d65f85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrique=20Gonz=C3=A1lez=20Paredes?= Date: Thu, 17 Sep 2026 14:38:39 +0200 Subject: [PATCH 2/3] connectivities-as-types: address review pass 1 (factual and framing fixes) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit No design changes. Fixes from an adversarial review of the proposal: - Keep the Cartesian FieldOffset form: as_offset() requires it (5 test modules, Ioff/Koff/EdgeOffset fixtures, ICON4Py diffusion/dycore). - Attribute the (tag, kind) fingerprint deconstructor to #2844/ADR 0028, not ADR 0023; ADR 0023 is a consequence, not a reversed decision. - Correct the __main__/spawn claim: file scripts resolve via __mp_main__; the limitation is interactive __main__ (REPL, notebooks, python -c). - "" check described as a heuristic; pickle's save_global is the real check. Note Staggered[D] tags need a grammar for resolve(). - "Cartesian already solved this" narrowed: no *separate* tag and no provider lookup; dimension names still cross gtfn/DaCe as strings. - Hedge the reduction claim in the main note to match what was run (embedded fails, roundtrip passes, gtfn by reading). - Add the roundtrip backend as a source-emitting consumer of dimension names; add the compat name-table caveat for string-keyed providers. - Present both sides of the conflict with shared/dimensions-as-types (46 "I" declarations, downstream == reliance, first-declaration-wins; and the typing subscription-cache aliasing that type identity removes). - Link mesh-and-first-class-halos and dimension-generic-fields; fix the dependent-local-dimensions citation (shared core is §6, U0/U1 are §9) and list the U0/U1 divergences. - Account for "seven of ten": A1-A5 dissolve, A6-A8 become one bind-time check. New open question on what an instance of V2E is. - Line-reference nits (common.py:62/1176/1177, fbuiltins.py:509, gtfn_module.py:118/132), _CONST_DIM has 12 use sites, concept-count row clarified, add type-checking tag (index keywords synced). Deferred to a second pass (design decisions): V2E.Local is not usable in annotations and Local[V2E] reconciliation is runtime-only (B1); the string-key shim needs a registry (B2); owner-less local dims such as ICON4Py's LsqUnkDim (B4); static-only max_neighbors vs generic meshes (S8). --- content/index.md | 2 +- .../connectivities-as-types.md | 180 +++++++++++++----- .../connectivities-as-types_research.md | 14 +- 3 files changed, 137 insertions(+), 59 deletions(-) diff --git a/content/index.md b/content/index.md index 61ddbc5..5e7a122 100644 --- a/content/index.md +++ b/content/index.md @@ -44,7 +44,7 @@ Work-in-progress proposals, organized by contributor. - [[personal/egparedes/layered-architecture|A layered architecture for gt4py with enforced public/internal decoupling]] — keywords: architecture, layering, tach, modularity, public-api, semi-public, internal, packaging, dependencies, refactoring, infrastructure, over-engineering, otf, workflow, dsl, config, allocators, caching, fingerprint, compiled-program, process-pool, factory-boy, instrumentation, adr, jax, decoupling, migration, tech-debt - [[personal/egparedes/otf-toolchain-split|Dissolving gt4py.next.otf into a toolchain core and DSL-agnostic build infrastructure]] — keywords: otf, toolchain, workflow, pipeline, refactoring, architecture, layering, modularity, backend, bindings, build-system, caching, runners, compiled-program, factory-boy, naming, observability, stage-inspection, adr, tach, tech-debt -- [[personal/egparedes/connectivities-as-types/connectivities-as-types|Connectivities as types: one declaration for offset, local dimension and provider key]] — keywords: type-system, dimensions, local-dimensions, connectivities, offset-provider, unstructured, neighbor-sum, reduction, frontend, foast, gtir, embedded, gtfn, dace, nominal-types, metaclass, serialization, fingerprint, staggering, migration, adr, tech-debt +- [[personal/egparedes/connectivities-as-types/connectivities-as-types|Connectivities as types: one declaration for offset, local dimension and provider key]] — keywords: type-system, type-checking, dimensions, local-dimensions, connectivities, offset-provider, unstructured, neighbor-sum, reduction, frontend, foast, gtir, embedded, gtfn, dace, nominal-types, metaclass, serialization, fingerprint, staggering, migration, adr, tech-debt - [[personal/egparedes/discretization-independent-fd-syntax|A discretization-independent surface syntax for finite-difference computations]] — keywords: finite-difference, finite-volume, mesh-invariant, structured, unstructured, exterior-calculus, dec, mimetic, de-rham, location-typing, stencil, weight-generation, rbf-fd, gfdm, moment-matching, conservation, field-operators, icon, pace, pmap, fv3, ifs-fvm, arakawa-staggering, c-grid, d-grid, a-grid, dynamical-core, hevi, vertical-solve, elliptic-solve, mpdata, semi-implicit, miura, atlas, prior-art, dsl-design