Skip to content

Introduce AbstractLocation hierarchy and replace Nothing with Reduced#5614

Open
glwagner wants to merge 15 commits into
mainfrom
glw/abstract-location-reduced
Open

Introduce AbstractLocation hierarchy and replace Nothing with Reduced#5614
glwagner wants to merge 15 commits into
mainfrom
glw/abstract-location-reduced

Conversation

@glwagner

Copy link
Copy Markdown
Member

Summary

Introduces abstract type AbstractLocation with Center, Face, and a new singleton Reduced, and constrains the type parameters of AbstractField{LX, LY, LZ, ...} so that location parameters must subtype AbstractLocation. Reduced and Flat dimensions, which previously used Nothing as a location, now use Reduced.

Fixes the AbstractOperations type piracy flagged in #5601:

  • Every operator signature in AbstractOperations (+, -, *, /, ^, <, >, <=, >=, atan, atand, mod, plus the unary forms) now dispatches on Oceananigans-owned types (LocationTuple, LocationTypeTuple) instead of bare Base.Tuples.
  • After this change *((Nothing, Nothing, Nothing), nothing, nothing) correctly throws MethodError even after using Oceananigans — see the regression added to test_quality_assurance.jl.
  • AbstractOperations is removed from the pirate_modules = (...) skip list in the Aqua test, and Aqua.test_piracies(Oceananigans.AbstractOperations) now passes.

Naming

We picked Reduced because it cohabits cleanly with existing vocabulary in the codebase: ReducedField, XReducedField, reduced_dimensions(::AbstractField), etc. Alternatives we considered:

  • Reduced (chosen) — matches existing aliases, no rename cascade, semantically clear in the reduction context. Honest critique: names the state of the dimension (past-tense) rather than a position on the cell the way Center and Face do. It also has to cover both "a dim that got averaged/integrated out" and "a Flat-topology dim that was never there." Semantically these are one concept (no spatial coordinate in this slot), but the name leans toward the reduction story.
  • Collapsed — similarly state-describing, and less tightly tied to existing terms.
  • Singleton — describes the size-1 array dimension neutrally on cause. Less tied to existing API.
  • Nil — short and "nothing-like", but cryptic.

Happy to revisit if any of those land better with reviewers.

Breaking

This breaks any downstream code that writes Field{Nothing, ...} (or AbstractField{Nothing, ...}, or matches (Nothing, Center, Center) location tuples in dispatch). All such sites in src/, test/, and ext/ are updated. The Julia pre-1.0 convention puts breaking changes in the minor version, so this bumps 0.108.0 → 0.109.0.

What changed

  • src/Grids/Grids.jl — new AbstractLocation, Reduced, LocationTuple, LocationTypeTuple; Center and Face retroactively made subtypes; types exported from Oceananigans.
  • src/Fields/abstract_field.jlAbstractField{LX, LY, LZ, G, T, N} now requires LX/LY/LZ <: AbstractLocation.
  • src/Fields/field.jl, src/Fields/abstract_field.jl — the XReducedField, XReducedAF, ReducedField and related aliases now use Reduced instead of Nothing.
  • src/Fields/show_fields.jllocation_str(::Type{Reduced}) = "⋅".
  • src/AbstractOperations/{unary,binary,multiary}_operations.jl — operator codegen retyped; choose_location(::Reduced, ...) etc. AbstractOperations.Location = AbstractLocation.
  • src/Grids/{grid_utils,nodes_and_spacings,vertical_discretization,new_data,input_validation}.jl — all ::Nothing dispatches that take a location switched to ::Reduced.
  • src/Fields/{scans,interpolate}.jl — same; reduced_location now returns Reduced/Reduced(); filter_nothing_dims renamed to filter_reduced_dims with predicate loc[d] isa Reduced.
  • src/BoundaryConditions/{field_boundary_conditions,fill_halo_kernels}.jldefault_prognostic_bc(...) etc. retyped; halo bookkeeping uses Reduced.
  • src/Models/{boundary_mean,boundary_condition_operation,NonhydrostaticModels/nonhydrostatic_model,HydrostaticFreeSurfaceModels/SplitExplicitFreeSurfaces/split_explicit_free_surface}.jl, src/ImmersedBoundaries/{grid_fitted_bottom,partial_cell_bottom,active_cells_map,immersed_reductions,mask_immersed_field}.jl, src/MultiRegion/cubed_sphere_grid.jl, src/StokesDrifts.jl, src/DistributedComputations/distributed_immersed_boundaries.jl, src/OutputReaders/field_time_series_indexing.jl, src/TurbulenceClosures/.../catke_vertical_diffusivity.jlField{..., Nothing} / (..., Nothing) literals replaced with Reduced (the Nothing-as-grid slot is preserved everywhere).
  • ext/OceananigansReactantExt/Grids/Grids.jlreactant_offset_indices(::Reduced, ...).
  • ext/OceananigansNCDatasetsExt/utils.jl — docstring example updated.
  • test/ — all Field{Nothing, ...} / FieldTimeSeries{Nothing, ...} / (Nothing, ..., ...) test fixtures updated to Reduced.
  • test/test_quality_assurance.jlAbstractOperations removed from the broken-piracy skip list; new regression @test_throws MethodError *((Nothing, Nothing, Nothing), nothing, nothing).
  • Project.tomlversion = "0.109.0".

Test plan

  • CI passes on CPU
  • CI passes on GPU
  • Aqua piracy test passes on Oceananigans.AbstractOperations
  • Downstream packages (ClimaOcean, NumericalEarth, Breeze) build against this branch — at minimum, identify the Field{Nothing, ...} literals that need updating downstream
  • Smoke test locally: using Oceananigans; Average/Integral; Field{Center, Center, Reduced}; arithmetic on reduced fields — all green

🤖 Generated with Claude Code

…ced`

Adds `abstract type AbstractLocation` with `Center`, `Face`, and a new
`Reduced` singleton, and constrains `AbstractField{LX,LY,LZ,...}` so
location parameters must subtype `AbstractLocation`. Reduced and Flat
dimensions, which previously used `Nothing` as a location, now use
`Reduced`.

This eliminates the type piracy flagged in #5601: every `AbstractOperations`
operator signature now dispatches on Oceananigans-owned types
(`LocationTuple`, `LocationTypeTuple`) instead of bare `Base.Tuple`s.
After this change `*((Nothing, Nothing, Nothing), nothing, nothing)`
correctly throws `MethodError` even after `using Oceananigans` — see the
regression test added to `test_quality_assurance.jl`.

Naming alternatives we considered for the new type: `Reduced` (chosen,
matches existing `ReducedField`/`reduced_dimensions` vocabulary),
`Collapsed`, `Singleton`, and `Nil`. `Reduced` won on cohesion with the
codebase even though it names the *state of the dimension* rather than
a position on the cell.

This is a breaking change for downstream code that writes
`Field{Nothing, ...}` type literals, so the version is bumped to 0.109.0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@navidcy navidcy added cleanup 🧹 Paying off technical debt abstractions 🎨 Whatever that means labels May 21, 2026
@giordano giordano added the breaking change 💔 Concerning a change which breaks the API label May 21, 2026
@giordano giordano force-pushed the glw/abstract-location-reduced branch from b60cc80 to 1ca7f8f Compare May 21, 2026 11:19
Comment thread src/Grids/Grids.jl Outdated
const C = Center

const LocationTuple = Tuple{<:AbstractLocation, <:AbstractLocation, <:AbstractLocation}
const LocationTypeTuple = Tuple{Type{<:AbstractLocation}, Type{<:AbstractLocation}, Type{<:AbstractLocation}}

@giordano giordano May 21, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see LLMs don't understand Julia's type system. This tuple doesn't make sense.

julia> typeof((Reduced, Reduced, Reduced))
Tuple{DataType, DataType, DataType}

there's no way that's matched by LocationTypeTuple

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ahah no that is me not understanding it

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok no I asked for LocationTuple but not the "type tuple". ummm

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ahah no that is me not understanding it

I tried hard to make ChatGPT reason about it, and it always gave me falsities like the following will be true

julia> (Reduced,) isa Tuple{Type{Reduced}}
false

do we need <:Type{<:AbstractLocation} is that right?

No, any of this can't possibly work, because the type in the type parameter will always have type DataType, so you can't really dispatch on something related to AbstractLocation.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LLMs don't understand anything at all btw -- not just Julia's type system. "Understanding" is not the point of an LLM


# instantiate location if types are passed
$op(Lc::Tuple, a, b) = $op((Lc[1](), Lc[2](), Lc[3]()), a, b)
$op(Lc::$LocationTypeTuple, a, b) = $op((Lc[1](), Lc[2](), Lc[3]()), a, b)

@giordano giordano May 21, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replacing Nothing with Reduced is definitely a move in the right direction, but this tuple-based method (together with all the similar ones below) is still problematic: as mentioned above, using a tuple here is flawed (and LocationTypeTuple in particular is useless), because tuples are special

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can instantiate the locations upon input, before they arrive in this function

Comment thread src/Grids/Grids.jl Outdated
const C = Center

const LocationTuple = Tuple{<:AbstractLocation, <:AbstractLocation, <:AbstractLocation}
const LocationTypeTuple = Tuple # {Type{<:AbstractLocation}, Type{<:AbstractLocation}, Type{<:AbstractLocation}}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LocationTypeTuple = Tuple is not right because this is still type piracy (the test checking for type piracies in AbstractOperators will fail), but I want to see what else breaks besides the type piracy

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can I remove LocationTypeTuple or are you still testing?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The non-distributed tests on Buildkite haven't started yet, I was hoping to see what still fails.

Of the tests in GitHub Actions, most of them are now passing, but I'm confused by https://github.com/CliMA/Oceananigans.jl/actions/runs/26230990735/job/77191416849?pr=5614#step:6:570

Distributed output combining - RectilinearGrid (2x2): Error During Test at /home/runner/work/Oceananigans.jl/Oceananigans.jl/test/test_distributed_output_combining.jl:331
  Got exception outside of a @test
  BoundsError: attempt to access 10×10×1 Array{Float32, 3} at index [4:7, 4:7, 4:7]
  Stacktrace:
    [1] throw_boundserror(A::Array{Float32, 3}, I::Tuple{UnitRange{Int64}, UnitRange{Int64}, UnitRange{Int64}})
      @ Base ./essentials.jl:15
    [2] checkbounds
      @ ./abstractarray.jl:699 [inlined]
    [3] view(::Array{Float32, 3}, ::UnitRange{Int64}, ::UnitRange{Int64}, ::UnitRange{Int64})
      @ Base ./subarray.jl:214
    [4] load_combined_field_data!(field::Field{Center, Center, Reduced, Nothing, RectilinearGrid{Float64, Periodic, Periodic, Bounded, Oceananigans.Grids.StaticVerticalDiscretization{OffsetVector{Float64, StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}}, OffsetVector{Float64, StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}}, Float64, Float64}, Float64, Float64, OffsetVector{Float64, StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}}, OffsetVector{Float64, StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}}, CPU}, Tuple{Colon, Colon, Colon}, OffsetArray{Float64, 3, SubArray{Float64, 3, Array{Float64, 4}, Tuple{Base.Slice{Base.OneTo{Int64}}, Base.Slice{Base.OneTo{Int64}}, Base.Slice{Base.OneTo{Int64}}, Int64}, true}}, Float64, Nothing, Oceananigans.Fields.FixedTime{Float64}, Nothing}, all_ranks::Vector{Oceananigans.OutputReaders.RankOutputData{RectilinearGrid{Float64, FullyConnected, FullyConnected, Bounded, Oceananigans.Grids.StaticVerticalDiscretization{OffsetVector{Float64, StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}}, OffsetVector{Float64, StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}}, Float64, Float64}, Float64, Float64, OffsetVector{Float64, StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}}, OffsetVector{Float64, StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}}, Distributed{CPU, false, Partition{Int64, Int64, Nothing}, Tuple{Int64, Int64, Int64}, Int64, Tuple{Int64, Int64, Int64}, Oceananigans.DistributedComputations.NeighboringRanks{Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64}, MPI.Comm, Vector{MPI.Request}, Base.RefValue{Int64}, Nothing}}, Tuple{Int64, Int64, Int64}}}, name::String, iter::Int64; reader_kw::@NamedTuple{})
      @ Oceananigans.OutputReaders ~/work/Oceananigans.jl/Oceananigans.jl/src/OutputReaders/combining_field_time_series.jl:452
  [...]

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let me know when you're ready for more commits here

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feel free to push now, thanks for waiting

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I can fix this as well. Since we require the types (not Type{location}) we have to instantiate at all entry points. Doing so in broadcasting fixes this error

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

possibly fixed by this: 9ea6d96

@glwagner

Copy link
Copy Markdown
Member Author

@navidcy @simone-silvestri this PR is going to cause a nuclear explosion throughout the ecosystem.

We need to be absolutely sure that "Reduced" is the name we want here.

There is some illogic in it, because it is not actually a location at all. Instead it refers to (supposed) the operation that produced the data.

"Nothing" was actually a good name, because it is more explicitly true. Another one could be "All" ... I think it's worth discussing before merging this PR hastily

@simone-silvestri

Copy link
Copy Markdown
Collaborator

What about NoLocation or Absent to return to the Nothing idea?

@glwagner

glwagner commented May 21, 2026

Copy link
Copy Markdown
Member Author

What about NoLocation or Absent to return to the Nothing idea?

Those do seem better.

One crucial property of this location is that it implies that the "nothing" location is extruded for operations. In other words, if we form Uc = U * c where U is averaged in the vertical, the result Uc is a 3D field in which each c[i, j, k] is multiplied by the column-average value of U[i, j, 1].

This concept could be captured by All (this also corresponds to the "a" that we use in notation). But it's also not so intuitive to me.

Claude also suggested Nil which is more compact (this is nice bc we have to write Field{Center, Center, NoLocation})

glwagner and others added 4 commits May 21, 2026 08:41
`LocationTypeTuple = Tuple{Type{<:AbstractLocation}, ...}` never actually
dispatched: `typeof((Center, Reduced, Reduced)) === Tuple{DataType, DataType, DataType}`
which is not a subtype of the parametric form. The `$op(Lc::LocationTypeTuple, ...)`
fallbacks in `unary_operations.jl`, `binary_operations.jl`, and
`multiary_operations.jl` were therefore dead code — and with the temporary
`LocationTypeTuple = Tuple` they were still pirate.

Per @glwagner: instantiate the locations upon input, before they arrive in
the operator function. Concretely:

- Delete the `$op(Lc::LocationTypeTuple, ...)` fallback methods in
  unary/binary/multiary operations and the parallel
  `grid_metric_operation(Loc::Tuple, ...)` in `grid_metrics.jl`.
- Switch the internal callers in `boundary_transport.jl` and
  `boundary_mean.jl` to pass instantiated tuples
  (`(Face(), Center(), Center())` etc.).
- Update the `grid_metrics.jl` doctest and `test_abstract_operations.jl`
  test cases to use instance-form tuples.
- Remove the `LocationTypeTuple` const and export entirely from
  `src/Grids/Grids.jl` and the corresponding import in
  `src/AbstractOperations/AbstractOperations.jl`.

`*((Center(), Center(), Center()), Δx, c)` still works.
`*((Center, Center, Center), Δx, c)` now throws `MethodError` — type-form
sugar for the operator API is gone (breaking change, documented).
`Aqua.test_piracies(Oceananigans.AbstractOperations)` passes.

Also fix the distributed-output combining failure reported in PR #5614:
`flatten_nothing_dimension(::Nothing, range) = 1:1` in
`combining_field_time_series.jl` only matched `nothing`, so reduced fields
(now carrying `Reduced()` instances) fell through to the generic method
that returned the full range, triggering a `BoundsError` when loading data
into a 1-element z slice. Renamed to `flatten_reduced_dimension` and
retyped on `::Reduced`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`Base.copyto!(::Field, ::Broadcasted)` was passing `location(dest)` — a
tuple of singleton location *types* like `(Center, Center, Center)` — into
`broadcasted_to_abstract_operation`, which then forwarded it as the first
argument to `bc.f`. For broadcasts like `pNHS ./= Δt`, that produced
`/((Center, Center, Center), pNHS, Δt)`, which relied on the type-tuple
operator fallback we just removed.

Switch to `instantiated_location(dest)` so the operator only ever sees
instance-form tuples (`LocationTuple`), matching the convention used
throughout the operator codegen.

This fixes the MPI `test_distributed_output_combining.jl` failure surfaced
in PR #5614:

  MethodError: no method matching /(::Tuple{DataType, DataType, DataType},
                                     ::Field{Center, Center, Center, ...},
                                     ::Float64)

Verified locally with `JULIA_PROJECT=test julia --project=test
test/test_distributed_output_combining.jl`:
  - RectilinearGrid (2x2): 18/18
  - RectilinearGrid slab (1x4): 6/6
  - LatitudeLongitudeGrid (1x4): pass
  - TripolarGrid (1x4): 6/6

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`pNHS ./= Δt⁺` went through the Field broadcast pipeline, which builds an
AbstractOperation, infers a location tuple, and launches a kernel — heavy
machinery for a scalar in-place divide where every cell (interior + halo)
gets the same treatment. Switch to `parent(pNHS) ./= Δt⁺` so the broadcast
goes straight to the OffsetArray storage, matching the idiom used in
`distributed_fields.jl`, `conjugate_gradient_solver.jl`, and elsewhere.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@glwagner

Copy link
Copy Markdown
Member Author

More thoughts:

  • All is nice for @at -- for example, new_op = @at (Center, Center, All) old_op. This will move/interpolate horizontal locations to Center, but keep the vertical location untouched (will get default).

  • Uniform is another idea --- for example Field{Center, Center, Uniform}(grid) implies the Field is uniform in the vertical. Constant is similar to Uniform.

  • Singleton might work. This is connected to broadcasting semantics ("singleton" dimensions are extruded in broadcasting). Field{Center, Center, Singleton}(grid) is interpretable I think.

  • Absent, trying to parse if Field{Center, Center, Absent}(grid) is intuitive

@glwagner

glwagner commented May 21, 2026

Copy link
Copy Markdown
Member Author

Math / set-theory family

  • Singular — "singular dimension" is established applied-math vocabulary for length-1. Reads cleanly: Field{Center, Center, Singular}.
    Pro: physics/math native. Con: also means "weird/anomalous" colloquially, mild ambiguity.
  • Unit — math-native "unit set" / "unit length". Short (4 chars). Field{Center, Center, Unit}. Con: collides with Unitful.jl-style
    unit-of-measure intuition.
  • Atom / Atomic — set-theory "atom" = singleton element. Short and evocative. Field{Center, Center, Atomic}. Vibe: physics-ish,
    indivisible.

Geometric family

  • Point — "this dim collapses to a point." Short, evocative. Field{Center, Center, Point} reads as "x and y vary; z is a point." Slight
    collision with point-coordinate intuition though.

English "just one" family

  • Single — direct. Field{Center, Center, Single}. Reads naturally; faint MtG / Java-singleton echoes.
  • Sole — "sole z value." Slightly archaic.
  • Solo — Field{Center, Center, Solo}. Charming but too informal.
  • Solitary — accurate, long, melodramatic.
  • Lone — poetic.
  • Standalone — too programmer-y.

Greek/Latin roots

  • Mono — Greek "one." Compact (4 chars). Field{Center, Center, Mono}. Technical-sounding without being heavy; slight
    foreign-prefix-without-a-suffix awkwardness ("mono-what?").
  • Uni — Latin "one." Field{Center, Center, Uni}. Even shorter. Same incomplete-prefix feel.

Programming / API ecosystem

  • Singleton (the original)
  • Squeezed — past tense of NumPy/Torch squeeze. State-describing like Reduced.

@simone-silvestri

Copy link
Copy Markdown
Collaborator

Singleton is nice but maybe a bit verbose? All is also nice and short and connects well with the idea of Reduced

@ewquon

ewquon commented May 21, 2026

Copy link
Copy Markdown
Collaborator

Maybe I'm missing the point, but what about NA (not applicable)?

@glwagner

glwagner commented May 21, 2026

Copy link
Copy Markdown
Member Author

Maybe I'm missing the point, but what about NA (not applicable)?

acronym ❌

more seriously, I don't think it is interpretable, Field{Center, Center, NA} -- what's "NA"?

But None could work. That is not taken, is it?

@ewquon

ewquon commented May 21, 2026

Copy link
Copy Markdown
Collaborator

Interesting that both All and None are on the table 🤯

None would be a very small shift away from "Nothing" and has compactness going for it -- I'm unopposed.

@glwagner

Copy link
Copy Markdown
Member Author

Interesting that both All and None are on the table 🤯

None would be a very small shift away from "Nothing" and has compactness going for it -- I'm unopposed.

Indeed, this is a philosophical question. Does the field have "all" locations (this is how it is treated in broadcasting) or does it have "no location" (which implies that it must be broadcasted with all locations of a 3D field)

@tomchor

tomchor commented May 26, 2026

Copy link
Copy Markdown
Member

I'm kinda late to the discussion so forgive me if I'm missing something, but does it make sense to lump together the location for a Field in a Flat dimension with the location of a Field in a non-Flat dimension that was reduced?

To me it makes sense that a Field has location==nothing in a dimension that is Flat since there was never any reasonable location to begin with. But that is different (at least conceptually to me) from a Field dimension that had a well-defined location that then was reduced. But maybe that doesn't matter numerically? Anyway I just wanted to ask.

@glwagner

glwagner commented May 26, 2026

Copy link
Copy Markdown
Member Author

@tomchor I'm not entirely sure how to achieve your suggestion. For example what should Field{Center, Center, Center}(grid) do if grid is Flat in the 3rd direction? Convert the 3rd location under the hood, or perhaps error?

We have strived to always return the type asked for by the user (eg if you read a type from a constructor, it unambiguously tells you the type of the object that is constructed). I don't think we can preserve this convention without taking the erroring approach. But on the other hand, I worry that throwing such an error is inconvenient. It might require a lot of special casing, especially in downstream packages.

This is actually the benefit of distinguishing between the grid topology and the field location, I think. It allows us to write more general code more easily.

@tomchor

tomchor commented May 27, 2026

Copy link
Copy Markdown
Member

This is actually the benefit of distinguishing between the grid topology and the field location, I think. It allows us to write more general code more easily.

Hmm, I see your point. Thanks for clarifying!

@glwagner

Copy link
Copy Markdown
Member Author

This is actually the benefit of distinguishing between the grid topology and the field location, I think. It allows us to write more general code more easily.

Hmm, I see your point. Thanks for clarifying!

In part this lesson was learned when we switched the set! / function interface so that the coordinate match the dimensionality of the field. Ie you need to write set!(vertically_reduced, (x, y) -> x + y). This results in scripts that I think are easier to read. BUT it also makes it more difficult to prototype code on a 2D grid, and then switch the grid to 3D. I am actually still not sure the readability benefit is worth the productivity cost 🥲

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

abstractions 🎨 Whatever that means breaking change 💔 Concerning a change which breaks the API cleanup 🧹 Paying off technical debt

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants