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 4 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?
#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):
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 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_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) ...
Sorry saw this after I was typing all the above content. I'll think about this and we can discuss tomorrow? |
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: