From 93f5963665f9aa210237caf62b7376b3aed0c436 Mon Sep 17 00:00:00 2001 From: Pascal Bourgault Date: Tue, 25 Oct 2022 14:23:56 -0400 Subject: [PATCH 1/3] do not define % as unit --- xclim/core/units.py | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/xclim/core/units.py b/xclim/core/units.py index eb9cc458f..08995f78b 100644 --- a/xclim/core/units.py +++ b/xclim/core/units.py @@ -13,12 +13,9 @@ from inspect import signature from typing import Any, Callable -import pint.converters -import pint.unit +import pint import xarray as xr from boltons.funcutils import wraps -from pint import Unit -from pint.definitions import UnitDefinition from .calendar import date_range, get_calendar, parse_offset from .options import datacheck @@ -42,11 +39,8 @@ units = pint.UnitRegistry(autoconvert_offset_to_baseunit=True, on_redefinition="ignore") -units.define( - pint.unit.UnitDefinition( - "percent", "%", ("pct",), pint.converters.ScaleConverter(0.01) - ) -) +# The % is a valid python operator and thus an invalid pint unit name +units.define("percent = 0.01 count = pct") # In pint, the default symbol for year is "a" which is not CF-compliant (stands for "are") units.define("year = 365.25 * day = yr") @@ -110,7 +104,7 @@ units.define("[radiation] = [power] / [length]**2") -def units2pint(value: xr.DataArray | str | units.Quantity) -> Unit: +def units2pint(value: xr.DataArray | str | units.Quantity) -> pint.Unit: """Return the pint Unit for the DataArray units. Parameters @@ -120,15 +114,12 @@ def units2pint(value: xr.DataArray | str | units.Quantity) -> Unit: Returns ------- - pint.unit.UnitDefinition + pint.Unit Units of the data array. """ def _transform(s): """Convert a CF-unit string to a pint expression.""" - if s == "%": - return "percent" - return re.subn(r"([a-zA-Z]+)\^?(-?\d)", r"\g<1>**\g<2>", s)[0] if isinstance(value, str): @@ -175,7 +166,7 @@ def _transform(s): # Note: The pint library does not have a generic Unit or Quantity type at the moment. Using "Any" as a stand-in. -def pint2cfunits(value: UnitDefinition) -> str: +def pint2cfunits(value: pint.Unit) -> str: """Return a CF-compliant unit string from a `pint` unit. Parameters @@ -210,7 +201,8 @@ def repl(m): out = out.replace(" * ", " ") # Delta degrees: out = out.replace("Δ°", "delta_deg") - return out.replace("percent", "%") + # Percents + return out.replace("percent", "%").replace("pct", "%") def ensure_cf_units(ustr: str) -> str: From e75adf1a188b4de6388ce8f60de6581d44ca22dd Mon Sep 17 00:00:00 2001 From: Pascal Bourgault Date: Tue, 25 Oct 2022 15:49:12 -0400 Subject: [PATCH 2/3] Adopt registry declaration from cf-xarray --- HISTORY.rst | 1 + xclim/core/units.py | 35 ++++++++++++++++++----------------- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 77c40817c..3ef1bad6d 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -35,6 +35,7 @@ Bug fixes ^^^^^^^^^ * The docstring of ``cool_night_index`` suggested that `lat` was an optional parameter. This has been corrected. (:issue:`1179`, :pull:`1180`). * The ``mean_radiant_temperature`` indice was accessing hardcoded `lat` and `lon` coordinates from passed DataArrays. This now uses `cf-xarray` accessors. (:pull:`1180`). +* Adopt (and adapt) unit registry declaration and preprocessors from ``cf-xarray`` to circumvent bugs caused by a refactor in ``pint`` 0.20. It also cleans the code a little bit. (:issue:`1211`). Internal changes ^^^^^^^^^^^^^^^^ diff --git a/xclim/core/units.py b/xclim/core/units.py index 08995f78b..2d79122ec 100644 --- a/xclim/core/units.py +++ b/xclim/core/units.py @@ -8,6 +8,7 @@ """ from __future__ import annotations +import functools import re import warnings from inspect import signature @@ -38,9 +39,22 @@ ] -units = pint.UnitRegistry(autoconvert_offset_to_baseunit=True, on_redefinition="ignore") -# The % is a valid python operator and thus an invalid pint unit name -units.define("percent = 0.01 count = pct") +# 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])(? pint.Unit: pint.Unit Units of the data array. """ - - def _transform(s): - """Convert a CF-unit string to a pint expression.""" - return re.subn(r"([a-zA-Z]+)\^?(-?\d)", r"\g<1>**\g<2>", s)[0] - if isinstance(value, str): unit = value elif isinstance(value, xr.DataArray): @@ -154,15 +163,7 @@ def _transform(s): "Remove white space from temperature units, e.g. use `degC`." ) - try: # Pint compatible - return units.parse_units(unit) - except ( - pint.UndefinedUnitError, - pint.DimensionalityError, - AttributeError, - TypeError, - ): # Convert from CF-units to pint-compatible - return units.parse_units(_transform(unit)) + return units.parse_units(unit) # Note: The pint library does not have a generic Unit or Quantity type at the moment. Using "Any" as a stand-in. From 0a82f653a527a95f9ec9479070765ed4cc3c1638 Mon Sep 17 00:00:00 2001 From: Pascal Bourgault Date: Tue, 25 Oct 2022 16:10:09 -0400 Subject: [PATCH 3/3] add pr to hist --- HISTORY.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HISTORY.rst b/HISTORY.rst index 3ef1bad6d..e86099a27 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -35,7 +35,7 @@ Bug fixes ^^^^^^^^^ * The docstring of ``cool_night_index`` suggested that `lat` was an optional parameter. This has been corrected. (:issue:`1179`, :pull:`1180`). * The ``mean_radiant_temperature`` indice was accessing hardcoded `lat` and `lon` coordinates from passed DataArrays. This now uses `cf-xarray` accessors. (:pull:`1180`). -* Adopt (and adapt) unit registry declaration and preprocessors from ``cf-xarray`` to circumvent bugs caused by a refactor in ``pint`` 0.20. It also cleans the code a little bit. (:issue:`1211`). +* Adopt (and adapt) unit registry declaration and preprocessors from ``cf-xarray`` to circumvent bugs caused by a refactor in ``pint`` 0.20. It also cleans the code a little bit. (:issue:`1211`, :pull:`1212`). Internal changes ^^^^^^^^^^^^^^^^