uxarray 2026.4.0__tar.gz → 2026.6.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {uxarray-2026.4.0 → uxarray-2026.6.0}/PKG-INFO +1 -1
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/__init__.py +2 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/core/dataarray.py +153 -14
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/core/dataset.py +26 -5
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/core/gradient.py +26 -1
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/core/zonal.py +230 -71
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/grid/coordinates.py +84 -1
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/grid/geometry.py +6 -6
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/grid/grid.py +3 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/plot/accessor.py +6 -4
- uxarray-2026.6.0/uxarray/remap/__init__.py +13 -0
- uxarray-2026.6.0/uxarray/remap/accessor.py +374 -0
- uxarray-2026.6.0/uxarray/remap/apply_weights.py +120 -0
- uxarray-2026.6.0/uxarray/remap/structured.py +216 -0
- uxarray-2026.6.0/uxarray/remap/weights.py +194 -0
- uxarray-2026.6.0/uxarray/remap/yac.py +516 -0
- uxarray-2026.6.0/uxarray/tutorial/__init__.py +208 -0
- uxarray-2026.6.0/uxarray/tutorial/registry.py +102 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray.egg-info/PKG-INFO +1 -1
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray.egg-info/SOURCES.txt +6 -0
- uxarray-2026.4.0/uxarray/remap/__init__.py +0 -7
- uxarray-2026.4.0/uxarray/remap/accessor.py +0 -112
- {uxarray-2026.4.0 → uxarray-2026.6.0}/.codecov.yml +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/.git_archival.txt +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/.gitattributes +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/CITATION.cff +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/CODE_OF_CONDUCT.md +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/CONTRIBUTING.md +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/INSTALLATION.md +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/LICENSE +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/MANIFEST.in +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/README.md +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/build.sh +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/pyproject.toml +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/setup.cfg +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/setup.py +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/constants.py +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/conventions/__init__.py +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/conventions/descriptors.py +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/conventions/ugrid.py +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/core/__init__.py +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/core/accessors.py +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/core/aggregation.py +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/core/api.py +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/core/utils.py +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/cross_sections/__init__.py +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/cross_sections/dataarray_accessor.py +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/cross_sections/grid_accessor.py +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/cross_sections/sample.py +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/formatting_html.py +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/grid/__init__.py +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/grid/arcs.py +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/grid/area.py +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/grid/bounds.py +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/grid/connectivity.py +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/grid/dual.py +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/grid/integrate.py +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/grid/intersections.py +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/grid/neighbors.py +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/grid/point_in_face.py +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/grid/slice.py +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/grid/utils.py +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/grid/validation.py +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/io/__init__.py +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/io/_delaunay.py +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/io/_esmf.py +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/io/_exodus.py +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/io/_fesom2.py +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/io/_geopandas.py +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/io/_geos.py +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/io/_healpix.py +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/io/_icon.py +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/io/_mpas.py +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/io/_scrip.py +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/io/_structured.py +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/io/_topology.py +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/io/_ugrid.py +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/io/_vertices.py +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/io/_voronoi.py +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/io/utils.py +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/plot/__init__.py +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/plot/constants.py +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/plot/matplotlib.py +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/plot/utils.py +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/remap/bilinear.py +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/remap/inverse_distance_weighted.py +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/remap/nearest_neighbor.py +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/remap/spatial_coords_remap.py +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/remap/utils.py +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/subset/__init__.py +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/subset/dataarray_accessor.py +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/subset/grid_accessor.py +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/utils/__init__.py +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray/utils/computing.py +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray.egg-info/dependency_links.txt +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray.egg-info/requires.txt +0 -0
- {uxarray-2026.4.0 → uxarray-2026.6.0}/uxarray.egg-info/top_level.txt +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from . import tutorial
|
|
1
2
|
from .constants import INT_DTYPE, INT_FILL_VALUE
|
|
2
3
|
from .core.api import (
|
|
3
4
|
concat,
|
|
@@ -30,6 +31,7 @@ __all__ = (
|
|
|
30
31
|
"open_multigrid",
|
|
31
32
|
"list_grid_names",
|
|
32
33
|
"concat",
|
|
34
|
+
"tutorial",
|
|
33
35
|
"UxDataset",
|
|
34
36
|
"UxDataArray",
|
|
35
37
|
"INT_DTYPE",
|
|
@@ -24,6 +24,7 @@ from uxarray.core.utils import _map_dims_to_ugrid
|
|
|
24
24
|
from uxarray.core.zonal import (
|
|
25
25
|
_compute_conservative_zonal_mean_bands,
|
|
26
26
|
_compute_non_conservative_zonal_mean,
|
|
27
|
+
_compute_zonal_anomaly,
|
|
27
28
|
)
|
|
28
29
|
from uxarray.cross_sections import UxDataArrayCrossSectionAccessor
|
|
29
30
|
from uxarray.formatting_html import array_repr
|
|
@@ -465,12 +466,7 @@ class UxDataArray(xr.DataArray):
|
|
|
465
466
|
else:
|
|
466
467
|
|
|
467
468
|
def _is_default_extent() -> bool:
|
|
468
|
-
|
|
469
|
-
# when autoscale is still on (no extent has been explicitly set)
|
|
470
|
-
if not ax.get_autoscale_on():
|
|
471
|
-
return False
|
|
472
|
-
xlim, ylim = ax.get_xlim(), ax.get_ylim()
|
|
473
|
-
return np.allclose(xlim, (0.0, 1.0)) and np.allclose(ylim, (0.0, 1.0))
|
|
469
|
+
return ax.get_autoscale_on()
|
|
474
470
|
|
|
475
471
|
if _is_default_extent():
|
|
476
472
|
try:
|
|
@@ -772,6 +768,70 @@ class UxDataArray(xr.DataArray):
|
|
|
772
768
|
"""Alias of zonal_mean; prefer `zonal_mean` for primary API."""
|
|
773
769
|
return self.zonal_mean(lat=lat, conservative=conservative, **kwargs)
|
|
774
770
|
|
|
771
|
+
def zonal_anomaly(self, lat=(-90, 90, 10), conservative: bool = False):
|
|
772
|
+
"""Compute the zonal anomaly: each face value minus the mean of its latitude band.
|
|
773
|
+
|
|
774
|
+
Returns a new ``UxDataArray`` with the same dimensions as the input,
|
|
775
|
+
where each face holds its original value minus the zonal mean of the
|
|
776
|
+
latitude band it belongs to.
|
|
777
|
+
|
|
778
|
+
Parameters
|
|
779
|
+
----------
|
|
780
|
+
lat : tuple or array-like, default=(-90, 90, 10)
|
|
781
|
+
Latitude band specification:
|
|
782
|
+
- tuple (start, end, step): band edges via np.linspace(start, end, n)
|
|
783
|
+
- array-like: explicit band edges in degrees
|
|
784
|
+
conservative : bool, default=False
|
|
785
|
+
If True, uses area-weighted band means and blends across bands for
|
|
786
|
+
faces that straddle a band boundary, reusing the face-band weight
|
|
787
|
+
matrix computed for zonal_mean so no geometry is duplicated.
|
|
788
|
+
If False, assigns each face to a band by its centroid latitude.
|
|
789
|
+
|
|
790
|
+
Returns
|
|
791
|
+
-------
|
|
792
|
+
UxDataArray
|
|
793
|
+
Same dimensions as input with per-face band mean subtracted.
|
|
794
|
+
|
|
795
|
+
Examples
|
|
796
|
+
--------
|
|
797
|
+
>>> uxds["var"].zonal_anomaly()
|
|
798
|
+
>>> uxds["var"].zonal_anomaly(lat=(-60, 60, 5), conservative=True)
|
|
799
|
+
"""
|
|
800
|
+
if not self._face_centered():
|
|
801
|
+
raise ValueError(
|
|
802
|
+
"Zonal anomaly is only supported for face-centered data variables."
|
|
803
|
+
)
|
|
804
|
+
|
|
805
|
+
if isinstance(lat, tuple):
|
|
806
|
+
start, end, step = lat
|
|
807
|
+
if step <= 0:
|
|
808
|
+
raise ValueError("Step size must be positive.")
|
|
809
|
+
num_points = int(round((end - start) / step)) + 1
|
|
810
|
+
edges = np.linspace(start, end, num_points)
|
|
811
|
+
edges = np.clip(edges, -90, 90)
|
|
812
|
+
elif isinstance(lat, (list, np.ndarray)):
|
|
813
|
+
edges = np.asarray(lat, dtype=float)
|
|
814
|
+
else:
|
|
815
|
+
raise ValueError(
|
|
816
|
+
"Invalid value for 'lat'. Must be a tuple (start, end, step) or array-like band edges."
|
|
817
|
+
)
|
|
818
|
+
|
|
819
|
+
if edges.ndim != 1 or edges.size < 2:
|
|
820
|
+
raise ValueError("Band edges must be 1D with at least two values.")
|
|
821
|
+
|
|
822
|
+
res = _compute_zonal_anomaly(self, edges, conservative=conservative)
|
|
823
|
+
|
|
824
|
+
return UxDataArray(
|
|
825
|
+
res,
|
|
826
|
+
dims=self.dims,
|
|
827
|
+
coords=self.coords,
|
|
828
|
+
name=self.name + "_zonal_anomaly"
|
|
829
|
+
if self.name is not None
|
|
830
|
+
else "zonal_anomaly",
|
|
831
|
+
attrs={"zonal_anomaly": True, "conservative": conservative},
|
|
832
|
+
uxgrid=self.uxgrid,
|
|
833
|
+
)
|
|
834
|
+
|
|
775
835
|
def azimuthal_mean(
|
|
776
836
|
self,
|
|
777
837
|
center_coord,
|
|
@@ -1416,10 +1476,15 @@ class UxDataArray(xr.DataArray):
|
|
|
1416
1476
|
"""
|
|
1417
1477
|
return _uxda_grid_aggregate(self, destination, "any", **kwargs)
|
|
1418
1478
|
|
|
1419
|
-
def gradient(self, **kwargs) -> UxDataset:
|
|
1479
|
+
def gradient(self, scale_by_radius: bool = True, **kwargs) -> UxDataset:
|
|
1420
1480
|
"""
|
|
1421
1481
|
Computes the gradient of a data variable.
|
|
1422
1482
|
|
|
1483
|
+
Parameters
|
|
1484
|
+
----------
|
|
1485
|
+
scale_by_radius : bool, default=True
|
|
1486
|
+
Divide unit-sphere derivatives by ``uxgrid.sphere_radius``.
|
|
1487
|
+
|
|
1423
1488
|
Returns
|
|
1424
1489
|
-------
|
|
1425
1490
|
gradient: UxDataset
|
|
@@ -1446,7 +1511,9 @@ class UxDataArray(xr.DataArray):
|
|
|
1446
1511
|
)
|
|
1447
1512
|
|
|
1448
1513
|
# Compute the zonal and meridional gradient components of the stored data variable
|
|
1449
|
-
grad_zonal_da, grad_meridional_da = _compute_gradient(
|
|
1514
|
+
grad_zonal_da, grad_meridional_da = _compute_gradient(
|
|
1515
|
+
self, scale_by_radius=scale_by_radius
|
|
1516
|
+
)
|
|
1450
1517
|
|
|
1451
1518
|
# Create a dataset containing both gradient components
|
|
1452
1519
|
return UxDataset(
|
|
@@ -1459,7 +1526,9 @@ class UxDataArray(xr.DataArray):
|
|
|
1459
1526
|
coords=self.coords,
|
|
1460
1527
|
)
|
|
1461
1528
|
|
|
1462
|
-
def curl(
|
|
1529
|
+
def curl(
|
|
1530
|
+
self, other: "UxDataArray", scale_by_radius: bool = True, **kwargs
|
|
1531
|
+
) -> "UxDataArray":
|
|
1463
1532
|
"""
|
|
1464
1533
|
Computes the curl of a vector field.
|
|
1465
1534
|
|
|
@@ -1469,6 +1538,8 @@ class UxDataArray(xr.DataArray):
|
|
|
1469
1538
|
The second component of the vector field. This UxDataArray should
|
|
1470
1539
|
represent the meridional (v) component, while self represents the
|
|
1471
1540
|
zonal (u) component.
|
|
1541
|
+
scale_by_radius : bool, default=True
|
|
1542
|
+
Divide unit-sphere derivatives by ``uxgrid.sphere_radius``.
|
|
1472
1543
|
**kwargs : dict
|
|
1473
1544
|
Additional keyword arguments (currently unused, reserved for future extensions).
|
|
1474
1545
|
|
|
@@ -1519,21 +1590,30 @@ class UxDataArray(xr.DataArray):
|
|
|
1519
1590
|
)
|
|
1520
1591
|
|
|
1521
1592
|
# Compute gradients of both components
|
|
1522
|
-
grad_u_zonal, grad_u_meridional = _compute_gradient(
|
|
1523
|
-
|
|
1593
|
+
grad_u_zonal, grad_u_meridional = _compute_gradient(
|
|
1594
|
+
self, scale_by_radius=scale_by_radius
|
|
1595
|
+
)
|
|
1596
|
+
grad_v_zonal, grad_v_meridional = _compute_gradient(
|
|
1597
|
+
other, scale_by_radius=scale_by_radius
|
|
1598
|
+
)
|
|
1524
1599
|
|
|
1525
1600
|
# Compute curl = ∂v/∂x - ∂u/∂y
|
|
1526
1601
|
curl_values = grad_v_zonal.values - grad_u_meridional.values
|
|
1527
1602
|
|
|
1603
|
+
u_units = self.attrs.get("units", "")
|
|
1604
|
+
has_sphere_radius = "sphere_radius" in self.uxgrid._ds.attrs
|
|
1605
|
+
if scale_by_radius and has_sphere_radius:
|
|
1606
|
+
curl_units = f"({u_units})/m" if u_units else "1/s"
|
|
1607
|
+
else:
|
|
1608
|
+
curl_units = f"({u_units})/rad" if u_units else "1/rad"
|
|
1609
|
+
|
|
1528
1610
|
# Create the result UxDataArray
|
|
1529
1611
|
curl_da = UxDataArray(
|
|
1530
1612
|
curl_values,
|
|
1531
1613
|
dims=self.dims,
|
|
1532
1614
|
attrs={
|
|
1533
1615
|
"long_name": f"Curl of ({self.name}, {other.name})",
|
|
1534
|
-
"units":
|
|
1535
|
-
if "units" not in self.attrs
|
|
1536
|
-
else f"({self.attrs.get('units', '1')})/m",
|
|
1616
|
+
"units": curl_units,
|
|
1537
1617
|
"description": "Curl of vector field computed as ∂v/∂x - ∂u/∂y",
|
|
1538
1618
|
},
|
|
1539
1619
|
uxgrid=self.uxgrid,
|
|
@@ -1619,6 +1699,65 @@ class UxDataArray(xr.DataArray):
|
|
|
1619
1699
|
|
|
1620
1700
|
return divergence_da
|
|
1621
1701
|
|
|
1702
|
+
def scalardotgradient(self, v: "UxDataArray", q: "UxDataArray") -> "UxDataArray":
|
|
1703
|
+
"""
|
|
1704
|
+
Compute the dot product between a vector field and the gradient of a scalar field.
|
|
1705
|
+
|
|
1706
|
+
Parameters
|
|
1707
|
+
----------
|
|
1708
|
+
v : UxDataArray
|
|
1709
|
+
The meridional component of the vector field. ``self`` is treated as
|
|
1710
|
+
the zonal component.
|
|
1711
|
+
q : UxDataArray
|
|
1712
|
+
Scalar field whose gradient is dotted with the vector field.
|
|
1713
|
+
|
|
1714
|
+
Returns
|
|
1715
|
+
-------
|
|
1716
|
+
scalar_dot_gradient : UxDataArray
|
|
1717
|
+
Dot product ``self * dq/dx + v * dq/dy``.
|
|
1718
|
+
"""
|
|
1719
|
+
if not isinstance(v, UxDataArray):
|
|
1720
|
+
raise TypeError("v must be a UxDataArray")
|
|
1721
|
+
|
|
1722
|
+
if not isinstance(q, UxDataArray):
|
|
1723
|
+
raise TypeError("q must be a UxDataArray")
|
|
1724
|
+
|
|
1725
|
+
if self.uxgrid != v.uxgrid or self.uxgrid != q.uxgrid:
|
|
1726
|
+
raise ValueError("All UxDataArrays must have the same grid")
|
|
1727
|
+
|
|
1728
|
+
if self.dims != v.dims or self.dims != q.dims:
|
|
1729
|
+
raise ValueError("All UxDataArrays must have the same dimensions")
|
|
1730
|
+
|
|
1731
|
+
if self.ndim > 1:
|
|
1732
|
+
raise ValueError(
|
|
1733
|
+
"Scalar dot gradient currently requires 1D face-centered data. "
|
|
1734
|
+
"Consider selecting a single slice before computing."
|
|
1735
|
+
)
|
|
1736
|
+
|
|
1737
|
+
if not (self._face_centered() and v._face_centered() and q._face_centered()):
|
|
1738
|
+
raise ValueError(
|
|
1739
|
+
"Computing the scalar dot gradient is only supported for face-centered data variables."
|
|
1740
|
+
)
|
|
1741
|
+
|
|
1742
|
+
# Validate coordinate alignment up-front so a misaligned input fails
|
|
1743
|
+
# before the (potentially expensive) gradient call.
|
|
1744
|
+
u_aligned, v_aligned, q_aligned = xr.align(self, v, q, join="exact", copy=False)
|
|
1745
|
+
|
|
1746
|
+
q_gradient = q_aligned.gradient()
|
|
1747
|
+
q_zonal = q_gradient["zonal_gradient"]
|
|
1748
|
+
q_meridional = q_gradient["meridional_gradient"]
|
|
1749
|
+
|
|
1750
|
+
scalar_dot_gradient = (u_aligned * q_zonal) + (v_aligned * q_meridional)
|
|
1751
|
+
scalar_dot_gradient.name = "scalar_dot_gradient"
|
|
1752
|
+
scalar_dot_gradient.attrs.update(
|
|
1753
|
+
{
|
|
1754
|
+
"long_name": "scalar dot gradient",
|
|
1755
|
+
"description": "Dot product u * (dq/dx) + v * (dq/dy).",
|
|
1756
|
+
}
|
|
1757
|
+
)
|
|
1758
|
+
|
|
1759
|
+
return UxDataArray(scalar_dot_gradient, uxgrid=self.uxgrid)
|
|
1760
|
+
|
|
1622
1761
|
def difference(self, destination: str | None = "edge"):
|
|
1623
1762
|
"""Computes the absolute difference of a data variable.
|
|
1624
1763
|
|
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import os
|
|
4
4
|
import sys
|
|
5
5
|
from html import escape
|
|
6
|
-
from typing import IO, Any, Mapping
|
|
6
|
+
from typing import IO, Any, Hashable, Mapping
|
|
7
7
|
from warnings import warn
|
|
8
8
|
|
|
9
9
|
import numpy as np
|
|
@@ -621,11 +621,32 @@ class UxDataset(xr.Dataset):
|
|
|
621
621
|
|
|
622
622
|
return integral
|
|
623
623
|
|
|
624
|
-
def to_array(
|
|
625
|
-
|
|
626
|
-
|
|
624
|
+
def to_array(
|
|
625
|
+
self,
|
|
626
|
+
dim: Hashable = "variable",
|
|
627
|
+
name: Hashable = None,
|
|
628
|
+
) -> UxDataArray:
|
|
629
|
+
"""Convert this ``uxarray.UxDataset`` into a ``uxarray.UxDataArray``,
|
|
630
|
+
attaching this UxDataset's uxgrid to the result.
|
|
631
|
+
|
|
632
|
+
Similarly to xarray.Dataset.to_array(), the data variables will be
|
|
633
|
+
broadcast against each other and stacked along the first axis of
|
|
634
|
+
the new array. All coordinates of this dataset will remain coordinates.
|
|
635
|
+
|
|
636
|
+
Parameters
|
|
637
|
+
----------
|
|
638
|
+
dim : Hashable, optional
|
|
639
|
+
Name of the new dimension. Defaults to "variable"
|
|
640
|
+
name : Hashable or None, optional
|
|
641
|
+
Name of the new data array.
|
|
642
|
+
|
|
643
|
+
Returns
|
|
644
|
+
-------
|
|
645
|
+
UxDataArray
|
|
646
|
+
The ``uxarray.UxDataset`` represented as a ``uxarray.UxDataArray``
|
|
647
|
+
"""
|
|
627
648
|
|
|
628
|
-
xarr = super().to_array()
|
|
649
|
+
xarr = super().to_array(dim=dim, name=name)
|
|
629
650
|
return UxDataArray(xarr, uxgrid=self.uxgrid)
|
|
630
651
|
|
|
631
652
|
def to_xarray(self, grid_format: str = "UGRID") -> xr.Dataset:
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import warnings
|
|
2
|
+
|
|
1
3
|
import numpy as np
|
|
2
4
|
from numba import njit, prange
|
|
3
5
|
|
|
@@ -88,7 +90,7 @@ def _check_node_on_boundary_and_gather_node_neighbors(
|
|
|
88
90
|
return bool_bdy, node_neighbors[0:num_node_neighbors]
|
|
89
91
|
|
|
90
92
|
|
|
91
|
-
def _compute_gradient(data):
|
|
93
|
+
def _compute_gradient(data, scale_by_radius=True):
|
|
92
94
|
from uxarray import UxDataArray
|
|
93
95
|
|
|
94
96
|
uxgrid = data.uxgrid
|
|
@@ -186,15 +188,38 @@ def _compute_gradient(data):
|
|
|
186
188
|
"Computing the gradient is only supported for face-centered data variables."
|
|
187
189
|
)
|
|
188
190
|
|
|
191
|
+
has_sphere_radius = "sphere_radius" in uxgrid._ds.attrs
|
|
192
|
+
if scale_by_radius:
|
|
193
|
+
if has_sphere_radius:
|
|
194
|
+
radius = uxgrid.sphere_radius
|
|
195
|
+
grad_zonal = grad_zonal / radius
|
|
196
|
+
grad_meridional = grad_meridional / radius
|
|
197
|
+
else:
|
|
198
|
+
warnings.warn(
|
|
199
|
+
"scale_by_radius=True but the grid has no 'sphere_radius' "
|
|
200
|
+
"attribute; result is left on the unit sphere. Set "
|
|
201
|
+
"uxgrid.sphere_radius or pass scale_by_radius=False.",
|
|
202
|
+
UserWarning,
|
|
203
|
+
stacklevel=2,
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
base_units = data.attrs.get("units", "")
|
|
207
|
+
if scale_by_radius and has_sphere_radius:
|
|
208
|
+
grad_units = f"{base_units}/m" if base_units else "1/m"
|
|
209
|
+
else:
|
|
210
|
+
grad_units = f"{base_units}/rad" if base_units else "1/rad"
|
|
211
|
+
|
|
189
212
|
# Zonal
|
|
190
213
|
grad_zonal_da = UxDataArray(
|
|
191
214
|
data=grad_zonal, name="zonal_gradient", dims=data.dims, uxgrid=uxgrid
|
|
192
215
|
)
|
|
216
|
+
grad_zonal_da.attrs["units"] = grad_units
|
|
193
217
|
|
|
194
218
|
# Meridional
|
|
195
219
|
grad_meridional_da = UxDataArray(
|
|
196
220
|
data=grad_meridional, name="meridional_gradient", dims=data.dims, uxgrid=uxgrid
|
|
197
221
|
)
|
|
222
|
+
grad_meridional_da.attrs["units"] = grad_units
|
|
198
223
|
|
|
199
224
|
return grad_zonal_da, grad_meridional_da
|
|
200
225
|
|