Skip to content

Commit 263713e

Browse files
authored
Merge branch 'main' into rajeeja/zonal-mean-analytic-band
2 parents bd36a75 + 72b88f3 commit 263713e

File tree

16 files changed

+1570
-186
lines changed

16 files changed

+1570
-186
lines changed

docs/_static/examples/gradient/fig.svg

Lines changed: 1 addition & 0 deletions
Loading

docs/user-guide/calculus.ipynb

Lines changed: 0 additions & 42 deletions
This file was deleted.

docs/user-guide/gradients.ipynb

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"id": "c51bdab8875e6859",
6+
"metadata": {},
7+
"source": [
8+
"## Gradients\n",
9+
"\n",
10+
"This user-guide notebook showcases how to compute the gradient of a data variable."
11+
]
12+
},
13+
{
14+
"cell_type": "code",
15+
"id": "initial_id",
16+
"metadata": {},
17+
"source": [
18+
"import holoviews as hv\n",
19+
"import numpy as np\n",
20+
"\n",
21+
"import uxarray as ux\n",
22+
"\n",
23+
"hv.extension(\"bokeh\")"
24+
],
25+
"outputs": [],
26+
"execution_count": null
27+
},
28+
{
29+
"cell_type": "markdown",
30+
"id": "ac4c02fb09053a2d",
31+
"metadata": {},
32+
"source": [
33+
"## Data\n",
34+
"\n",
35+
"This notebook uses a subset of a 30km MPAS stmopshere grid, taken centered at 45 degrees longitiude and 0 degrees latitude with a radius of 2 degrees. \n",
36+
"- `face_lon`: Longitude at cell-centers\n",
37+
"- `face_lat`: Latitude at cell-centers\n",
38+
"- `gaussian`: Gaussian initialized at the center of the grid\n",
39+
"- `inverse_gaussian`: Inverse of the gaussian above. "
40+
]
41+
},
42+
{
43+
"cell_type": "code",
44+
"id": "51f3db3a8821295c",
45+
"metadata": {},
46+
"source": [
47+
"base_path = \"../../test/meshfiles/mpas/dyamond-30km/\"\n",
48+
"grid_path = base_path + \"gradient_grid_subset.nc\"\n",
49+
"data_path = base_path + \"gradient_data_subset.nc\"\n",
50+
"uxds = ux.open_dataset(grid_path, data_path)\n",
51+
"uxds"
52+
],
53+
"outputs": [],
54+
"execution_count": null
55+
},
56+
{
57+
"cell_type": "markdown",
58+
"id": "5614a18b0a8033ec",
59+
"metadata": {},
60+
"source": [
61+
"## Gradient Computation\n",
62+
"\n",
63+
"### Background\n",
64+
"\n",
65+
"Suppose our scalar field values are stored on the faces of a hexagonal grid and we wish to approximate the gradient at the cell center \\$C^\\*\\$. We leverage the **Green–Gauss theorem**:\n",
66+
"\n",
67+
"$$\n",
68+
"\\int_V \\nabla\\phi \\, dV = \\oint_{\\partial V} \\phi \\, dS\n",
69+
"$$\n",
70+
"\n",
71+
"To apply this:\n",
72+
"\n",
73+
"1. Construct a closed control volume around \\$C^\\*\\$ by connecting the centroids of its neighboring cells.\n",
74+
"2. Approximate the surface integral on each face using a midpoint (or trapezoidal) rule.\n",
75+
"3. Sum the contributions and normalize by the cell volume.\n",
76+
"\n",
77+
"While the schematic below is drawn on a “flat” hexagon, in practice our grid resides on the sphere, so all lengths \\$l\\_{ij}\\$ and normals \\$\\mathbf{n}\\_{ij}\\$ are computed on the curved surface.\n",
78+
"\n",
79+
"\n",
80+
"### Implementation\n",
81+
"\n",
82+
"In a finite-volume context, the gradient of a scalar field \\$\\phi\\$ is obtained by summing fluxes across each cell face and dividing by the cell’s volume.\n",
83+
"\n",
84+
"| **Input** | **Usage** | **Output** |\n",
85+
"| --------------------- | :------------: | --------------------------- |\n",
86+
"| Scalar field \\$\\phi\\$ | `φ.gradient()` | Vector field \\$\\nabla\\phi\\$ |\n",
87+
"\n",
88+
"#### Finite-volume discretization\n",
89+
"\n",
90+
"$$\n",
91+
"\\int_V \\nabla\\phi \\, dV = \\oint_{\\partial V} \\phi \\, dS\n",
92+
"$$\n",
93+
"\n",
94+
"#### Discrete gradient at cell center \\$C^\\*\\$\n",
95+
"\n",
96+
"$$\n",
97+
"\\nabla\\phi(C^*)\n",
98+
"\\;\\approx\\;\n",
99+
"\\frac{1}{\\mathrm{Vol}(C^*)}\n",
100+
"\\sum_{f\\in\\partial C^*}\n",
101+
"\\left(\n",
102+
" \\frac{\\phi(C_i) + \\phi(C_j)}{2}\n",
103+
"\\right)\n",
104+
"\\;l_{ij}\\;\\mathbf{n}_{ij}\n",
105+
"$$\n",
106+
"\n",
107+
"<div style=\"text-align: center;\">\n",
108+
" <img src=\"../_static/examples/gradient/fig.svg\" alt=\"Gradient schematic\" width=\"300\"/>\n",
109+
"</div>\n",
110+
"\n",
111+
"\n",
112+
"\n",
113+
"\n",
114+
"\n",
115+
"\n",
116+
"\n",
117+
"\n",
118+
"\n"
119+
]
120+
},
121+
{
122+
"cell_type": "markdown",
123+
"id": "57434fa8-bc70-47aa-a40c-420fadb8c9fa",
124+
"metadata": {},
125+
"source": [
126+
"## Usage\n",
127+
"\n",
128+
"Gradients can be computed using the `UxDataArray.gradient()` method on a face-centered data variable. \n"
129+
]
130+
},
131+
{
132+
"cell_type": "code",
133+
"id": "584726e9-6d27-45f1-a39a-6d07c2b9ca06",
134+
"metadata": {},
135+
"source": [
136+
"grad_lat = uxds[\"face_lat\"].gradient()\n",
137+
"grad_lon = uxds[\"face_lon\"].gradient()\n",
138+
"grad_gauss = uxds[\"gaussian\"].gradient()\n",
139+
"grad_inv_gauss = uxds[\"inverse_gaussian\"].gradient()"
140+
],
141+
"outputs": [],
142+
"execution_count": null
143+
},
144+
{
145+
"cell_type": "markdown",
146+
"id": "98448402-76ed-492f-8ddd-5b6e25db8e8d",
147+
"metadata": {},
148+
"source": [
149+
"Examining one of the outputs, we find that the `zonal_gradient` and `meridional_gradient` data variables store the rate of change along longitude (east–west) and latitude (north–south), respectively."
150+
]
151+
},
152+
{
153+
"cell_type": "markdown",
154+
"id": "7c38c68e-18e5-4b3a-b4e2-eb3806c3427a",
155+
"metadata": {},
156+
"source": [
157+
"## Plotting\n",
158+
"\n",
159+
"To visualuze the gradients, we can represent them as a `hv.VectorField` and overlay the vectors on top of the original data variable. Below is a utility function that can be used."
160+
]
161+
},
162+
{
163+
"cell_type": "code",
164+
"id": "1b5e1fa5-a455-4fb6-b637-38ab3b948e13",
165+
"metadata": {},
166+
"source": [
167+
"def plot_gradient_vectors(uxda_grad, **kwargs):\n",
168+
" \"\"\"\n",
169+
" Plots gradient vectors using HoloViews\n",
170+
" \"\"\"\n",
171+
" uxgrid = uxda_grad.uxgrid\n",
172+
" mag = np.hypot(uxda_grad.zonal_gradient, uxda_grad.meridional_gradient)\n",
173+
" angle = np.arctan2(uxda_grad.meridional_gradient, uxda_grad.zonal_gradient)\n",
174+
"\n",
175+
" return hv.VectorField(\n",
176+
" (uxgrid.face_lon, uxgrid.face_lat, angle, mag), **kwargs\n",
177+
" ).opts(magnitude=\"Magnitude\")"
178+
],
179+
"outputs": [],
180+
"execution_count": null
181+
},
182+
{
183+
"cell_type": "code",
184+
"id": "7a8ae073-3dd7-48dd-b152-58fa5265d775",
185+
"metadata": {},
186+
"source": [
187+
"# Overlay the gradient vector field on top of the original data variable\n",
188+
"p1 = (\n",
189+
" uxds[\"face_lat\"].plot(cmap=\"Oranges\", aspect=1) * plot_gradient_vectors(grad_lat)\n",
190+
").opts(title=\"∇ Cell Latitudes\")\n",
191+
"p2 = (\n",
192+
" uxds[\"face_lon\"].plot(cmap=\"Oranges\", aspect=1) * plot_gradient_vectors(grad_lon)\n",
193+
").opts(title=\"∇ Cell Longitudes\")\n",
194+
"p3 = (\n",
195+
" uxds[\"gaussian\"].plot(cmap=\"Oranges\", aspect=1) * plot_gradient_vectors(grad_gauss)\n",
196+
").opts(title=\"∇ Gaussian\")\n",
197+
"p4 = (\n",
198+
" uxds[\"inverse_gaussian\"].plot(cmap=\"Oranges\", aspect=1)\n",
199+
" * plot_gradient_vectors(grad_inv_gauss)\n",
200+
").opts(title=\"∇ Inverse Gaussian\")\n",
201+
"\n",
202+
"# Compose all four plots in a 2 column layout\n",
203+
"(p1 + p2 + p3 + p4).cols(2).opts(shared_axes=False)"
204+
],
205+
"outputs": [],
206+
"execution_count": null
207+
}
208+
],
209+
"metadata": {
210+
"kernelspec": {
211+
"display_name": "Python 3 (ipykernel)",
212+
"language": "python",
213+
"name": "python3"
214+
},
215+
"language_info": {
216+
"codemirror_mode": {
217+
"name": "ipython",
218+
"version": 3
219+
},
220+
"file_extension": ".py",
221+
"mimetype": "text/x-python",
222+
"name": "python",
223+
"nbconvert_exporter": "python",
224+
"pygments_lexer": "ipython3",
225+
"version": "3.12.7"
226+
}
227+
},
228+
"nbformat": 4,
229+
"nbformat_minor": 5
230+
}

docs/userguide.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,8 @@ These user guides provide detailed explanations of the core functionality in UXa
6767
`Weighted Mean <user-guide/weighted_mean.ipynb>`_
6868
Compute the weighted average
6969

70-
`Calculus Operators <user-guide/calculus.ipynb>`_
71-
Apply calculus operators (gradient, integral) on unstructured grid data
70+
`Gradients <user-guide/gradients.ipynb>`_
71+
Compute the gradient of a data variable
7272

7373
`Tree Structures <user-guide/tree_structures.ipynb>`_
7474
Data structures for nearest neighbor queries
@@ -117,7 +117,7 @@ These user guides provide additional details about specific features in UXarray.
117117
user-guide/remapping.ipynb
118118
user-guide/topological-aggregations.ipynb
119119
user-guide/weighted_mean.ipynb
120-
user-guide/calculus.ipynb
120+
user-guide/gradients.ipynb
121121
user-guide/tree_structures.ipynb
122122
user-guide/area_calc.ipynb
123123
user-guide/dual-mesh.ipynb
10.2 KB
Binary file not shown.
142 KB
Binary file not shown.

0 commit comments

Comments
 (0)