Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Inverse Face Indices to Subsetted Grids #1122

Merged
merged 20 commits into from
Jan 9, 2025
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ Indexing
:toctree: generated/

Grid.isel
Grid.inverse_indices

Dimensions
~~~~~~~~~~
Expand Down
99 changes: 98 additions & 1 deletion docs/user-guide/subset.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@
"cell_type": "markdown",
philipc2 marked this conversation as resolved.
Show resolved Hide resolved
"metadata": {
"collapsed": false,
"jp-MarkdownHeadingCollapsed": true,
"jupyter": {
"outputs_hidden": false
}
Expand Down Expand Up @@ -553,6 +554,102 @@
"print(\"Bounding Box Mean: \", bbox_subset_nodes.values.mean())\n",
"print(\"Bounding Circle Mean: \", bcircle_subset.values.mean())"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Retrieving Orignal Grid Indices"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Sometimes having the original grids' indices is useful. These indices can be stored within the subset with the `inverse_indices` variable. This can be used to store the indices of the original face centers, edge centers, and node indices. This variable can be used within the subset as follows:\n",
"\n",
"* Passing in `True`, which will store the face center indices\n",
"* Passing in a list of which indices to store, along with `True`, to indicate what kind of original grid indices to store.\n",
" * Options for which indices to select include: `face`, `node`, and `edge`\n",
"\n",
"This currently only works when the element is `face centers`. Elements `nodes` and `edge centers` will be supported in the future."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"subset_indices = uxds[\"relhum_200hPa\"][0].subset.bounding_circle(\n",
" center_coord,\n",
" r,\n",
" element=\"face centers\",\n",
" inverse_indices=([\"face\", \"node\", \"edge\"], True),\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"These indices can be retrieve through the grid:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"subset_indices.uxgrid.inverse_indices"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Determining if a Grid is a Subset"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"To check if a Grid (or dataset using `.uxgrid`) is a subset, we can use `Grid.is_subset`, which will return either `True` or `False`, depending on whether the `Grid` is a subset. Since `subset_indices` is a subset, using this feature we will return `True`:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"subset_indices.uxgrid.is_subset"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The file we have been using to create these subsets, `uxds`, is not a subset, so using the same call we will return `False:`"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"uxds.uxgrid.is_subset"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
Expand All @@ -571,7 +668,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.9"
"version": "3.12.7"
}
},
"nbformat": 4,
Expand Down
34 changes: 32 additions & 2 deletions test/test_subset.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,19 +104,49 @@ def test_grid_bounding_box_subset():
bbox_antimeridian[0], bbox_antimeridian[1], element=element)




def test_uxda_isel():
uxds = ux.open_dataset(GRID_PATHS[0], DATA_PATHS[0])

sub = uxds['bottomDepth'].isel(n_face=[1, 2, 3])

assert len(sub) == 3


def test_uxda_isel_with_coords():
uxds = ux.open_dataset(GRID_PATHS[0], DATA_PATHS[0])
uxds = uxds.assign_coords({"lon_face": uxds.uxgrid.face_lon})
sub = uxds['bottomDepth'].isel(n_face=[1, 2, 3])

assert "lon_face" in sub.coords
assert len(sub.coords['lon_face']) == 3


def test_inverse_indices():
philipc2 marked this conversation as resolved.
Show resolved Hide resolved
grid = ux.open_grid(GRID_PATHS[0])

# Test nearest neighbor subsetting
coord = [0, 0]
subset = grid.subset.nearest_neighbor(coord, k=1, element="face centers", inverse_indices=True)

assert subset.inverse_indices is not None

# Test bounding box subsetting
box = [(-10, 10), (-10, 10)]
subset = grid.subset.bounding_box(box[0], box[1], element="face centers", inverse_indices=True)

assert subset.inverse_indices is not None

# Test bounding circle subsetting
center_coord = [0, 0]
subset = grid.subset.bounding_circle(center_coord, r=10, element="face centers", inverse_indices=True)

assert subset.inverse_indices is not None

# Ensure code raises exceptions when the element is edges or nodes or inverse_indices is incorrect
assert pytest.raises(Exception, grid.subset.bounding_circle, center_coord, r=10, element="edge centers", inverse_indices=True)
assert pytest.raises(Exception, grid.subset.bounding_circle, center_coord, r=10, element="nodes", inverse_indices=True)
assert pytest.raises(ValueError, grid.subset.bounding_circle, center_coord, r=10, element="face center", inverse_indices=(['not right'], True))

# Test isel directly
subset = grid.isel(n_face=[1], inverse_indices=True)
philipc2 marked this conversation as resolved.
Show resolved Hide resolved
assert subset.inverse_indices is not None
14 changes: 10 additions & 4 deletions uxarray/core/dataarray.py
Original file line number Diff line number Diff line change
Expand Up @@ -1035,7 +1035,7 @@ def _edge_centered(self) -> bool:
"n_edge" dimension)"""
return "n_edge" in self.dims

def isel(self, ignore_grid=False, *args, **kwargs):
def isel(self, ignore_grid=False, inverse_indices=False, *args, **kwargs):
"""Grid-informed implementation of xarray's ``isel`` method, which
enables indexing across grid dimensions.

Expand Down Expand Up @@ -1069,11 +1069,17 @@ def isel(self, ignore_grid=False, *args, **kwargs):
raise ValueError("Only one grid dimension can be sliced at a time")

if "n_node" in kwargs:
sliced_grid = self.uxgrid.isel(n_node=kwargs["n_node"])
sliced_grid = self.uxgrid.isel(
n_node=kwargs["n_node"], inverse_indices=inverse_indices
)
elif "n_edge" in kwargs:
sliced_grid = self.uxgrid.isel(n_edge=kwargs["n_edge"])
sliced_grid = self.uxgrid.isel(
n_edge=kwargs["n_edge"], inverse_indices=inverse_indices
)
else:
sliced_grid = self.uxgrid.isel(n_face=kwargs["n_face"])
sliced_grid = self.uxgrid.isel(
n_face=kwargs["n_face"], inverse_indices=inverse_indices
)

return self._slice_from_grid(sliced_grid)

Expand Down
20 changes: 15 additions & 5 deletions uxarray/cross_sections/dataarray_accessor.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from __future__ import annotations


from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Union, List, Set

if TYPE_CHECKING:
pass
Expand All @@ -22,7 +22,9 @@ def __repr__(self):

return prefix + methods_heading

def constant_latitude(self, lat: float):
def constant_latitude(
self, lat: float, inverse_indices: Union[List[str], Set[str], bool] = False
):
"""Extracts a cross-section of the data array by selecting all faces that
intersect with a specified line of constant latitude.

Expand All @@ -31,6 +33,9 @@ def constant_latitude(self, lat: float):
lat : float
The latitude at which to extract the cross-section, in degrees.
Must be between -90.0 and 90.0
inverse_indices : Union[List[str], Set[str], bool], optional
Indicates whether to store the original grids indices. Passing `True` stores the original face centers,
other reverse indices can be stored by passing any or all of the following: (["face", "edge", "node"], True)

Returns
-------
Expand Down Expand Up @@ -60,9 +65,11 @@ def constant_latitude(self, lat: float):

faces = self.uxda.uxgrid.get_faces_at_constant_latitude(lat)

return self.uxda.isel(n_face=faces)
return self.uxda.isel(n_face=faces, inverse_indices=inverse_indices)

def constant_longitude(self, lon: float):
def constant_longitude(
self, lon: float, inverse_indices: Union[List[str], Set[str], bool] = False
):
"""Extracts a cross-section of the data array by selecting all faces that
intersect with a specified line of constant longitude.

Expand All @@ -71,6 +78,9 @@ def constant_longitude(self, lon: float):
lon : float
The latitude at which to extract the cross-section, in degrees.
Must be between -180.0 and 180.0
inverse_indices : Union[List[str], Set[str], bool], optional
Indicates whether to store the original grids indices. Passing `True` stores the original face centers,
other reverse indices can be stored by passing any or all of the following: (["face", "edge", "node"], True)

Returns
-------
Expand Down Expand Up @@ -102,7 +112,7 @@ def constant_longitude(self, lon: float):
lon,
)

return self.uxda.isel(n_face=faces)
return self.uxda.isel(n_face=faces, inverse_indices=inverse_indices)

def gca(self, *args, **kwargs):
raise NotImplementedError
Expand Down
18 changes: 15 additions & 3 deletions uxarray/cross_sections/grid_accessor.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from __future__ import annotations


from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Union, List, Set

if TYPE_CHECKING:
from uxarray.grid import Grid
Expand All @@ -25,6 +25,7 @@ def constant_latitude(
self,
lat: float,
return_face_indices: bool = False,
inverse_indices: Union[List[str], Set[str], bool] = False,
):
"""Extracts a cross-section of the grid by selecting all faces that
intersect with a specified line of constant latitude.
Expand All @@ -36,6 +37,9 @@ def constant_latitude(
Must be between -90.0 and 90.0
return_face_indices : bool, optional
If True, also returns the indices of the faces that intersect with the line of constant latitude.
inverse_indices : Union[List[str], Set[str], bool], optional
Indicates whether to store the original grids indices. Passing `True` stores the original face centers,
other reverse indices can be stored by passing any or all of the following: (["face", "edge", "node"], True)

Returns
-------
Expand Down Expand Up @@ -66,7 +70,9 @@ def constant_latitude(
if len(faces) == 0:
raise ValueError(f"No intersections found at lat={lat}.")

grid_at_constant_lat = self.uxgrid.isel(n_face=faces)
grid_at_constant_lat = self.uxgrid.isel(
n_face=faces, inverse_indices=inverse_indices
)

if return_face_indices:
return grid_at_constant_lat, faces
Expand All @@ -77,6 +83,7 @@ def constant_longitude(
self,
lon: float,
return_face_indices: bool = False,
inverse_indices: Union[List[str], Set[str], bool] = False,
):
"""Extracts a cross-section of the grid by selecting all faces that
intersect with a specified line of constant longitude.
Expand All @@ -88,6 +95,9 @@ def constant_longitude(
Must be between -90.0 and 90.0
return_face_indices : bool, optional
If True, also returns the indices of the faces that intersect with the line of constant longitude.
inverse_indices : Union[List[str], Set[str], bool], optional
Indicates whether to store the original grids indices. Passing `True` stores the original face centers,
other reverse indices can be stored by passing any or all of the following: (["face", "edge", "node"], True)

Returns
-------
Expand Down Expand Up @@ -117,7 +127,9 @@ def constant_longitude(
if len(faces) == 0:
raise ValueError(f"No intersections found at lon={lon}")

grid_at_constant_lon = self.uxgrid.isel(n_face=faces)
grid_at_constant_lon = self.uxgrid.isel(
n_face=faces, inverse_indices=inverse_indices
)

if return_face_indices:
return grid_at_constant_lon, faces
Expand Down
Loading
Loading