Uh oh!
There was an error while loading. Please reload this page.
Filtering when reading a Grid file, avoid time leaking into the Grid - #1667
Filtering when reading a Grid file, avoid time leaking into the Grid#1667dylannelson wants to merge 5 commits into
time leaking into the Grid#1667Conversation
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
left a comment
There was a problem hiding this comment.
Maybe too early for a review, but I'd like to share some thoughts:
timewas shown to exist in a grid from a user reading in a grid and data from a single fileA little more clarity could be helpful here. IIRC,
timeis not shown directly in theGridobject but in theGrid._ds.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 throughxarray.Dataset, I'd say this dropping at the level of_dsmight break that.Maybe we'd want to fix it at the
Griditself rather than altering_ds, but would it be possible?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
commented
Aug 11, 2026
#1444 — Solution historyThis 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 — |
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.timeis dropped as the grid is read; Path B never enters_read_ugrid, so itssubgrid_*_indicessurvive, and its sliced grid (copied from the already-cleangrid._ds) comes out clean for free. - Filter in
Grid.__init__: both paths. It would stripsubgrid_face_indicesfrom Path B's sliced grid →_slice_from_gridhits aKeyError. 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):
importnumpyasnpimportxarrayasxrimportuxarray.conventions.ugridasugridfromuxarray.constantsimportINT_DTYPE, INT_FILL_VALUEfromuxarray.grid.connectivityimport_replace_fill_valuesfromuxarray.conventions.descriptorsimportDESCRIPTOR_NAMES# <---- this lineEdit 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_DIMds=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 =====returnds, 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-latkeep.update(ugrid.CARTESIAN_COORD_NAMES) # node/edge/face x-y-zkeep.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= [namefornameinds.variablesifnamenotinkeep]
returnds.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"inds:
ds=ds.drop_vars(["grid_topology"])
# ... (rest of _encode_ugrid, unchanged) ...
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 So I had to look at 2 questions:
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
So basically from what I'm seeing right now, we have 3 different outcomes
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
commented
Aug 12, 2026
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
commented
Aug 12, 2026
@erogluorhan "the |
It's no leak for UGRID in my current version, which fixed it. |
and for
that's effectively what I had originally, via 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 |
erogluorhan
commented
Aug 13, 2026
You're right; "without altering |
rajeeja
commented
Aug 17, 2026
Other readers like MPAS, ESMF, Exodus, ICON, and FESOM2 still leave |
dylannelson
commented
Aug 17, 2026
This new push should have everything we have wanted from the last few discussions extra_dims=set(grid_ds.dims) -set(DIM_NAMES)
grid_ds=grid_ds.drop_dims(extra_dims)The 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= [cforcinds.coordsifcnotingrid_coord_names]
returnds.drop_vars(stray, errors="ignore")TestingNotebook Preview: Downstream impactPerformed a few downstream tests with claude code to test what affects it may have, here's the results of what it looked at:
|


Closes#1444 (and others (?) planned to update)
Overview
timewas shown to exist in a grid from a user reading in a grid and data from a single file.subsetthere was a crash related to time being different on the grid vs the datatimeshouldn'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 dataGrid.__init__or_read_ugrid, both were tested extensivelyGrid.__init__is run in multiple scenarios, including when a file is read from disk, but in other scenarios like_slice_from_gridwhich can occur when subsetting_read_ugridoccurs primarily when a file is read from disk, like:ux.open_grid(..)->Grid.from_dataset(..)->_read_ugrid(ds)_read_ugrid(ds)main function is: "Parses an unstructured grid dataset and encodes it in the UGRID" so this seems like the right place for cleaningtimegone from grid coords and grid variablesnode_lon,node_lat,face_node_connectivitypreservedn_node,n_face> 0)bounding_boxruns, no errortimepreservedsubgrid_face_indicessel(time=...)still workstimeaxis + coord strippedExpected Usage
Changes are all located in
uxarray\uxarray\io\_ugrid.pyTest these changes on an old version and the new version. This test likely fails in old uxarray and succeeds in this branch
PR Checklist
General
Testing & Benchmarking
Documentation
docs/api.rst_)Examples
docs/examples/; guide:docs/user-guide/; quickstart:docs/getting-started/)docs/gallery.rst; guide:docs/userguide.rst; quickstart:docs/quickstart.rst)docs/gallery.ymlwith appropriate thumbnail photo indocs/_static/thumbnails/AI Disclosure
AI Usage: