Skip to content

Commit 8293a3a

Browse files
authored
Merge branch 'main' into rajeeja/onefile_open_dataset
2 parents d8e44fa + 29da953 commit 8293a3a

16 files changed

Lines changed: 1145 additions & 46 deletions

File tree

.github/ISSUE_TEMPLATE/release_request.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
---
22
name: Release Request
33
about: Request a release for this package
4-
title: '[Release]: <version-number>'
5-
labels: release, high-priority
6-
assignees: 'philipc2'
4+
title: "[Release]: <version-number>"
5+
labels: release
6+
assignees: rajeeja
7+
78
---
89

910
Date of intended release:

.github/workflows/asv-benchmarking-pr.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ jobs:
6565
6666
- name: Post or update result comment
6767
id: comment
68-
uses: actions/github-script@v8
68+
uses: actions/github-script@v9
6969
with:
7070
github-token: ${{ secrets.GITHUB_TOKEN }}
7171
script: |

.github/workflows/asv-benchmarking.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ jobs:
3838
path: uxarray-asv
3939

4040
- name: Setup Miniforge
41-
uses: conda-incubator/setup-miniconda@v3
41+
uses: conda-incubator/setup-miniconda@v4
4242
with:
4343
miniforge-version: "24.1.2-0"
4444
activate-environment: asv

.github/workflows/ci.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ jobs:
1111
# github.repository == 'UXARRAY/uxarray'
1212
name: Python (${{ matrix.python-version }}, ${{ matrix.os }})
1313
runs-on: ${{ matrix.os }}
14+
env:
15+
MPLBACKEND: Agg
1416
defaults:
1517
run:
1618
shell: bash -l {0}
@@ -32,7 +34,7 @@ jobs:
3234
token: ${{ github.token }}
3335

3436
- name: conda_setup (x64)
35-
uses: conda-incubator/setup-miniconda@v3
37+
uses: conda-incubator/setup-miniconda@v4
3638
if: matrix.os != 'macos-14'
3739
with:
3840
activate-environment: uxarray_build
@@ -44,7 +46,7 @@ jobs:
4446
miniforge-version: latest
4547

4648
- name: conda_setup (ARM64)
47-
uses: conda-incubator/setup-miniconda@v3
49+
uses: conda-incubator/setup-miniconda@v4
4850
if: matrix.os == 'macos-14'
4951
with:
5052
activate-environment: uxarray_build

.github/workflows/upstream-dev-ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ jobs:
7070
shell: bash -l {0}
7171
steps:
7272
- name: checkout
73-
uses: actions/checkout@v6.0.2
73+
uses: actions/checkout@v6
7474

7575
- name: Create or update failure issue
7676
shell: bash

.github/workflows/yac-optional.yml

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
name: YAC Optional CI
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- ".github/workflows/yac-optional.yml"
7+
- "uxarray/remap/**"
8+
- "test/test_remap_yac.py"
9+
workflow_dispatch:
10+
11+
jobs:
12+
yac-optional:
13+
name: YAC core v3.14.0_p1 (Ubuntu)
14+
runs-on: ubuntu-latest
15+
defaults:
16+
run:
17+
shell: bash -l {0}
18+
env:
19+
YAC_VERSION: v3.14.0_p1
20+
YAXT_VERSION: v0.11.5.1
21+
MPIEXEC: /usr/bin/mpirun
22+
MPIRUN: /usr/bin/mpirun
23+
MPICC: /usr/bin/mpicc
24+
MPIFC: /usr/bin/mpif90
25+
MPIF90: /usr/bin/mpif90
26+
OMPI_ALLOW_RUN_AS_ROOT: 1
27+
OMPI_ALLOW_RUN_AS_ROOT_CONFIRM: 1
28+
steps:
29+
- name: checkout
30+
uses: actions/checkout@v6
31+
with:
32+
token: ${{ github.token }}
33+
34+
- name: conda_setup
35+
uses: conda-incubator/setup-miniconda@v4
36+
with:
37+
activate-environment: uxarray_build
38+
channel-priority: strict
39+
python-version: "3.11"
40+
channels: conda-forge
41+
environment-file: ci/environment.yml
42+
miniforge-variant: Miniforge3
43+
miniforge-version: latest
44+
45+
- name: Install build dependencies (apt)
46+
run: |
47+
sudo apt-get update
48+
sudo apt-get install -y \
49+
autoconf \
50+
automake \
51+
gawk \
52+
gfortran \
53+
libopenmpi-dev \
54+
libtool \
55+
make \
56+
openmpi-bin \
57+
pkg-config
58+
- name: Verify MPI tools
59+
run: |
60+
which mpirun
61+
which mpicc
62+
which mpif90
63+
mpirun --version
64+
mpicc --version
65+
mpif90 --version
66+
- name: Install Python build dependencies
67+
run: |
68+
python -m pip install --upgrade pip
69+
python -m pip install cython wheel
70+
- name: Build and install YAXT
71+
run: |
72+
set -euxo pipefail
73+
YAC_PREFIX="${GITHUB_WORKSPACE}/yac_prefix"
74+
echo "YAC_PREFIX=${YAC_PREFIX}" >> "${GITHUB_ENV}"
75+
git clone --depth 1 --branch "${YAXT_VERSION}" https://gitlab.dkrz.de/dkrz-sw/yaxt.git
76+
if [ ! -x yaxt/configure ]; then
77+
if [ -x yaxt/autogen.sh ]; then
78+
(cd yaxt && ./autogen.sh)
79+
else
80+
(cd yaxt && autoreconf -i)
81+
fi
82+
fi
83+
mkdir -p yaxt/build
84+
cd yaxt/build
85+
../configure \
86+
--prefix="${YAC_PREFIX}" \
87+
--without-regard-for-quality \
88+
CC="${MPICC}" \
89+
FC="${MPIF90}"
90+
make -j2
91+
make install
92+
- name: Build and install YAC
93+
run: |
94+
set -euxo pipefail
95+
git clone --depth 1 --branch "${YAC_VERSION}" https://gitlab.dkrz.de/dkrz-sw/yac.git
96+
if [ ! -x yac/configure ]; then
97+
if [ -x yac/autogen.sh ]; then
98+
(cd yac && ./autogen.sh)
99+
else
100+
(cd yac && autoreconf -i)
101+
fi
102+
fi
103+
mkdir -p yac/build
104+
cd yac/build
105+
../configure \
106+
--prefix="${YAC_PREFIX}" \
107+
--with-yaxt-root="${YAC_PREFIX}" \
108+
--disable-mci \
109+
--disable-utils \
110+
--disable-examples \
111+
--disable-tools \
112+
--disable-netcdf \
113+
--enable-python-bindings \
114+
CC="${MPICC}" \
115+
FC="${MPIF90}"
116+
make -j2
117+
make install
118+
- name: Configure YAC runtime paths
119+
run: |
120+
set -euxo pipefail
121+
PY_VER="$(python -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")')"
122+
echo "LD_LIBRARY_PATH=${YAC_PREFIX}/lib:${LD_LIBRARY_PATH:-}" >> "${GITHUB_ENV}"
123+
echo "PYTHONPATH=${YAC_PREFIX}/lib/python${PY_VER}/site-packages:${YAC_PREFIX}/lib/python${PY_VER}/dist-packages:${PYTHONPATH:-}" >> "${GITHUB_ENV}"
124+
- name: Verify YAC core Python bindings
125+
run: |
126+
python - <<'PY'
127+
from pathlib import Path
128+
import sys
129+
candidates = []
130+
for entry in sys.path:
131+
pkg = Path(entry) / "yac"
132+
candidates.extend(pkg.glob("core*.so"))
133+
candidates.extend(pkg.glob("core*.pyd"))
134+
assert candidates, "yac.core extension not found on sys.path"
135+
print("Found yac.core extension:", candidates[0])
136+
PY
137+
- name: Install uxarray
138+
run: |
139+
python -m pip install . --no-deps
140+
- name: Run tests (uxarray with YAC)
141+
run: |
142+
python -m pytest test/test_remap_yac.py

ci/install-upstream.sh

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ conda remove -y --force \
88
datashader \
99
distributed \
1010
holoviews \
11-
numpy \
1211
pandas \
1312
scikit-learn \
1413
scipy \
@@ -33,7 +32,6 @@ python -m pip install \
3332
--no-deps \
3433
--pre \
3534
--upgrade \
36-
numpy \
3735
scikit-learn \
3836
scipy \
3937
xarray

docs/user-guide/remapping.ipynb

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,17 @@
1515
"\n",
1616
"- **Nearest Neighbor** \n",
1717
"- **Inverse Distance Weighted**\n",
18-
"- **Bilinear**\n"
18+
"- **Bilinear**\n",
19+
"\n",
20+
"```{admonition} Optional YAC backend\n",
21+
":class: tip\n",
22+
"\n",
23+
"UXarray uses its native remapping backend by default. If [YAC](https://dkrz-sw.gitlab-pages.dkrz.de/yac/) is installed with its `yac.core` Python bindings, `.remap(...)`, `.remap.nearest_neighbor(...)`, and `.remap.bilinear(...)` can use `backend=\"yac\"` to route remapping through YAC.\n",
24+
"\n",
25+
"When `backend=\"yac\"`, the `yac_method` parameter selects the YAC interpolation method. Supported values are `nnn`, `average`, and `conservative`. `inverse_distance_weighted()` remains UXarray-only, and `bilinear(..., backend=\"yac\")` is a convenience wrapper for `yac_method=\"average\"`. For conservative face-centered remapping, use the generic `.remap(..., backend=\"yac\", yac_method=\"conservative\")` entrypoint.\n",
26+
"\n",
27+
"See the [YAC documentation](https://dkrz-sw.gitlab-pages.dkrz.de/yac/) and [YAC installation guide](https://dkrz-sw.gitlab-pages.dkrz.de/yac/d1/d9f/installing_yac.html) for build instructions, including enabling the Python bindings.\n",
28+
"```\n"
1929
]
2030
},
2131
{
@@ -132,6 +142,14 @@
132142
"- **remap_to** \n",
133143
" The grid element where values should be placed, one of `faces`, `edges`, or `nodes`.\n",
134144
"\n",
145+
"- **backend** \n",
146+
" The remapping backend to use. The default is `\"uxarray\"`; set `backend=\"yac\"` to route the remap through YAC.\n",
147+
"\n",
148+
"- **yac_method** \n",
149+
" Required only when `backend=\"yac\"`. Supported values are `nnn`, `average`, and `conservative`; `nearest_neighbor()` defaults to `nnn`.\n",
150+
"\n",
151+
"- **yac_options** \n",
152+
" Optional dictionary of method-specific keyword arguments forwarded to the selected YAC interpolation routine.\n",
135153
"\n",
136154
"```{warning}\n",
137155
"Nearest-neighbor remapping is fast and simple, but it does **not** conserve integrated quantities\n",
@@ -480,7 +498,9 @@
480498
"id": "6bec26ce-67b6-4300-a310-63cbac2b289a",
481499
"metadata": {},
482500
"source": [
483-
"Bilinear remapping breaks down the grid into triangles, and then finds the triangle that contains each point on the destinitation grid. It then uses the values stored at each vertex to bilinearly find a value for the point, depending on it's postion inside the triangle."
501+
"Bilinear remapping breaks down the grid into triangles, and then finds the triangle that contains each point on the destinitation grid. It then uses the values stored at each vertex to bilinearly find a value for the point, depending on it's postion inside the triangle.\n",
502+
"\n",
503+
"When `backend=\"yac\"`, `remap.bilinear()` delegates to YAC's `average` method. This is the only YAC method exposed through the bilinear convenience accessor; use `.remap(..., backend=\"yac\", yac_method=...)` if you need to select another YAC method explicitly."
484504
]
485505
},
486506
{

test/core/test_dataset.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,3 +99,16 @@ def test_sel_method_forwarded(gridpath, datasetpath):
9999
nearest["time"].values,
100100
np.array(uxds["time"].values[2], dtype="datetime64[ns]"),
101101
)
102+
103+
def test_uxdataset_init_from_xarray_dataset():
104+
ds = xr.Dataset(
105+
data_vars={"a": ("x", [1, 2])},
106+
coords={"x": [10, 20]},
107+
attrs={"source": "testing"},
108+
)
109+
110+
uxds = ux.UxDataset(ds)
111+
112+
assert "a" in uxds.data_vars
113+
assert "x" in uxds.coords
114+
assert uxds.attrs["source"] == "testing"

test/io/test_utils.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import numpy as np
2+
import pytest
3+
import xarray as xr
4+
5+
from uxarray.io.utils import _parse_grid_type
6+
7+
8+
@pytest.mark.parametrize(
9+
("path_args", "expected_spec"),
10+
[
11+
(("exodus", "outCSne8", "outCSne8.g"), "Exodus"),
12+
(("scrip", "outCSne8", "outCSne8.nc"), "Scrip"),
13+
(("ugrid", "outCSne30", "outCSne30.ug"), "UGRID"),
14+
(("mpas", "QU", "mesh.QU.1920km.151026.nc"), "MPAS"),
15+
(("esmf", "ne30", "ne30pg3.grid.nc"), "ESMF"),
16+
(("geos-cs", "c12", "test-c12.native.nc4"), "GEOS-CS"),
17+
(("icon", "R02B04", "icon_grid_0010_R02B04_G.nc"), "ICON"),
18+
(("fesom", "soufflet-netcdf", "grid.nc"), "FESOM2"),
19+
],
20+
)
21+
def test_parse_grid_type_detects_supported_formats(gridpath, path_args, expected_spec):
22+
with xr.open_dataset(gridpath(*path_args)) as ds:
23+
source_grid_spec, lon_name, lat_name = _parse_grid_type(ds)
24+
25+
assert source_grid_spec == expected_spec
26+
assert lon_name is None
27+
assert lat_name is None
28+
29+
30+
def test_parse_grid_type_detects_structured_grid():
31+
lon = xr.DataArray(
32+
np.array([0.0, 1.0, 2.0]),
33+
dims=["lon"],
34+
attrs={"standard_name": "longitude"},
35+
)
36+
lat = xr.DataArray(
37+
np.array([-1.0, 0.0, 1.0]),
38+
dims=["lat"],
39+
attrs={"standard_name": "latitude"},
40+
)
41+
ds = xr.Dataset(coords={"lon": lon, "lat": lat})
42+
43+
source_grid_spec, lon_name, lat_name = _parse_grid_type(ds)
44+
45+
assert source_grid_spec == "Structured"
46+
assert lon_name == "lon"
47+
assert lat_name == "lat"
48+
49+
50+
@pytest.mark.parametrize(
51+
"dataset",
52+
[
53+
xr.Dataset({"grid_center_lon": xr.DataArray([0.0], dims=["grid_size"])}),
54+
xr.Dataset(
55+
{
56+
"coordx": xr.DataArray([0.0, 1.0], dims=["num_nodes"]),
57+
"coordy": xr.DataArray([0.0, 1.0], dims=["num_nodes"]),
58+
}
59+
),
60+
xr.Dataset({"verticesOnCell": xr.DataArray([[1, 2, 3]], dims=["nCells", "nVert"])}),
61+
],
62+
)
63+
def test_parse_grid_type_rejects_incomplete_format_signals(dataset):
64+
with pytest.raises(RuntimeError, match="Could not recognize dataset format"):
65+
_parse_grid_type(dataset)

0 commit comments

Comments
 (0)