From ba0254b2270e132117100de80c6b5bc541ad0b69 Mon Sep 17 00:00:00 2001 From: Pascal Bourgault Date: Wed, 10 Jan 2024 15:05:27 -0500 Subject: [PATCH 1/5] Take units from cf_xarray --- CHANGES.rst | 1 + tests/test_generic_indicators.py | 2 +- tests/test_sdba/test_properties.py | 2 +- tests/test_seaice.py | 12 ++--- tests/test_units.py | 17 ++----- xclim/core/units.py | 82 +++++------------------------- 6 files changed, 24 insertions(+), 92 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index a670f5556..481045352 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -20,6 +20,7 @@ New features and enhancements Breaking changes ^^^^^^^^^^^^^^^^ * `bump2version` has been replaced with `bump-my-version` to bump the version number using configurations set in the `pyproject.toml` file. (:issue:`1557`, :pull:`1569`). +* Xclim's units registry and units formatting are now copied from `cf_xarray` with some negligeable tweaks. The exponent sign "^" is now never added in the ``units`` attribute. For example, square meters are given as "m2" instead of "m^2" by xclim, both are still accepted as input (:issue:`1010`, :pull:`1582`). Bug fixes ^^^^^^^^^ diff --git a/tests/test_generic_indicators.py b/tests/test_generic_indicators.py index f8718e659..b197eb88e 100644 --- a/tests/test_generic_indicators.py +++ b/tests/test_generic_indicators.py @@ -106,5 +106,5 @@ def test_missing(self, ndq_series): def test_3hourly(self, pr_hr_series, random): pr = pr_hr_series(random.random(366 * 24)).resample(time="3H").mean() out = generic.stats(pr, freq="MS", op="var") - assert out.units == "kg^2 m-4 s-2" + assert out.units == "kg2 m-4 s-2" assert out.long_name == "Variance of variable" diff --git a/tests/test_sdba/test_properties.py b/tests/test_sdba/test_properties.py index 1e03303d2..bac3720a1 100644 --- a/tests/test_sdba/test_properties.py +++ b/tests/test_sdba/test_properties.py @@ -42,7 +42,7 @@ def test_var(self, open_dataset): [3.9270796e-09, 1.2538864e-09, 1.9057025e-09, 2.8776632e-09], ) assert out_season.long_name.startswith("Variance") - assert out_season.units == "kg^2 m-4 s-2" + assert out_season.units == "kg2 m-4 s-2" def test_std(self, open_dataset): sim = ( diff --git a/tests/test_seaice.py b/tests/test_seaice.py index c5bf55eb9..ad4fd0803 100644 --- a/tests/test_seaice.py +++ b/tests/test_seaice.py @@ -24,7 +24,7 @@ def test_simple(self, areacello): a = sea_ice_extent(sic, area) expected = 4 * np.pi * area.r**2 / 2.0 np.testing.assert_array_almost_equal(a / expected, 1, 3) - assert a.units == "m^2" + assert a.units == "m2" def test_indicator(self, areacello): area, sic = self.values(areacello) @@ -40,7 +40,7 @@ def test_dimensionless(self, areacello): a = sea_ice_extent(sic, area) expected = 4 * np.pi * area.r**2 / 2.0 np.testing.assert_array_almost_equal(a / expected, 1, 3) - assert a.units == "m^2" + assert a.units == "m2" def test_area_units(self, areacello): area, sic = self.values(areacello) @@ -50,7 +50,7 @@ def test_area_units(self, areacello): area.attrs["units"] = "km^2" a = sea_ice_extent(sic, area) - assert a.units == "km^2" + assert a.units == "km2" expected = 4 * np.pi * area.r**2 / 2.0 / 1e6 np.testing.assert_array_almost_equal(a / expected, 1, 3) @@ -63,7 +63,7 @@ def test_simple(self, areacello): a = sea_ice_area(sic, area) expected = 4 * np.pi * area.r**2 / 2.0 / 2.0 np.testing.assert_array_almost_equal(a / expected, 1, 3) - assert a.units == "m^2" + assert a.units == "m2" def test_indicator(self, areacello): area, sic = self.values(areacello) @@ -79,7 +79,7 @@ def test_dimensionless(self, areacello): a = sea_ice_area(sic, area) expected = 4 * np.pi * area.r**2 / 2.0 / 2.0 np.testing.assert_array_almost_equal(a / expected, 1, 3) - assert a.units == "m^2" + assert a.units == "m2" def test_area_units(self, areacello): area, sic = self.values(areacello) @@ -89,7 +89,7 @@ def test_area_units(self, areacello): area.attrs["units"] = "km^2" a = sea_ice_area(sic, area) - assert a.units == "km^2" + assert a.units == "km2" expected = 4 * np.pi * area.r**2 / 2.0 / 2.0 / 1e6 np.testing.assert_array_almost_equal(a / expected, 1, 3) diff --git a/tests/test_units.py b/tests/test_units.py index 5f9efa97b..116e4c2a3 100644 --- a/tests/test_units.py +++ b/tests/test_units.py @@ -130,27 +130,16 @@ def test_pint2cfunits(self): def test_units2pint(self, pr_series): u = units2pint(pr_series([1, 2])) - assert (str(u)) == "kilogram / meter ** 2 / second" assert pint2cfunits(u) == "kg m-2 s-1" u = units2pint("m^3 s-1") - assert str(u) == "meter ** 3 / second" - assert pint2cfunits(u) == "m^3 s-1" - - u = units2pint("kg m-2 s-1") - assert (str(u)) == "kilogram / meter ** 2 / second" + assert pint2cfunits(u) == "m3 s-1" u = units2pint("%") - assert str(u) == "percent" + assert pint2cfunits(u) == "%" u = units2pint("1") - assert str(u) == "dimensionless" - - u = units2pint("mm s-1") - assert str(u) == "millimeter / second" - - u = units2pint("degrees_north") - assert str(u) == "degrees_north" + assert pint2cfunits(u) == "" def test_pint_multiply(self, pr_series): a = pr_series([1, 2, 3]) diff --git a/xclim/core/units.py b/xclim/core/units.py index e8cf71fb3..fad3baa6b 100644 --- a/xclim/core/units.py +++ b/xclim/core/units.py @@ -7,10 +7,9 @@ """ from __future__ import annotations -import functools import logging -import re import warnings +from copy import deepcopy try: from importlib.resources import files @@ -20,6 +19,7 @@ from inspect import _empty, signature # noqa from typing import Any, Callable +import cf_xarray.units import numpy as np import pint import xarray as xr @@ -57,37 +57,14 @@ # shamelessly adapted from `cf-xarray` (which adopted it from MetPy and xclim itself) -units = pint.UnitRegistry( - autoconvert_offset_to_baseunit=True, - preprocessors=[ - functools.partial( - re.compile( - r"(?<=[A-Za-z])(?![A-Za-z])(? [length]: value / 1000 / kg / m ** 3 -# [mass] / [length]**2 / [time] -> [length] / [time] : value / 1000 / kg * m ** 3 -# [length] / [time] -> [mass] / [length]**2 / [time] : value * 1000 * kg / m ** 3 -# @end - # Radiation units units.define("[radiation] = [power] / [length]**2") @@ -181,10 +146,6 @@ def units2pint(value: xr.DataArray | str | units.Quantity) -> pint.Unit: else: raise NotImplementedError(f"Value of type `{type(value)}` not supported.") - unit = unit.replace("%", "pct") - if unit == "1": - unit = "" - # Catch user errors undetected by Pint degree_ex = ["deg", "degree", "degrees"] unit_ex = [ @@ -223,27 +184,8 @@ def pint2cfunits(value: units.Quantity | units.Unit) -> str: if isinstance(value, (pint.Quantity, units.Quantity)): value = value.units # noqa reason: units.Quantity really have .units property - # Print units using abbreviations (millimeter -> mm) - s = f"{value:~}" - - # Search and replace patterns - pat = r"(?P/ )?(?P\w+)(?: \*\* (?P\d))?" - - def repl(m): - i, u, p = m.groups() - p = p or (1 if i else "") - neg = "-" if i else ("^" if p else "") - - return f"{u}{neg}{p}" - - out, _ = re.subn(pat, repl, s) - - # Remove multiplications - out = out.replace(" * ", " ") - # Delta degrees: - out = out.replace("Δ°", "delta_deg") - # Percents - return out.replace("percent", "%").replace("pct", "%") + # The replacement is due to hgrecco/pint#1486 + return f"{value:cf}".replace("dimensionless", "") def ensure_cf_units(ustr: str) -> str: From 7d32e7030763674b7d414787a157cd0fa2c7c764 Mon Sep 17 00:00:00 2001 From: Pascal Bourgault Date: Wed, 10 Jan 2024 15:09:04 -0500 Subject: [PATCH 2/5] edit doc --- CHANGES.rst | 2 +- docs/notebooks/units.ipynb | 2 +- xclim/core/units.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 481045352..09957c4cf 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -20,7 +20,7 @@ New features and enhancements Breaking changes ^^^^^^^^^^^^^^^^ * `bump2version` has been replaced with `bump-my-version` to bump the version number using configurations set in the `pyproject.toml` file. (:issue:`1557`, :pull:`1569`). -* Xclim's units registry and units formatting are now copied from `cf_xarray` with some negligeable tweaks. The exponent sign "^" is now never added in the ``units`` attribute. For example, square meters are given as "m2" instead of "m^2" by xclim, both are still accepted as input (:issue:`1010`, :pull:`1582`). +* Xclim's units registry and units formatting are now extended from `cf_xarray`. The exponent sign "^" is now never added in the ``units`` attribute. For example, square meters are given as "m2" instead of "m^2" by xclim, both are still accepted as input (:issue:`1010`, :pull:`1582`). Bug fixes ^^^^^^^^^ diff --git a/docs/notebooks/units.ipynb b/docs/notebooks/units.ipynb index 5ff1dbe6b..f4076a6cd 100644 --- a/docs/notebooks/units.ipynb +++ b/docs/notebooks/units.ipynb @@ -39,7 +39,7 @@ "source": [ "A lot of effort has been placed into automatic handling of input data units. `xclim` will automatically detect the input variable(s) units (e.g. °C versus K or mm/s versus mm/day etc.) and adjust on-the-fly in order to calculate indices in the consistent manner. This comes with the obvious caveat that input data requires a metadata attribute for units : the `units` attribute is required, and the `standard_name` can be useful for automatic conversions.\n", "\n", - "The main unit handling method is [`xclim.core.units.convert_units_to`](../xclim.core.rst#xclim.core.units.convert_units_to) which can also be useful on its own. `xclim` relies on [pint](https://pint.readthedocs.io/) for unit handling.\n", + "The main unit handling method is [`xclim.core.units.convert_units_to`](../xclim.core.rst#xclim.core.units.convert_units_to) which can also be useful on its own. `xclim` relies on [pint](https://pint.readthedocs.io/) for unit handling and extends the units registry and formatting functions of [cf_xarray](https://cf-xarray.readthedocs.io/en/latest/units.html).\n", "\n", "## Simple example: Temperature" ] diff --git a/xclim/core/units.py b/xclim/core/units.py index fad3baa6b..bcbea7028 100644 --- a/xclim/core/units.py +++ b/xclim/core/units.py @@ -2,7 +2,7 @@ Units Handling Submodule ======================== -`Pint` is used to define the :py:data:`xclim.core.units.units` `UnitRegistry`. +Xclim's pint unit registry is copied from cf_xarray. This module defines most unit handling methods. """ from __future__ import annotations From 1bbbaab09d944ce086f248c6a3c557eff6915a74 Mon Sep 17 00:00:00 2001 From: Pascal Bourgault Date: Wed, 10 Jan 2024 15:15:14 -0500 Subject: [PATCH 3/5] edit pull number --- CHANGES.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 09957c4cf..33cae4ef9 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -20,7 +20,7 @@ New features and enhancements Breaking changes ^^^^^^^^^^^^^^^^ * `bump2version` has been replaced with `bump-my-version` to bump the version number using configurations set in the `pyproject.toml` file. (:issue:`1557`, :pull:`1569`). -* Xclim's units registry and units formatting are now extended from `cf_xarray`. The exponent sign "^" is now never added in the ``units`` attribute. For example, square meters are given as "m2" instead of "m^2" by xclim, both are still accepted as input (:issue:`1010`, :pull:`1582`). +* Xclim's units registry and units formatting are now extended from `cf_xarray`. The exponent sign "^" is now never added in the ``units`` attribute. For example, square meters are given as "m2" instead of "m^2" by xclim, both are still accepted as input (:issue:`1010`, :pull:`1590`). Bug fixes ^^^^^^^^^ From 4127f7931beb4b377bc212133dedaae5e0a9eab3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 10 Jan 2024 20:15:17 +0000 Subject: [PATCH 4/5] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ec0cbc64e..0e5de1f8c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -149,7 +149,6 @@ values = [ "release" ] - [tool.codespell] skip = 'xclim/data/*.json,docs/_build,docs/notebooks/xclim_training/*.ipynb,docs/references.bib,__pycache__,*.nc,*.png,*.gz,*.whl' ignore-words-list = "absolue,astroid,bloc,bui,callendar,degreee,environnement,hanel,inferrable,lond,nam,nd,ressources,vas" From 160026525e50d7bd61a1837c19361a2bd19a9f02 Mon Sep 17 00:00:00 2001 From: Pascal Bourgault Date: Wed, 10 Jan 2024 17:31:22 -0500 Subject: [PATCH 5/5] Apply suggestions from code review Co-authored-by: Trevor James Smith <10819524+Zeitsperre@users.noreply.github.com> --- CHANGES.rst | 2 +- docs/notebooks/units.ipynb | 2 +- xclim/core/units.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 2950da3f9..7ea8596d5 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -20,7 +20,7 @@ New features and enhancements Breaking changes ^^^^^^^^^^^^^^^^ * `bump2version` has been replaced with `bump-my-version` to bump the version number using configurations set in the `pyproject.toml` file. (:issue:`1557`, :pull:`1569`). -* Xclim's units registry and units formatting are now extended from `cf_xarray`. The exponent sign "^" is now never added in the ``units`` attribute. For example, square meters are given as "m2" instead of "m^2" by xclim, both are still accepted as input (:issue:`1010`, :pull:`1590`). +* `xclim`'s units registry and units formatting are now extended from `cf-xarray`. The exponent sign "^" is now never added in the ``units`` attribute. For example, square meters are given as "m2" instead of "m^2" by xclim, both are still accepted as input. (:issue:`1010`, :pull:`1590`). Bug fixes ^^^^^^^^^ diff --git a/docs/notebooks/units.ipynb b/docs/notebooks/units.ipynb index f4076a6cd..beaaa6a4b 100644 --- a/docs/notebooks/units.ipynb +++ b/docs/notebooks/units.ipynb @@ -39,7 +39,7 @@ "source": [ "A lot of effort has been placed into automatic handling of input data units. `xclim` will automatically detect the input variable(s) units (e.g. °C versus K or mm/s versus mm/day etc.) and adjust on-the-fly in order to calculate indices in the consistent manner. This comes with the obvious caveat that input data requires a metadata attribute for units : the `units` attribute is required, and the `standard_name` can be useful for automatic conversions.\n", "\n", - "The main unit handling method is [`xclim.core.units.convert_units_to`](../xclim.core.rst#xclim.core.units.convert_units_to) which can also be useful on its own. `xclim` relies on [pint](https://pint.readthedocs.io/) for unit handling and extends the units registry and formatting functions of [cf_xarray](https://cf-xarray.readthedocs.io/en/latest/units.html).\n", + "The main unit handling method is [`xclim.core.units.convert_units_to`](../xclim.core.rst#xclim.core.units.convert_units_to) which can also be useful on its own. `xclim` relies on [pint](https://pint.readthedocs.io/) for unit handling and extends the units registry and formatting functions of [cf-xarray](https://cf-xarray.readthedocs.io/en/latest/units.html).\n", "\n", "## Simple example: Temperature" ] diff --git a/xclim/core/units.py b/xclim/core/units.py index bcbea7028..1ffc9d567 100644 --- a/xclim/core/units.py +++ b/xclim/core/units.py @@ -2,7 +2,7 @@ Units Handling Submodule ======================== -Xclim's pint unit registry is copied from cf_xarray. +`xclim`'s `pint`-based unit registry is an extension of the registry defined in `cf-xarray`. This module defines most unit handling methods. """ from __future__ import annotations