Skip to content

Commit

Permalink
Structured Grid Reader (#1040)
Browse files Browse the repository at this point in the history
* structured grid reader

* update userguide.rst

* update API and notebooks

* add tests

* update notebook

* add docstrings and update api reference

* fix failing tests

* add pooch to testing enviroment

* fix comment

* update readme to include structured grids

* update order in user guide

* update order in user guide

* update api ref

* update comment

* update notebooks

* update notebook

* update notebook

* address rachel's review
  • Loading branch information
philipc2 authored Oct 30, 2024
1 parent e30586f commit 948b0f5
Show file tree
Hide file tree
Showing 13 changed files with 646 additions and 68 deletions.
5 changes: 1 addition & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ recognizing unstructured grid model outputs. We picked the name "UXarray"

* ``Grid`` class for storing grid information and providing grid-specific functionality
* Support for reading UGRID, MPAS, ESMF, ICON, GEOS-CS, SCRIP, and EXODUS grid formats
* Support for reader structured (i.e. latitude longitude) grids
* Extension of xarray's ``DataArray`` and ``Dataset`` classe to support unstructured grid operations
* ``uxarray.UxDataArray`` inherits ``xarray.DataArray`` and is attached to a ``Grid`` instance through the ``.uxgrid`` accessor
* ``uxarray.UxDataset`` inherits ``xarray.Dataset`` and is attached to a ``Grid`` instance through the ``.uxgrid`` accessor
Expand All @@ -71,10 +72,6 @@ Raijin, and on several community platforms such as [Xarray GitHub
Repository](https://github.com/pydata/xarray/issues/4222). The UXarray team
is receptive to additional functionality requests.


* Support for arbitrary structured and unstructured grids on the sphere,
including latitude-longitude grids, grids with only partial coverage of
the sphere, and grids with concave faces.
* Support for finite volume and finite element outputs.
* Triangular decompositions.
* Calculation of supermeshes (consisting of grid lines from two input grids).
Expand Down
1 change: 1 addition & 0 deletions ci/environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ dependencies:
- scipy
- shapely
- spatialpandas
- pooch
- geopandas
- xarray
- asv
Expand Down
8 changes: 8 additions & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ I/O & Conversion
Grid.from_dataset
Grid.from_file
Grid.from_topology
Grid.from_structured
Grid.to_xarray
Grid.to_geodataframe
Grid.to_polycollection
Expand Down Expand Up @@ -205,6 +206,13 @@ Grid Accessor

UxDataset.uxgrid

I/O & Conversion
~~~~~~~~~~~~~~~~

.. autosummary::
:toctree: generated/

UxDataset.from_structured

Plotting
--------
Expand Down
256 changes: 256 additions & 0 deletions docs/user-guide/structured.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "4201b69329447989",
"metadata": {},
"source": [
"# Reading Structured Grids\n",
"\n",
"UXarray supports reading structured grids and representing them as unstructured grids. This user-guide section will discuss how to load in structured grids using UXarray."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "149f175e67dc1a07",
"metadata": {},
"outputs": [],
"source": [
"import uxarray as ux\n",
"import xarray as xr\n",
"import cartopy.crs as ccrs\n",
"\n",
"import warnings\n",
"\n",
"warnings.filterwarnings(\"ignore\")"
]
},
{
"cell_type": "markdown",
"id": "5a6a3b7f5930ab21",
"metadata": {},
"source": [
"## Data\n",
"\n",
"For this notebook, we will be using datasets from the [Xarray tutorial](https://docs.xarray.dev/en/stable/generated/xarray.tutorial.open_dataset.html)."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c63337e3083b6ca5",
"metadata": {},
"outputs": [],
"source": [
"ds_air_temp = xr.tutorial.open_dataset(\"air_temperature\")\n",
"ds_air_temp"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "bc0fd470a7f64504",
"metadata": {},
"outputs": [],
"source": [
"ds_ersstv5 = xr.tutorial.open_dataset(\"ersstv5\")\n",
"ds_ersstv5"
]
},
{
"cell_type": "markdown",
"id": "3696e3df89a57d96",
"metadata": {},
"source": [
"## Grid\n",
"\n",
"A structured grid can be converted into an unstructured grid by using the ``ux.Grid.from_structured()`` class method. An ``xarray.Dataset`` can be passed in, with CF-compliant longitude and latitude coordinates parsed and converted to an unstructured grid."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d16555e6ec8dee74",
"metadata": {},
"outputs": [],
"source": [
"uxgrid = ux.Grid.from_structured(ds_air_temp)\n",
"uxgrid"
]
},
{
"cell_type": "markdown",
"id": "1bbd88b0954b53f8",
"metadata": {},
"source": "You can also manually pass in longitude and latitude coordinates."
},
{
"cell_type": "code",
"execution_count": null,
"id": "d121ef33c3aaed90",
"metadata": {},
"outputs": [],
"source": [
"uxgrid = ux.Grid.from_structured(lon=ds_air_temp.lon, lat=ds_air_temp.lat)\n",
"uxgrid"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ab3c5ca428b99bdc",
"metadata": {},
"outputs": [],
"source": [
"uxgrid.plot(\n",
" title=\"Structured Grid loaded using UXarray\",\n",
" backend=\"matplotlib\",\n",
" width=1000,\n",
" coastline=True,\n",
")"
]
},
{
"cell_type": "markdown",
"id": "2ddf203ac8e9a0c2",
"metadata": {},
"source": [
"## Dataset\n",
"\n",
"If you have a dataset that contains data variables, you can convert the entire ``xarray.Dataset`` into a ``uxarray.UxDataset`` using the ``ux.UxDataset.from_structured()`` class method. "
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7ed9a15c25a14c3e",
"metadata": {},
"outputs": [],
"source": [
"uxds = ux.UxDataset.from_structured(ds_air_temp)\n",
"uxds"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8037fd0309774356",
"metadata": {},
"outputs": [],
"source": [
"uxds[\"air\"][0].plot(\n",
" title=\"Structured Grid with Data loaded using UXarray\",\n",
" backend=\"matplotlib\",\n",
" width=500,\n",
" coastline=True,\n",
")"
]
},
{
"cell_type": "markdown",
"id": "3badcc09659920b5",
"metadata": {},
"source": [
"## Limitations of Structured Grids\n",
"\n",
"Structured grids are a common choice in climate modeling due to their ease of implementation. However, they come with many limitations, which have been a driving factor in the adoption of unstructured grids.\n",
"\n",
"\n",
"\n",
"#### Limited Flexibility in Handling Complex Geometries\n",
"\n",
"Structured grids, with their regular and grid-aligned cells, struggle to capture complex geometries. This limitation forces modelers to use a higher number of grid points to approximate complex features, which can lead to increased computational costs and reduced simulation efficiency. Additionally, the inability to precisely represent intricate boundaries can result in inaccuracies in modeling climate processes that are influenced by these geographical features, potentially affecting the reliability of the simulation outcomes.\n",
"\n",
"#### Difficulty in Local Grid Refinement\n",
"\n",
"Climate phenomena like tropical cyclones, atmospheric fronts, and localized convection require high-resolution grids to be accurately modeled. Structured grids make it challenging to refine the grid locally in regions where such detailed resolution is needed without uniformly increasing the grid resolution across the entire model domain. This uniform refinement results in inefficient use of computational resources, as large areas of the model may have unnecessarily high resolution where it is not required.\n",
"\n",
"#### Pole Point Singularities\n",
"\n",
"Pole point singularities are a significant challenge in climate models that utilize structured latitude-longitude grids. In such grid systems, the lines of longitude converge as they approach the Earth's poles, causing the grid cells to become increasingly small near these regions. This convergence leads to several issues:\n",
"\n",
"- **Numerical Instability:** The drastically reduced cell size near the poles can cause numerical methods to become unstable, requiring smaller time steps to maintain accuracy and stability in simulations.\n",
"\n",
"- **Accuracy Problems:** The distortion of grid cell shapes near the poles can lead to inaccuracies in representing physical processes, such as atmospheric circulation and ocean currents, which are critical for realistic climate simulations.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b6b872c3341c3eba",
"metadata": {},
"outputs": [],
"source": [
"uxds = ux.UxDataset.from_structured(ds_ersstv5)"
]
},
{
"cell_type": "markdown",
"id": "556a04ca5c799cbb",
"metadata": {},
"source": "Below is a plot of Sea Surface Temperature on a structured grid."
},
{
"cell_type": "code",
"execution_count": null,
"id": "dd07e0e5848d5095",
"metadata": {},
"outputs": [],
"source": [
"uxds[\"sst\"][0].plot(\n",
" projection=ccrs.Orthographic(central_latitude=60),\n",
" periodic_elements=\"split\",\n",
" coastline=True,\n",
" grid=True,\n",
" title=\"SST Near North Pole\",\n",
")"
]
},
{
"cell_type": "markdown",
"id": "fd7779c434574e45",
"metadata": {},
"source": "If we expose the grid structure, we can observe the singularity at the poles."
},
{
"cell_type": "code",
"execution_count": null,
"id": "f9ded6909c6adc18",
"metadata": {},
"outputs": [],
"source": [
"uxds.uxgrid.plot(\n",
" projection=ccrs.Orthographic(central_latitude=60),\n",
" periodic_elements=\"split\",\n",
" width=600,\n",
" linewidth=1,\n",
" coastline=True,\n",
" title=\"Grid Near NorthPole\",\n",
")"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 2
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython2",
"version": "2.7.6"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
7 changes: 6 additions & 1 deletion docs/userguide.rst
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ These user guides provide detailed explanations of the core functionality in UXa
`Advanced Plotting Techniques <user-guide/advanced-plotting.ipynb>`_
Deeper dive into getting the most out of UXarray's plotting functionality


`Subsetting <user-guide/subset.ipynb>`_
Select specific regions of a grid

Expand All @@ -64,13 +65,16 @@ These user guides provide detailed explanations of the core functionality in UXa
`Face Area Calculations <user-guide/area_calc.ipynb>`_
Methods for computing the area of each face

`Structured Grids <user-guide/structured.ipynb>`_
Loading structured (latitude-longitude) grids

`Dual Mesh Construction <user-guide/dual-mesh.ipynb>`_
Construct the Dual Mesh of an unstructured grid

Supplementary Guides
--------------------

These user guides provide additional detail about specific features in UXarray.
These user guides provide additional details about specific features in UXarray.

`Compatibility with HoloViz Tools <user-guide/holoviz.ipynb>`_
Use UXarray with HoloViz tools
Expand All @@ -96,4 +100,5 @@ These user guides provide additional detail about specific features in UXarray.
user-guide/tree_structures.ipynb
user-guide/area_calc.ipynb
user-guide/dual-mesh.ipynb
user-guide/structured.ipynb
user-guide/holoviz.ipynb
36 changes: 36 additions & 0 deletions test/test_structured.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import uxarray as ux
import xarray as xr
import pytest


@pytest.mark.parametrize("ds_name", ["air_temperature", "ersstv5"])
def test_read_structured_grid_from_ds(ds_name):
ds = xr.tutorial.open_dataset(ds_name)
uxgrid = ux.Grid.from_structured(ds)

assert uxgrid.n_face == ds.sizes['lon'] * ds.sizes['lat']

assert uxgrid.validate()


@pytest.mark.parametrize("ds_name", ["air_temperature", "ersstv5"])
def test_read_structured_grid_from_latlon(ds_name):
ds = xr.tutorial.open_dataset(ds_name)
uxgrid = ux.Grid.from_structured(lon=ds.lon, lat=ds.lat)
assert uxgrid.n_face == ds.sizes['lon'] * ds.sizes['lat']
assert uxgrid.validate()

@pytest.mark.parametrize("ds_name", ["air_temperature", "ersstv5"])
def test_read_structured_uxds_from_ds(ds_name):
# Load the dataset using xarray's tutorial module
ds = xr.tutorial.open_dataset(ds_name)

# Create a uxarray Grid from the structured dataset
uxds = ux.UxDataset.from_structured(ds)

assert "n_face" in uxds.dims

assert "lon" not in uxds.dims
assert "lat" not in uxds.dims

assert uxds.uxgrid.validate()
4 changes: 0 additions & 4 deletions uxarray/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
# Sets the version of uxarray currently installeds
# Attempt to import the needed modules

import uxarray.constants

from .core.api import open_grid, open_dataset, open_mfdataset

from .core.dataset import UxDataset
Expand Down
Loading

0 comments on commit 948b0f5

Please sign in to comment.