From 9ebd6dcd2e48b05acecdfb3755328c0aa7c02ac7 Mon Sep 17 00:00:00 2001 From: Rajeev Jain Date: Tue, 16 Dec 2025 08:30:21 -0600 Subject: [PATCH 1/3] o Fix ICON Reader added to _icon_to_ugrid_dims --- uxarray/io/_icon.py | 62 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/uxarray/io/_icon.py b/uxarray/io/_icon.py index 4e3e42c2a..ce92fd29b 100644 --- a/uxarray/io/_icon.py +++ b/uxarray/io/_icon.py @@ -5,8 +5,66 @@ def _icon_to_ugrid_dims(in_ds): - source_dims_dict = {"vertex": "n_node", "edge": "n_edge", "cell": "n_face"} - return source_dims_dict + """Parse ICON dimension names and map them to UGRID conventions.""" + source_dims_dict = {} + + # Coordinate-driven mappings + if "vlat" in in_ds: + source_dims_dict[in_ds["vlat"].dims[0]] = ugrid.NODE_DIM + elif "vlon" in in_ds: + source_dims_dict[in_ds["vlon"].dims[0]] = ugrid.NODE_DIM + + if "elat" in in_ds: + source_dims_dict[in_ds["elat"].dims[0]] = ugrid.EDGE_DIM + elif "elon" in in_ds: + source_dims_dict[in_ds["elon"].dims[0]] = ugrid.EDGE_DIM + + if "clat" in in_ds: + source_dims_dict[in_ds["clat"].dims[0]] = ugrid.FACE_DIM + elif "clon" in in_ds: + source_dims_dict[in_ds["clon"].dims[0]] = ugrid.FACE_DIM + + # Connectivity-driven mappings + if "vertex_of_cell" in in_ds: + n_max_face_nodes_dim, face_dim = in_ds["vertex_of_cell"].dims + source_dims_dict.setdefault(face_dim, ugrid.FACE_DIM) + source_dims_dict.setdefault(n_max_face_nodes_dim, ugrid.N_MAX_FACE_NODES_DIM) + + if "edge_of_cell" in in_ds: + n_max_face_edges_dim, face_dim = in_ds["edge_of_cell"].dims + source_dims_dict.setdefault(face_dim, ugrid.FACE_DIM) + source_dims_dict.setdefault( + n_max_face_edges_dim, ugrid.FACE_EDGE_CONNECTIVITY_DIMS[1] + ) + + if "neighbor_cell_index" in in_ds: + n_max_face_faces_dim, face_dim = in_ds["neighbor_cell_index"].dims + source_dims_dict.setdefault(face_dim, ugrid.FACE_DIM) + source_dims_dict.setdefault( + n_max_face_faces_dim, ugrid.FACE_FACE_CONNECTIVITY_DIMS[1] + ) + + if "adjacent_cell_of_edge" in in_ds: + two_dim, edge_dim = in_ds["adjacent_cell_of_edge"].dims + source_dims_dict.setdefault(edge_dim, ugrid.EDGE_DIM) + source_dims_dict.setdefault(two_dim, ugrid.EDGE_FACE_CONNECTIVITY_DIMS[1]) + + if "edge_vertices" in in_ds: + two_dim, edge_dim = in_ds["edge_vertices"].dims + source_dims_dict.setdefault(edge_dim, ugrid.EDGE_DIM) + source_dims_dict.setdefault(two_dim, ugrid.EDGE_NODE_CONNECTIVITY_DIMS[1]) + + # Fall back to common ICON dimension names if they were not detected above + for dim, ugrid_dim in { + "vertex": ugrid.NODE_DIM, + "edge": ugrid.EDGE_DIM, + "cell": ugrid.FACE_DIM, + }.items(): + if dim in in_ds.dims: + source_dims_dict.setdefault(dim, ugrid_dim) + + # Keep only dims that actually exist on the dataset + return {dim: name for dim, name in source_dims_dict.items() if dim in in_ds.dims} def _primal_to_ugrid(in_ds, out_ds): From 6d02c05de07c8f427710770e437318a27c0cb453 Mon Sep 17 00:00:00 2001 From: erogluorhan Date: Tue, 16 Dec 2025 11:41:01 -0700 Subject: [PATCH 2/3] Fix cross section coords handling and further fix ICON reader dim parser --- uxarray/cross_sections/dataarray_accessor.py | 6 +++++- uxarray/io/_icon.py | 6 +++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/uxarray/cross_sections/dataarray_accessor.py b/uxarray/cross_sections/dataarray_accessor.py index a925c122e..b27e5c2b0 100644 --- a/uxarray/cross_sections/dataarray_accessor.py +++ b/uxarray/cross_sections/dataarray_accessor.py @@ -132,7 +132,11 @@ def __call__( data = np.moveaxis(filled, -1, dim_axis) # Build coords dict: keep everything except 'n_face' - coords = {d: self.uxda.coords[d] for d in self.uxda.coords if d != "n_face"} + coords = { + name: self.uxda.coords[name] + for name in self.uxda.coords + if name != "n_face" and "n_face" not in self.uxda.coords[name].dims + } # index along the arc coords[new_dim] = np.arange(steps) diff --git a/uxarray/io/_icon.py b/uxarray/io/_icon.py index ce92fd29b..9921535e0 100644 --- a/uxarray/io/_icon.py +++ b/uxarray/io/_icon.py @@ -11,17 +11,17 @@ def _icon_to_ugrid_dims(in_ds): # Coordinate-driven mappings if "vlat" in in_ds: source_dims_dict[in_ds["vlat"].dims[0]] = ugrid.NODE_DIM - elif "vlon" in in_ds: + if "vlon" in in_ds: source_dims_dict[in_ds["vlon"].dims[0]] = ugrid.NODE_DIM if "elat" in in_ds: source_dims_dict[in_ds["elat"].dims[0]] = ugrid.EDGE_DIM - elif "elon" in in_ds: + if "elon" in in_ds: source_dims_dict[in_ds["elon"].dims[0]] = ugrid.EDGE_DIM if "clat" in in_ds: source_dims_dict[in_ds["clat"].dims[0]] = ugrid.FACE_DIM - elif "clon" in in_ds: + if "clon" in in_ds: source_dims_dict[in_ds["clon"].dims[0]] = ugrid.FACE_DIM # Connectivity-driven mappings From bdcf02a7093a2e8e9b862b0f57a8c384bbf1f94d Mon Sep 17 00:00:00 2001 From: Rajeev Jain Date: Wed, 17 Dec 2025 11:36:43 -0600 Subject: [PATCH 3/3] test: add icon cross_section test case --- test/io/test_icon.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test/io/test_icon.py b/test/io/test_icon.py index a9c3cd457..e8344ff85 100644 --- a/test/io/test_icon.py +++ b/test/io/test_icon.py @@ -8,3 +8,17 @@ def test_read_icon_grid(gridpath): def test_read_icon_dataset(gridpath): grid_path = gridpath("icon", "R02B04", "icon_grid_0010_R02B04_G.nc") uxds = ux.open_dataset(grid_path, grid_path) + +def test_icon_cross_section(gridpath): + grid_path = gridpath("icon", "R02B04", "icon_grid_0010_R02B04_G.nc") + uxds = ux.open_dataset(grid_path, grid_path) + + # Test cross_section with cell_area variable + result = uxds.cell_area.cross_section(start=(0, -90), end=(0, 90)) + assert result is not None + assert len(result) == 100 # 100 steps by default + assert result.dims == ('steps',) + assert all(result.coords['lon'] == 0.0) # Constant longitude + assert result.coords['lat'].min() == -90.0 + assert result.coords['lat'].max() == 90.0 + assert result.attrs['units'] == 'steradian'