Skip to content

Commit

Permalink
Merge branch 'master' into fix-719-hour-alias
Browse files Browse the repository at this point in the history
  • Loading branch information
jules-ch authored Feb 4, 2022
2 parents 71a11f2 + 5403f46 commit f8375bc
Show file tree
Hide file tree
Showing 16 changed files with 126 additions and 75 deletions.
File renamed without changes.
13 changes: 5 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,18 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: [3.7, 3.8, 3.9]
numpy: [null, "numpy>=1.17,<2.0.0"]
uncertainties: [null, "uncertainties==3.1.4", "uncertainties>=3.1.4,<4.0.0"]
python-version: [3.8, 3.9, "3.10"]
numpy: [null, "numpy>=1.19,<2.0.0"]
uncertainties: [null, "uncertainties==3.1.6", "uncertainties>=3.1.6,<4.0.0"]
extras: [null]
include:
- python-version: 3.7 # Minimal versions
numpy: numpy==1.17.5
- python-version: 3.8 # Minimal versions
numpy: numpy==1.19.5
extras: matplotlib==2.2.5
- python-version: 3.8
numpy: "numpy"
uncertainties: "uncertainties"
extras: "sparse xarray netCDF4 dask[complete] graphviz babel==2.8"
- python-version: "3.10"
numpy: null
extras: null
runs-on: ubuntu-latest

env:
Expand Down
7 changes: 7 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,25 @@ Pint Changelog
0.19 (unreleased)
-----------------

- Fix a bug for offset units of higher dimension, e.g. gauge pressure.
(Issue #1066, thanks dalito)
- Fix type hints of function wrapper (Issue #1431)
- Upgrade min version of uncertainties to 3.1.4
- Fix setting options of the application registry (Issue #1403).
- Fix Quantity & Unit `is_compatible_with` with registry active contexts (Issue #1424).
- Allow Quantity to parse 'NaN' and 'inf(inity)', case insensitive
- Fix casting error when using to_reduced_units with array of int.
(Issue #1184)
- Use default numpy `np.printoptions` available since numpy 1.15.
- Implement `numpy.nanprod` (Issue #1369)
- Fix default_format ignored for measurement (Issue #1456)

### Breaking Changes

- Update hour default symobl to `h`. (Issue #719)
- Replace `h` with `ℎ` (U+210E) as default symbol for planck constant.
- Change minimal Python version support to 3.8+
- Change minimal Numpy version support to 1.19+

0.18 (2021-10-26)
-----------------
Expand Down
6 changes: 4 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
:target: https://pypi.python.org/pypi/pint
:alt: Latest Version

.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
:target: https://github.com/python/black

.. image:: https://readthedocs.org/projects/pint/badge/
:target: https://pint.readthedocs.org/
:alt: Documentation
Expand Down Expand Up @@ -40,8 +43,7 @@ and constants. Due to its modular design, you can extend (or even rewrite!)
the complete list without changing the source code. It supports a lot of
numpy mathematical operations **without monkey patching or wrapping numpy**.

It has a complete test coverage. It runs in Python 3.7+ with no other dependency.
If you need Python 3.6 compatibility, use Pint 0.17.
It has a complete test coverage. It runs in Python 3.8+ with no other dependency.
It is licensed under BSD.

It is extremely easy and natural to use:
Expand Down
2 changes: 1 addition & 1 deletion docs/getting.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Installation
============

Pint has no dependencies except Python_ itself. In runs on Python 3.7+.
Pint has no dependencies except Python_ itself. In runs on Python 3.8+.

You can install it (or upgrade to the latest version) using pip_::

Expand Down
2 changes: 1 addition & 1 deletion docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Due to its modular design, you can extend (or even rewrite!) the complete list
without changing the source code. It supports a lot of numpy mathematical
operations **without monkey patching or wrapping numpy**.

It has a complete test coverage. It runs in Python 3.7+ with no other
It has a complete test coverage. It runs in Python 3.8+ with no other
dependencies. It is licensed under a `BSD 3-clause style license`_.

It is extremely easy and natural to use:
Expand Down
9 changes: 3 additions & 6 deletions pint/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
:license: BSD, see LICENSE for more details.
"""

from importlib.metadata import version

from .context import Context
from .errors import ( # noqa: F401
DefinitionSyntaxError,
Expand All @@ -29,12 +31,6 @@
from .unit import Unit
from .util import logger, pi_theorem # noqa: F401

try:
from importlib.metadata import version
except ImportError:
# Backport for Python < 3.8
from importlib_metadata import version

try: # pragma: no cover
__version__ = version("pint")
except Exception: # pragma: no cover
Expand Down Expand Up @@ -138,6 +134,7 @@ def test():
"UnitRegistry",
"PintError",
"DefinitionSyntaxError",
"LogarithmicUnitCalculusError",
"DimensionalityError",
"OffsetUnitCalculusError",
"RedefinitionError",
Expand Down
3 changes: 3 additions & 0 deletions pint/measurement.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ def __str__(self):
return "{}".format(self)

def __format__(self, spec):

spec = spec or self.default_format

# special cases
if "Lx" in spec: # the LaTeX siunitx code
# the uncertainties module supports formatting
Expand Down
65 changes: 40 additions & 25 deletions pint/numpy_func.py
Original file line number Diff line number Diff line change
Expand Up @@ -679,34 +679,49 @@ def _all(a, *args, **kwargs):
raise ValueError("Boolean value of Quantity with offset unit is ambiguous.")


@implements("prod", "function")
def _prod(a, *args, **kwargs):
arg_names = ("axis", "dtype", "out", "keepdims", "initial", "where")
all_kwargs = dict(**dict(zip(arg_names, args)), **kwargs)
axis = all_kwargs.get("axis", None)
where = all_kwargs.get("where", None)

registry = a.units._REGISTRY

if axis is not None and where is not None:
_, where_ = np.broadcast_arrays(a._magnitude, where)
exponents = np.unique(np.sum(where_, axis=axis))
if len(exponents) == 1 or (len(exponents) == 2 and 0 in exponents):
units = a.units ** np.max(exponents)
def implement_prod_func(name):
if np is None:
return

func = getattr(np, name, None)
if func is None:
return

@implements(name, "function")
def _prod(a, *args, **kwargs):
arg_names = ("axis", "dtype", "out", "keepdims", "initial", "where")
all_kwargs = dict(**dict(zip(arg_names, args)), **kwargs)
axis = all_kwargs.get("axis", None)
where = all_kwargs.get("where", None)

registry = a.units._REGISTRY

if axis is not None and where is not None:
_, where_ = np.broadcast_arrays(a._magnitude, where)
exponents = np.unique(np.sum(where_, axis=axis))
if len(exponents) == 1 or (len(exponents) == 2 and 0 in exponents):
units = a.units ** np.max(exponents)
else:
units = registry.dimensionless
a = a.to(units)
elif axis is not None:
units = a.units ** a.shape[axis]
elif where is not None:
exponent = np.sum(where)
units = a.units ** exponent
else:
units = registry.dimensionless
a = a.to(units)
elif axis is not None:
units = a.units ** a.shape[axis]
elif where is not None:
exponent = np.sum(where)
units = a.units ** exponent
else:
units = a.units ** a.size
exponent = (
np.sum(np.logical_not(np.isnan(a))) if name == "nanprod" else a.size
)
units = a.units ** exponent

result = func(a._magnitude, *args, **kwargs)

return registry.Quantity(result, units)

result = np.prod(a._magnitude, *args, **kwargs)

return registry.Quantity(result, units)
for name in ["prod", "nanprod"]:
implement_prod_func(name)


# Implement simple matching-unit or stripped-unit functions based on signature
Expand Down
27 changes: 8 additions & 19 deletions pint/quantity.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
from __future__ import annotations

import bisect
import contextlib
import copy
import datetime
import functools
Expand Down Expand Up @@ -168,20 +167,6 @@ def wrapper(self, *args, **kwargs):
return wrapper


@contextlib.contextmanager
def printoptions(*args, **kwargs):
"""Numpy printoptions context manager released with version 1.15.0
https://docs.scipy.org/doc/numpy/reference/generated/numpy.printoptions.html
"""

opts = np.get_printoptions()
try:
np.set_printoptions(*args, **kwargs)
yield np.get_printoptions()
finally:
np.set_printoptions(**opts)


# Workaround to bypass dynamically generated Quantity with overload method
Magnitude = TypeVar("Magnitude")

Expand Down Expand Up @@ -413,7 +398,9 @@ def __format__(self, spec: str) -> str:
allf = plain_allf = "{} {}"
mstr = formatter.format(obj.magnitude)
else:
with printoptions(formatter={"float_kind": formatter.format}):
with np.printoptions(
formatter={"float_kind": formatter.format}
):
mstr = (
"<pre>"
+ format(obj.magnitude).replace("\n", "<br>")
Expand All @@ -440,7 +427,7 @@ def __format__(self, spec: str) -> str:
if obj.magnitude.ndim == 0:
mstr = formatter.format(obj.magnitude)
else:
with printoptions(formatter={"float_kind": formatter.format}):
with np.printoptions(formatter={"float_kind": formatter.format}):
mstr = format(obj.magnitude).replace("\n", "")
else:
mstr = format(obj.magnitude, mspec).replace("\n", "")
Expand Down Expand Up @@ -771,6 +758,8 @@ def _get_reduced_units(self, units):
if unit1 not in units:
continue
for unit2 in units:
# get exponent after reduction
exp = units[unit1]
if unit1 != unit2:
power = self._REGISTRY._get_dimensionality_ratio(unit1, unit2)
if power:
Expand Down Expand Up @@ -803,9 +792,9 @@ def to_reduced_units(self) -> Quantity[_MagnitudeType]:

# shortcuts in case we're dimensionless or only a single unit
if self.dimensionless:
return self.ito({})
return self.to({})
if len(self._units) == 1:
return None
return self

units = self._units.copy()
new_units = self._get_reduced_units(units)
Expand Down
10 changes: 5 additions & 5 deletions pint/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
from ._typing import F, QuantityOrUnitLike
from .compat import HAS_BABEL, babel_parse, tokenizer
from .context import Context, ContextChain
from .converters import LogarithmicConverter, ScaleConverter
from .converters import ScaleConverter
from .definitions import (
AliasDefinition,
Definition,
Expand Down Expand Up @@ -1463,10 +1463,10 @@ def _validate_and_extract(self, units):

return None

def _add_ref_of_log_unit(self, offset_unit, all_units):
def _add_ref_of_log_or_offset_unit(self, offset_unit, all_units):

slct_unit = self._units[offset_unit]
if isinstance(slct_unit.converter, LogarithmicConverter):
if slct_unit.is_logarithmic or (not slct_unit.is_multiplicative):
# Extract reference unit
slct_ref = slct_unit.reference
# If reference unit is not dimensionless
Expand Down Expand Up @@ -1534,13 +1534,13 @@ def _convert(self, value, src, dst, inplace=False):
value = self._units[src_offset_unit].converter.to_reference(value, inplace)
src = src.remove([src_offset_unit])
# Add reference unit for multiplicative section
src = self._add_ref_of_log_unit(src_offset_unit, src)
src = self._add_ref_of_log_or_offset_unit(src_offset_unit, src)

# clean dst units from offset units
if dst_offset_unit:
dst = dst.remove([dst_offset_unit])
# Add reference unit for multiplicative section
dst = self._add_ref_of_log_unit(dst_offset_unit, dst)
dst = self._add_ref_of_log_or_offset_unit(dst_offset_unit, dst)

# Convert non multiplicative units to the dst.
value = super()._convert(value, src, dst, inplace, False)
Expand Down
11 changes: 11 additions & 0 deletions pint/testsuite/test_issues.py
Original file line number Diff line number Diff line change
Expand Up @@ -717,6 +717,17 @@ def test_issue1062_issue1097(self):
q = ureg.Quantity(1, "nm")
q.to("J")

def test_issue1066(self):
"""Verify calculations for offset units of higher dimension"""
ureg = UnitRegistry()
ureg.define("barga = 1e5 * Pa; offset: 1e5")
ureg.define("bargb = 1 * bar; offset: 1")
q_4barg_a = ureg.Quantity(4, ureg.barga)
q_4barg_b = ureg.Quantity(4, ureg.bargb)
q_5bar = ureg.Quantity(5, ureg.bar)
helpers.assert_quantity_equal(q_4barg_a, q_5bar)
helpers.assert_quantity_equal(q_4barg_b, q_5bar)

def test_issue1086(self):
# units with prefixes should correctly test as 'in' the registry
assert "bits" in ureg
Expand Down
22 changes: 22 additions & 0 deletions pint/testsuite/test_measurement.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,28 @@ def test_format_exponential_neg(self, subtests):
with subtests.test(spec):
assert spec.format(m) == result

def test_format_default(self, subtests):
v, u = self.Q_(4.0, "s ** 2"), self.Q_(0.1, "s ** 2")
m = self.ureg.Measurement(v, u)

for spec, result in (
("", "(4.00 +/- 0.10) second ** 2"),
("P", "(4.00 ± 0.10) second²"),
("L", r"\left(4.00 \pm 0.10\right)\ \mathrm{second}^{2}"),
("H", "(4.00 &plusmn; 0.10) second<sup>2</sup>"),
("C", "(4.00+/-0.10) second**2"),
("Lx", r"\SI{4.00 +- 0.10}{\second\squared}"),
(".1f", "(4.0 +/- 0.1) second ** 2"),
(".1fP", "(4.0 ± 0.1) second²"),
(".1fL", r"\left(4.0 \pm 0.1\right)\ \mathrm{second}^{2}"),
(".1fH", "(4.0 &plusmn; 0.1) second<sup>2</sup>"),
(".1fC", "(4.0+/-0.1) second**2"),
(".1fLx", r"\SI{4.0 +- 0.1}{\second\squared}"),
):
with subtests.test(spec):
self.ureg.default_format = spec
assert "{}".format(m) == result

def test_raise_build(self):
v, u = self.Q_(1.0, "s"), self.Q_(0.1, "s")
o = self.Q_(0.1, "m")
Expand Down
10 changes: 10 additions & 0 deletions pint/testsuite/test_numpy.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,16 @@ def test_prod_numpy_func(self):
np.prod(self.q, axis=axis, where=[True, False]), [3, 1] * self.ureg.m ** 2
)

@helpers.requires_array_function_protocol()
def test_nanprod_numpy_func(self):
helpers.assert_quantity_equal(np.nanprod(self.q_nan), 6 * self.ureg.m ** 3)
helpers.assert_quantity_equal(
np.nanprod(self.q_nan, axis=0), [3, 2] * self.ureg.m ** 2
)
helpers.assert_quantity_equal(
np.nanprod(self.q_nan, axis=1), [2, 3] * self.ureg.m ** 2
)

def test_sum(self):
assert self.q.sum() == 10 * self.ureg.m
helpers.assert_quantity_equal(self.q.sum(0), [4, 6] * self.ureg.m)
Expand Down
3 changes: 3 additions & 0 deletions pint/testsuite/test_quantity.py
Original file line number Diff line number Diff line change
Expand Up @@ -642,6 +642,9 @@ def test_to_reduced_units(self):
q.to_reduced_units(), self.Q_([3000.0, 4000.0], "ms**2")
)

q = self.Q_(0.5, "g*t/kg")
helpers.assert_quantity_equal(q.to_reduced_units(), self.Q_(0.5, "kg"))


class TestQuantityToCompact(QuantityTestCase):
def assertQuantityAlmostIdentical(self, q1, q2):
Expand Down
Loading

0 comments on commit f8375bc

Please sign in to comment.