Skip to content

Commit

Permalink
Merge branch 'master' into array_ufunc_multiplication
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewgsavage authored Oct 25, 2023
2 parents aeae5e5 + f9e139e commit d42da07
Show file tree
Hide file tree
Showing 24 changed files with 516 additions and 77 deletions.
1 change: 0 additions & 1 deletion .readthedocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,3 @@ python:
- requirements: requirements_docs.txt
- method: pip
path: .
system_packages: false
11 changes: 9 additions & 2 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,16 @@ Pint Changelog
-----------------

- Fixed Transformation type protocol.
(PR #1805)
(PR #1805, PR #1832)
- Documented to_preferred and created added an autoautoconvert_to_preferred registry option.
(PR #1803)
- Fixed bug causing operations between arrays of quantity scalars and quantity holding
array resulting in incorrect units.
(PR #1677)
- Optimize matplotlib unit conversion for Quantity arrays
(PR #1819)

- Add numpy.linalg.norm implementation.
(PR #1251)

0.22 (2023-05-25)
-----------------
Expand Down Expand Up @@ -108,6 +109,12 @@ Pint Changelog
(Issue #1030, #574)
- Added angular frequency documentation page.
- Move ASV benchmarks to dedicated folder. (Issue #1542)
- An ndim attribute has been added to Quantity and DataFrame has been added to upcast
types for pint-pandas compatibility. (#1596)
- Fix a recursion error that would be raised when passing quantities to `cond` and `x`.
(Issue #1510, #1530)
- Update test_non_int tests for pytest.
- Better support for uncertainties (See #1611, #1614)
- Implement `numpy.broadcast_arrays` (#1607)
- An ndim attribute has been added to Quantity and DataFrame has been added to upcast
types for pint-pandas compatibility. (#1596)
Expand Down
2 changes: 1 addition & 1 deletion docs/getting/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ The getting started guide aims to get you using pint productively as quickly as
Installation
------------

Pint has no dependencies except Python itself. In runs on Python 3.9+.
Pint has no dependencies except Python itself. It runs on Python 3.9+.

.. grid:: 2

Expand Down
2 changes: 1 addition & 1 deletion docs/user/angular_frequency.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ pint follows the conventions of SI. The SI BIPM Brochure (Bureau International d
Although it is formally correct to write all three of these units as the reciprocal second, the
use of the different names emphasizes the different nature of the quantities concerned. It is
especially important to carefully distinguish frequencies from angular frequencies, because
by definition their numerical values differ by a factor1 of 2π. Ignoring this fact may cause
by definition their numerical values differ by a factor of 2π. Ignoring this fact may cause
an error of 2π. Note that in some countries, frequency values are conventionally expressed
using “cycle/s” or “cps” instead of the SI unit Hz, although “cycle” and “cps” are not units
in the SI. Note also that it is common, although not recommended, to use the term
Expand Down
19 changes: 6 additions & 13 deletions downstream_status.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,9 @@ if you need a template.

Then, add your project badges to this file so it can be used as a Dashboard (always putting the stable first)

[Pint Downstream Demo](https://github.com/hgrecco/pint-downstream-demo)
[![CI](https://github.com/hgrecco/pint-downstream-demo/actions/workflows/ci.yml/badge.svg)](https://github.com/hgrecco/pint-downstream-demo/actions/workflows/ci.yml)
[![CI-pint-pre](https://github.com/hgrecco/pint-downstream-demo/actions/workflows/ci-pint-pre.yml/badge.svg)](https://github.com/hgrecco/pint-downstream-demo/actions/workflows/ci-pint-pre.yml)
[![CI-pint-master](https://github.com/hgrecco/pint-downstream-demo/actions/workflows/ci-pint-master.yml/badge.svg)](https://github.com/hgrecco/pint-downstream-demo/actions/workflows/ci-pint-master.yml)

[Pint Pandas](https://github.com/hgrecco/pint-pandas)
[![CI](https://github.com/hgrecco/pint-pandas/actions/workflows/ci.yml/badge.svg)](https://github.com/hgrecco/pint-pandas/actions/workflows/ci.yml)
[![CI-pint-pre](https://github.com/hgrecco/pint-pandas/actions/workflows/ci-pint-pre.yml/badge.svg)](https://github.com/hgrecco/pint-pandas/actions/workflows/ci-pint-pre.yml)
[![CI-pint-master](https://github.com/hgrecco/pint-pandas/actions/workflows/ci-pint-master.yml/badge.svg)](https://github.com/hgrecco/pint-pandas/actions/workflows/ci-pint-master.yml)

[MetPy](https://github.com/Unidata/MetPy)
[![CI](https://github.com/Unidata/MetPy/actions/workflows/tests-pypi.yml/badge.svg)](https://github.com/Unidata/MetPy/actions/workflows/tests-pypi.yml)
[![CI-pint-master](https://github.com/Unidata/MetPy/actions/workflows/nightly-builds.yml/badge.svg)](https://github.com/Unidata/MetPy/actions/workflows/nightly-builds.yml)
| Project | stable | pre-release | nightly |
| ----------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Pint Downstream Demo](https://github.com/hgrecco/pint-downstream-demo) | [![CI](https://github.com/hgrecco/pint-downstream-demo/actions/workflows/ci.yml/badge.svg)](https://github.com/hgrecco/pint-downstream-demo/actions/workflows/ci.yml) | [![CI-pint-pre](https://github.com/hgrecco/pint-downstream-demo/actions/workflows/ci-pint-pre.yml/badge.svg)](https://github.com/hgrecco/pint-downstream-demo/actions/workflows/ci-pint-pre.yml) | [![CI-pint-master](https://github.com/hgrecco/pint-downstream-demo/actions/workflows/ci-pint-master.yml/badge.svg)](https://github.com/hgrecco/pint-downstream-demo/actions/workflows/ci-pint-master.yml) |
| [Pint Pandas](https://github.com/hgrecco/pint-pandas) | [![CI](https://github.com/hgrecco/pint-pandas/actions/workflows/ci.yml/badge.svg)](https://github.com/hgrecco/pint-pandas/actions/workflows/ci.yml) | [![CI-pint-pre](https://github.com/hgrecco/pint-pandas/actions/workflows/ci-pint-pre.yml/badge.svg)](https://github.com/hgrecco/pint-pandas/actions/workflows/ci-pint-pre.yml) | [![CI-pint-master](https://github.com/hgrecco/pint-pandas/actions/workflows/ci-pint-master.yml/badge.svg)](https://github.com/hgrecco/pint-pandas/actions/workflows/ci-pint-master.yml) |
| [MetPy](https://github.com/Unidata/MetPy) | [![CI](https://github.com/Unidata/MetPy/actions/workflows/tests-pypi.yml/badge.svg)](https://github.com/Unidata/MetPy/actions/workflows/tests-pypi.yml) | | [![CI-pint-master](https://github.com/Unidata/MetPy/actions/workflows/nightly-builds.yml/badge.svg)](https://github.com/Unidata/MetPy/actions/workflows/nightly-builds.yml) |
| [pint-xarray](https://github.com/xarray-contrib/pint-xarray) | [![CI](https://github.com/xarray-contrib/pint-xarray/actions/workflows/ci.yml/badge.svg)](https://github.com/xarray-contrib/pint-xarray/actions/workflows/ci.yml) | | [![CI-pint-master](https://github.com/xarray-contrib/pint-xarray/actions/workflows/nightly.yml/badge.svg)](https://github.com/xarray-contrib/pint-xarray/actions/workflows/nightly.yml) |
64 changes: 36 additions & 28 deletions pint/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,21 @@

import sys
import math
import tokenize
from decimal import Decimal
from importlib import import_module
from io import BytesIO
from numbers import Number
from collections.abc import Mapping
from typing import Any, NoReturn, Callable, Optional, Union
from collections.abc import Generator, Iterable
from collections.abc import Iterable

try:
from uncertainties import UFloat, ufloat
from uncertainties import unumpy as unp

HAS_UNCERTAINTIES = True
except ImportError:
UFloat = ufloat = unp = None
HAS_UNCERTAINTIES = False


if sys.version_info >= (3, 10):
Expand Down Expand Up @@ -58,19 +65,6 @@ def _inner(*args: Any, **kwargs: Any) -> NoReturn:
return _inner


def tokenizer(input_string: str) -> Generator[tokenize.TokenInfo, None, None]:
"""Tokenize an input string, encoded as UTF-8
and skipping the ENCODING token.
See Also
--------
tokenize.tokenize
"""
for tokinfo in tokenize.tokenize(BytesIO(input_string.encode("utf-8")).readline):
if tokinfo.type != tokenize.ENCODING:
yield tokinfo


# TODO: remove this warning after v0.10
class BehaviorChangeWarning(UserWarning):
pass
Expand All @@ -83,7 +77,10 @@ class BehaviorChangeWarning(UserWarning):

HAS_NUMPY = True
NUMPY_VER = np.__version__
NUMERIC_TYPES = (Number, Decimal, ndarray, np.number)
if HAS_UNCERTAINTIES:
NUMERIC_TYPES = (Number, Decimal, ndarray, np.number, UFloat)
else:
NUMERIC_TYPES = (Number, Decimal, ndarray, np.number)

def _to_magnitude(value, force_ndarray=False, force_ndarray_like=False):
if isinstance(value, (dict, bool)) or value is None:
Expand All @@ -92,6 +89,11 @@ def _to_magnitude(value, force_ndarray=False, force_ndarray_like=False):
raise ValueError("Quantity magnitude cannot be an empty string.")
elif isinstance(value, (list, tuple)):
return np.asarray(value)
elif HAS_UNCERTAINTIES:
from pint.facets.measurement.objects import Measurement

if isinstance(value, Measurement):
return ufloat(value.value, value.error)
if force_ndarray or (
force_ndarray_like and not is_duck_array_type(type(value))
):
Expand Down Expand Up @@ -144,16 +146,13 @@ def _to_magnitude(value, force_ndarray=False, force_ndarray_like=False):
"lists and tuples are valid magnitudes for "
"Quantity only when NumPy is present."
)
return value
elif HAS_UNCERTAINTIES:
from pint.facets.measurement.objects import Measurement

if isinstance(value, Measurement):
return ufloat(value.value, value.error)
return value

try:
from uncertainties import ufloat

HAS_UNCERTAINTIES = True
except ImportError:
ufloat = None
HAS_UNCERTAINTIES = False

try:
from babel import Locale
Expand Down Expand Up @@ -329,16 +328,25 @@ def isnan(obj: Any, check_all: bool) -> Union[bool, Iterable[bool]]:
Always return False for non-numeric types.
"""
if is_duck_array_type(type(obj)):
if obj.dtype.kind in "if":
if obj.dtype.kind in "ifc":
out = np.isnan(obj)
elif obj.dtype.kind in "Mm":
out = np.isnat(obj)
else:
# Not a numeric or datetime type
out = np.full(obj.shape, False)
if HAS_UNCERTAINTIES:
try:
out = unp.isnan(obj)
except TypeError:
# Not a numeric or UFloat type
out = np.full(obj.shape, False)
else:
# Not a numeric or datetime type
out = np.full(obj.shape, False)
return out.any() if check_all else out
if isinstance(obj, np_datetime64):
return np.isnat(obj)
elif HAS_UNCERTAINTIES and isinstance(obj, UFloat):
return unp.isnan(obj)
try:
return math.isnan(obj)
except TypeError:
Expand Down
2 changes: 1 addition & 1 deletion pint/facets/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
keeping each part small enough to be hackable.
Each facet contains one or more of the following modules:
- definitions: classes describing an specific unit related definiton.
- definitions: classes describing specific unit-related definitons.
These objects must be immutable, pickable and not reference the registry (e.g. ContextDefinition)
- objects: classes and functions that encapsulate behavior (e.g. Context)
- registry: implements a subclass of PlainRegistry or class that can be
Expand Down
4 changes: 2 additions & 2 deletions pint/facets/context/objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@

class Transformation(Protocol):
def __call__(
self, ureg: UnitRegistry, value: Magnitude, **kwargs: Any
) -> Magnitude:
self, ureg: UnitRegistry, value: PlainQuantity, **kwargs: Any
) -> PlainQuantity:
...


Expand Down
19 changes: 10 additions & 9 deletions pint/facets/measurement/objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ class Measurement(PlainQuantity):
"""

def __new__(cls, value, error, units=MISSING):
def __new__(cls, value, error=MISSING, units=MISSING):
if units is MISSING:
try:
value, units = value.magnitude, value.units
Expand All @@ -64,17 +64,18 @@ def __new__(cls, value, error, units=MISSING):
error = MISSING # used for check below
else:
units = ""
try:
error = error.to(units).magnitude
except AttributeError:
pass

if error is MISSING:
# We've already extracted the units from the Quantity above
mag = value
elif error < 0:
raise ValueError("The magnitude of the error cannot be negative")
else:
mag = ufloat(value, error)
try:
error = error.to(units).magnitude
except AttributeError:
pass
if error < 0:
raise ValueError("The magnitude of the error cannot be negative")
else:
mag = ufloat(value, error)

inst = super().__new__(cls, mag, units)
return inst
Expand Down
10 changes: 9 additions & 1 deletion pint/facets/numpy/numpy_func.py
Original file line number Diff line number Diff line change
Expand Up @@ -1015,7 +1015,15 @@ def implementation(a, *args, **kwargs):
implement_func("function", func_str, input_units=None, output_unit=None)

# Handle functions with output unit defined by operation
for func_str in ("std", "nanstd", "sum", "nansum", "cumsum", "nancumsum"):
for func_str in (
"std",
"nanstd",
"sum",
"nansum",
"cumsum",
"nancumsum",
"linalg.norm",
):
implement_func("function", func_str, input_units=None, output_unit="sum")
for func_str in ("diff", "ediff1d"):
implement_func("function", func_str, input_units=None, output_unit="delta")
Expand Down
15 changes: 15 additions & 0 deletions pint/facets/numpy/quantity.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,16 @@
set_units_ufuncs,
)

try:
import uncertainties.unumpy as unp
from uncertainties import ufloat, UFloat

HAS_UNCERTAINTIES = True
except ImportError:
unp = np
ufloat = Ufloat = None
HAS_UNCERTAINTIES = False


def method_wraps(numpy_func):
if isinstance(numpy_func, str):
Expand Down Expand Up @@ -224,6 +234,11 @@ def __getattr__(self, item) -> Any:
)
else:
raise exc
elif (
HAS_UNCERTAINTIES and item == "ndim" and isinstance(self._magnitude, UFloat)
):
# Dimensionality of a single UFloat is 0, like any other scalar
return 0

try:
return getattr(self._magnitude, item)
Expand Down
23 changes: 22 additions & 1 deletion pint/facets/plain/quantity.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,17 @@
if HAS_NUMPY:
import numpy as np # noqa

try:
import uncertainties.unumpy as unp
from uncertainties import ufloat, UFloat

HAS_UNCERTAINTIES = True
except ImportError:
unp = np
ufloat = Ufloat = None
HAS_UNCERTAINTIES = False


MagnitudeT = TypeVar("MagnitudeT", bound=Magnitude)
ScalarT = TypeVar("ScalarT", bound=Scalar)

Expand Down Expand Up @@ -133,6 +144,8 @@ class PlainQuantity(Generic[MagnitudeT], PrettyIPython, SharedRegistryObject):
def ndim(self) -> int:
if isinstance(self.magnitude, numbers.Number):
return 0
if str(self.magnitude) == "<NA>":
return 0
return self.magnitude.ndim

@property
Expand Down Expand Up @@ -256,7 +269,12 @@ def __bytes__(self) -> bytes:
return str(self).encode(locale.getpreferredencoding())

def __repr__(self) -> str:
if isinstance(self._magnitude, float):
if HAS_UNCERTAINTIES:
if isinstance(self._magnitude, UFloat):
return f"<Quantity({self._magnitude:.6}, '{self._units}')>"
else:
return f"<Quantity({self._magnitude}, '{self._units}')>"
elif isinstance(self._magnitude, float):
return f"<Quantity({self._magnitude:.9}, '{self._units}')>"

return f"<Quantity({self._magnitude}, '{self._units}')>"
Expand Down Expand Up @@ -1288,6 +1306,9 @@ def bool_result(value):
# We compare to the plain class of PlainQuantity because
# each PlainQuantity class is unique.
if not isinstance(other, PlainQuantity):
if other is None:
# A loop in pandas-dev/pandas/core/common.py(86)consensus_name_attr() can result in OTHER being None
return bool_result(False)
if zero_or_nan(other, True):
# Handle the special case in which we compare to zero or NaN
# (or an array of zeros or NaNs)
Expand Down
5 changes: 3 additions & 2 deletions pint/facets/plain/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,9 @@
Handler,
)

from ... import pint_eval
from ..._vendor import appdirs
from ...compat import babel_parse, tokenizer, TypeAlias, Self
from ...compat import babel_parse, TypeAlias, Self
from ...errors import DimensionalityError, RedefinitionError, UndefinedUnitError
from ...pint_eval import build_eval_tree
from ...util import ParserHelper
Expand Down Expand Up @@ -1324,7 +1325,7 @@ def parse_expression(
for p in self.preprocessors:
input_string = p(input_string)
input_string = string_preprocessor(input_string)
gen = tokenizer(input_string)
gen = pint_eval.tokenizer(input_string)

def _define_op(s: str):
return self._eval_token(s, case_sensitive=case_sensitive, **values)
Expand Down
10 changes: 7 additions & 3 deletions pint/formatting.py
Original file line number Diff line number Diff line change
Expand Up @@ -375,9 +375,13 @@ def formatter(
# Don't remove this positional! This is the format used in Babel
key = pat.replace("{0}", "").strip()
break
division_fmt = compound_unit_patterns.get("per", {}).get(
babel_length, division_fmt
)

tmp = compound_unit_patterns.get("per", {}).get(babel_length, division_fmt)

try:
division_fmt = tmp.get("compound", division_fmt)
except AttributeError:
division_fmt = tmp
power_fmt = "{}{}"
exp_call = _pretty_fmt_exponent
if value == 1:
Expand Down
Loading

0 comments on commit d42da07

Please sign in to comment.