|
109 | 109 | "cell_type": "markdown", |
110 | 110 | "id": "b464bd06cf9dbf3b", |
111 | 111 | "metadata": {}, |
112 | | - "source": "However, even if you load in a HEALPix grid specifying that you do not want the connectivity upfront, they can still be constructed when desired because of UXarray's internal design." |
| 112 | + "source": [ |
| 113 | + "However, even if you load in a HEALPix grid specifying that you do not want the connectivity upfront, they can still be constructed when desired because of UXarray's internal design." |
| 114 | + ] |
113 | 115 | }, |
114 | 116 | { |
115 | 117 | "cell_type": "code", |
|
191 | 193 | "cell_type": "markdown", |
192 | 194 | "id": "e1ef5671b3006b0c", |
193 | 195 | "metadata": {}, |
194 | | - "source": "Before remapping, we can plot our Source and Destination grids." |
| 196 | + "source": [ |
| 197 | + "Before remapping, we can plot our Source and Destination grids." |
| 198 | + ] |
195 | 199 | }, |
196 | 200 | { |
197 | 201 | "cell_type": "code", |
|
209 | 213 | "cell_type": "markdown", |
210 | 214 | "id": "d9f677166dd1b9f", |
211 | 215 | "metadata": {}, |
212 | | - "source": "We can now perform our remapping. In this case, we apply a simple nearest neighbor remapping." |
| 216 | + "source": [ |
| 217 | + "We can now perform our remapping. In this case, we apply a simple nearest neighbor remapping." |
| 218 | + ] |
213 | 219 | }, |
214 | 220 | { |
215 | 221 | "cell_type": "code", |
|
226 | 232 | "cell_type": "markdown", |
227 | 233 | "id": "f829232283eeb4", |
228 | 234 | "metadata": {}, |
229 | | - "source": "Our original data variable now resides on our HEALPix grid." |
| 235 | + "source": [ |
| 236 | + "Our original data variable now resides on our HEALPix grid." |
| 237 | + ] |
230 | 238 | }, |
231 | 239 | { |
232 | 240 | "cell_type": "code", |
|
259 | 267 | "psi_hp.to_dataset().to_xarray(grid_format=\"HEALPix\").to_netcdf(\"psi_healpix.nc\")" |
260 | 268 | ] |
261 | 269 | }, |
| 270 | + { |
| 271 | + "cell_type": "markdown", |
| 272 | + "id": "healpix_equal_area_section", |
| 273 | + "metadata": {}, |
| 274 | + "source": [ |
| 275 | + "## HEALPix Equal Area Property\n", |
| 276 | + "\n", |
| 277 | + "HEALPix's fundamental property is that **all pixels at a given resolution have exactly the same spherical area**. This \"equal area\" property is crucial for scientific applications such as global averaging, zonal means, and conservation properties in regridding operations.\n", |
| 278 | + "\n", |
| 279 | + "The theoretical area of each HEALPix pixel is given by:\n", |
| 280 | + "\n", |
| 281 | + "$$\n", |
| 282 | + "A_{\\text{pixel}} = \\frac{4\\pi}{N_{\\text{pix}}} = \\frac{4\\pi}{12 \\cdot 4^{\\text{resolution}}}\n", |
| 283 | + "$$\n", |
| 284 | + "\n", |
| 285 | + "where \n", |
| 286 | + "$N_{\\text{pix}} = 12 \\cdot 4^{\\text{resolution}}$ is the total number of pixels at a given resolution level, and \n", |
| 287 | + "$4\\pi$ is the total surface area of the unit sphere in steradians. \n", |
| 288 | + "\n", |
| 289 | + "### Geometric Representation Considerations\n", |
| 290 | + "\n", |
| 291 | + "UXarray represents HEALPix grids using Great Circle Arcs (GCAs) to define pixel boundaries, following UGRID conventions. However, this geometric representation can introduce systematic errors when computing areas numerically, potentially violating HEALPix's equal-area property. \n", |
| 292 | + "\n", |
| 293 | + "```{note}\n", |
| 294 | + "To alleviate the impacts of this systematic differences between UXarray and HEALPix, we adjust our `Grid.face_areas` property to fulfill the HEALPix equal area property, making sure that all the faces in a healpix mesh have the same theoretical HEALPix area.\n", |
| 295 | + "```\n", |
| 296 | + "\n", |
| 297 | + "Let's demonstrate this with HEALPix's 12 base pixels:" |
| 298 | + ] |
| 299 | + }, |
| 300 | + { |
| 301 | + "cell_type": "code", |
| 302 | + "execution_count": null, |
| 303 | + "id": "healpix_area_demo", |
| 304 | + "metadata": {}, |
| 305 | + "outputs": [], |
| 306 | + "source": [ |
| 307 | + "import numpy as np\n", |
| 308 | + "\n", |
| 309 | + "# Create HEALPix grid at resolution 0 (12 base pixels)\n", |
| 310 | + "grid = ux.Grid.from_healpix(0, pixels_only=False)\n", |
| 311 | + "\n", |
| 312 | + "# Using face_areas property ensures equal areas for HEALPix\n", |
| 313 | + "hp_areas = grid.face_areas.values\n", |
| 314 | + "print(f\"Standard deviation: {np.std(hp_areas):.2e}\")\n", |
| 315 | + "print(f\"All pixels have area: {hp_areas[0]:.6f} steradians\")" |
| 316 | + ] |
| 317 | + }, |
| 318 | + { |
| 319 | + "cell_type": "markdown", |
| 320 | + "id": "6e8b7185-c3ae-4f50-939b-4cb28a952e6f", |
| 321 | + "metadata": {}, |
| 322 | + "source": [ |
| 323 | + "### Still need geometric face area calculations for a HEALPix mesh?\n", |
| 324 | + "\n", |
| 325 | + "For most use cases, the `Grid.face_areas` property provides the recommended approach for accessing face areas. However, if you specifically need geometric calculations of individual HEALPix faces as they are represented in UXarray (rather than the theoretical equal areas), you may want to access the internal computation method, `Grid._compute_face_areas()`. Note that this approach may not preserve HEALPix's equal-area property due to geometric representation differences. \n", |
| 326 | + "\n", |
| 327 | + "Look at the following example:" |
| 328 | + ] |
| 329 | + }, |
| 330 | + { |
| 331 | + "cell_type": "code", |
| 332 | + "execution_count": null, |
| 333 | + "id": "dfd494b5-0b3e-472f-8896-bd3566747bdb", |
| 334 | + "metadata": {}, |
| 335 | + "outputs": [], |
| 336 | + "source": [ |
| 337 | + "# For advanced use cases: access geometric calculations (may not preserve equal-area property)\n", |
| 338 | + "hp_geometric_areas, face_jacobians = grid._compute_face_areas(\n", |
| 339 | + " quadrature_rule=\"triangular\", order=4\n", |
| 340 | + ")\n", |
| 341 | + "print(\n", |
| 342 | + " f\"Geometric std deviation: {hp_geometric_areas.std():.2e} (vs theoretical equal areas)\"\n", |
| 343 | + ")" |
| 344 | + ] |
| 345 | + }, |
| 346 | + { |
| 347 | + "cell_type": "markdown", |
| 348 | + "id": "healpix_error_analysis", |
| 349 | + "metadata": {}, |
| 350 | + "source": [ |
| 351 | + "If you are interested in futher details of the systematic errors due to geometric calculation with a clear spatial pattern, our analysis across resolution levels 0-7 shows:\n", |
| 352 | + "\n", |
| 353 | + "- **Equatorial pixels** (lat≈0°): +20.5% area error\n", |
| 354 | + "- **Mid-latitude pixels** (lat≈±42°): -9.9% area error \n", |
| 355 | + "- **Maximum errors**: Persist at ~10% even at fine resolutions\n" |
| 356 | + ] |
| 357 | + }, |
262 | 358 | { |
263 | 359 | "cell_type": "markdown", |
264 | 360 | "id": "7988c071af403bf2", |
|
293 | 389 | "cell_type": "markdown", |
294 | 390 | "id": "11ff4e1ad036ea53", |
295 | 391 | "metadata": {}, |
296 | | - "source": "The interface above looks almost identical to what you would see if you loaded in the file directly with Xarray." |
| 392 | + "source": [ |
| 393 | + "The interface above looks almost identical to what you would see if you loaded in the file directly with Xarray." |
| 394 | + ] |
297 | 395 | }, |
298 | 396 | { |
299 | 397 | "cell_type": "code", |
|
359 | 457 | "name": "python", |
360 | 458 | "nbconvert_exporter": "python", |
361 | 459 | "pygments_lexer": "ipython3", |
362 | | - "version": "3.12.3" |
| 460 | + "version": "3.12.6" |
363 | 461 | } |
364 | 462 | }, |
365 | 463 | "nbformat": 4, |
|
0 commit comments