Skip to content

Filtering when reading a Grid file, avoid time leaking into the Grid - #1667

Open
dylannelson wants to merge 9 commits into
mainfrom
dylannelson/time-filtering
Open

Filtering when reading a Grid file, avoid time leaking into the Grid#1667
dylannelson wants to merge 9 commits into
mainfrom
dylannelson/time-filtering

Conversation

@dylannelson

@dylannelson dylannelson commented Aug 11, 2026

Copy link
Copy Markdown
Member

Closes #1444 (

Overview

  1. time was shown to exist in a grid from a user reading in a grid and data from a single file
  2. Later when the user tried to use .subset there was a crash related to time being different on the grid vs the data
  3. time shouldn't realistically be on the grid to start with, so this was a concern, and was causing downstream issues not addressed when reading in the data
  4. There was 2 main ideas of where to place filtering to avoid this, Grid.__init__ or _read_ugrid, both were tested extensively
    • Grid.__init__ is run in multiple scenarios, including when a file is read from disk, but
      • in other scenarios like _slice_from_grid it can cause issues.
      • This solution also wasn't properly removing the time because it wasn't a dimension
    • the path to _read_ugrid occurs primarily when a file is read from disk
      • _read_ugrid(ds) only solved the the issue for that file type
    • We went back to Grid.__init__ as it solved the issue in all data types, but required a different approach
  5. This has been tested in multiple ways and is shown to:
    • Scalar time gone from grid coords and grid variables
    • node_lon, node_lat, face_node_connectivity preserved
    • Grid non-empty (n_node, n_face > 0)
    • bounding_box runs, no error
    • Data time preserved
    • Subset returned faces
    • Sliced grid keeps subgrid_face_indices
    • sel(time=...) still works
    • Dimensional time axis + coord stripped
    • Leaked data variable stripped

Changes Made:

  1. Added test for _drop_non_grid_coords
  2. Added function _drop_non_grid_coords for cleaning stray coordinates
  3. Called _drop_non_grid_coords in Grid.__init__

Expected Usage

This cleaning will occur during any and all grid creations

PR Checklist

General

  • An issue is created and linked
  • Added appropriate labels (if your uxarray repo permissions allow it)
  • Filled out Overview and Expected Usage (if applicable) sections

Testing & Benchmarking

  • Adequate tests are created if there is new functionality
  • Tests are not too basic (such as simply calling a function and nothing else)
  • Tests cover all major paths in your new functions
  • If this PR could affect performance, ran ASV benchmarks and confirmed they show expected behavior (add a new benchmark if necessary)

Documentation

  • [N/A] Docstrings have been added to all new functions
  • [N/A] Docstrings have been updated with any function changes
  • [N/A] User (public) functions have been added to docs/api.rst
  • Internal (private) function names start with an underscore (_)

AI Disclosure

AI Usage:

  • I take responsibility for all AI-generated content in my PR.
  • I have tested all AI-generated content in my PR.

Time was getting into grid data, causing issues when subsetting down the line. This should clean the data in a way that shouldn't happen in the future
pre-commit run --files uxarray/io/_ugrid.py
Seemed to turn up a few issues with spaces and line lengths

@erogluorhan erogluorhan left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Maybe too early for a review, but I'd like to share some thoughts:

  1. time was shown to exist in a grid from a user reading in a grid and data from a single file

    A little more clarity could be helpful here. IIRC, time is not shown directly in the Grid object but in the Grid._ds.

  2. That said, I'd like to know what actual purpose we had with that object attribute, i.e. _ds. If it was to keep complete track of the original file content through xarray.Dataset, I'd say this dropping at the level of _ds might break that.

  3. Maybe we'd want to fix it at the Grid itself rather than altering _ds, but would it be possible?

  4. Finally, ugrid (i.e. _ugrid.py) is only one of the several formats we support. What about other formats in our I/O, e.g. _mpas.py etc. Their grid files can come in a similar way. Maybe we will need to do this fix at the Grid level rather than particular I/O modules?

@dylannelson

Copy link
Copy Markdown
Member Author

#1444 — Solution history

This is a bit about each version of the idea, why it's located where it is, and what the final code looks like.


Version 1 — .values guard in _slice_from_grid

# uxarray/core/dataarray.py — _slice_from_grid()
n_face=sliced_grid._ds["subgrid_face_indices"].values   # was: [...["subgrid_face_indices"]]
  • Location: within _slice_from_grid in core/dataarray.py.
  • Idea: Pass the face indexer as .values (a plain array) so xarray does positional indexing with no coordinate alignment. It fixes the crash for every subset method, but only guards the symptom — the stray time stays on the grid, so it never addresses why the grid carries a time.

Version 2 — Name-based clean build in _read_ugrid

# uxarray/io/_ugrid.py — end of _read_ugrid()
ds = _keep_only_grid_vars(ds)   # keep only recognized grid vars/coords, drop the rest
  • Location: at the end of _read_ugrid in io/_ugrid.py.
  • Idea: Filter the parsed dataset down to recognized grid variables, dropping extras where the grid is built. This removes the stray time at its source (the leaky reader), so nothing downstream can collide. It's essentially the final answer — a simpler-looking alternative was proposed next, which prompted the comparison below.

Version 3 — drop_dims in Grid.__init__ (team's idea)

# uxarray/grid/grid.py — Grid.__init__
extra_dims = set(grid_ds.dims) - set(DIM_NAMES)
grid_ds = grid_ds.drop_dims(extra_dims)
  • Location: at the top of Grid.__init__ in grid/grid.py.
  • Idea: Drop any dimension not in DIM_NAMES, centrally for every reader, to remove stray non-grid data by axis. It captured a dimensional time, but the leaked time here is a scalar (0-d) coord with no dimension — so drop_dims finds nothing to remove and the bug survives.

Version 4 — "Best of both" (drop_dims + 0-d/name drop)

grid_ds = grid_ds.drop_dims(set(grid_ds.dims) - set(DIM_NAMES))              # dimensional extras
drop = [n for n, v in grid_ds.variables.items() if n not in KEEP and v.ndim == 0]
grid_ds = grid_ds.drop_vars(drop, errors="ignore")                          # scalar extras
  • Location: at the top of Grid.__init__ in grid/grid.py.
  • Idea: Keep V3's drop_dims and add a second pass dropping 0-d non-grid vars, catching the scalar time it missed. It works, but uses two mechanisms for what one can do — V5's name-based keep-list covers scalar, dimensional, and data-var leaks in a single pass.

Version 5 — Single name-based keep-list in _read_ugrid (final)

# uxarray/io/_ugrid.py — end of _read_ugrid()
def _keep_only_grid_vars(ds):
    keep = {"grid_topology", *SPHERICAL_COORD_NAMES, *CARTESIAN_COORD_NAMES,
            *CONNECTIVITY_NAMES, *DESCRIPTOR_NAMES}
    return ds.drop_vars([n for n in ds.variables if n not in keep], errors="ignore")
  • Location: at the end of _read_ugrid in io/_ugrid.py — different location (see next section).
  • Idea: One name-based keep-list that drops every kind of non-grid extra (scalar, dimensional, data vars) in a single pass, collapsing V2–V4 into the simplest complete fix. Could be paired with the .values option, but didn't seem necessary.

Why V5 lives in _read_ugrid, not Grid.__init__

Two code paths build a Grid. They share Grid.__init__ but not _read_ugrid, so why is it in _read_ugrid?

Path A — opening a grid from a file (where the time leaks in):

ux.open_grid(...)                   core/api.py
  └─ _open_dataset_with_fallback()  # full xr.Dataset — stray `time` included
  └─ Grid.from_dataset(...)         grid.py
       └─ no source_grid_spec
       └─ _parse_grid_type()
       └─ _read_ugrid(...)          io/_ugrid.py   ← renames in place, returns EVERYTHING (leak enters here)
  └─ Grid.__init__(...)             grid.py   # self._ds = grid_ds

Path B — building a sliced grid during a subset (trying not to disturb this path):

uxda.subset.bounding_box(...)
  └─ Grid.isel(...)                    grid.py
       └─ _slice_face_indices(...)     grid/slice.py
            ds = grid._ds.isel(...)    # copy of cleaned grid
            ds["subgrid_face_indices"] = ...
       └─ Grid.from_dataset(...)       slice.py
            └─ grid_ds = ds            (_read_ugrid is skipped)
  └─ Grid.__init__(...)                grid.py   # self._ds holds subgrid_*_indices
Through _read_ugrid? Through Grid.__init__? _ds holds subgrid_*_indices?
Path A (file open) Yes Yes No
Path B (subset) No Yes Yes (needed by _slice_from_grid)
  • Filter in _read_ugrid (V5): Path A only. time is dropped as the grid is read; Path B never enters _read_ugrid, so its subgrid_*_indices survive, and its sliced grid (copied from the already-clean grid._ds) comes out clean for free.
  • Filter in Grid.__init__: both paths. It would strip subgrid_face_indices from Path B's sliced grid → _slice_from_grid hits a KeyError. Fixes the leak but breaks subsetting.

Final code preview

Basically came down to two edits, both in uxarray/io/_ugrid.py.

Edit 1 — add one import at the top of the file (ugrid is already imported; only DESCRIPTOR_NAMES is new):

import numpy as np
import xarray as xr

import uxarray.conventions.ugrid as ugrid
from uxarray.constants import INT_DTYPE, INT_FILL_VALUE
from uxarray.grid.connectivity import _replace_fill_values
from uxarray.conventions.descriptors import DESCRIPTOR_NAMES  # <---- this line

Edit 2 — filter at the end of _read_ugrid, then define the helper directly after it:

def _read_ugrid(ds):
    """Parses an unstructured grid dataset and encodes it in the UGRID
    conventions."""

    # ... (topology parse, coord/connectivity renames, dim swaps — unchanged) ...

    dim_dict[ds["face_node_connectivity"].dims[1]] = ugrid.N_MAX_FACE_NODES_DIM

    ds = ds.swap_dims(dim_dict)

    # ===== NEW =====
    # Strip non-grid extras (e.g. a stray scalar `time` coordinate, or unrelated data variables)
    ds = _keep_only_grid_vars(ds)
    # ===== end NEW =====

    return ds, dim_dict


# ===== NEW helper — place directly after _read_ugrid =====
def _keep_only_grid_vars(ds):
    """Return ``ds`` with only recognized UGRID grid variables/coordinates.

    Anything else on the dataset (a stray scalar ``time`` coordinate, unrelated
    data variables, etc.) is dropped so it cannot leak onto ``grid._ds``.

    Runs on the file-read path only.
    """
    # uxarray's own canonical grid-variable names (the same lists Grid filters against)
    keep = {"grid_topology"}
    keep.update(ugrid.SPHERICAL_COORD_NAMES)   # node/edge/face lon-lat
    keep.update(ugrid.CARTESIAN_COORD_NAMES)   # node/edge/face x-y-z
    keep.update(ugrid.CONNECTIVITY_NAMES)      # face_node_connectivity, edge_node_connectivity, ...
    keep.update(DESCRIPTOR_NAMES)              # n_nodes_per_face, face_areas, boundary_*_indices, ...

    # drop_vars removes variables/coords by name (never bare dimensions), so grid
    # dims survive with their variables; errors="ignore" tolerates absent names.
    drop = [name for name in ds.variables if name not in keep]
    return ds.drop_vars(drop, errors="ignore")
# ===== end NEW helper =====


# ===== existing code =====
def _encode_ugrid(ds):
    """Encodes an unstructured grid represented under a ``Grid`` object as a
    ``xr.Dataset`` with an updated grid topology variable."""

    if "grid_topology" in ds:
        ds = ds.drop_vars(["grid_topology"])
    # ... (rest of _encode_ugrid, unchanged) ...

@dylannelson

dylannelson commented Aug 11, 2026

Copy link
Copy Markdown
Member Author

@erogluorhan

4. Finally, ugrid (i.e. `_ugrid.py`) is only one of the several formats we support. What about other formats in our I/O, e.g. _mpas.py etc. Their grid files can come in a similar way. Maybe we will need to do this fix at the Grid level rather than particular I/O modules?

Sorry saw this after I was typing all the above content.

Edit: Here's an update, more focused on # 4 to start

This is a great question and I wish I had realized sooner. Trying to find a solution that was very high level, like in Grid.__innit__ seemed like a it made the most sense but kept causing problems downstream. Looking to something less high up made it easier, but I totally missed that it would only work for certain file types

So I had to look at 2 questions:

  1. Does this leak happen for other file types?
    • If no, we are good, and it was a ugrid specific issue
    • If yes, and it's every other file, then we may have to find some function at a higher level that can handle all the leaks the same
  2. Is there somewhere up, that can solve this problem by interacting with all the different file types in the same way, without disturbing other functionality
    • I've looked at this before and was struggling to find a place

Looking at # 1, I performed a similar check like before, read in a demo dataset, add time, save, reopen, check if there's time, and see if it crashes when there's a subset. Here's what I got

Reader Demo grid tested (test/meshfiles/…) How grid vars are populated Leaks?
MPAS mpas/QU/mesh.QU.1920km.151026.nc DataArrays derived from in_ds yes
ESMF esmf/ne30/ne30pg3.grid.nc xr.DataArray(<DataArray>, …) yes
ICON icon/R02B04/icon_grid_0010_R02B04_G.nc in_ds["…"].T - 1 (DataArray) yes
Exodus exodus/outCSne8/outCSne8.g DataArrays from the source yes
SCRIP scrip/ne30pg2/grid.nc xr.DataArray(<numpy>, …) no
GEOS-CS geos-cs/c12/test-c12.native.nc4 in_ds["…"].values.ravel() no
UGRID ugrid/quad-hexagon/grid.nc renames the input dataset in place no
Structured / FESOM2 structured/outCSne30_vortex_structured.nc (FESOM2 untested) crashes when re-opening yes but worse

So basically from what I'm seeing right now, we have 3 different outcomes

  • it's fine as is (good, 3 file types)
  • it's the same problem as ugrid (bad)
  • it introduces a problem that crashes when reading (worse than what happened with ugrid)

I'll keep looking for a solution for # 2, but worried at it's impact, especially since every file type seems to have a unique way to handle the data when opening.

@erogluorhan

Copy link
Copy Markdown
Member

The table in your latest comment showing no leaks for UGRID confused me a bit (From your other words, it sounds like UGRID has the time leaking). Could you clarify?

@dylannelson

Copy link
Copy Markdown
Member Author

@erogluorhan
For # 1 and # 2 and # 3, I think from what I'm seeing, _ds is used for a lot of functionality, and the added time being in there causes it to leak out into anything that can call _ds (which is a lot, shown below). The "Grid" and "_ds" can't be thought of as separate, because they are so heavily intertwined.

"the Grid" is essentially _ds

Almost every Grid property is a thin read of self._ds. Straight from uxarray/grid/grid.py (trimmed to the returns):

@property
def n_face(self):                 return self._ds.sizes["n_face"]          # :878
@property
def face_node_connectivity(self): return self._ds["face_node_connectivity"]  # :1261
@property
def attrs(self):                  return self._ds.attrs                    # :836
@property
def coordinates(self):            # only the NAMES the conventions recognize:
    return {c for c in SPHERICAL_COORD_NAMES if c in self._ds} | \
           {c for c in CARTESIAN_COORD_NAMES if c in self._ds}            # :799
@property
def dims(self):
    return {d for d in DIM_NAMES if d in self._ds.dims}                   # :787

Line references (pinned to 17c20f91):

The same pattern appears to be true for others as well, like: n_node, n_edge, node_lon, every *_connectivity,
face_areas, bounds, … — all return self._ds[...].

This can be a problem because time can leak onto other calls, like what we saw with subset. When the subset pulls _ds["subgrid_face_indices"] out as an index array, time rides along and collides with the data's time.

@dylannelson

dylannelson commented Aug 12, 2026

Copy link
Copy Markdown
Member Author

@erogluorhan

The table in your latest comment showing no leaks for UGRID confused me a bit (From your other words, it sounds like UGRID has the time leaking). Could you clarify?

It's no leak for UGRID in my current version, which fixed it. main would instead show that yes, there is a leak in UGRID

@dylannelson

dylannelson commented Aug 12, 2026

Copy link
Copy Markdown
Member Author

and for

fixing at the Grid without altering _ds,

that's effectively what I had originally, via .values. (Version 1 in the big comment above)

The issue with this is that it fixes a symptom, without addressing the cause. We discussed this early on and it was turned down when we originally discussed it. It's basically a patch in the .isel indexer (_slice_from_grid) so that when the data is on the way out, we strip any extra data that may cause a clash. This works for our subset incident, but it won't permanently remove the leaked time and it won't guarantee it won't cause issues in other functions,

@erogluorhan

Copy link
Copy Markdown
Member

and for

fixing at the Grid without altering _ds,

that's effectively what I had originally, via .values. (Version 1 in the big comment above)

The issue with this is that it fixes a symptom, without addressing the cause. We discussed this early on and it was turned down when we originally discussed it. It's basically a patch in the .isel indexer (_slice_from_grid) so that when the data is on the way out, we strip any extra data that may cause a clash. This works for our subset incident, but it won't permanently remove the leaked time and it won't guarantee it won't cause issues in other functions,

You're right; "without altering _ds" doesn't seem feasible or ideal, but I think we should discuss if the fix should be in the individual I/O modules (like _mpas etc.) or right before the Grid object creation, i.e. after the specific grid format reading. Thougts?

@rajeeja

rajeeja commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Other readers like MPAS, ESMF, Exodus, ICON, and FESOM2 still leave time on _ds — MPAS reproduces the same IndexError from #1444. Could the filter move to the end of the auto-detect branch in Grid.from_dataset, before the return cls(...) at grid.py:323, so it covers all formats at once? The keep-list also drops bounds and max_face_radius when re-reading a uxarray-written grid.

@dylannelson

Copy link
Copy Markdown
Member Author

This new push should have everything we have wanted from the last few discussions
Early on we had the idea of moving the solution to grid.py in Grid.__init__ and doing the filtering there. The filtering would look at the dimensions and remove any that didn't belong. Unfortunately this didn't work, while also introducing downstream issues. It looked like this:

extra_dims = set(grid_ds.dims) - set(DIM_NAMES)
grid_ds = grid_ds.drop_dims(extra_dims)

The time in #1444 is a scalar (0-d) coordinate — it has no dimension, so it never appears in grid_ds.dims, extra_dims comes back empty, and drop_dims doesn't change it. I moved on from looking in grid.py at this point but now I'm back to it, and it seems to be working.

This solution is different in that it is dropping non-grid coordinates by name, which catches the scalar one that we've been having trouble with. Here's what this solution looks like instead:

grid_coord_names = set(ugrid.SPHERICAL_COORD_NAMES) | set(ugrid.CARTESIAN_COORD_NAMES)
stray = [c for c in ds.coords if c not in grid_coord_names]
return ds.drop_vars(stray, errors="ignore")

Testing

Before:
image
After:
image

Notebook Preview:
18-new-fix-demo.html

Downstream impact

Performed a few downstream tests with claude code to test what affects it may have, here's the results of what it looked at:

__init__ runs on every construction path — from_dataset (all formats), from_topology/from_healpix/etc., the subset's sliced grids, and Grid.copy(). Static sweep:

Area Finding Verdict
Grid properties (n_face, node_lon, …) read by name; recognized coords kept safe
Reads of stray coords off grid._ds grep '_ds.coords' / _ds["time"] → none safe
Grid↔data linking (open_dataset) by dimensions (_map_dims_to_ugrid), not coords safe
Subset bookkeeping subgrid_face/edge/node_indices are data vars safe
Grid.copy() re-runs __init__ on a clean _ds safe (idempotent)
Encoders / to_xarray no .coords use safe
Every .coords read in the package on the data objects / shapely — never grid._ds safe
Tests none assert coords on grid._ds safe

@dylannelson dylannelson added run-benchmark Run ASV benchmark workflow extra dims Handling of non-grid dimensions, such as time or altitude labels Aug 19, 2026
@github-actions

github-actions Bot commented Aug 19, 2026

Copy link
Copy Markdown

ASV Benchmarking

Benchmark Comparison Results

Benchmarks that have improved:

Change Before [b674956] After [c3b0a07] Ratio Benchmark (Parameter)
- 449±20μs 388±10μs 0.86 mpas_ocean.PointInPolygon.time_face_search_lonlat('120km')
- 6.60±0.2ms 5.73±0.03ms 0.87 quad_hexagon.QuadHexagon.time_open_grid

Benchmarks that have stayed the same:

Change Before [b674956] After [c3b0a07] Ratio Benchmark (Parameter)
202±0.6ms 204±1ms 1.01 bench_connectivity.Connectivity.time_edge_face('120km')
12.2±0.08ms 12.2±0.1ms 0.99 bench_connectivity.Connectivity.time_edge_face('480km')
198±0.8ms 200±1ms 1.01 bench_connectivity.Connectivity.time_edge_node('120km')
11.2±0.2ms 11.1±0.1ms 0.99 bench_connectivity.Connectivity.time_edge_node('480km')
200±1ms 199±0.7ms 1.00 bench_connectivity.Connectivity.time_face_edge('120km')
11.3±0.2ms 11.6±0.3ms 1.02 bench_connectivity.Connectivity.time_face_edge('480km')
888±4ms 900±4ms 1.01 bench_connectivity.Connectivity.time_face_face('120km')
57.8±0.5ms 57.8±0.3ms 1.00 bench_connectivity.Connectivity.time_face_face('480km')
72.0±1μs 71.3±3μs 0.99 bench_connectivity.Connectivity.time_face_node('120km')
68.9±2μs 71.6±1μs 1.04 bench_connectivity.Connectivity.time_face_node('480km')
414±10μs 432±9μs 1.04 bench_connectivity.Connectivity.time_n_nodes_per_face('120km')
348±1μs 368±7μs 1.06 bench_connectivity.Connectivity.time_n_nodes_per_face('480km')
201±2ms 225±20ms ~1.12 bench_connectivity.Connectivity.time_node_edge('120km')
11.6±0.05ms 11.4±0.07ms 0.99 bench_connectivity.Connectivity.time_node_edge('480km')
84.4±1ms 78.4±3ms 0.93 bench_connectivity.Connectivity.time_node_face('120km')
5.28±0.05ms 5.18±0.09ms 0.98 bench_connectivity.Connectivity.time_node_face('480km')
8.75±0.2ms 8.70±0.1ms 0.99 face_bounds.FaceBounds.time_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/mpas/QU/oQU480.231010.nc'))
2.74±0.06ms 2.78±0.08ms 1.01 face_bounds.FaceBounds.time_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/scrip/outCSne8/outCSne8.nc'))
10.4±10s 10.3±10ms ~0.00 face_bounds.FaceBounds.time_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/ugrid/geoflow-small/grid.nc'))
2.15±0.01ms 2.15±0.02ms 1.00 face_bounds.FaceBounds.time_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/ugrid/quad-hexagon/grid.nc'))
57.3k 57.3k 1.00 face_bounds.FaceBounds.track_nbytes_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/mpas/QU/oQU480.231010.nc'))
12.3k 12.3k 1.00 face_bounds.FaceBounds.track_nbytes_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/scrip/outCSne8/outCSne8.nc'))
123k 123k 1.00 face_bounds.FaceBounds.track_nbytes_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/ugrid/geoflow-small/grid.nc'))
128 128 1.00 face_bounds.FaceBounds.track_nbytes_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/ugrid/quad-hexagon/grid.nc'))
1.27M 1.27M 1.00 face_bounds.FaceBounds.track_nbytes_grid_with_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/mpas/QU/oQU480.231010.nc'))
50.1k 50.1k 1.00 face_bounds.FaceBounds.track_nbytes_grid_with_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/scrip/outCSne8/outCSne8.nc'))
1.48M 1.48M 1.00 face_bounds.FaceBounds.track_nbytes_grid_with_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/ugrid/geoflow-small/grid.nc'))
712 712 1.00 face_bounds.FaceBounds.track_nbytes_grid_with_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/ugrid/quad-hexagon/grid.nc'))
1.98M 1.98M 1.00 face_bounds.FaceBounds.track_peakmem_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/mpas/QU/oQU480.231010.nc'))
1.98M 1.98M 1.00 face_bounds.FaceBounds.track_peakmem_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/scrip/outCSne8/outCSne8.nc'))
2.15M 2.15M 1.00 face_bounds.FaceBounds.track_peakmem_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/ugrid/geoflow-small/grid.nc'))
38.3k 38.3k 1.00 face_bounds.FaceBounds.track_peakmem_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/ugrid/quad-hexagon/grid.nc'))
335M 335M 1.00 face_bounds.FaceBoundsColdStartRss.peakmem_open_and_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/mpas/QU/oQU480.231010.nc'))
367M 365M 0.99 face_bounds.FaceBoundsColdStartRss.peakmem_open_and_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/scrip/outCSne8/outCSne8.nc'))
337M 337M 1.00 face_bounds.FaceBoundsColdStartRss.peakmem_open_and_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/ugrid/geoflow-small/grid.nc'))
337M 337M 1.00 face_bounds.FaceBoundsColdStartRss.peakmem_open_and_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/ugrid/quad-hexagon/grid.nc'))
1.21±0.07μs 1.26±0.04μs 1.04 geometry_kernels.AccucrossKernels.time_accucross
2.76±0.06μs 2.77±0.03μs 1.00 geometry_kernels.AccucrossKernels.time_accucross_pair
446±20ns 451±30ns 1.01 geometry_kernels.EFTPrimitives.time_acc_sqrt_re
461±20ns 430±20ns 0.93 geometry_kernels.EFTPrimitives.time_diff_of_products
371±20ns 386±30ns 1.04 geometry_kernels.EFTPrimitives.time_two_prod
411±20ns 380±8ns 0.93 geometry_kernels.EFTPrimitives.time_two_sum
1.66±0.02μs 1.61±0.02μs 0.97 geometry_kernels.GCAConstLatIntersection.time_accux_constlat_kernel
1.15±0.01μs 1.14±0.05μs 0.99 geometry_kernels.GCAConstLatIntersection.time_gca_const_lat_intersection
2.00±0.03μs 1.93±0.04μs 0.97 geometry_kernels.GCAConstLatIntersection.time_try_gca_const_lat_intersection
1.70±0.05μs 1.84±0.08μs 1.08 geometry_kernels.GCAGCAIntersection.time_accux_gca_kernel
1.40±0.02μs 1.41±0.02μs 1.00 geometry_kernels.GCAGCAIntersection.time_gca_gca_intersection
2.21±0.03μs 2.18±0.04μs 0.99 geometry_kernels.GCAGCAIntersection.time_try_gca_gca_intersection
52.9±0.5μs 52.2±1μs 0.99 geometry_kernels.OrientPredicates.time_on_minor_arc
1.10±0.05μs 1.12±0.03μs 1.02 geometry_kernels.OrientPredicates.time_orient3d_on_sphere
2.71±0.1ms 2.61±0.01ms 0.96 geometry_samebody.SameBodyConstLat.time_accux_dispatch
1.17±0ms 1.17±0ms 1.00 geometry_samebody.SameBodyConstLat.time_accux_kernel
1.73±0.01ms 1.71±0.01ms 0.99 geometry_samebody.SameBodyConstLat.time_fp64_dispatch
148±3μs 147±2μs 0.99 geometry_samebody.SameBodyConstLat.time_fp64_kernel
32.2±0.01ms 32.1±0.02ms 1.00 geometry_samebody_gcagca.SameBodyGcaGca.time_accux_dispatch
10.2±0ms 10.2±0.01ms 1.00 geometry_samebody_gcagca.SameBodyGcaGca.time_accux_kernel
26.4±0.02ms 26.5±0.01ms 1.00 geometry_samebody_gcagca.SameBodyGcaGca.time_fp64_dispatch
4.84±0.02ms 4.92±0.04ms 1.02 geometry_samebody_gcagca.SameBodyGcaGca.time_fp64_kernel
842±20ms 814±5ms 0.97 import.Imports.timeraw_import_uxarray
295M 293M 0.99 import.Imports.track_peakmem_import_uxarray
2.80±0.04ms 2.79±0.1ms 0.99 mpas_ocean.CheckNorm.time_check_norm('120km')
2.33±0.05ms 2.27±0.02ms 0.98 mpas_ocean.CheckNorm.time_check_norm('480km')
843±20ms 836±5ms 0.99 mpas_ocean.ConnectivityConstruction.time_face_face_connectivity('120km')
55.5±0.5ms 55.0±0.3ms 0.99 mpas_ocean.ConnectivityConstruction.time_face_face_connectivity('480km')
677±20μs 678±9μs 1.00 mpas_ocean.ConnectivityConstruction.time_n_nodes_per_face('120km')
616±10μs 618±40μs 1.00 mpas_ocean.ConnectivityConstruction.time_n_nodes_per_face('480km')
5.53±0.01ms 5.50±0.05ms 0.99 mpas_ocean.ConstructFaceLatLon.time_cartesian_averaging('120km')
4.08±0.04ms 3.91±0.02ms 0.96 mpas_ocean.ConstructFaceLatLon.time_cartesian_averaging('480km')
99.4±0.4ms 99.6±0.3ms 1.00 mpas_ocean.ConstructFaceLatLon.time_welzl('120km')
10.8±0.09ms 10.4±0.3ms 0.96 mpas_ocean.ConstructFaceLatLon.time_welzl('480km')
18.2±0.04ms 18.1±0ms 1.00 mpas_ocean.ConstructTreeStructures.time_ball_tree('120km')
1.05±0.01ms 1.01±0.02ms 0.96 mpas_ocean.ConstructTreeStructures.time_ball_tree('480km')
10.6±0.02ms 10.6±0.04ms 1.00 mpas_ocean.ConstructTreeStructures.time_kd_tree('120km')
730±20μs 731±10μs 1.00 mpas_ocean.ConstructTreeStructures.time_kd_tree('480km')
596±10ms 584±6ms 0.98 mpas_ocean.CrossSections.time_const_lat('120km', 1)
299±4ms 294±3ms 0.98 mpas_ocean.CrossSections.time_const_lat('120km', 2)
155±2ms 154±0.06ms 0.99 mpas_ocean.CrossSections.time_const_lat('120km', 4)
541±5ms 532±0.8ms 0.98 mpas_ocean.CrossSections.time_const_lat('480km', 1)
271±3ms 271±1ms 1.00 mpas_ocean.CrossSections.time_const_lat('480km', 2)
141±3ms 138±0.4ms 0.98 mpas_ocean.CrossSections.time_const_lat('480km', 4)
356M 356M 1.00 mpas_ocean.CrossSectionsPeakMem.peakmem_const_lat('120km', 1)
356M 358M 1.01 mpas_ocean.CrossSectionsPeakMem.peakmem_const_lat('120km', 2)
356M 355M 1.00 mpas_ocean.CrossSectionsPeakMem.peakmem_const_lat('120km', 4)
339M 339M 1.00 mpas_ocean.CrossSectionsPeakMem.peakmem_const_lat('480km', 1)
339M 339M 1.00 mpas_ocean.CrossSectionsPeakMem.peakmem_const_lat('480km', 2)
339M 339M 1.00 mpas_ocean.CrossSectionsPeakMem.peakmem_const_lat('480km', 4)
25.2±0.3ms 24.8±0.1ms 0.98 mpas_ocean.DualMesh.time_dual_mesh_construction('120km')
3.31±0.08ms 3.36±0.09ms 1.02 mpas_ocean.DualMesh.time_dual_mesh_construction('480km')
61.9±0.1ms 61.7±0.3ms 1.00 mpas_ocean.FaceAreas.time_face_areas('120km')
4.96±5s 8.10±5ms ~0.00 mpas_ocean.FaceAreas.time_face_areas('480km')
229k 229k 1.00 mpas_ocean.FaceAreas.track_nbytes_face_areas('120km')
14.3k 14.3k 1.00 mpas_ocean.FaceAreas.track_nbytes_face_areas('480km')
2.12M 2.12M 1.00 mpas_ocean.FaceAreas.track_peakmem_face_areas('120km')
823k 823k 1.00 mpas_ocean.FaceAreas.track_peakmem_face_areas('480km')
949±4ms 944±3ms 1.00 mpas_ocean.GeoDataFrame.time_to_geodataframe('120km', False)
55.7±2ms 54.2±2ms 0.97 mpas_ocean.GeoDataFrame.time_to_geodataframe('120km', True)
86.3±0.8ms 83.0±0.3ms 0.96 mpas_ocean.GeoDataFrame.time_to_geodataframe('480km', False)
6.12±0.09ms 5.76±0.1ms 0.94 mpas_ocean.GeoDataFrame.time_to_geodataframe('480km', True)
177±0.3ms 175±0.4ms 0.99 mpas_ocean.Gradient.time_gradient('120km')
12.8±0.04ms 12.3±0.04ms 0.96 mpas_ocean.Gradient.time_gradient('480km')
457k 457k 1.00 mpas_ocean.Gradient.track_nbytes_gradient('120km')
28.7k 28.7k 1.00 mpas_ocean.Gradient.track_nbytes_gradient('480km')
5.08M 5.08M 1.00 mpas_ocean.Gradient.track_peakmem_gradient('120km')
328k 327k 1.00 mpas_ocean.Gradient.track_peakmem_gradient('480km')
352M 352M 1.00 mpas_ocean.GradientColdStartRss.peakmem_gradient('120km')
331M 331M 1.00 mpas_ocean.GradientColdStartRss.peakmem_gradient('480km')
393±9μs 361±20μs 0.92 mpas_ocean.HoleEdgeIndices.time_construct_hole_edge_indices('120km')
234±10μs 203±3μs ~0.87 mpas_ocean.HoleEdgeIndices.time_construct_hole_edge_indices('480km')
606±6μs 554±20μs 0.91 mpas_ocean.Integrate.time_integrate('120km')
541±20μs 492±20μs 0.91 mpas_ocean.Integrate.time_integrate('480km')
18.4M 18.4M 1.00 mpas_ocean.Integrate.track_nbytes_integrate('120km')
1.2M 1.2M 1.00 mpas_ocean.Integrate.track_nbytes_integrate('480km')
183±0.7ms 181±1ms 0.99 mpas_ocean.MatplotlibConversion.time_dataarray_to_polycollection('120km', 'exclude')
183±0.8ms 178±1ms 0.98 mpas_ocean.MatplotlibConversion.time_dataarray_to_polycollection('120km', 'include')
182±2ms 181±1ms 1.00 mpas_ocean.MatplotlibConversion.time_dataarray_to_polycollection('120km', 'split')
14.4±0.2ms 13.4±0.2ms 0.93 mpas_ocean.MatplotlibConversion.time_dataarray_to_polycollection('480km', 'exclude')
14.2±0.4ms 13.3±0.2ms 0.94 mpas_ocean.MatplotlibConversion.time_dataarray_to_polycollection('480km', 'include')
14.2±0.6ms 13.6±0.09ms 0.96 mpas_ocean.MatplotlibConversion.time_dataarray_to_polycollection('480km', 'split')
425±20μs 398±10μs 0.94 mpas_ocean.PointInPolygon.time_face_search_lonlat('480km')
414±9μs 376±10μs ~0.91 mpas_ocean.PointInPolygon.time_face_search_xyz('120km')
392±10μs 371±5μs 0.95 mpas_ocean.PointInPolygon.time_face_search_xyz('480km')
237±1ms 230±3ms 0.97 mpas_ocean.RemapDownsample.time_bilinear_remapping
293±1ms 287±2ms 0.98 mpas_ocean.RemapDownsample.time_inverse_distance_weighted_remapping
16.1±0.6ms 15.6±0.1ms 0.97 mpas_ocean.RemapDownsample.time_nearest_neighbor_remapping
1.38±0.01s 1.36±0s 0.99 mpas_ocean.RemapUpsample.time_bilinear_remapping
37.2±0.8ms 36.4±0.5ms 0.98 mpas_ocean.RemapUpsample.time_inverse_distance_weighted_remapping
12.7±0.2ms 12.3±0.2ms 0.97 mpas_ocean.RemapUpsample.time_nearest_neighbor_remapping
9.60±0.2ms 9.16±0.1ms 0.95 mpas_ocean.ZonalAverage.time_zonal_average('120km')
4.95±0.2ms 4.73±0.06ms 0.95 mpas_ocean.ZonalAverage.time_zonal_average('480km')
358M 358M 1.00 mpas_ocean.ZonalAveragePeakMem.peakmem_zonal_average('120km')
341M 341M 1.00 mpas_ocean.ZonalAveragePeakMem.peakmem_zonal_average('480km')
7.27±0.6ms 6.77±0.01ms 0.93 quad_hexagon.QuadHexagon.time_open_dataset
408 408 1.00 quad_hexagon.QuadHexagon.track_nbytes_open_dataset
392 392 1.00 quad_hexagon.QuadHexagon.track_nbytes_open_grid
73.5k 73.4k 1.00 quad_hexagon.QuadHexagon.track_peakmem_open_dataset
72.8k 72.8k 1.00 quad_hexagon.QuadHexagon.track_peakmem_open_grid

@dylannelson
dylannelson marked this pull request as ready for review August 19, 2026 19:34

@Sevans711 Sevans711 left a comment

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.

This looks like a relatively clean fix overall, with lots of tests run by hand to confirm things seem to be working!

I left a few minor comments inline, and I have a couple larger comments here:

  1. I had a suspicion that extra dimensions (not just extra coordinates) on input datasets would be an issue, but I tested a few things locally and found that it wasn't a problem (for some reason…). For example, if you had a time dimension on the input dataset, instead of just a time coordinate, it doesn't seem to lead to a crash later (although, it does leak into Grid._ds). It isn't really obvious to me why it wouldn't crash…. Can you clarify why this might be the case, and/or add a test to ensure subsetting still works even after adding a time dimension to the grid?
  2. Would you also be able to create a file with a time coordinate somewhere in the test suite (in meshfiles folder), and test that, too, instead of only having a test where the time coordinate gets assigned after loading the dataset? That would help me to be more convinced that this fully closes the original issue. It's a bit tricky to know for certain because the original issue report doesn't contain a complete verifiable example. But, the workflow there seemed to be "load from file, then subset (which crashes)" so ideally a test here could include that complete workflow as well.

Comment thread test/test_subset.py Outdated
import uxarray.grid.slice as slice_module
from uxarray.grid.slice import _remap_dense, _remap_kernel, _remap_searchsorted

import numpy as np

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.

remove repeated numpy import

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.

No clue how I missed that twice, thanks for the catch, should be gone

Comment thread uxarray/grid/grid.py Outdated
from uxarray.core.dataarray import UxDataArray


def _drop_non_grid_coords(ds):

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.

Relocate to uxarray/grid/utils.py or some other file, rather than defining this helper function directly in the main grid.py file

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.

Got it, should be moved there now

@erogluorhan

Copy link
Copy Markdown
Member
  1. I had a suspicion that extra dimensions (not just extra coordinates) on input datasets would be an issue, but I tested a few things locally and found that it wasn't a problem (for some reason…). For example, if you had a time dimension on the input dataset, instead of just a time coordinate, it doesn't seem to lead to a crash later (although, it does leak into Grid._ds). It isn't really obvious to me why it wouldn't crash…. Can you clarify why this might be the case, and/or add a test to ensure subsetting still works even after adding a time dimension to the grid?

I agree with this, I think we shouldn't let extra dimensions either into the Grid regardless of whether they lead to crash or not.

  1. Would you also be able to create a file with a time coordinate somewhere in the test suite (in meshfiles folder), and test that, too, instead of only having a test where the time coordinate gets assigned after loading the dataset? That would help me to be more convinced that this fully closes the original issue. It's a bit tricky to know for certain because the original issue report doesn't contain a complete verifiable example. But, the workflow there seemed to be "load from file, then subset (which crashes)" so ideally a test here could include that complete workflow as well.

I don't think we should expose a grid file with time coord/dim into our testing suite as it is uncommon to have such a presence and it would be misleading in the future.

@erogluorhan erogluorhan left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks very much for this fix! @Sevans711 has already made great points inline and overall ( see (1) here, but I believe (2) should be avoided), so I am approving it to make way once they're addressed.

@Sevans711

Copy link
Copy Markdown
Collaborator

Thanks very much for this fix! @Sevans711 has already made great points inline and overall ( see (1) here, but I believe (2) should be avoided), so I am approving it to make way once they're addressed.

Ah, for (2), I was going based on the file at gridpath("mpas", "QU", "oQU480.231010.nc") which is treated as dataset data and grid data during test_open_dataset_single_combined_xarray_dataset in test_api.py (related: #1479). That file already has a Time dimension in it. So, the testing suite does already include a "grid file" with time dimension, though it isn't really a simple grid file, rather it is a single file grid + data situation.

That was leading me to think it would be reasonable enough to include a second version of this file which is extremely similar but just has a scalar time coordinate instead of a Time dimension, and then call ux.open_dataset on it. What would your thoughts be on something like that, @erogluorhan?

Either way, I do still want to see evidence of the full original workflow being reproduced somewhere before I am ready to approve this as fully closing the original issue. Ideally this would involve an example of "load from file, then subset" crashing on main but not crashing on this branch. I spent a little time trying to reproduce something like that on my end, but I'm not succeeding quickly and I want to avoid duplicating work if you've already run such a test. @dylannelson my apologies if you have already shared test results like this - would you be able to point me to them if this test was already performed somewhere?

@dylannelson

Copy link
Copy Markdown
Member Author

@Sevans711
For 1, yeah the weirdness of dimensions vs coordinates here is what has taken me a while to understand and work around. It seems to have to do with how dimensions are used during .isel indexing. When it's a scalar coordinate it clashes with other existing times, whereas when its a dimension, it only attaches to variables that share a time with it, so time doesn't end up on the index at all.

  • this may be why other issues related to this one claim that subsetting didn't work, maybe because it had a time but didn't share the time so it did nothing or something wrong? (really hard to tell without seeing the data) But I'm not sure, I haven't explore this case much as it was never a dimension, and we don't know if it can be naturally.
  • note the time dimension will still be in the Grid._ds, it just won't raise conflicts. We can forcefully make it a dim, but it doesn't get carried into subsetting functionality at all.

The summary of a path deep dive through many files and functions basically came down to:

xarray attaches a coordinate to a variable only when the coordinate's dimensions are a subset of the variable's dimensions.

When it's scalar time → dims = (). And () is a subset of everything, so it glues onto the n_face index array.
Dimensional time → dims = (time,). (time,) is not a subset of (n_face,), so it stays off the index array.

Which happens about here it xarray: xarray/core/coordinates.py#L1214

Filtering dimensions seemed to be distinct from this problem (filtering coordinates) in my mind. Like I don't think we saw anything that indicates a time dimension could appear naturally like this. The downstream affects of filtering dimensions is different than coordinates too. If my understanding seems wrong, and a dimension filter here seems good, maybe we can scope this out over a call because there's lots of threads that have felt tangled while exploring this, and I don't want to overcompensate in this solution because it might introduce new problems.

Maybe of note, that uxarray also won't report any Dims that it has that aren't in DIM_NAMES - which is a static list (in grid.py):

    @property
    def dims(self) -> set:
        """Names of all unstructured grid dimensions."""
        from uxarray.conventions.ugrid import DIM_NAMES

        return set([dim for dim in DIM_NAMES if dim in self._ds.dims])

so it shouldn't ever really report anything outside of:

DIM_NAMES = [
    "n_node",
    "n_edge",
    "n_face",
    "n_max_face_nodes",
    "n_max_face_edges",
    "n_max_face_faces",
    "n_max_edge_nodes",
    "n_max_edge_edges",
    "n_max_edge_faces",
    "n_max_node_faces",
    "n_max_node_edges",
    "two",
]
  1. Was about to respond why we didn't add local files, but @erogluorhan got there first
    Basically all my local tests read a file, made a alternate version with time, saved, reopened, check if it has time, and run subset to see if it crashes. This effectively does the same because the same Grid.__init__ should be called either way. If you're interested in seeing that we could hop on a call and I can demo. I have a few notebooks dedicated to this.

@dylannelson

Copy link
Copy Markdown
Member Author

@Sevans711 I think you're looking for the notebook I made in this comment here:
#1667 (comment)

There's a link in the middle to a notebook. It should be what I did and referenced above, and what I think what you're suggesting you wanted to see

@rajeeja rajeeja left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This also silently drops ICON's clon/clat/vlon/vlat/elon/elat and FESOM2's lon/lat — since only 0-d coords collide, can you narrow it to and ds[coord].ndim == 0? That still strips the scalar time from #1444 but leaves every 1-d native coordinate in place, so ICON and FESOM2 grids round-trip unchanged.

@Sevans711

Copy link
Copy Markdown
Collaborator

I think you're looking for the notebook I made in this comment here: #1667 (comment)

There's a link in the middle to a notebook. It should be what I did and referenced above, and what I think what you're suggesting you wanted to see

Ah, thank you! This is much closer to what I wanted. Though, I do think it isn't fully there yet (but I also might not be fully understanding). I'm hoping that the full workflow implied in #1444 (comment) can be followed as closely as possible. I understand the workflow to be something like:

  1. uxds = ux.open_dataset(datafile, datafile) (or just ux.open_dataset(datafile) now that one-file open_dataset is supported)
  2. uxda = uxds.sel(time=[val1, val2])[varname]
  3. uxda.subset.bounding_box(...)

The tests you pointed to are subtly different; you manually attached the uxgrid to an entirely different Dataset, rather than getting the values for both from the same source. But, as per comment linked above, it sounds like the original issue used the same exact data for the grid and for the underlying dataset data. Are you still able to reproduce the issue when utilizing the same exact data in both cases?

I believe your clarifications to my original question (1) make sense; it sounds like the original issue could maybe only occur specifically from a time coordinate, but not from a time dimension. Is that correct? Assuming it is true, and there is separately a desire to avoid time dimension leaking into the Grid (as per #1667 (comment)), I would lean towards scoping the "strip extra dimensions from Grid" fix towards a separate PR to avoid too many things getting tangled here.

@dylannelson

Copy link
Copy Markdown
Member Author

@rajeeja Good catch, I implemented the change and everything works the same on my end so looks good. Pushed the changes.
@Sevans711 I will try to make a custom notebook for you, may take a bit

@erogluorhan

Copy link
Copy Markdown
Member

This also silently drops ICON's clon/clat/vlon/vlat/elon/elat and FESOM2's lon/lat — since only 0-d coords collide, can you narrow it to and ds[coord].ndim == 0? That still strips the scalar time from #1444 but leaves every 1-d native coordinate in place, so ICON and FESOM2 grids round-trip unchanged.

Rajeev, this is being called after our I/O modules (e.g. _read_icon()) work, and I believe grid_ds shouldn't have those grid model-specific coords, but did you test this?

Comment thread uxarray/grid/grid.py
# internal xarray dataset for storing grid variables.
# drop stray coordinates (e.g. a `time` carried in from the source file) so they
# can't leak onto the grid and collide during subsetting (see #1444).
# drop stray scalar coordinates (e.g. a `time` carried in from the source file)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What if the dataset file ( that has the grid definition embedded into it) has a non-scalar time?

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

Labels

extra dims Handling of non-grid dimensions, such as time or altitude run-benchmark Run ASV benchmark workflow

Projects

None yet

Development

Successfully merging this pull request may close these issues.

.subset with da that has time dimension

4 participants