Skip to content

Commit ce81a17

Browse files
authored
replace healpy with cdshealpix (#92)
* start replacing `healpy` with `cdshealpix` * work around a dtype casting bug in cdshealpix * reorder the cell vertices (`cdshealpix` starts with a different pixel than `healpy` – otherwise they're the same) * depend on `cdshealpix` instead of `healpy` * add `cdshealpix-python` to intersphinx * also install `cdshealpix` instead of `healpy` in the upstream-dev ci * change the backend library in the docstring of `from_dict` * test with more dtypes * run CI on windows * don't install `conda` since we don't need it * install `cdshealpix` from PyPI * only enforce colors for `pytest` * override `FORCE_COLOR` for `setup-micromamba` specifically * pin `micromamba` instead * use `assert_allclose` to compare
1 parent 61c5ac2 commit ce81a17

File tree

10 files changed

+85
-61
lines changed

10 files changed

+85
-61
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ jobs:
4545
fail-fast: false
4646

4747
matrix:
48-
os: ["ubuntu-latest", "macos-latest"]
48+
os: ["ubuntu-latest", "macos-latest", "windows-latest"]
4949
python-version: ["3.10", "3.11", "3.12"]
5050

5151
steps:
@@ -61,13 +61,13 @@ jobs:
6161
- name: Setup micromamba
6262
uses: mamba-org/setup-micromamba@v2
6363
with:
64+
micromamba-version: "1.5.10-0"
6465
environment-file: ${{ env.CONDA_ENV_FILE }}
6566
environment-name: xdggs-tests
6667
cache-environment: true
6768
cache-environment-key: "${{runner.os}}-${{runner.arch}}-py${{matrix.python-version}}-${{env.TODAY}}-${{hashFiles(env.CONDA_ENV_FILE)}}"
6869
create-args: >-
6970
python=${{matrix.python-version}}
70-
conda
7171
7272
- name: Install xdggs
7373
run: |

ci/docs.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ dependencies:
1212
- sphinx-inline-tabs
1313
- sphinxcontrib-bibtex
1414
- xarray
15-
- healpy
1615
- h5netcdf
1716
- netcdf4
1817
- pooch
@@ -28,4 +27,5 @@ dependencies:
2827
- pip
2928
- pip:
3029
- h3ronpy
30+
- cdshealpix
3131
- -e ..

ci/environment.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ channels:
22
- conda-forge
33
dependencies:
44
- xarray
5-
- healpy
65
- h5netcdf
76
- netcdf4
87
- pooch
@@ -18,3 +17,4 @@ dependencies:
1817
- pip
1918
- pip:
2019
- h3ronpy
20+
- cdshealpix

ci/install-upstream-wheels.sh

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ fi
1010
$conda remove -y --force \
1111
xarray \
1212
pandas \
13-
healpy
13+
cdshealpix
1414
python -m pip uninstall -y h3ronpy
1515

1616
# install from scientific-python wheels
@@ -36,23 +36,5 @@ python -m pip install \
3636
python -m pip install --no-deps --upgrade \
3737
git+https://github.com/Unidata/cftime \
3838
git+https://github.com/astropy/astropy \
39-
"git+https://github.com/nmandery/h3ronpy#subdirectory=h3ronpy"
40-
41-
# install healpy from github
42-
# need to run `auditwheel` to include the shared libs
43-
python -m pip install auditwheel
44-
45-
# build and repair the wheel
46-
# - manual clone
47-
mkdir deps
48-
git clone --filter=blob:none --quiet https://github.com/healpy/healpy deps/healpy
49-
pushd deps/healpy
50-
git submodule update --init --recursive -q
51-
# - build and repair the wheel
52-
mkdir -p built_wheel repaired_wheel
53-
python -m pip wheel --no-deps . --wheel-dir built_wheel
54-
auditwheel repair --plat linux_x86_64 -w repaired_wheel built_wheel/healpy-*.whl
55-
# - install the repaired wheel
56-
python -m pip install --upgrade --no-deps repaired_wheel/healpy-*.whl
57-
# - clean up
58-
popd; rm -rf deps
39+
"git+https://github.com/nmandery/h3ronpy#subdirectory=h3ronpy" \
40+
git+https://github.com/cds-astro/cds-healpix-python

docs/conf.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@
9797
"pandas": ("https://pandas.pydata.org/pandas-docs/stable", None),
9898
"lonboard": ("https://developmentseed.org/lonboard/latest", None),
9999
"healpy": ("https://healpy.readthedocs.io/en/latest", None),
100+
"cdshealpix-python": ("https://cds-astro.github.io/cds-healpix-python", None),
100101
"shapely": ("https://shapely.readthedocs.io/en/stable", None),
101102
}
102103

docs/tutorials/healpix.ipynb

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -152,19 +152,29 @@
152152
"derived_ds"
153153
]
154154
},
155+
{
156+
"cell_type": "markdown",
157+
"id": "11",
158+
"metadata": {},
159+
"source": [
160+
"```{note}\n",
161+
"We need to use {py:func}`xarray.testing.assert_allclose` to compare cell centers because the cell center coordinates were computed using a [different library](https://github.com/healpy/healpy) with a slightly different implementation, resulting in small floating point differences.\n",
162+
"```"
163+
]
164+
},
155165
{
156166
"cell_type": "code",
157167
"execution_count": null,
158-
"id": "11",
168+
"id": "12",
159169
"metadata": {},
160170
"outputs": [],
161171
"source": [
162-
"xr.testing.assert_equal(derived_ds, original_ds)"
172+
"xr.testing.assert_allclose(derived_ds, original_ds)"
163173
]
164174
},
165175
{
166176
"cell_type": "markdown",
167-
"id": "12",
177+
"id": "13",
168178
"metadata": {},
169179
"source": [
170180
"### Cell boundary polygons\n",
@@ -175,7 +185,7 @@
175185
{
176186
"cell_type": "code",
177187
"execution_count": null,
178-
"id": "13",
188+
"id": "14",
179189
"metadata": {},
180190
"outputs": [],
181191
"source": [
@@ -185,7 +195,7 @@
185195
},
186196
{
187197
"cell_type": "markdown",
188-
"id": "14",
198+
"id": "15",
189199
"metadata": {},
190200
"source": [
191201
"## Plotting\n",
@@ -200,7 +210,7 @@
200210
{
201211
"cell_type": "code",
202212
"execution_count": null,
203-
"id": "15",
213+
"id": "16",
204214
"metadata": {},
205215
"outputs": [],
206216
"source": [
@@ -219,7 +229,7 @@
219229
"name": "python",
220230
"nbconvert_exporter": "python",
221231
"pygments_lexer": "ipython3",
222-
"version": "3.12.7"
232+
"version": "3.11.6"
223233
}
224234
},
225235
"nbformat": 4,

environment.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ channels:
33
- conda-forge
44
dependencies:
55
- xarray
6-
- healpy
6+
- cdshealpix
77
- ruff
88
- pytest
99
- h5netcdf

pyproject.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ classifiers = [
3838
requires-python = ">=3.10"
3939
dependencies = [
4040
"xarray",
41-
"healpy",
41+
"cdshealpix",
4242
"h3ronpy",
4343
"typing-extensions",
4444
"lonboard>=0.9.3",
@@ -90,7 +90,8 @@ fixable = ["I", "TID252"]
9090
known-first-party = ["xdggs"]
9191
known-third-party = [
9292
"xarray",
93-
"healpy",
93+
"cdshealpix",
94+
"astropy",
9495
"h3ronpy",
9596
]
9697

xdggs/healpix.py

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
except ImportError: # pragma: no cover
99
from typing_extensions import Self
1010

11-
import healpy
11+
import cdshealpix.nested
12+
import cdshealpix.ring
1213
import numpy as np
1314
import xarray as xr
1415
from xarray.indexes import PandasIndex
@@ -105,7 +106,7 @@ class HealpixInfo(DGGSInfo):
105106
106107
.. warning::
107108
Note that ``"unique"`` is currently not supported as the underlying library
108-
(:doc:`healpy <healpy:index>`) does not support it.
109+
(:doc:`cdshealpix <cdshealpix-python:index>`) does not support it.
109110
"""
110111

111112
level: int
@@ -210,9 +211,17 @@ def cell_ids2geographic(self, cell_ids):
210211
lat : array-like
211212
The latitude coordinate values of the grid cells in degree
212213
"""
213-
lon, lat = healpy.pix2ang(self.nside, cell_ids, nest=self.nest, lonlat=True)
214+
converters = {
215+
"nested": cdshealpix.nested.healpix_to_lonlat,
216+
"ring": lambda cell_ids, level: cdshealpix.ring.healpix_to_lonlat(
217+
cell_ids, nside=2**level
218+
),
219+
}
220+
converter = converters[self.indexing_scheme]
221+
222+
lon, lat = converter(cell_ids, self.level)
214223

215-
return lon, lat
224+
return np.asarray(lon.to("degree")), np.asarray(lat.to("degree"))
216225

217226
def geographic2cell_ids(self, lon, lat):
218227
"""
@@ -233,7 +242,20 @@ def geographic2cell_ids(self, lon, lat):
233242
cell_ids : array-like
234243
Array-like containing the cell ids.
235244
"""
236-
return healpy.ang2pix(self.nside, lon, lat, lonlat=True, nest=self.nest)
245+
from astropy.coordinates import Latitude, Longitude
246+
247+
converters = {
248+
"nested": cdshealpix.nested.lonlat_to_healpix,
249+
"ring": lambda lon, lat, level: cdshealpix.ring.lonlat_to_healpix(
250+
lon, lat, nside=2**level
251+
),
252+
}
253+
converter = converters[self.indexing_scheme]
254+
255+
longitude = Longitude(lon, unit="degree")
256+
latitude = Latitude(lat, unit="degree")
257+
258+
return converter(longitude, latitude, self.level)
237259

238260
def cell_boundaries(self, cell_ids: Any, backend="shapely") -> np.ndarray:
239261
"""
@@ -255,11 +277,19 @@ def cell_boundaries(self, cell_ids: Any, backend="shapely") -> np.ndarray:
255277
- ``"shapely"``: return a array of :py:class:`shapely.Polygon` objects
256278
- ``"geoarrow"``: return a ``geoarrow`` array
257279
"""
258-
boundary_vectors = healpy.boundaries(
259-
self.nside, cell_ids, step=1, nest=self.nest
260-
)
280+
converters = {
281+
"nested": cdshealpix.nested.vertices,
282+
"ring": lambda cell_ids, level, **kwargs: cdshealpix.ring.vertices(
283+
cell_ids, nside=2**level, **kwargs
284+
),
285+
}
286+
converter = converters[self.indexing_scheme]
287+
288+
lon_, lat_ = converter(cell_ids, self.level, step=1)
289+
290+
lon = np.asarray(lon_.to("degree"))
291+
lat = np.asarray(lat_.to("degree"))
261292

262-
lon, lat = healpy.vec2ang(np.moveaxis(boundary_vectors, 1, -1), lonlat=True)
263293
lon_reshaped = np.reshape(lon, (-1, 4))
264294
lat_reshaped = np.reshape(lat, (-1, 4))
265295

xdggs/tests/test_healpix.py

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,7 @@ def grid_mappings(cls):
5555

5656
def cell_ids(max_value=None, dtypes=None):
5757
if dtypes is None:
58-
# healpy can't deal with `uint32` or less (it segfaults occasionally)
59-
dtypes = st.sampled_from(["uint64"])
58+
dtypes = st.sampled_from(["int32", "int64", "uint32", "uint64"])
6059
shapes = npst.array_shapes(min_dims=1, max_dims=1)
6160

6261
return npst.arrays(
@@ -89,9 +88,7 @@ def grid_and_cell_ids(
8988
cell_levels = st.shared(levels, key="common-levels")
9089
grid_levels = st.shared(levels, key="common-levels")
9190
cell_ids_ = cell_levels.flatmap(
92-
lambda level: cls.cell_ids(
93-
max_value=12 * 2 ** (level * 2) - 1, dtypes=dtypes
94-
)
91+
lambda level: cls.cell_ids(max_value=12 * 4**level - 1, dtypes=dtypes)
9592
)
9693
grids_ = cls.grids(
9794
levels=grid_levels,
@@ -222,10 +219,10 @@ def test_roundtrip(self, level, indexing_scheme):
222219
np.array([2]),
223220
np.array(
224221
[
225-
[-135.0, 90.0],
226-
[-180.0, 41.8103149],
227222
[-135.0, 0.0],
228223
[-90.0, 41.8103149],
224+
[-135.0, 90.0],
225+
[-180.0, 41.8103149],
229226
]
230227
),
231228
),
@@ -235,16 +232,16 @@ def test_roundtrip(self, level, indexing_scheme):
235232
np.array(
236233
[
237234
[
238-
[0.0, 66.44353569],
239-
[0.0, 54.3409123],
240235
[22.5, 41.8103149],
241236
[30.0, 54.3409123],
237+
[0.0, 66.44353569],
238+
[0.0, 54.3409123],
242239
],
243240
[
244-
[-45.0, 41.8103149],
245-
[-56.25, 30.0],
246241
[-45.0, 19.47122063],
247242
[-33.75, 30.0],
243+
[-45.0, 41.8103149],
244+
[-56.25, 30.0],
248245
],
249246
]
250247
),
@@ -255,16 +252,16 @@ def test_roundtrip(self, level, indexing_scheme):
255252
np.array(
256253
[
257254
[
258-
[-5.625, 4.78019185],
259-
[-11.25, 0.0],
260255
[-5.625, -4.78019185],
261256
[0.0, 0.0],
257+
[-5.625, 4.78019185],
258+
[-11.25, 0.0],
262259
],
263260
[
264-
[73.125, 35.68533471],
265-
[67.5, 30.0],
266261
[73.125, 24.62431835],
267262
[78.75, 30.0],
263+
[73.125, 35.68533471],
264+
[67.5, 30.0],
268265
],
269266
]
270267
),
@@ -274,10 +271,10 @@ def test_roundtrip(self, level, indexing_scheme):
274271
np.array([79]),
275272
np.array(
276273
[
277-
[0.0, 41.8103149],
278-
[-11.25, 30],
279274
[0.0, 19.47122063],
280275
[11.25, 30],
276+
[0.0, 41.8103149],
277+
[-11.25, 30],
281278
]
282279
),
283280
),
@@ -300,6 +297,9 @@ def test_cell_boundaries(self, params, cell_ids, backend, expected_coords):
300297

301298
@given(
302299
*strategies.grid_and_cell_ids(
300+
# a dtype casting bug in the valid range check of `cdshealpix`
301+
# causes this test to fail for large levels
302+
levels=st.integers(min_value=0, max_value=10),
303303
indexing_schemes=st.sampled_from(["nested", "ring"]),
304304
dtypes=st.sampled_from(["int64"]),
305305
)

0 commit comments

Comments
 (0)