Skip to content

Commit af335d1

Browse files
authored
Merge branch 'main' into rajeeja/issue_669
2 parents 3ee2ce8 + 5c74b0c commit af335d1

9 files changed

Lines changed: 33 additions & 172 deletions

File tree

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ repos:
1414

1515
- repo: https://github.com/astral-sh/ruff-pre-commit
1616
# Ruff version.
17-
rev: v0.11.10
17+
rev: v0.11.13
1818
hooks:
1919
- id: ruff
2020
name: lint with ruff

ci/asv.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ dependencies:
1313
- healpix
1414
- netcdf4
1515
- numba
16-
- numpy
16+
- numpy<2.3
1717
- pandas
1818
- pathlib
1919
- pre_commit

ci/docs.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ dependencies:
77
- cmocean
88
- netcdf4
99
- numba
10-
- numpy
10+
- numpy<2.3
1111
- pathlib
1212
- pre_commit
1313
- pytest

ci/environment.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ dependencies:
1818
- matplotlib-inline
1919
- netcdf4
2020
- numba
21-
- numpy
21+
- numpy<2.3
2222
- pandas
2323
- pathlib
2424
- pre_commit

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ dependencies = [
3131
"matplotlib-inline",
3232
"netcdf4",
3333
"numba",
34-
"numpy",
34+
"numpy<2.3",
3535
"pandas",
3636
"pyarrow",
3737
"requests",

test/test_healpix.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import pytest
55
import os
66
import xarray as xr
7+
import pandas as pd
78
from pathlib import Path
89

910

@@ -38,6 +39,17 @@ def test_boundaries(resolution_level):
3839
# check for the correct number of boundary nodes
3940
assert (uxgrid.n_node == uxgrid.n_face + 2)
4041

42+
@pytest.mark.parametrize("pixels_only", [True, False])
43+
def test_time_dimension_roundtrip(pixels_only):
44+
ds = xr.open_dataset(ds_path)
45+
dummy_time = pd.to_datetime(["2025-01-01T00:00:00"])
46+
ds_time = ds.expand_dims(time=dummy_time)
47+
uxds = ux.UxDataset.from_healpix(ds_time, pixels_only=pixels_only)
48+
49+
# Ensure time dimension is preserved and that the conversion worked
50+
assert "time" in uxds.dims
51+
assert uxds.sizes["time"] == 1
52+
4153
def test_dataset():
4254
uxds = ux.UxDataset.from_healpix(ds_path)
4355

uxarray/core/utils.py

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -44,21 +44,17 @@ def _map_dims_to_ugrid(
4444
# drop dimensions not present in the original dataset
4545
_source_dims_dict.pop(key)
4646

47-
# only check edge dimension if it is present (to avoid overhead of computing connectivity)
48-
if "n_edge" in grid._ds.dims:
49-
n_edge = grid._ds.sizes["n_edge"]
50-
else:
51-
n_edge = None
52-
53-
for dim in set(ds.dims) ^ _source_dims_dict.keys():
54-
# obtain dimensions that were not parsed source_dims_dict and attempt to match to a grid element
55-
if ds.sizes[dim] == grid.n_face:
56-
_source_dims_dict[dim] = "n_face"
57-
elif ds.sizes[dim] == grid.n_node:
58-
_source_dims_dict[dim] = "n_node"
59-
elif n_edge is not None:
60-
if ds.sizes[dim] == n_edge:
61-
_source_dims_dict[dim] = "n_edge"
47+
# build a reverse map
48+
size_to_name = {
49+
grid._ds.sizes[name]: name
50+
for name in ("n_face", "n_node", "n_edge")
51+
if name in grid._ds.dims
52+
}
53+
54+
for dim in set(ds.dims) - _source_dims_dict.keys():
55+
name = size_to_name.get(ds.sizes[dim])
56+
if name:
57+
_source_dims_dict[dim] = name
6258

6359
# rename dimensions to follow the UGRID conventions
6460
ds = ds.swap_dims(_source_dims_dict)

uxarray/grid/coordinates.py

Lines changed: 3 additions & 152 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ def _xyz_to_lonlat_rad_no_norm(
5454
lon = np.arctan2(y, x)
5555
lat = np.asin(z)
5656

57-
# set longitude range to [0, pi]
57+
# set longitude range to [0, 2*pi]
5858
lon = np.mod(lon, 2 * np.pi)
5959

6060
z_mask = np.abs(z) > 1.0 - ERROR_TOLERANCE
@@ -130,7 +130,7 @@ def _xyz_to_lonlat_rad(
130130
lon = np.arctan2(y, x)
131131
lat = np.arcsin(z)
132132

133-
# set longitude range to [0, pi]
133+
# set longitude range to [0, 2*pi]
134134
lon = np.mod(lon, 2 * np.pi)
135135

136136
z_mask = np.abs(z) > 1.0 - ERROR_TOLERANCE
@@ -182,7 +182,7 @@ def _normalize_xyz(
182182
y: Union[np.ndarray, float],
183183
z: Union[np.ndarray, float],
184184
) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
185-
"""Normalizes a set of Cartesiain coordinates."""
185+
"""Normalizes a set of Cartesian coordinates."""
186186
denom = np.linalg.norm(
187187
np.asarray(np.array([x, y, z]), dtype=np.float64), ord=2, axis=0
188188
)
@@ -699,155 +699,6 @@ def _set_desired_longitude_range(uxgrid):
699699
uxgrid._ds[lon_name] = (uxgrid._ds[lon_name] + 180) % 360 - 180
700700

701701

702-
def _xyz_to_lonlat_rad(
703-
x: Union[np.ndarray, float],
704-
y: Union[np.ndarray, float],
705-
z: Union[np.ndarray, float],
706-
normalize: bool = True,
707-
) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
708-
"""Converts Cartesian x, y, z coordinates in Spherical latitude and
709-
longitude coordinates in degrees.
710-
711-
Parameters
712-
----------
713-
x : Union[np.ndarray, float]
714-
Cartesian x coordinates
715-
y: Union[np.ndarray, float]
716-
Cartesiain y coordinates
717-
z: Union[np.ndarray, float]
718-
Cartesian z coordinates
719-
normalize: bool
720-
Flag to select whether to normalize the coordinates
721-
722-
Returns
723-
-------
724-
lon : Union[np.ndarray, float]
725-
Longitude in radians
726-
lat: Union[np.ndarray, float]
727-
Latitude in radians
728-
"""
729-
730-
if normalize:
731-
x, y, z = _normalize_xyz(x, y, z)
732-
denom = np.abs(x * x + y * y + z * z)
733-
x /= denom
734-
y /= denom
735-
z /= denom
736-
737-
lon = np.arctan2(y, x, dtype=np.float64)
738-
lat = np.arcsin(z, dtype=np.float64)
739-
740-
# set longitude range to [0, pi]
741-
lon = np.mod(lon, 2 * np.pi)
742-
743-
z_mask = np.abs(z) > 1.0 - ERROR_TOLERANCE
744-
745-
lat = np.where(z_mask, np.sign(z) * np.pi / 2, lat)
746-
lon = np.where(z_mask, 0.0, lon)
747-
748-
return lon, lat
749-
750-
751-
@njit(cache=True)
752-
def _xyz_to_lonlat_rad_no_norm(
753-
x: Union[np.ndarray, float],
754-
y: Union[np.ndarray, float],
755-
z: Union[np.ndarray, float],
756-
):
757-
"""Converts a Cartesian x,y,z coordinates into Spherical latitude and
758-
longitude without normalization, decorated with Numba.
759-
760-
Parameters
761-
----------
762-
x : float
763-
Cartesian x coordinate
764-
y: float
765-
Cartesiain y coordinate
766-
z: float
767-
Cartesian z coordinate
768-
769-
770-
Returns
771-
-------
772-
lon : float
773-
Longitude in radians
774-
lat: float
775-
Latitude in radians
776-
"""
777-
778-
lon = np.arctan2(y, x)
779-
lat = np.asin(z)
780-
781-
# set longitude range to [0, pi]
782-
lon = np.mod(lon, 2 * np.pi)
783-
784-
z_mask = np.abs(z) > 1.0 - ERROR_TOLERANCE
785-
786-
lat = np.where(z_mask, np.sign(z) * np.pi / 2, lat)
787-
lon = np.where(z_mask, 0.0, lon)
788-
789-
return lon, lat
790-
791-
792-
@njit(cache=True)
793-
def _lonlat_rad_to_xyz(
794-
lon: Union[np.ndarray, float],
795-
lat: Union[np.ndarray, float],
796-
) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
797-
"""Converts Spherical lon and lat coordinates into Cartesian x, y, z
798-
coordinates."""
799-
x = np.cos(lon) * np.cos(lat)
800-
y = np.sin(lon) * np.cos(lat)
801-
z = np.sin(lat)
802-
803-
return x, y, z
804-
805-
806-
def _xyz_to_lonlat_deg(
807-
x: Union[np.ndarray, float],
808-
y: Union[np.ndarray, float],
809-
z: Union[np.ndarray, float],
810-
normalize: bool = True,
811-
) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
812-
"""Converts Cartesian x, y, z coordinates in Spherical latitude and
813-
longitude coordinates in degrees.
814-
815-
Parameters
816-
----------
817-
x : Union[np.ndarray, float]
818-
Cartesian x coordinates
819-
y: Union[np.ndarray, float]
820-
Cartesiain y coordinates
821-
z: Union[np.ndarray, float]
822-
Cartesian z coordinates
823-
normalize: bool
824-
Flag to select whether to normalize the coordinates
825-
826-
Returns
827-
-------
828-
lon : Union[np.ndarray, float]
829-
Longitude in degrees
830-
lat: Union[np.ndarray, float]
831-
Latitude in degrees
832-
"""
833-
lon_rad, lat_rad = _xyz_to_lonlat_rad(x, y, z, normalize=normalize)
834-
835-
lon = np.rad2deg(lon_rad)
836-
lat = np.rad2deg(lat_rad)
837-
838-
lon = (lon + 180) % 360 - 180
839-
return lon, lat
840-
841-
842-
@njit(cache=True)
843-
def _normalize_xyz_scalar(x: float, y: float, z: float):
844-
denom = np.linalg.norm(np.asarray(np.array([x, y, z]), dtype=np.float64), ord=2)
845-
x_norm = x / denom
846-
y_norm = y / denom
847-
z_norm = z / denom
848-
return x_norm, y_norm, z_norm
849-
850-
851702
def prepare_points(points, normalize):
852703
"""Prepares points for use with ``Grid.from_points()``"""
853704
if len(points) == 2:

uxarray/grid/grid.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -817,6 +817,8 @@ def attrs(self) -> dict:
817817
@property
818818
def n_node(self) -> int:
819819
"""Total number of nodes."""
820+
if "face_node_connectivity" not in self._ds:
821+
_ = self.face_node_connectivity
820822
return self._ds.sizes["n_node"]
821823

822824
@property

0 commit comments

Comments
 (0)