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

Fast Constant Latitude Cross Sections #989

Merged
merged 33 commits into from
Oct 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
42e3036
initial work on fast cross-sections
philipc2 Oct 4, 2024
5c8d9d5
add API
philipc2 Oct 4, 2024
aeb544b
add section for cross sections
philipc2 Oct 4, 2024
a2b247f
parallel numba
philipc2 Oct 4, 2024
82e49ea
update notebook
philipc2 Oct 7, 2024
79958f6
Merge branch 'main' into philipc2/vectorized-cross-sections
philipc2 Oct 7, 2024
b5965e6
add docstrings
philipc2 Oct 7, 2024
46b72f9
Merge branch 'philipc2/vectorized-cross-sections' of https://github.c…
philipc2 Oct 7, 2024
d2f0a86
add benchmark
philipc2 Oct 7, 2024
995bc97
update benchmark
philipc2 Oct 7, 2024
ac64eeb
update quad hex grid
philipc2 Oct 7, 2024
8376f17
add tests
philipc2 Oct 7, 2024
d539dee
update tests
philipc2 Oct 7, 2024
0174977
update quad hex grid
philipc2 Oct 7, 2024
f35b999
update name of intersection func
philipc2 Oct 7, 2024
2adbcde
update value error
philipc2 Oct 7, 2024
b769d67
update user guide
philipc2 Oct 7, 2024
b974436
add docstring for uxda
philipc2 Oct 7, 2024
c1d0538
update docstring of intersections
philipc2 Oct 7, 2024
d2ab10f
fix benchmark
philipc2 Oct 7, 2024
884139e
Merge branch 'main' into philipc2/vectorized-cross-sections
philipc2 Oct 7, 2024
32dde5f
add tests for north and south pole
philipc2 Oct 7, 2024
0362afe
Merge branch 'main' into philipc2/vectorized-cross-sections
philipc2 Oct 7, 2024
d7c9714
Update intersections.py
philipc2 Oct 7, 2024
9178f0f
Merge branch 'main' into philipc2/vectorized-cross-sections
philipc2 Oct 8, 2024
67f19d9
Merge branch 'main' into philipc2/vectorized-cross-sections
philipc2 Oct 8, 2024
07db497
update user guide
philipc2 Oct 8, 2024
4d4d167
Merge branch 'main' into philipc2/vectorized-cross-sections
philipc2 Oct 8, 2024
61f6383
Delete docs/user-guide/grid.nc
philipc2 Oct 9, 2024
5dbb557
use accessor
philipc2 Oct 9, 2024
7b78daa
update notebook
philipc2 Oct 9, 2024
abf2ba1
merge main
philipc2 Oct 10, 2024
9f862e5
fix docs
philipc2 Oct 10, 2024
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
10 changes: 10 additions & 0 deletions benchmarks/mpas_ocean.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

import uxarray as ux

import numpy as np

current_path = Path(os.path.dirname(os.path.realpath(__file__)))

data_var = 'bottomDepth'
Expand Down Expand Up @@ -164,3 +166,11 @@ def teardown(self, resolution):
def time_check_norm(self, resolution):
from uxarray.grid.validation import _check_normalization
_check_normalization(self.uxgrid)


class CrossSections(DatasetBenchmark):
param_names = DatasetBenchmark.param_names + ['n_lat']
params = DatasetBenchmark.params + [[1, 2, 4, 8]]
def time_constant_lat_fast(self, resolution, n_lat):
for lat in np.linspace(-89, 89, n_lat):
self.uxds.uxgrid.constant_latitude_cross_section(lat, method='fast')
24 changes: 24 additions & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,30 @@ UxDataArray
UxDataArray.subset.bounding_circle


Cross Sections
--------------


Grid
~~~~

.. autosummary::
:toctree: generated/
:template: autosummary/accessor_method.rst

Grid.cross_section
Grid.cross_section.constant_latitude

UxDataArray
~~~~~~~~~~~

.. autosummary::
:toctree: generated/
:template: autosummary/accessor_method.rst

UxDataArray.cross_section
UxDataArray.cross_section.constant_latitude

Remapping
---------

Expand Down
208 changes: 208 additions & 0 deletions docs/user-guide/cross-sections.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "4a432a8bf95d9cdb",
"metadata": {},
"source": [
"# Cross-Sections\n",
"\n",
"This section demonstrates how to extract cross-sections from an unstructured grid using UXarray, which allows the analysis and visualization across slices of grids.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b35ba4a2c30750e4",
"metadata": {
"ExecuteTime": {
"end_time": "2024-10-09T17:50:50.244285Z",
"start_time": "2024-10-09T17:50:50.239653Z"
}
},
"outputs": [],
"source": [
"import uxarray as ux\n",
"import geoviews.feature as gf\n",
"\n",
"import cartopy.crs as ccrs\n",
"import geoviews as gv\n",
"\n",
"projection = ccrs.Robinson()"
]
},
{
"cell_type": "markdown",
"id": "395a3db7-495c-4cff-b733-06bbe522a604",
"metadata": {},
"source": [
"## Data"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b4160275c09fe6b0",
"metadata": {
"ExecuteTime": {
"end_time": "2024-10-09T17:50:51.217211Z",
"start_time": "2024-10-09T17:50:50.540946Z"
}
},
"outputs": [],
"source": [
"base_path = \"../../test/meshfiles/ugrid/outCSne30/\"\n",
"grid_path = base_path + \"outCSne30.ug\"\n",
"data_path = base_path + \"outCSne30_vortex.nc\"\n",
"\n",
"uxds = ux.open_dataset(grid_path, data_path)\n",
"uxds[\"psi\"].plot(\n",
" cmap=\"inferno\",\n",
" periodic_elements=\"split\",\n",
" projection=projection,\n",
" title=\"Global Plot\",\n",
")"
]
},
{
"cell_type": "markdown",
"id": "a7a40958-0a4d-47e4-9e38-31925261a892",
"metadata": {},
"source": [
"## Constant Latitude\n",
"\n",
"Cross-sections along constant latitude lines can be obtained using the ``.cross_section.constant_latitude`` method, available for both ``ux.Grid`` and ``ux.DataArray`` objects. This functionality allows users to extract and analyze slices of data at specified latitudes, providing insights into variations along horizontal sections of the grid.\n"
]
},
{
"cell_type": "markdown",
"id": "2fbe9f6e5bb59a17",
"metadata": {},
"source": [
"For example, we can obtain a cross-section at 30 degrees latitude by doing the following:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3775daa1-2f1d-4738-bab5-2b69ebd689d9",
"metadata": {
"ExecuteTime": {
"end_time": "2024-10-09T17:50:53.093314Z",
"start_time": "2024-10-09T17:50:53.077719Z"
}
},
"outputs": [],
"source": [
"lat = 30\n",
"\n",
"uxda_constant_lat = uxds[\"psi\"].cross_section.constant_latitude(lat)"
]
},
{
"cell_type": "markdown",
"id": "dcec0b96b92e7f4",
"metadata": {},
"source": [
"Since the result is a new ``UxDataArray``, we can directly plot the result to see the cross-section."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "484b77a6-86da-4395-9e63-f5ac56e37deb",
"metadata": {},
"outputs": [],
"source": [
"(\n",
" uxda_constant_lat.plot(\n",
" rasterize=False,\n",
" backend=\"bokeh\",\n",
" cmap=\"inferno\",\n",
" projection=projection,\n",
" global_extent=True,\n",
" coastline=True,\n",
" title=f\"Cross Section at {lat} degrees latitude\",\n",
" )\n",
" * gf.grid(projection=projection)\n",
")"
]
},
{
"cell_type": "markdown",
"id": "c7cca7de4722c121",
"metadata": {},
"source": [
"You can also perform operations on the cross-section, such as taking the mean."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1cbee722-34a4-4e67-8e22-f393d7d36c99",
"metadata": {},
"outputs": [],
"source": [
"print(f\"Global Mean: {uxds['psi'].data.mean()}\")\n",
"print(f\"Mean at {lat} degrees lat: {uxda_constant_lat.data.mean()}\")"
]
},
{
"cell_type": "markdown",
"id": "c4a7ee25-0b60-470f-bab7-92ff70563076",
"metadata": {},
"source": [
"## Constant Longitude"
]
},
{
"cell_type": "markdown",
"id": "9fcc8ec5-c6a8-4bde-a33d-7f37f9116ee2",
"metadata": {},
"source": [
"```{warning}\n",
"Constant longitude cross sections are not yet supported.\n",
"```"
]
},
{
"cell_type": "markdown",
"id": "54d9eff1-67f1-4691-a3b0-1ee0c874c98f",
"metadata": {},
"source": [
"## Arbitrary Great Circle Arc (GCA)"
]
},
{
"cell_type": "markdown",
"id": "ea94ff9f-fe86-470d-813b-45f32a633ffc",
"metadata": {},
"source": [
"```{warning}\n",
"Arbitrary great circle arc cross sections are not yet supported.\n",
"```"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.8"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
4 changes: 4 additions & 0 deletions docs/userguide.rst
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ These user guides provide detailed explanations of the core functionality in UXa
`Subsetting <user-guide/subset.ipynb>`_
Select specific regions of a grid

`Cross-Sections <user-guide/cross-sections.ipynb>`_
Select cross-sections of a grid

`Remapping <user-guide/remapping.ipynb>`_
Remap (a.k.a Regrid) between unstructured grids

Expand Down Expand Up @@ -82,6 +85,7 @@ These user guides provide additional detail about specific features in UXarray.
user-guide/mpl.ipynb
user-guide/advanced-plotting.ipynb
user-guide/subset.ipynb
user-guide/cross-sections.ipynb
user-guide/remapping.ipynb
user-guide/topological-aggregations.ipynb
user-guide/calculus.ipynb
Expand Down
Binary file modified test/meshfiles/ugrid/quad-hexagon/grid.nc
Binary file not shown.
94 changes: 94 additions & 0 deletions test/test_cross_sections.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import uxarray as ux
import pytest
from pathlib import Path
import os

import numpy.testing as nt

# Define the current path and file paths for grid and data
current_path = Path(os.path.dirname(os.path.realpath(__file__)))
quad_hex_grid_path = current_path / 'meshfiles' / "ugrid" / "quad-hexagon" / 'grid.nc'
quad_hex_data_path = current_path / 'meshfiles' / "ugrid" / "quad-hexagon" / 'data.nc'

cube_sphere_grid = current_path / "meshfiles" / "geos-cs" / "c12" / "test-c12.native.nc4"



class TestQuadHex:
"""The quad hexagon grid contains four faces.

Top Left Face: Index 1

Top Right Face: Index 2

Bottom Left Face: Index 0

Bottom Right Face: Index 3

The top two faces intersect a constant latitude of 0.1

The bottom two faces intersect a constant latitude of -0.1

All four faces intersect a constant latitude of 0.0
"""

def test_constant_lat_cross_section_grid(self):
uxgrid = ux.open_grid(quad_hex_grid_path)

grid_top_two = uxgrid.cross_section.constant_latitude(lat=0.1)

assert grid_top_two.n_face == 2

grid_bottom_two = uxgrid.cross_section.constant_latitude(lat=-0.1)

assert grid_bottom_two.n_face == 2

grid_all_four = uxgrid.cross_section.constant_latitude(lat=0.0)

assert grid_all_four.n_face == 4

with pytest.raises(ValueError):
# no intersections found at this line
uxgrid.cross_section.constant_latitude(lat=10.0)


def test_constant_lat_cross_section_uxds(self):
Copy link
Contributor

Choose a reason for hiding this comment

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

Please add some test case when lattitude is around pole area,

Copy link
Member Author

Choose a reason for hiding this comment

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

I've added tests for both poles for a cube-sphere grid.

uxds = ux.open_dataset(quad_hex_grid_path, quad_hex_data_path)

da_top_two = uxds['t2m'].cross_section.constant_latitude(lat=0.1)

nt.assert_array_equal(da_top_two.data, uxds['t2m'].isel(n_face=[1, 2]).data)

da_bottom_two = uxds['t2m'].cross_section.constant_latitude(lat=-0.1)

nt.assert_array_equal(da_bottom_two.data, uxds['t2m'].isel(n_face=[0, 3]).data)

da_all_four = uxds['t2m'].cross_section.constant_latitude(lat=0.0)

nt.assert_array_equal(da_all_four.data , uxds['t2m'].data)

with pytest.raises(ValueError):
# no intersections found at this line
uxds['t2m'].cross_section.constant_latitude(lat=10.0)


class TestGeosCubeSphere:
def test_north_pole(self):
uxgrid = ux.open_grid(cube_sphere_grid)

lats = [89.85, 89.9, 89.95, 89.99]

for lat in lats:
cross_grid = uxgrid.cross_section.constant_latitude(lat=lat)
# Cube sphere grid should have 4 faces centered around the pole
assert cross_grid.n_face == 4

def test_south_pole(self):
uxgrid = ux.open_grid(cube_sphere_grid)

lats = [-89.85, -89.9, -89.95, -89.99]

for lat in lats:
cross_grid = uxgrid.cross_section.constant_latitude(lat=lat)
# Cube sphere grid should have 4 faces centered around the pole
assert cross_grid.n_face == 4
2 changes: 2 additions & 0 deletions uxarray/core/dataarray.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
from uxarray.plot.accessor import UxDataArrayPlotAccessor
from uxarray.subset import DataArraySubsetAccessor
from uxarray.remap import UxDataArrayRemapAccessor
from uxarray.cross_sections import UxDataArrayCrossSectionAccessor
from uxarray.core.aggregation import _uxda_grid_aggregate

import warnings
Expand Down Expand Up @@ -85,6 +86,7 @@ def __init__(self, *args, uxgrid: Grid = None, **kwargs):
plot = UncachedAccessor(UxDataArrayPlotAccessor)
subset = UncachedAccessor(DataArraySubsetAccessor)
remap = UncachedAccessor(UxDataArrayRemapAccessor)
cross_section = UncachedAccessor(UxDataArrayCrossSectionAccessor)

def _repr_html_(self) -> str:
if OPTIONS["display_style"] == "text":
Expand Down
7 changes: 7 additions & 0 deletions uxarray/cross_sections/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from .dataarray_accessor import UxDataArrayCrossSectionAccessor
from .grid_accessor import GridCrossSectionAccessor

__all__ = (
"GridCrossSectionAccessor",
"UxDataArrayCrossSectionAccessor",
)
Loading
Loading