Skip to content

Commit 24bf4d3

Browse files
jthielenkeewisdcherian
authored
Add unit support to cf-xarray (#197)
Co-authored-by: keewis <[email protected]> Co-authored-by: dcherian <[email protected]> Co-authored-by: Deepak Cherian <[email protected]> Co-authored-by: Keewis <[email protected]>
1 parent 5d3e5fe commit 24bf4d3

File tree

8 files changed

+164
-1
lines changed

8 files changed

+164
-1
lines changed

.github/workflows/ci.yaml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,29 @@ jobs:
4848
name: codecov-umbrella
4949
fail_ci_if_error: false
5050

51+
no-optional-deps:
52+
name: no-optional-deps
53+
runs-on: ubuntu-latest
54+
steps:
55+
- uses: actions/checkout@v2
56+
- uses: conda-incubator/setup-miniconda@v2
57+
with:
58+
channels: conda-forge
59+
mamba-version: "*"
60+
activate-environment: cf_xarray_test
61+
auto-update-conda: false
62+
python-version: ${{ matrix.python-version }}
63+
- name: Set up conda environment
64+
shell: bash -l {0}
65+
run: |
66+
mamba env update -f ci/environment-no-optional-deps.yml
67+
python -m pip install -e .
68+
conda list
69+
- name: Run Tests
70+
shell: bash -l {0}
71+
run: |
72+
pytest -n 2
73+
5174
upstream-dev:
5275
name: upstream-dev
5376
runs-on: ubuntu-latest

cf_xarray/tests/test_units.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
r"""Tests the operation of cf_xarray's ported unit support code.
2+
3+
Reused with modification from MetPy under the terms of the BSD 3-Clause License.
4+
Copyright (c) 2017 MetPy Developers.
5+
"""
6+
7+
import pytest
8+
9+
pytest.importorskip("pint")
10+
11+
from ..units import units
12+
13+
14+
def test_added_degrees_units():
15+
"""Test that our added degrees units are present in the registry."""
16+
# Test equivalence of abbreviations/aliases to our defined names
17+
assert str(units("degrees_N").units) == "degrees_north"
18+
assert str(units("degreesN").units) == "degrees_north"
19+
assert str(units("degree_north").units) == "degrees_north"
20+
assert str(units("degree_N").units) == "degrees_north"
21+
assert str(units("degreeN").units) == "degrees_north"
22+
assert str(units("degrees_E").units) == "degrees_east"
23+
assert str(units("degreesE").units) == "degrees_east"
24+
assert str(units("degree_east").units) == "degrees_east"
25+
assert str(units("degree_E").units) == "degrees_east"
26+
assert str(units("degreeE").units) == "degrees_east"
27+
28+
# Test equivalence of our defined units to base units
29+
assert units("degrees_north") == units("degrees")
30+
assert units("degrees_north").to_base_units().units == units.radian
31+
assert units("degrees_east") == units("degrees")
32+
assert units("degrees_east").to_base_units().units == units.radian
33+
34+
35+
def test_gpm_unit():
36+
"""Test that the gpm unit does alias to meters."""
37+
x = 1 * units("gpm")
38+
assert str(x.units) == "meter"
39+
40+
41+
def test_psu_unit():
42+
"""Test that the psu unit are present in the registry."""
43+
x = 1 * units("psu")
44+
assert str(x.units) == "practical_salinity_unit"
45+
46+
47+
def test_percent_units():
48+
"""Test that percent sign units are properly parsed and interpreted."""
49+
assert str(units("%").units) == "percent"
50+
51+
52+
@pytest.mark.xfail(reason="not supported by pint, yet: hgrecco/pint#1295")
53+
def test_udunits_power_syntax():
54+
"""Test that UDUNITS style powers are properly parsed and interpreted."""
55+
assert units("m2 s-2").units == units.m ** 2 / units.s ** 2
56+
57+
58+
def test_udunits_power_syntax_parse_units():
59+
"""Test that UDUNITS style powers are properly parsed and interpreted."""
60+
assert units.parse_units("m2 s-2") == units.m ** 2 / units.s ** 2

cf_xarray/units.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
r"""Module to provide unit support via pint approximating UDUNITS/CF.
2+
3+
Reused with modification from MetPy under the terms of the BSD 3-Clause License.
4+
Copyright (c) 2015,2017,2019 MetPy Developers.
5+
"""
6+
import functools
7+
import re
8+
import warnings
9+
10+
import pint
11+
from pint import ( # noqa: F401
12+
DimensionalityError,
13+
UndefinedUnitError,
14+
UnitStrippedWarning,
15+
)
16+
17+
# Create registry, with preprocessors for UDUNITS-style powers (m2 s-2) and percent signs
18+
units = pint.UnitRegistry(
19+
autoconvert_offset_to_baseunit=True,
20+
preprocessors=[
21+
functools.partial(
22+
re.compile(
23+
r"(?<=[A-Za-z])(?![A-Za-z])(?<![0-9\-][eE])(?<![0-9\-])(?=[0-9\-])"
24+
).sub,
25+
"**",
26+
),
27+
lambda string: string.replace("%", "percent"),
28+
],
29+
force_ndarray_like=True,
30+
)
31+
32+
units.define(
33+
pint.unit.UnitDefinition("percent", "%", (), pint.converters.ScaleConverter(0.01))
34+
)
35+
36+
# Define commonly encoutered units (both CF and non-CF) not defined by pint
37+
units.define(
38+
"degrees_north = degree = degrees_N = degreesN = degree_north = degree_N = degreeN"
39+
)
40+
units.define(
41+
"degrees_east = degree = degrees_E = degreesE = degree_east = degree_E = degreeE"
42+
)
43+
units.define("@alias meter = gpm")
44+
units.define("practical_salinity_unit = [] = psu")
45+
46+
# Enable pint's built-in matplotlib support
47+
try:
48+
units.setup_matplotlib()
49+
except ImportError:
50+
warnings.warn(
51+
"Import(s) unavailable to set up matplotlib support...skipping this portion "
52+
"of the setup."
53+
)
54+
55+
# Set as application registry
56+
pint.set_application_registry(units)
57+
58+
del pint

ci/environment-no-optional-deps.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
name: cf_xarray_test
2+
channels:
3+
- conda-forge
4+
dependencies:
5+
- pytest-cov
6+
- pytest
7+
- pytest-xdist
8+
- dask
9+
- matplotlib-base
10+
- netcdf4
11+
- pandas
12+
- pooch
13+
- xarray

ci/environment.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,6 @@ dependencies:
99
- matplotlib-base
1010
- netcdf4
1111
- pandas
12+
- pint
1213
- pooch
1314
- xarray

ci/upstream-dev-env.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ dependencies:
1212
- pooch
1313
- pip:
1414
- git+https://github.com/pydata/xarray
15+
- git+https://github.com/hgrecco/pint

doc/whats-new.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@
33
What's New
44
----------
55

6+
v0.5.3 (unreleased)
7+
===================
8+
- Begin adding support for units with a unit registry for pint arrays. :pr:`197`.
9+
By `Jon Thielen`_ and `Justus Magin`_.
10+
611
v0.5.2 (May 11, 2021)
712
=====================
813

@@ -102,6 +107,8 @@ v0.1.3
102107
- Support expanding key to multiple dimension names.
103108

104109
.. _`Mattia Almansi`: https://github.com/malmans2
110+
.. _`Justus Magin`: https://github.com/keewis
111+
.. _`Jon Thielen`: https://github.com/jthielen
105112
.. _`Anderson Banihirwe`: https://github.com/andersy005
106113
.. _`Pascal Bourgault`: https://github.com/aulemahal
107114
.. _`Deepak Cherian`: https://github.com/dcherian

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ skip_gitignore = true
4141
force_to_top = true
4242
default_section = THIRDPARTY
4343
known_first_party = cf_xarray
44-
known_third_party = dask,matplotlib,numpy,pandas,pkg_resources,pytest,setuptools,sphinx_autosummary_accessors,xarray
44+
known_third_party = dask,matplotlib,numpy,pandas,pint,pkg_resources,pytest,setuptools,sphinx_autosummary_accessors,xarray
4545

4646
# Most of the numerical computing stack doesn't have type annotations yet.
4747
[mypy-affine.*]

0 commit comments

Comments
 (0)