From 6ce0fab7f75a9b98b4d4bd09b76fbbaafc0466bb Mon Sep 17 00:00:00 2001 From: Trevor James Smith <10819524+Zeitsperre@users.noreply.github.com> Date: Tue, 6 Aug 2024 12:44:03 -0400 Subject: [PATCH 01/18] synchronize and update version pins --- environment.yml | 47 ++++++++++++++++---------------- pyproject.toml | 72 ++++++++++++++++++++++++++----------------------- 2 files changed, 62 insertions(+), 57 deletions(-) diff --git a/environment.yml b/environment.yml index 8ea404ff8..67745f31a 100644 --- a/environment.yml +++ b/environment.yml @@ -10,35 +10,36 @@ dependencies: - cftime >=1.4.1 - click >=8.1 - dask >=2.6.0 - - jsonpickle - - numba + - filelock >=3.14.0 + - jsonpickle >=3.1.0 + - numba # numpy v2.0 compatibility is in numba v0.60.0, but not pinning it to avoid breaking older numpy versions - numpy >=1.20.0 + - packaging >=24.0 - pandas >=2.2.0 - pint >=0.18.0 - poppler >=0.67 - - pyarrow # Strongly encouraged for Pandas v2.2.0+ - - pyyaml + - pyarrow >=15.0.0 # Strongly encouraged for Pandas v2.2.0+ + - pyyaml >=6.0.1 - scikit-learn >=0.21.3 - scipy >=1.9.0 - - statsmodels + - statsmodels >=0.14.2 - xarray >=2023.11.0 - - yamale + - yamale >=5.0.0 # Extras - flox - lmoments3 # Required for some Jupyter notebooks # Testing and development dependencies - black ==24.4.2 - blackdoc ==0.3.9 - - bump-my-version >=0.23.0 + - bump-my-version >=0.24.3 - cairosvg - codespell ==2.3.0 - coverage >=7.5.0 - coveralls >=4.0.0 - - deptry ==0.16.1 + - deptry ==0.18.0 - distributed >=2.0 - - filelock - - flake8 >=7.0.0 - - flake8-rst-docstrings + - flake8 >=7.1.1 + - flake8-rst-docstrings >=0.3.0 - flit >=3.9.0 - furo >=2023.9.10 - h5netcdf >=1.3.0 @@ -46,24 +47,24 @@ dependencies: - ipython - isort ==5.13.2 - matplotlib - - mypy + - mypy >=1.10.0 - nbconvert <7.14 # Pinned due to directive errors in sphinx. See: https://github.com/jupyter/nbconvert/issues/2092 - - nbqa + - nbqa >=1.8.2 - nbsphinx - nbval >=0.11.0 - - nc-time-axis + - nc-time-axis >=1.4.1 - notebook - - pandas-stubs - - platformdirs - - pooch + - pandas-stubs >=2.2 + - platformdirs >=3.2 + - pooch >=1.8.0 - pre-commit >=3.7 - pybtex >=0.24.0 - pylint >=3.1 - pytest >=8.0.0 - - pytest-cov - - pytest-socket + - pytest-cov >=5.0.0 + - pytest-socket >=0.6.0 - pytest-xdist >=3.2 - - ruff >=0.4.10 + - ruff >=0.5.6 - sphinx >=7.0.0 - sphinx-autobuild >=2024.4.16 - sphinx-autodoc-typehints @@ -71,12 +72,12 @@ dependencies: - sphinx-copybutton - sphinx-mdinclude - sphinxcontrib-bibtex - - tokenize-rt + - tokenize-rt >=5.2.0 - tox >=4.16.0 # - tox-conda # Will be added when a tox@v4.0+ compatible plugin is released. - vulture # ==2.11 # The conda-forge version is out of date. - xdoctest >=1.1.5 - - yamllint - - pip + - yamllint >=1.35.1 + - pip >=24.0 - pip: - sphinxcontrib-svg2pdfconverter diff --git a/pyproject.toml b/pyproject.toml index 0f7773845..4e0fc46fa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,65 +27,69 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", - "Topic :: Scientific/Engineering :: Atmospheric Science" + "Programming Language :: Python :: 3 :: Only", + "Topic :: Scientific/Engineering :: Atmospheric Science", + "Topic :: Scientific/Engineering :: Hydrology", + "Typing :: Typed" ] dynamic = ["description", "version"] dependencies = [ - "boltons>=20.1", - "bottleneck>=1.3.1", + "boltons >=20.1", + "bottleneck >=1.3.1", # cf-xarray is differently named on conda-forge - "cf-xarray>=0.6.1", - "cftime>=1.4.1", - "click>=8.1", - "dask[array]>=2.6", - "filelock", - "jsonpickle", - "numba", - "numpy>=1.20.0", - "packaging", - "pandas>=2.2", - "pint>=0.18", + "cf-xarray >=0.6.1", + "cftime >=1.4.1", + "click >=8.1", + "dask[array] >=2.6", + "filelock >=3.14.0", + "jsonpickle >=3.1.0", + "numba", # numpy v2.0 compatibility is in numba v0.60.0, but not pinning it to avoid breaking older numpy versions + "numpy >=1.20.0", + "packaging >=24.0", + "pandas >=2.2", + "pint >=0.18", "platformdirs >=3.2", - "pyarrow", # Strongly encouraged for pandas v2.2.0+ - "pyyaml", - "scikit-learn>=0.21.3", - "scipy>=1.9.0", - "statsmodels", - "xarray>=2023.11.0", - "yamale" + "pyarrow >=15.0.0", # Strongly encouraged for pandas v2.2.0+ + "pyyaml >=6.0.1", + "scikit-learn >=0.21.3", + "scipy >=1.9.0", + "statsmodels >=0.14.2", + "xarray >=2023.11.0", + "yamale >=5.0.0" ] [project.optional-dependencies] dev = [ # Dev tools and testing - "black[jupyter] ==24.4.2", + "black[jupyter] ==24.8.0", "blackdoc ==0.3.9", "bump-my-version ==0.24.3", "codespell ==2.3.0", "coverage[toml] >=7.5.0", "coveralls >=4.0.0", "deptry ==0.18.0", - "flake8 >=7.1.0", - "flake8-rst-docstrings", + "flake8 >=7.1.1", + "flake8-rst-docstrings >=0.3.0", "h5netcdf>=1.3.0", "ipython", "isort ==5.13.2", - "mypy", + "mypy >=1.10.0", "nbconvert <7.14", # Pinned due to directive errors in sphinx. See: https://github.com/jupyter/nbconvert/issues/2092 "nbqa >=1.8.2", "nbval >=0.11.0", "pandas-stubs >=2.2", + "pip >=24.0", "platformdirs >=3.2", - "pooch", + "pooch >=1.8.0", "pre-commit >=3.7", "pylint >=3.2.4", "pytest >=8.0.0", - "pytest-cov", - "pytest-socket", + "pytest-cov >=5.0.0", + "pytest-socket >=0.6.0", "pytest-xdist[psutil] >=3.2", - "ruff >=0.4.10", - "tokenize-rt", - "tox >=4.16.0", + "ruff >=0.5.6", + "tokenize-rt >=5.2.0", + "tox >=4.17.0", # "tox-conda", # Will be added when a tox@v4.0+ compatible plugin is released. "vulture ==2.11", "xdoctest >=1.1.5", @@ -98,8 +102,8 @@ docs = [ "ipykernel", "matplotlib", "nbsphinx", - "nc-time-axis", - "pooch", + "nc-time-axis >=1.4.1", + "pooch >=1.8.0", "pybtex >=0.24.0", "sphinx >=7.0.0", "sphinx-autobuild >=2024.4.16", @@ -110,7 +114,7 @@ docs = [ "sphinxcontrib-bibtex", "sphinxcontrib-svg2pdfconverter[Cairosvg]" ] -extras = ["fastnanquantile"] +extras = ["fastnanquantile >=0.0.2"] all = ["xclim[dev]", "xclim[docs]", "xclim[extras]"] [project.scripts] From e77acf4ca9025e98778d314984cb1e3900afa60f Mon Sep 17 00:00:00 2001 From: Trevor James Smith <10819524+Zeitsperre@users.noreply.github.com> Date: Tue, 6 Aug 2024 12:44:19 -0400 Subject: [PATCH 02/18] fix typo --- xclim/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xclim/cli.py b/xclim/cli.py index ebb35aba1..67a6da1eb 100644 --- a/xclim/cli.py +++ b/xclim/cli.py @@ -416,7 +416,7 @@ def get_command(self, ctx, name): @click.option( "--engine", help="Engine to use when opening the input dataset(s). " - "If not specified, xarrat decides.", + "If not specified, xarray decides.", ) @click.pass_context def cli(ctx, **kwargs): From 79228faa1f031d550ff67fb8a642aa9c6fe544bd Mon Sep 17 00:00:00 2001 From: Trevor James Smith <10819524+Zeitsperre@users.noreply.github.com> Date: Tue, 6 Aug 2024 12:48:20 -0400 Subject: [PATCH 03/18] remove poppler --- environment.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/environment.yml b/environment.yml index 67745f31a..564ddb456 100644 --- a/environment.yml +++ b/environment.yml @@ -17,7 +17,6 @@ dependencies: - packaging >=24.0 - pandas >=2.2.0 - pint >=0.18.0 - - poppler >=0.67 - pyarrow >=15.0.0 # Strongly encouraged for Pandas v2.2.0+ - pyyaml >=6.0.1 - scikit-learn >=0.21.3 From 39aad930e9cca273981d458aa5e5f9b453142118 Mon Sep 17 00:00:00 2001 From: Trevor James Smith <10819524+Zeitsperre@users.noreply.github.com> Date: Tue, 6 Aug 2024 13:06:16 -0400 Subject: [PATCH 04/18] fix coveralls reporting --- .github/workflows/main.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8e4ab2a82..7147ac9ac 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -103,6 +103,7 @@ jobs: python -m tox -- -m 'not slow' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + COVERALLS_PARALLEL: true test-pypi: needs: lint @@ -192,7 +193,6 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} COVERALLS_FLAG_NAME: run-${{ matrix.python-version }}-${{ matrix.os }}-${{ matrix.tox-env }} COVERALLS_PARALLEL: true - COVERALLS_SERVICE_NAME: github - name: Test with tox (specialized tests) if: ${{ matrix.tox-env != 'standard' }} run: | @@ -201,7 +201,6 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} COVERALLS_FLAG_NAME: run-${{ matrix.python-version }}-${{ matrix.os }}-${{ matrix.tox-env }} COVERALLS_PARALLEL: true - COVERALLS_SERVICE_NAME: github test-conda: needs: lint @@ -278,7 +277,6 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} COVERALLS_FLAG_NAME: run-{{ matrix.python-version }}-conda COVERALLS_PARALLEL: true - COVERALLS_SERVICE_NAME: github finish: needs: @@ -301,4 +299,3 @@ jobs: python -m coveralls --finish env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - COVERALLS_SERVICE_NAME: github From f08a9d0151815fc40224f9165a82bc40a4811698 Mon Sep 17 00:00:00 2001 From: Trevor James Smith <10819524+Zeitsperre@users.noreply.github.com> Date: Tue, 6 Aug 2024 14:00:09 -0400 Subject: [PATCH 05/18] adjust tests for older cf-xarray version --- tests/test_atmos.py | 2 +- tests/test_generic.py | 7 +------ tests/test_hydrology.py | 2 +- tests/test_indices.py | 45 ++++++++++++++++++++++++----------------- tests/test_land.py | 6 +++--- tests/test_snow.py | 8 ++++---- tests/test_units.py | 12 +++++++---- tox.ini | 1 + xclim/core/indicator.py | 2 +- 9 files changed, 47 insertions(+), 38 deletions(-) diff --git a/tests/test_atmos.py b/tests/test_atmos.py index d705e7493..cace721b8 100644 --- a/tests/test_atmos.py +++ b/tests/test_atmos.py @@ -264,7 +264,7 @@ def test_wind_power_potential(atmosds): out = atmos.wind_power_potential(wind_speed=atmosds.sfcWind) if Version(__cfxr_version__) < Version("0.9.3"): - assert out.attrs["units"] == "" + assert out.attrs["units"] == "dimensionless" else: assert out.attrs["units"] == "1" assert (out >= 0).all() diff --git a/tests/test_generic.py b/tests/test_generic.py index ef129121a..8448a5607 100644 --- a/tests/test_generic.py +++ b/tests/test_generic.py @@ -5,8 +5,6 @@ import numpy as np import pytest import xarray as xr -from cf_xarray import __version__ as __cfxr_version__ -from packaging.version import Version from xclim.core.calendar import doy_to_days_since, select_time from xclim.indices import generic @@ -104,10 +102,7 @@ def test_doyminmax(self, q_series): for attr in ["units", "is_dayofyear", "calendar"]: assert attr in da.attrs.keys() - if Version(__cfxr_version__) < Version("0.9.3"): - assert da.attrs["units"] == "" - else: - assert da.attrs["units"] == "1" + assert da.attrs["units"] == "1" assert da.attrs["is_dayofyear"] == 1 diff --git a/tests/test_hydrology.py b/tests/test_hydrology.py index b2276bda7..5003877d1 100644 --- a/tests/test_hydrology.py +++ b/tests/test_hydrology.py @@ -44,7 +44,7 @@ def test_simple(self, snw_series): np.testing.assert_array_equal(out, [11, np.nan]) if Version(__cfxr_version__) < Version("0.9.3"): - assert out.attrs["units"] == "" + assert out.attrs["units"] == "dimensionless" else: assert out.attrs["units"] == "1" diff --git a/tests/test_indices.py b/tests/test_indices.py index a5981e6cc..caf3103bf 100644 --- a/tests/test_indices.py +++ b/tests/test_indices.py @@ -143,7 +143,7 @@ def test_simple(self, tas_series): np.testing.assert_array_equal(out, [1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]) if Version(__cfxr_version__) < Version("0.9.3"): - assert out.units == "" + assert out.units == "dimensionless" else: assert out.units == "1" @@ -151,7 +151,7 @@ def test_simple(self, tas_series): np.testing.assert_array_equal(out, 3) if Version(__cfxr_version__) < Version("0.9.3"): - assert out.units == "" + assert out.units == "dimensionless" else: assert out.units == "1" @@ -930,7 +930,7 @@ def test_simple(self, tas_series): assert attr in lsf.attrs.keys() if Version(__cfxr_version__) < Version("0.9.3"): - assert lsf.attrs["units"] == "" + assert lsf.attrs["units"] == "dimensionless" else: assert lsf.attrs["units"] == "1" @@ -956,7 +956,7 @@ def test_simple(self, tas_series): assert attr in fdb.attrs.keys() if Version(__cfxr_version__) < Version("0.9.3"): - assert fdb.attrs["units"] == "" + assert fdb.attrs["units"] == "dimensionless" else: assert fdb.attrs["units"] == "1" @@ -992,7 +992,7 @@ def test_simple(self, tas_series): assert attr in fda.attrs.keys() if Version(__cfxr_version__) < Version("0.9.3"): - assert fda.attrs["units"] == "" + assert fda.attrs["units"] == "dimensionless" else: assert fda.attrs["units"] == "1" @@ -1022,7 +1022,7 @@ def test_thresholds(self, tas_series): assert attr in out.attrs.keys() if Version(__cfxr_version__) < Version("0.9.3"): - assert out.attrs["units"] == "" + assert out.attrs["units"] == "dimensionless" else: assert out.attrs["units"] == "1" @@ -1114,7 +1114,7 @@ def test_simple(self, tas_series): assert attr in out.attrs.keys() if Version(__cfxr_version__) < Version("0.9.3"): - assert out.attrs["units"] == "" + assert out.attrs["units"] == "dimensionless" else: assert out.attrs["units"] == "1" @@ -1150,7 +1150,10 @@ def test_varying_mid_dates(self, tas_series, d1, d2, mid_date, expected): np.testing.assert_array_equal(gs_end, expected) for attr in ["units", "is_dayofyear", "calendar"]: assert attr in gs_end.attrs.keys() - assert gs_end.attrs["units"] == "1" + if Version(__cfxr_version__) < Version("0.9.3"): + assert gs_end.attrs["units"] == "dimensionless" + else: + assert gs_end.attrs["units"] == "1" assert gs_end.attrs["is_dayofyear"] == 1 @@ -1232,7 +1235,7 @@ def test_simple(self, tasmin_series): assert attr in out.attrs.keys() if Version(__cfxr_version__) < Version("0.9.3"): - assert out.attrs["units"] == "" + assert out.attrs["units"] == "dimensionless" else: assert out.attrs["units"] == "1" @@ -1270,7 +1273,7 @@ def test_varying_mid_dates(self, tasmin_series, d1, d2, mid_date, expected): assert attr in gs_end.attrs.keys() if Version(__cfxr_version__) < Version("0.9.3"): - assert gs_end.attrs["units"] == "" + assert gs_end.attrs["units"] == "dimensionless" else: assert gs_end.attrs["units"] == "1" @@ -2784,7 +2787,7 @@ def test_degree_days_exceedance_date(tas_series): assert attr in out.attrs.keys() if Version(__cfxr_version__) < Version("0.9.3"): - assert out.attrs["units"] == "" + assert out.attrs["units"] == "dimensionless" else: assert out.attrs["units"] == "1" @@ -2829,7 +2832,7 @@ def test_first_snowfall(prsn_series, prsnd_series): assert attr in out.attrs.keys() if Version(__cfxr_version__) < Version("0.9.3"): - assert out.attrs["units"] == "" + assert out.attrs["units"] == "dimensionless" else: assert out.attrs["units"] == "1" assert out.attrs["is_dayofyear"] == 1 @@ -2840,7 +2843,10 @@ def test_first_snowfall(prsn_series, prsnd_series): assert out[0] == 166 for attr in ["units", "is_dayofyear", "calendar"]: assert attr in out.attrs.keys() - assert out.attrs["units"] == "1" + if Version(__cfxr_version__) < Version("0.9.3"): + assert out.attrs["units"] == "dimensionless" + else: + assert out.attrs["units"] == "1" assert out.attrs["is_dayofyear"] == 1 # test with prsn [kg m-2 s-1] @@ -2854,7 +2860,7 @@ def test_first_snowfall(prsn_series, prsnd_series): assert attr in out.attrs.keys() if Version(__cfxr_version__) < Version("0.9.3"): - assert out.attrs["units"] == "" + assert out.attrs["units"] == "dimensionless" else: assert out.attrs["units"] == "1" assert out.attrs["is_dayofyear"] == 1 @@ -2995,7 +3001,7 @@ def test_continous_snow_season_start(self, snd_series, snw_series): assert attr in out.attrs.keys() if Version(__cfxr_version__) < Version("0.9.3"): - assert out.attrs["units"] == "" + assert out.attrs["units"] == "dimensionless" else: assert out.attrs["units"] == "1" @@ -3008,7 +3014,7 @@ def test_continous_snow_season_start(self, snd_series, snw_series): assert attr in out.attrs.keys() if Version(__cfxr_version__) < Version("0.9.3"): - assert out.attrs["units"] == "" + assert out.attrs["units"] == "dimensionless" else: assert out.attrs["units"] == "1" @@ -3035,7 +3041,7 @@ def test_snow_season_end(self, snd_series, snw_series): assert attr in out.attrs.keys() if Version(__cfxr_version__) < Version("0.9.3"): - assert out.attrs["units"] == "" + assert out.attrs["units"] == "dimensionless" else: assert out.attrs["units"] == "1" @@ -3047,7 +3053,10 @@ def test_snow_season_end(self, snd_series, snw_series): np.testing.assert_array_equal(out, [(doy + 219) % 366, np.nan]) for attr in ["units", "is_dayofyear", "calendar"]: assert attr in out.attrs.keys() - assert out.attrs["units"] == "1" + if Version(__cfxr_version__) < Version("0.9.3"): + assert out.attrs["units"] == "dimensionless" + else: + assert out.attrs["units"] == "1" assert out.attrs["is_dayofyear"] == 1 diff --git a/tests/test_land.py b/tests/test_land.py index 07841cd87..380d828bc 100644 --- a/tests/test_land.py +++ b/tests/test_land.py @@ -14,7 +14,7 @@ def test_base_flow_index(ndq_series): out = land.base_flow_index(ndq_series, freq="YS") if Version(__cfxr_version__) < Version("0.9.3"): - assert out.attrs["units"] == "" + assert out.attrs["units"] == "dimensionless" else: assert out.attrs["units"] == "1" @@ -25,7 +25,7 @@ def test_rb_flashiness_index(ndq_series): out = land.base_flow_index(ndq_series, freq="YS") if Version(__cfxr_version__) < Version("0.9.3"): - assert out.attrs["units"] == "" + assert out.attrs["units"] == "dimensionless" else: assert out.attrs["units"] == "1" @@ -36,7 +36,7 @@ def test_qdoy_max(ndq_series, q_series): out = land.doy_qmax(ndq_series, freq="YS", season="JJA") if Version(__cfxr_version__) < Version("0.9.3"): - assert out.attrs["units"] == "" + assert out.attrs["units"] == "dimensionless" else: assert out.attrs["units"] == "1" diff --git a/tests/test_snow.py b/tests/test_snow.py index 6cbb20c65..bdbcd64da 100644 --- a/tests/test_snow.py +++ b/tests/test_snow.py @@ -49,7 +49,7 @@ def test_simple(self, snd_series): out = land.snd_season_start(snd) if Version(__cfxr_version__) < Version("0.9.3"): - assert out.units == "" + assert out.units == "dimensionless" else: assert out.units == "1" @@ -58,7 +58,7 @@ def test_simple(self, snd_series): out = land.snd_season_end(snd) if Version(__cfxr_version__) < Version("0.9.3"): - assert out.units == "" + assert out.units == "dimensionless" else: assert out.units == "1" @@ -81,7 +81,7 @@ def test_simple(self, snw_series): out = land.snw_season_start(snw) if Version(__cfxr_version__) < Version("0.9.3"): - assert out.units == "" + assert out.units == "dimensionless" else: assert out.units == "1" @@ -90,7 +90,7 @@ def test_simple(self, snw_series): out = land.snw_season_end(snw) if Version(__cfxr_version__) < Version("0.9.3"): - assert out.units == "" + assert out.units == "dimensionless" else: assert out.units == "1" diff --git a/tests/test_units.py b/tests/test_units.py index 040d50dd0..ee05bc009 100644 --- a/tests/test_units.py +++ b/tests/test_units.py @@ -142,10 +142,8 @@ def test_units2pint(self, pr_series): assert pint2cfunits(u) == "%" u = units2pint("1") - assert pint2cfunits(u) == "1" - if Version(__cfxr_version__) < Version("0.9.3"): - assert pint2cfunits(u) == "" + assert pint2cfunits(u) == "dimensionless" else: assert pint2cfunits(u) == "1" @@ -338,7 +336,13 @@ def index( ("", "sum", "count", 365, "d"), ("", "sum", "count", 365, "d"), ("kg m-2", "var", "var", 0, "kg2 m-4"), - ("°C", "argmax", "doymax", 0, ("", "1")), # dependent on numpy/pint version + ( + "°C", + "argmax", + "doymax", + 0, + ("dimensionless", "1"), + ), # dependent on numpy/pint version ( "°C", "sum", diff --git a/tox.ini b/tox.ini index 05d1f3127..21d0b4222 100644 --- a/tox.ini +++ b/tox.ini @@ -118,6 +118,7 @@ extras = deps = lmoments: lmoments3 numpy: numpy>=1.20,<2.0 + numpy: cf-xarray>=0.6.1,<0.9.3 sbck: pybind11 upstream: -r CI/requirements_upstream.txt install_command = python -m pip install --no-user {opts} {packages} diff --git a/xclim/core/indicator.py b/xclim/core/indicator.py index 74777dbe9..3b68e09c2 100644 --- a/xclim/core/indicator.py +++ b/xclim/core/indicator.py @@ -1456,7 +1456,7 @@ def _postprocess(self, outs, das, params): and mask.time.size < outs[0].time.size ): mask = mask.reindex(time=outs[0].time, fill_value=True) - outs = [out.where(~mask) for out in outs] + outs = [out.where(np.logical_not(mask)) for out in outs] return outs From 4392a61559b3494500111d32f884a7cef4b92a75 Mon Sep 17 00:00:00 2001 From: Trevor James Smith <10819524+Zeitsperre@users.noreply.github.com> Date: Tue, 6 Aug 2024 15:38:28 -0400 Subject: [PATCH 06/18] support newer pint with older cf-xarray --- xclim/core/units.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xclim/core/units.py b/xclim/core/units.py index 183c66d4d..9e231e389 100644 --- a/xclim/core/units.py +++ b/xclim/core/units.py @@ -184,7 +184,7 @@ def pint2cfunits(value: units.Quantity | units.Unit) -> str: if isinstance(value, (pint.Quantity, units.Quantity)): value = value.units - return f"{value:cf}" + return f"{value:~cf}" def ensure_cf_units(ustr: str) -> str: From 708e85e82b5c8578eed78e1a191bc3e4ddd3c21b Mon Sep 17 00:00:00 2001 From: Trevor James Smith <10819524+Zeitsperre@users.noreply.github.com> Date: Tue, 6 Aug 2024 16:18:50 -0400 Subject: [PATCH 07/18] add warning for unsupported configuration --- tox.ini | 1 + xclim/core/units.py | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/tox.ini b/tox.ini index 21d0b4222..254faf863 100644 --- a/tox.ini +++ b/tox.ini @@ -119,6 +119,7 @@ deps = lmoments: lmoments3 numpy: numpy>=1.20,<2.0 numpy: cf-xarray>=0.6.1,<0.9.3 + numpy: pint>=0.18,<0.24.0 sbck: pybind11 upstream: -r CI/requirements_upstream.txt install_command = python -m pip install --no-user {opts} {packages} diff --git a/xclim/core/units.py b/xclim/core/units.py index 9e231e389..e4369f515 100644 --- a/xclim/core/units.py +++ b/xclim/core/units.py @@ -20,6 +20,9 @@ import pint import xarray as xr from boltons.funcutils import wraps +from cf_xarray import __version__ as __cf_xarray_version__ +from packaging.version import Version +from pint import __version__ as __pint_version__ from yaml import safe_load from .calendar import get_calendar, parse_offset @@ -52,6 +55,20 @@ "units2pint", ] +# TODO: Remove in xclim v0.53.0+ +older_cf_xarray = False +older_pint = False +if Version(__cf_xarray_version__) < Version("0.9.3"): + older_cf_xarray = True +if Version(__pint_version__) < Version("0.24.0"): + older_pint = True +if older_cf_xarray != older_pint: + warnings.warn( + f"The versions of pint ({__pint_version__}) and cf-xarray ({__cf_xarray_version__}) are not compatible. " + "Units handling may not work as expected. Please ensure that both are up-to-date.", + UserWarning, + ) + # shamelessly adapted from `cf-xarray` (which adopted it from MetPy and xclim itself) units = deepcopy(cf_xarray.units.units) From 0b48268cfac17ac8354bb16532a1e03c07ea4333 Mon Sep 17 00:00:00 2001 From: Trevor James Smith <10819524+Zeitsperre@users.noreply.github.com> Date: Tue, 6 Aug 2024 16:50:34 -0400 Subject: [PATCH 08/18] address warnings, set versions --- CHANGELOG.rst | 4 ++++ tests/test_temperature.py | 7 ++++++- tests/test_units.py | 4 ++-- tox.ini | 2 +- xclim/core/units.py | 2 +- 5 files changed, 14 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 8af1f4e08..2ced26ba6 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,10 @@ v0.52.0 (unreleased) -------------------- Contributors to this version: David Huard (:user:`huard`), Trevor James Smith (:user:`Zeitsperre`), Hui-Min Wang (:user:`Hem-W`), Éric Dupuis (:user:`coxipi`), Sarah Gammon (:user:`SarahG-579462`), Pascal Bourgault (:user:`aulemahal`), Juliette Lavoie (:user:`juliettelavoie`). +Announcements +^^^^^^^^^^^^^ +* `xclim` now supports `numpy` v2.0. (:pull:`1914`). + New features and enhancements ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ * ``xclim.sdba.nbutils.quantile`` and its child functions are now faster. If the module `fastnanquantile` is installed, it is used as the backend for the computation of quantiles and yields even faster results. This dependency is now listed in the `xclim[extras]` recipe. (:issue:`1255`, :pull:`1513`). diff --git a/tests/test_temperature.py b/tests/test_temperature.py index e91cee728..23ed23a0d 100644 --- a/tests/test_temperature.py +++ b/tests/test_temperature.py @@ -6,6 +6,8 @@ import numpy as np import pytest import xarray as xr +from packaging.version import Version +from pint import __version__ as __pint_version__ from xclim import atmos from xclim.core.calendar import percentile_doy @@ -828,7 +830,10 @@ def test_convert_units(self, open_dataset): tasmin.values[180, 1, 0] = np.nan with warnings.catch_warnings(): - warnings.simplefilter("error") + if Version(__pint_version__) < Version("0.24.0"): + warnings.simplefilter("always", DeprecationWarning) + else: + warnings.simplefilter("error") frzthw = atmos.daily_freezethaw_cycles( tasmin, tasmax, diff --git a/tests/test_units.py b/tests/test_units.py index ee05bc009..140c3c1f4 100644 --- a/tests/test_units.py +++ b/tests/test_units.py @@ -341,8 +341,8 @@ def index( "argmax", "doymax", 0, - ("dimensionless", "1"), - ), # dependent on numpy/pint version + "1", + ), ( "°C", "sum", diff --git a/tox.ini b/tox.ini index 254faf863..a636f84dc 100644 --- a/tox.ini +++ b/tox.ini @@ -118,7 +118,7 @@ extras = deps = lmoments: lmoments3 numpy: numpy>=1.20,<2.0 - numpy: cf-xarray>=0.6.1,<0.9.3 + numpy: cf-xarray>=0.6.1,<0.9.2 numpy: pint>=0.18,<0.24.0 sbck: pybind11 upstream: -r CI/requirements_upstream.txt diff --git a/xclim/core/units.py b/xclim/core/units.py index e4369f515..9947dac49 100644 --- a/xclim/core/units.py +++ b/xclim/core/units.py @@ -201,7 +201,7 @@ def pint2cfunits(value: units.Quantity | units.Unit) -> str: if isinstance(value, (pint.Quantity, units.Quantity)): value = value.units - return f"{value:~cf}" + return f"{value:cf}" def ensure_cf_units(ustr: str) -> str: From b29b89d95e9bcb606e6ee8446328a0ac50a8e3da Mon Sep 17 00:00:00 2001 From: Trevor James Smith <10819524+Zeitsperre@users.noreply.github.com> Date: Tue, 6 Aug 2024 17:09:36 -0400 Subject: [PATCH 09/18] update CHANGELOG.rst --- CHANGELOG.rst | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 2ced26ba6..929fc2059 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,20 +8,18 @@ Contributors to this version: David Huard (:user:`huard`), Trevor James Smith (: Announcements ^^^^^^^^^^^^^ -* `xclim` now supports `numpy` v2.0. (:pull:`1914`). +* `xclim` now supports both `numpy` versions `>=1.20` and `>=2.0`. (:issue:`1785`, :pull:`1814`, :pull:`1870`). +* `xclim` continues to support older versions of `pint` (`<0.24`) and `cf-xarray` (`<0.9.3`) for compatibility reasons, but will be raising these pins on these dependencies in `xclim>=0.53.0`. Mixing older and newer versions will raise ``UserWarnings`` and likely cause errors. (:pull:`1870`). New features and enhancements ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ * ``xclim.sdba.nbutils.quantile`` and its child functions are now faster. If the module `fastnanquantile` is installed, it is used as the backend for the computation of quantiles and yields even faster results. This dependency is now listed in the `xclim[extras]` recipe. (:issue:`1255`, :pull:`1513`). -* New multivariate bias adjustment class `MBCn`, giving a faster and more accurate implementation of the 'MBCn' algorithm (:issue:`1551`, :pull:`1580`). +* New multivariate bias adjustment class ``MBCn``, giving a faster and more accurate implementation of the ``MBCn`` algorithm. (:issue:`1551`, :pull:`1580`). * `xclim` is now compatible with `pytest` versions `>=8.0.0`. (:pull:`1632`). Breaking changes ^^^^^^^^^^^^^^^^ * As updated in ``cf_xarray>=0.9.3``, dimensionless quantities now use the "1" units attribute as specified by the CF conventions, previously an empty string was returned. (:pull:`1814`). - -Breaking changes -^^^^^^^^^^^^^^^^ * The definitions of the ``frost_free_season_start`` and ``frost_free_season_end`` have been slightly changed to be coherent with the ``frost_free_season_length`` and `xclim`'s notion of `season` in general. Indicator and indices signature have changed. (:pull:`1845`). * Season length indicators have been modified to return ``0`` for all cases where a proper season was not found, but the data is valid. Previously, a ``nan`` was given if neither a start or an end were found, even if the data was valid, and a ``0`` was given if an end was found but no start. (:pull:`1845`). @@ -29,8 +27,8 @@ Bug fixes ^^^^^^^^^ * Fixed the indexer bug in the ``xclim.indices.standardized_index_fit_params`` when multiple or non-array indexers are specified and fitted parameters are reloaded from netCDF. (:issue:`1842`, :pull:`1843`). * Addressed a bug found in ``wet_spell_*`` indicators that was contributing to erroneous results. A new generic spell length statistic function ``xclim.indices.generic.spell_length_statistics`` is now used in wet and dry spells indicators. (:issue:`1834`, :pull:`1838`). -* Syntax for ``nan`` and ``inf`` was adapted to support ``numpy>=2.0.0``. (:pull:`1814`, :issue:`1785`). -* Force type in `jitter` to work with new version of dask. (:pull:`1864`) +* Syntax for ``nan`` and ``inf`` was adapted to support `numpy>=2.0`. (:pull:`1814`, :issue:`1785`). +* Force type in ``jitter`` to work with new version of `dask`. (:pull:`1864`). Internal changes ^^^^^^^^^^^^^^^^ @@ -43,7 +41,7 @@ CI changes * `pip-tools` (`pip-compile`) has been used to generate a lock file with hashes for the CI dependencies. (:pull:`1841`). * The ``main.yml`` workflow has been updated to use simpler trigger logic. (:pull:`1841`). * A workflow bug has been fixed that was causing multiple duplicate comments to be made on Pull Requests originating from forks. (:pull:`1841`). -* The ``upstream.yml`` workflow was adapted to not install upstream Python dependencies using hashes (impossible to install directly from GitHub sources using --require-hashes). (:pull:`1859`). +* The ``upstream.yml`` workflow was adapted to not install upstream Python dependencies using hashes (as it is impossible to install directly from GitHub sources using ``--require-hashes``). (:pull:`1859`). * The `tox-gh` configuration has been set to handle the environment configurations on GitHub Workflows. The tox.ini file is also a bit more organized/consistent. (:pull:`1859`). v0.51.0 (2024-07-04) From 35a60188df02c330eb99d86e0546af963d8c278f Mon Sep 17 00:00:00 2001 From: Trevor James Smith <10819524+Zeitsperre@users.noreply.github.com> Date: Tue, 6 Aug 2024 17:18:35 -0400 Subject: [PATCH 10/18] pin base numba --- environment.yml | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/environment.yml b/environment.yml index 564ddb456..19d5b6142 100644 --- a/environment.yml +++ b/environment.yml @@ -12,7 +12,7 @@ dependencies: - dask >=2.6.0 - filelock >=3.14.0 - jsonpickle >=3.1.0 - - numba # numpy v2.0 compatibility is in numba v0.60.0, but not pinning it to avoid breaking older numpy versions + - numba >=0.54.1 - numpy >=1.20.0 - packaging >=24.0 - pandas >=2.2.0 diff --git a/pyproject.toml b/pyproject.toml index 4351b0a88..3eb366587 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,7 +43,7 @@ dependencies = [ "dask[array] >=2.6", "filelock >=3.14.0", "jsonpickle >=3.1.0", - "numba", # numpy v2.0 compatibility is in numba v0.60.0, but not pinning it to avoid breaking older numpy versions + "numba >=0.54.1", "numpy >=1.20.0", "packaging >=24.0", "pandas >=2.2", From 29c580d62eb8b35913c97f6af343af25b0241b86 Mon Sep 17 00:00:00 2001 From: Pascal Bourgault Date: Tue, 6 Aug 2024 17:52:08 -0400 Subject: [PATCH 11/18] Pin cfxarray --- environment.yml | 2 +- pyproject.toml | 2 +- tests/test_atmos.py | 8 +-- tests/test_hydrology.py | 8 +-- tests/test_indices.py | 112 +++++++--------------------------------- tests/test_land.py | 20 ++----- tests/test_snow.py | 26 ++-------- tests/test_units.py | 9 ++-- xclim/core/units.py | 20 +------ 9 files changed, 34 insertions(+), 173 deletions(-) diff --git a/environment.yml b/environment.yml index 19d5b6142..070f86943 100644 --- a/environment.yml +++ b/environment.yml @@ -6,7 +6,7 @@ dependencies: - python >=3.9 - boltons >=20.1 - bottleneck >=1.3.1 - - cf_xarray >=0.6.1 + - cf_xarray >=0.9.3 - cftime >=1.4.1 - click >=8.1 - dask >=2.6.0 diff --git a/pyproject.toml b/pyproject.toml index 3eb366587..c3b14d9c3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,7 +37,7 @@ dependencies = [ "boltons >=20.1", "bottleneck >=1.3.1", # cf-xarray is differently named on conda-forge - "cf-xarray >=0.6.1", + "cf-xarray >=0.9.3", "cftime >=1.4.1", "click >=8.1", "dask[array] >=2.6", diff --git a/tests/test_atmos.py b/tests/test_atmos.py index cace721b8..10d5d0efe 100644 --- a/tests/test_atmos.py +++ b/tests/test_atmos.py @@ -4,8 +4,6 @@ import numpy as np import xarray as xr -from cf_xarray import __version__ as __cfxr_version__ -from packaging.version import Version from xclim import atmos, set_options @@ -262,11 +260,7 @@ def test_wind_profile(atmosds): def test_wind_power_potential(atmosds): out = atmos.wind_power_potential(wind_speed=atmosds.sfcWind) - - if Version(__cfxr_version__) < Version("0.9.3"): - assert out.attrs["units"] == "dimensionless" - else: - assert out.attrs["units"] == "1" + assert out.attrs["units"] == "1" assert (out >= 0).all() assert (out <= 1).all() diff --git a/tests/test_hydrology.py b/tests/test_hydrology.py index 5003877d1..0a967a42a 100644 --- a/tests/test_hydrology.py +++ b/tests/test_hydrology.py @@ -1,8 +1,6 @@ from __future__ import annotations import numpy as np -from cf_xarray import __version__ as __cfxr_version__ -from packaging.version import Version from xclim import indices as xci @@ -42,11 +40,7 @@ def test_simple(self, snw_series): snw = snw_series(a, start="1999-01-01") out = xci.snw_max_doy(snw, freq="YS") np.testing.assert_array_equal(out, [11, np.nan]) - - if Version(__cfxr_version__) < Version("0.9.3"): - assert out.attrs["units"] == "dimensionless" - else: - assert out.attrs["units"] == "1" + assert out.attrs["units"] == "1" class TestSnowMeltWEMax: diff --git a/tests/test_indices.py b/tests/test_indices.py index 81045ed5f..a9386087f 100644 --- a/tests/test_indices.py +++ b/tests/test_indices.py @@ -19,7 +19,6 @@ import pandas as pd import pytest import xarray as xr -from cf_xarray import __version__ as __cfxr_version__ from numpy import __version__ as __numpy_version__ from packaging.version import Version from pint import __version__ as __pint_version__ @@ -141,19 +140,11 @@ def test_simple(self, tas_series): out = xci.cold_spell_frequency(da, thresh="-10. C", freq="ME") np.testing.assert_array_equal(out, [1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]) - - if Version(__cfxr_version__) < Version("0.9.3"): - assert out.units == "dimensionless" - else: - assert out.units == "1" + assert out.units == "1" out = xci.cold_spell_frequency(da, thresh="-10. C", freq="YS") np.testing.assert_array_equal(out, 3) - - if Version(__cfxr_version__) < Version("0.9.3"): - assert out.units == "dimensionless" - else: - assert out.units == "1" + assert out.units == "1" class TestColdSpellMaxLength: @@ -934,12 +925,7 @@ def test_simple(self, tas_series): assert lsf == 180 for attr in ["units", "is_dayofyear", "calendar"]: assert attr in lsf.attrs.keys() - - if Version(__cfxr_version__) < Version("0.9.3"): - assert lsf.attrs["units"] == "dimensionless" - else: - assert lsf.attrs["units"] == "1" - + assert lsf.attrs["units"] == "1" assert lsf.attrs["is_dayofyear"] == 1 assert lsf.attrs["is_dayofyear"].dtype == np.int32 @@ -960,12 +946,7 @@ def test_simple(self, tas_series): assert np.isnan(fdb) for attr in ["units", "is_dayofyear", "calendar"]: assert attr in fdb.attrs.keys() - - if Version(__cfxr_version__) < Version("0.9.3"): - assert fdb.attrs["units"] == "dimensionless" - else: - assert fdb.attrs["units"] == "1" - + assert fdb.attrs["units"] == "1" assert fdb.attrs["is_dayofyear"] == 1 def test_below_forbidden(self, tasmax_series): @@ -996,12 +977,7 @@ def test_simple(self, tas_series): assert np.isnan(fda) for attr in ["units", "is_dayofyear", "calendar"]: assert attr in fda.attrs.keys() - - if Version(__cfxr_version__) < Version("0.9.3"): - assert fda.attrs["units"] == "dimensionless" - else: - assert fda.attrs["units"] == "1" - + assert fda.attrs["units"] == "1" assert fda.attrs["is_dayofyear"] == 1 def test_thresholds(self, tas_series): @@ -1026,12 +1002,7 @@ def test_thresholds(self, tas_series): assert out[0] == tg.indexes["time"][30].dayofyear for attr in ["units", "is_dayofyear", "calendar"]: assert attr in out.attrs.keys() - - if Version(__cfxr_version__) < Version("0.9.3"): - assert out.attrs["units"] == "dimensionless" - else: - assert out.attrs["units"] == "1" - + assert out.attrs["units"] == "1" assert out.attrs["is_dayofyear"] == 1 def test_above_forbidden(self, tasmax_series): @@ -1118,12 +1089,7 @@ def test_simple(self, tas_series): assert out[0] == tg.indexes["time"][20].dayofyear for attr in ["units", "is_dayofyear", "calendar"]: assert attr in out.attrs.keys() - - if Version(__cfxr_version__) < Version("0.9.3"): - assert out.attrs["units"] == "dimensionless" - else: - assert out.attrs["units"] == "1" - + assert out.attrs["units"] == "1" assert out.attrs["is_dayofyear"] == 1 def test_no_start(self, tas_series): @@ -1156,10 +1122,7 @@ def test_varying_mid_dates(self, tas_series, d1, d2, mid_date, expected): np.testing.assert_array_equal(gs_end, expected) for attr in ["units", "is_dayofyear", "calendar"]: assert attr in gs_end.attrs.keys() - if Version(__cfxr_version__) < Version("0.9.3"): - assert gs_end.attrs["units"] == "dimensionless" - else: - assert gs_end.attrs["units"] == "1" + assert gs_end.attrs["units"] == "1" assert gs_end.attrs["is_dayofyear"] == 1 @@ -1239,11 +1202,7 @@ def test_simple(self, tasmin_series): assert out[0] == tn.indexes["time"][20].dayofyear for attr in ["units", "is_dayofyear", "calendar"]: assert attr in out.attrs.keys() - - if Version(__cfxr_version__) < Version("0.9.3"): - assert out.attrs["units"] == "dimensionless" - else: - assert out.attrs["units"] == "1" + assert out.attrs["units"] == "1" assert out.attrs["is_dayofyear"] == 1 @@ -1278,10 +1237,7 @@ def test_varying_mid_dates(self, tasmin_series, d1, d2, mid_date, expected): for attr in ["units", "is_dayofyear", "calendar"]: assert attr in gs_end.attrs.keys() - if Version(__cfxr_version__) < Version("0.9.3"): - assert gs_end.attrs["units"] == "dimensionless" - else: - assert gs_end.attrs["units"] == "1" + assert gs_end.attrs["units"] == "1" assert gs_end.attrs["is_dayofyear"] == 1 @@ -2791,11 +2747,7 @@ def test_degree_days_exceedance_date(tas_series): for attr in ["units", "is_dayofyear", "calendar"]: assert attr in out.attrs.keys() - - if Version(__cfxr_version__) < Version("0.9.3"): - assert out.attrs["units"] == "dimensionless" - else: - assert out.attrs["units"] == "1" + assert out.attrs["units"] == "1" assert out.attrs["is_dayofyear"] == 1 @@ -2836,11 +2788,7 @@ def test_first_snowfall(prsn_series, prsnd_series): assert out[0] == 166 for attr in ["units", "is_dayofyear", "calendar"]: assert attr in out.attrs.keys() - - if Version(__cfxr_version__) < Version("0.9.3"): - assert out.attrs["units"] == "dimensionless" - else: - assert out.attrs["units"] == "1" + assert out.attrs["units"] == "1" assert out.attrs["is_dayofyear"] == 1 # test with prsnd [m s-1] @@ -2849,10 +2797,7 @@ def test_first_snowfall(prsn_series, prsnd_series): assert out[0] == 166 for attr in ["units", "is_dayofyear", "calendar"]: assert attr in out.attrs.keys() - if Version(__cfxr_version__) < Version("0.9.3"): - assert out.attrs["units"] == "dimensionless" - else: - assert out.attrs["units"] == "1" + assert out.attrs["units"] == "1" assert out.attrs["is_dayofyear"] == 1 # test with prsn [kg m-2 s-1] @@ -2865,10 +2810,7 @@ def test_first_snowfall(prsn_series, prsnd_series): for attr in ["units", "is_dayofyear", "calendar"]: assert attr in out.attrs.keys() - if Version(__cfxr_version__) < Version("0.9.3"): - assert out.attrs["units"] == "dimensionless" - else: - assert out.attrs["units"] == "1" + assert out.attrs["units"] == "1" assert out.attrs["is_dayofyear"] == 1 @@ -3005,12 +2947,7 @@ def test_continous_snow_season_start(self, snd_series, snw_series): np.testing.assert_array_equal(out, [snd.time.dt.dayofyear[0].data + 2, np.nan]) for attr in ["units", "is_dayofyear", "calendar"]: assert attr in out.attrs.keys() - - if Version(__cfxr_version__) < Version("0.9.3"): - assert out.attrs["units"] == "dimensionless" - else: - assert out.attrs["units"] == "1" - + assert out.attrs["units"] == "1" assert out.attrs["is_dayofyear"] == 1 out = xci.snw_season_start(snw) @@ -3018,12 +2955,7 @@ def test_continous_snow_season_start(self, snd_series, snw_series): np.testing.assert_array_equal(out, [snw.time.dt.dayofyear[0].data + 1, np.nan]) for attr in ["units", "is_dayofyear", "calendar"]: assert attr in out.attrs.keys() - - if Version(__cfxr_version__) < Version("0.9.3"): - assert out.attrs["units"] == "dimensionless" - else: - assert out.attrs["units"] == "1" - + assert out.attrs["units"] == "1" assert out.attrs["is_dayofyear"] == 1 def test_snow_season_end(self, snd_series, snw_series): @@ -3045,12 +2977,7 @@ def test_snow_season_end(self, snd_series, snw_series): np.testing.assert_array_equal(out, [(doy + 219) % 366, np.nan]) for attr in ["units", "is_dayofyear", "calendar"]: assert attr in out.attrs.keys() - - if Version(__cfxr_version__) < Version("0.9.3"): - assert out.attrs["units"] == "dimensionless" - else: - assert out.attrs["units"] == "1" - + assert out.attrs["units"] == "1" assert out.attrs["is_dayofyear"] == 1 out = xci.snw_season_end(snw) @@ -3059,10 +2986,7 @@ def test_snow_season_end(self, snd_series, snw_series): np.testing.assert_array_equal(out, [(doy + 219) % 366, np.nan]) for attr in ["units", "is_dayofyear", "calendar"]: assert attr in out.attrs.keys() - if Version(__cfxr_version__) < Version("0.9.3"): - assert out.attrs["units"] == "dimensionless" - else: - assert out.attrs["units"] == "1" + assert out.attrs["units"] == "1" assert out.attrs["is_dayofyear"] == 1 diff --git a/tests/test_land.py b/tests/test_land.py index 380d828bc..a1782f1be 100644 --- a/tests/test_land.py +++ b/tests/test_land.py @@ -4,8 +4,6 @@ import numpy as np import xarray as xr -from cf_xarray import __version__ as __cfxr_version__ -from packaging.version import Version from xclim import land @@ -13,32 +11,20 @@ def test_base_flow_index(ndq_series): out = land.base_flow_index(ndq_series, freq="YS") - if Version(__cfxr_version__) < Version("0.9.3"): - assert out.attrs["units"] == "dimensionless" - else: - assert out.attrs["units"] == "1" - + assert out.attrs["units"] == "1" assert isinstance(out, xr.DataArray) def test_rb_flashiness_index(ndq_series): out = land.base_flow_index(ndq_series, freq="YS") - if Version(__cfxr_version__) < Version("0.9.3"): - assert out.attrs["units"] == "dimensionless" - else: - assert out.attrs["units"] == "1" - + assert out.attrs["units"] == "1" assert isinstance(out, xr.DataArray) def test_qdoy_max(ndq_series, q_series): out = land.doy_qmax(ndq_series, freq="YS", season="JJA") - - if Version(__cfxr_version__) < Version("0.9.3"): - assert out.attrs["units"] == "dimensionless" - else: - assert out.attrs["units"] == "1" + assert out.attrs["units"] == "1" a = np.ones(450) a[100] = 2 diff --git a/tests/test_snow.py b/tests/test_snow.py index bdbcd64da..5b598a596 100644 --- a/tests/test_snow.py +++ b/tests/test_snow.py @@ -2,8 +2,6 @@ import numpy as np import pytest -from cf_xarray import __version__ as __cfxr_version__ -from packaging.version import Version from xclim import land from xclim.core.utils import ValidationError @@ -47,20 +45,12 @@ def test_simple(self, snd_series): snd = snd.expand_dims(lat=[0, 1, 2]) out = land.snd_season_start(snd) - - if Version(__cfxr_version__) < Version("0.9.3"): - assert out.units == "dimensionless" - else: - assert out.units == "1" + assert out.units == "1" np.testing.assert_array_equal(out.isel(lat=0), snd.time.dt.dayofyear[100]) out = land.snd_season_end(snd) - - if Version(__cfxr_version__) < Version("0.9.3"): - assert out.units == "dimensionless" - else: - assert out.units == "1" + assert out.units == "1" np.testing.assert_array_equal(out.isel(lat=0), snd.time.dt.dayofyear[200]) @@ -79,20 +69,12 @@ def test_simple(self, snw_series): snw = snw.expand_dims(lat=[0, 1, 2]) out = land.snw_season_start(snw) - - if Version(__cfxr_version__) < Version("0.9.3"): - assert out.units == "dimensionless" - else: - assert out.units == "1" + assert out.units == "1" np.testing.assert_array_equal(out.isel(lat=0), snw.time.dt.dayofyear[100]) out = land.snw_season_end(snw) - - if Version(__cfxr_version__) < Version("0.9.3"): - assert out.units == "dimensionless" - else: - assert out.units == "1" + assert out.units == "1" np.testing.assert_array_equal(out.isel(lat=0), snw.time.dt.dayofyear[200]) diff --git a/tests/test_units.py b/tests/test_units.py index 140c3c1f4..5b8a0ac65 100644 --- a/tests/test_units.py +++ b/tests/test_units.py @@ -6,9 +6,9 @@ import pint.errors import pytest import xarray as xr -from cf_xarray import __version__ as __cfxr_version__ from dask import array as dsk from packaging.version import Version +from pint import __version__ as __pint_version__ from xclim import indices, set_options from xclim.core.units import ( @@ -142,10 +142,7 @@ def test_units2pint(self, pr_series): assert pint2cfunits(u) == "%" u = units2pint("1") - if Version(__cfxr_version__) < Version("0.9.3"): - assert pint2cfunits(u) == "dimensionless" - else: - assert pint2cfunits(u) == "1" + assert pint2cfunits(u) == "1" def test_pint_multiply(self, pr_series): a = pr_series([1, 2, 3]) @@ -365,7 +362,7 @@ def test_to_agg_units(in_u, opfunc, op, exp, exp_u): np.testing.assert_allclose(out, exp) if isinstance(exp_u, tuple): - if Version(__cfxr_version__) < Version("0.9.3"): + if Version(__pint_version__) < Version("0.24.1"): assert out.attrs["units"] == exp_u[0] else: assert out.attrs["units"] == exp_u[1] diff --git a/xclim/core/units.py b/xclim/core/units.py index 9947dac49..d6beefa89 100644 --- a/xclim/core/units.py +++ b/xclim/core/units.py @@ -20,9 +20,6 @@ import pint import xarray as xr from boltons.funcutils import wraps -from cf_xarray import __version__ as __cf_xarray_version__ -from packaging.version import Version -from pint import __version__ as __pint_version__ from yaml import safe_load from .calendar import get_calendar, parse_offset @@ -55,20 +52,6 @@ "units2pint", ] -# TODO: Remove in xclim v0.53.0+ -older_cf_xarray = False -older_pint = False -if Version(__cf_xarray_version__) < Version("0.9.3"): - older_cf_xarray = True -if Version(__pint_version__) < Version("0.24.0"): - older_pint = True -if older_cf_xarray != older_pint: - warnings.warn( - f"The versions of pint ({__pint_version__}) and cf-xarray ({__cf_xarray_version__}) are not compatible. " - "Units handling may not work as expected. Please ensure that both are up-to-date.", - UserWarning, - ) - # shamelessly adapted from `cf-xarray` (which adopted it from MetPy and xclim itself) units = deepcopy(cf_xarray.units.units) @@ -201,7 +184,8 @@ def pint2cfunits(value: units.Quantity | units.Unit) -> str: if isinstance(value, (pint.Quantity, units.Quantity)): value = value.units - return f"{value:cf}" + # Force "1" if the formatted string is "" (pint < 0.24) + return f"{value:~cf}" or "1" def ensure_cf_units(ustr: str) -> str: From f6f886f2e3e080508adf0bfc9d99300088690abb Mon Sep 17 00:00:00 2001 From: Pascal Bourgault Date: Tue, 6 Aug 2024 17:55:06 -0400 Subject: [PATCH 12/18] upd changelog --- CHANGELOG.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 929fc2059..6452d02c1 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,7 +9,7 @@ Contributors to this version: David Huard (:user:`huard`), Trevor James Smith (: Announcements ^^^^^^^^^^^^^ * `xclim` now supports both `numpy` versions `>=1.20` and `>=2.0`. (:issue:`1785`, :pull:`1814`, :pull:`1870`). -* `xclim` continues to support older versions of `pint` (`<0.24`) and `cf-xarray` (`<0.9.3`) for compatibility reasons, but will be raising these pins on these dependencies in `xclim>=0.53.0`. Mixing older and newer versions will raise ``UserWarnings`` and likely cause errors. (:pull:`1870`). +* `xclim` now needs ``cf_xarray>=0.9.3`` but continues to support older versions of `pint` (`<0.24`) for compatibility reasons. (:pull:`1870`). New features and enhancements ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -19,7 +19,7 @@ New features and enhancements Breaking changes ^^^^^^^^^^^^^^^^ -* As updated in ``cf_xarray>=0.9.3``, dimensionless quantities now use the "1" units attribute as specified by the CF conventions, previously an empty string was returned. (:pull:`1814`). +* As of ``cf_xarray>=0.9.3``, dimensionless quantities now use the "1" units attribute as specified by the CF conventions, previously an empty string was returned. (:pull:`1814`). * The definitions of the ``frost_free_season_start`` and ``frost_free_season_end`` have been slightly changed to be coherent with the ``frost_free_season_length`` and `xclim`'s notion of `season` in general. Indicator and indices signature have changed. (:pull:`1845`). * Season length indicators have been modified to return ``0`` for all cases where a proper season was not found, but the data is valid. Previously, a ``nan`` was given if neither a start or an end were found, even if the data was valid, and a ``0`` was given if an end was found but no start. (:pull:`1845`). From 90e1e1e3d434e9a553eaa5a3efcb72e5db32427f Mon Sep 17 00:00:00 2001 From: Trevor James Smith <10819524+Zeitsperre@users.noreply.github.com> Date: Tue, 6 Aug 2024 17:58:38 -0400 Subject: [PATCH 13/18] add platform specifier for macOS dask version --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index c3b14d9c3..c07bff59a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,6 +41,7 @@ dependencies = [ "cftime >=1.4.1", "click >=8.1", "dask[array] >=2.6", + "dask[array] >=2.6, <2024.8.0; platform_system == 'Darwin'", # FIXME: REMOVE BEFORE MERGE TO MAIN "filelock >=3.14.0", "jsonpickle >=3.1.0", "numba >=0.54.1", From 83757add319c5588abcaa5efd96873194bd22727 Mon Sep 17 00:00:00 2001 From: Trevor James Smith <10819524+Zeitsperre@users.noreply.github.com> Date: Wed, 7 Aug 2024 11:11:22 -0400 Subject: [PATCH 14/18] skip test on macOS --- tests/test_sdba/test_adjustment.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/test_sdba/test_adjustment.py b/tests/test_sdba/test_adjustment.py index aea505073..dabf5a840 100644 --- a/tests/test_sdba/test_adjustment.py +++ b/tests/test_sdba/test_adjustment.py @@ -1,9 +1,13 @@ # pylint: disable=no-member from __future__ import annotations +import sys + import numpy as np import pytest import xarray as xr +from dask import version as __dask_version__ +from packaging.version import Version from scipy.stats import genpareto, norm, uniform from xclim.core.calendar import stack_periods @@ -547,6 +551,16 @@ def test_mon_U(self, mon_series, series, mon_triangular, kind, name, random): @pytest.mark.parametrize("use_dask", [True, False]) @pytest.mark.filterwarnings("ignore::RuntimeWarning") def test_add_dims(self, use_dask, open_dataset): + # FIXME: Investigate issues with latest dask versions on macOS. + if ( + sys.platform.startswith("darwin") + and use_dask + and Version(__dask_version__) >= Version("2024.8.0") + ): + pytest.xfail( + "Newer versions of dask (>=2024.8.0) on macOS are failing for this test." + ) + with set_options(sdba_encode_cf=use_dask): if use_dask: chunks = {"location": -1} From 67cea44e44e969bec5e2a1ca8fea6f18837f04a0 Mon Sep 17 00:00:00 2001 From: Trevor James Smith <10819524+Zeitsperre@users.noreply.github.com> Date: Wed, 7 Aug 2024 11:20:34 -0400 Subject: [PATCH 15/18] remove platform specifier for mac, fix typo --- pyproject.toml | 1 - tests/test_sdba/test_adjustment.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index c07bff59a..c3b14d9c3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,6 @@ dependencies = [ "cftime >=1.4.1", "click >=8.1", "dask[array] >=2.6", - "dask[array] >=2.6, <2024.8.0; platform_system == 'Darwin'", # FIXME: REMOVE BEFORE MERGE TO MAIN "filelock >=3.14.0", "jsonpickle >=3.1.0", "numba >=0.54.1", diff --git a/tests/test_sdba/test_adjustment.py b/tests/test_sdba/test_adjustment.py index dabf5a840..eca01bfb8 100644 --- a/tests/test_sdba/test_adjustment.py +++ b/tests/test_sdba/test_adjustment.py @@ -6,7 +6,7 @@ import numpy as np import pytest import xarray as xr -from dask import version as __dask_version__ +from dask import __version__ as __dask_version__ from packaging.version import Version from scipy.stats import genpareto, norm, uniform From be949c1fc37a415d274d7931acf98f9bfd2c6de4 Mon Sep 17 00:00:00 2001 From: Pascal Bourgault Date: Wed, 7 Aug 2024 13:03:37 -0400 Subject: [PATCH 16/18] fix add_dims test --- tests/test_sdba/test_adjustment.py | 35 ++++++++++-------------------- 1 file changed, 12 insertions(+), 23 deletions(-) diff --git a/tests/test_sdba/test_adjustment.py b/tests/test_sdba/test_adjustment.py index eca01bfb8..4dc1a9735 100644 --- a/tests/test_sdba/test_adjustment.py +++ b/tests/test_sdba/test_adjustment.py @@ -1,13 +1,9 @@ # pylint: disable=no-member from __future__ import annotations -import sys - import numpy as np import pytest import xarray as xr -from dask import __version__ as __dask_version__ -from packaging.version import Version from scipy.stats import genpareto, norm, uniform from xclim.core.calendar import stack_periods @@ -551,21 +547,20 @@ def test_mon_U(self, mon_series, series, mon_triangular, kind, name, random): @pytest.mark.parametrize("use_dask", [True, False]) @pytest.mark.filterwarnings("ignore::RuntimeWarning") def test_add_dims(self, use_dask, open_dataset): - # FIXME: Investigate issues with latest dask versions on macOS. - if ( - sys.platform.startswith("darwin") - and use_dask - and Version(__dask_version__) >= Version("2024.8.0") - ): - pytest.xfail( - "Newer versions of dask (>=2024.8.0) on macOS are failing for this test." - ) - with set_options(sdba_encode_cf=use_dask): if use_dask: - chunks = {"location": -1} + chunks = {"location": 1} else: chunks = None + + dsim = open_dataset( + "sdba/CanESM2_1950-2100.nc", + chunks=chunks, + drop_variables=["lat", "lon"], + ).tasmax + hist = dsim.sel(time=slice("1981", "2010")) + sim = dsim.sel(time=slice("2041", "2070")) + ref = ( open_dataset( "sdba/ahccd_1950-2013.nc", @@ -577,14 +572,8 @@ def test_add_dims(self, use_dask, open_dataset): ) ref = convert_units_to(ref, "K") ref = ref.isel(location=1, drop=True).expand_dims(location=["Amos"]) - - dsim = open_dataset( - "sdba/CanESM2_1950-2100.nc", - chunks=chunks, - drop_variables=["lat", "lon"], - ).tasmax - hist = dsim.sel(time=slice("1981", "2010")) - sim = dsim.sel(time=slice("2041", "2070")) + # The idea is to have the same ref on all locations + ref, hist = xr.align(ref, hist, join="outer") # With add_dims, "does it run" test group = Grouper("time.dayofyear", window=5, add_dims=["location"]) From 6eaaf39bb4d90e811a654168f2a11dc7c95ec9d9 Mon Sep 17 00:00:00 2001 From: Pascal Bourgault Date: Wed, 7 Aug 2024 13:12:55 -0400 Subject: [PATCH 17/18] Rewrite test explicitly --- tests/test_sdba/test_adjustment.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_sdba/test_adjustment.py b/tests/test_sdba/test_adjustment.py index 4dc1a9735..0fdb354d2 100644 --- a/tests/test_sdba/test_adjustment.py +++ b/tests/test_sdba/test_adjustment.py @@ -549,7 +549,7 @@ def test_mon_U(self, mon_series, series, mon_triangular, kind, name, random): def test_add_dims(self, use_dask, open_dataset): with set_options(sdba_encode_cf=use_dask): if use_dask: - chunks = {"location": 1} + chunks = {"location": -1} else: chunks = None @@ -571,9 +571,9 @@ def test_add_dims(self, use_dask, open_dataset): .tasmax ) ref = convert_units_to(ref, "K") - ref = ref.isel(location=1, drop=True).expand_dims(location=["Amos"]) - # The idea is to have the same ref on all locations - ref, hist = xr.align(ref, hist, join="outer") + # The idea is to have ref defined only over 1 location + # But sdba needs the same dimensions on ref and hist for Grouping with add_dims + ref = ref.where(ref.location == "Amos") # With add_dims, "does it run" test group = Grouper("time.dayofyear", window=5, add_dims=["location"]) From daf69084489ee398f4a7f5a37979fd506128189a Mon Sep 17 00:00:00 2001 From: Trevor James Smith <10819524+Zeitsperre@users.noreply.github.com> Date: Wed, 7 Aug 2024 14:05:56 -0400 Subject: [PATCH 18/18] remove pin --- tox.ini | 1 - 1 file changed, 1 deletion(-) diff --git a/tox.ini b/tox.ini index a636f84dc..d88f67ca9 100644 --- a/tox.ini +++ b/tox.ini @@ -118,7 +118,6 @@ extras = deps = lmoments: lmoments3 numpy: numpy>=1.20,<2.0 - numpy: cf-xarray>=0.6.1,<0.9.2 numpy: pint>=0.18,<0.24.0 sbck: pybind11 upstream: -r CI/requirements_upstream.txt