diff options
29 files changed, 254 insertions, 141 deletions
diff --git a/.coveragerc b/.coveragerc index fbb079e..cd4d3d2 100644 --- a/.coveragerc +++ b/.coveragerc @@ -16,4 +16,4 @@ exclude_lines = AbstractMethodError # Don't complain if non-runnable code isn't run: - if TYPE_CHECKING:
\ No newline at end of file + if TYPE_CHECKING: diff --git a/pull_request_template.md b/.github/pull_request_template.md index 8ee5e75..8ee5e75 100644 --- a/pull_request_template.md +++ b/.github/pull_request_template.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e537403..4f0be8f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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: @@ -55,7 +52,7 @@ jobs: - name: Install numpy if: ${{ matrix.numpy != null }} run: pip install "${{matrix.numpy}}" - + - name: Install uncertainties if: ${{ matrix.uncertainties != null }} run: pip install "${{matrix.uncertainties}}" diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 726a7de..2340683 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -43,4 +43,3 @@ jobs: - name: Doc Tests run: sphinx-build -a -j auto -b doctest -d build/doctrees docs build/doctest - diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index b10a674..e2d2638 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -14,4 +14,4 @@ jobs: - name: Lint uses: pre-commit/action@v2.0.0 with: - extra_args: --all-files --show-diff-on-failure
\ No newline at end of file + extra_args: --all-files --show-diff-on-failure diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4b183a3..9a842e3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,14 +1,19 @@ repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.1.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml - repo: https://github.com/psf/black - rev: 21.9b0 + rev: 21.12b0 hooks: - id: black - repo: https://github.com/pycqa/isort - rev: 5.9.3 + rev: 5.10.1 hooks: - id: isort - repo: https://gitlab.com/pycqa/flake8 rev: 3.9.2 hooks: - id: flake8 - @@ -45,6 +45,7 @@ Other contributors, listed alphabetically, are: * Ryan Dwyer <ryanpdwyer@gmail.com> * Ryan Kingsbury <RyanSKingsbury@alumni.unc.edu> * Ryan May +* Sebastian Kosmeier * Sigvald Marholm <sigvald@marebakken.com> * Sundar Raman <cybertoast@gmail.com> * Tiago Coutinho <coutinho@esrf.fr> @@ -4,11 +4,24 @@ 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 - Add a example for `register_unit_format` to the formatting docs. - 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 + +- Change minimal Python version support to 3.8+ +- Change minimal Numpy version support to 1.19+ 0.18 (2021-10-26) ----------------- @@ -71,7 +84,7 @@ Pint Changelog - Fix tolist function with scalar ndarray. (Issue #1195, thanks jules-ch) - Corrected typos and dacstrings -- Implements a first benchmark suite in airspeed velocity (asv). +- Implements a first benchmark suite in airspeed velocity (asv). - Power for pseudo-dimensionless units. (Issue #1185, thanks Kevin Fuhr) @@ -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 @@ -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: diff --git a/docs/_templates/sidebarintro.html b/docs/_templates/sidebarintro.html index ff0666d..8e382a8 100644 --- a/docs/_templates/sidebarintro.html +++ b/docs/_templates/sidebarintro.html @@ -16,5 +16,3 @@ You are currently looking at the documentation of version {{ version }}. <li><a href="https://github.com/hgrecco/pint">Code in GitHub</a></li> <li><a href="https://github.com/hgrecco/pint/issues">Issue Tracker</a></li> </ul> - - diff --git a/docs/_themes/flask/static/flasky.css_t b/docs/_themes/flask/static/flasky.css_t index b5ca39b..4f78308 100644 --- a/docs/_themes/flask/static/flasky.css_t +++ b/docs/_themes/flask/static/flasky.css_t @@ -8,11 +8,11 @@ {% set page_width = '940px' %} {% set sidebar_width = '220px' %} - + @import url("basic.css"); - + /* -- page layout ----------------------------------------------------------- */ - + body { font-family: 'Georgia', serif; font-size: 17px; @@ -43,7 +43,7 @@ div.sphinxsidebar { hr { border: 1px solid #B1B4B6; } - + div.body { background-color: #ffffff; color: #3E4349; @@ -54,7 +54,7 @@ img.floatingflask { padding: 0 0 10px 10px; float: right; } - + div.footer { width: {{ page_width }}; margin: 20px auto 30px auto; @@ -70,7 +70,7 @@ div.footer a { div.related { display: none; } - + div.sphinxsidebar a { color: #444; text-decoration: none; @@ -80,7 +80,7 @@ div.sphinxsidebar a { div.sphinxsidebar a:hover { border-bottom: 1px solid #999; } - + div.sphinxsidebar { font-size: 14px; line-height: 1.5; @@ -95,7 +95,7 @@ div.sphinxsidebarwrapper p.logo { margin: 0; text-align: center; } - + div.sphinxsidebar h3, div.sphinxsidebar h4 { font-family: 'Garamond', 'Georgia', serif; @@ -109,7 +109,7 @@ div.sphinxsidebar h4 { div.sphinxsidebar h4 { font-size: 20px; } - + div.sphinxsidebar h3 a { color: #444; } @@ -120,7 +120,7 @@ div.sphinxsidebar p.logo a:hover, div.sphinxsidebar h3 a:hover { border: none; } - + div.sphinxsidebar p { color: #555; margin: 10px 0; @@ -131,25 +131,25 @@ div.sphinxsidebar ul { padding: 0; color: #000; } - + div.sphinxsidebar input { border: 1px solid #ccc; font-family: 'Georgia', serif; font-size: 1em; } - + /* -- body styles ----------------------------------------------------------- */ - + a { color: #004B6B; text-decoration: underline; } - + a:hover { color: #6D4100; text-decoration: underline; } - + div.body h1, div.body h2, div.body h3, @@ -169,25 +169,25 @@ div.indexwrapper h1 { height: {{ theme_index_logo_height }}; } {% endif %} - + div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; } div.body h2 { font-size: 180%; } div.body h3 { font-size: 150%; } div.body h4 { font-size: 130%; } div.body h5 { font-size: 100%; } div.body h6 { font-size: 100%; } - + a.headerlink { color: #ddd; padding: 0 4px; text-decoration: none; } - + a.headerlink:hover { color: #444; background: #eaeaea; } - + div.body p, div.body dd, div.body li { line-height: 1.4em; } @@ -234,20 +234,20 @@ div.note { background-color: #eee; border: 1px solid #ccc; } - + div.seealso { background-color: #ffc; border: 1px solid #ff6; } - + div.topic { background-color: #eee; } - + p.admonition-title { display: inline; } - + p.admonition-title:after { content: ":"; } @@ -341,7 +341,7 @@ ul, ol { margin: 10px 0 10px 30px; padding: 0; } - + pre { background: #eee; padding: 7px 30px; @@ -358,7 +358,7 @@ dl dl pre { margin-left: -90px; padding-left: 90px; } - + tt { background-color: #ecf0f3; color: #222; diff --git a/docs/_themes/flask/theme.conf b/docs/_themes/flask/theme.conf index 420c609..0b3d313 100644 --- a/docs/_themes/flask/theme.conf +++ b/docs/_themes/flask/theme.conf @@ -6,5 +6,5 @@ pygments_style = flask_theme_support.FlaskyStyle [options] index_logo = '' index_logo_height = 120px -touch_icon = +touch_icon = github_fork = hgrecco/pint diff --git a/docs/defining-quantities.rst b/docs/defining-quantities.rst index 7ab7157..845c81c 100644 --- a/docs/defining-quantities.rst +++ b/docs/defining-quantities.rst @@ -144,6 +144,20 @@ brackets to get the expected result: >>> Q_('3 l / (100 km)') <Quantity(0.03, 'liter / kilometer')> +Special strings for NaN (Not a Number) and inf(inity) are also handled in a case-insensitive fashion. +Note that, as usual, NaN != NaN. + +.. doctest:: + + >>> Q_('inf m') + <Quantity(inf, 'meter')> + >>> Q_('-INFINITY m') + <Quantity(-inf, 'meter')> + >>> Q_('nan m') + <Quantity(nan, 'meter')> + >>> Q_('NaN m') + <Quantity(nan, 'meter')> + .. note:: Since version 0.7, Pint **does not** use eval_ under the hood. This change removes the `serious security problems`_ that the system is exposed to when parsing information from untrusted sources. diff --git a/docs/getting.rst b/docs/getting.rst index c6f35cc..0afcea3 100644 --- a/docs/getting.rst +++ b/docs/getting.rst @@ -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_:: diff --git a/docs/index.rst b/docs/index.rst index 108b43e..806c0ff 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -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: diff --git a/docs/numpy.ipynb b/docs/numpy.ipynb index c221b53..34cbef4 100644 --- a/docs/numpy.ipynb +++ b/docs/numpy.ipynb @@ -503,4 +503,4 @@ }, "nbformat": 4, "nbformat_minor": 4 -}
\ No newline at end of file +} diff --git a/docs/performance.rst b/docs/performance.rst index 74a11d8..d2d3e64 100644 --- a/docs/performance.rst +++ b/docs/performance.rst @@ -37,23 +37,23 @@ This is especially important when using pint Quantities in conjunction with an i In [2]: def foobar_with_quantity(x): # find the value of x that equals q2 - + # assign x the same units as q2 qx = ureg(str(x)+str(q2.units)) - + # compare the two quantities, then take their magnitude because # brentq requires a dimensionless return type return (qx - q2).magnitude - + In [3]: def foobar_with_magnitude(x): # find the value of x that equals q2 - + # don't bother converting x to a quantity, just compare it with q2's magnitude return x - q2.magnitude - + In [4]: %timeit brentq(foobar_with_quantity,0,q2.magnitude) 1000 loops, best of 3: 310 µs per loop - + In [5]: %timeit brentq(foobar_with_magnitude,0,q2.magnitude) 1000000 loops, best of 3: 1.63 µs per loop diff --git a/pint/__init__.py b/pint/__init__.py index 1ddc7e6..5ab92f0 100644 --- a/pint/__init__.py +++ b/pint/__init__.py @@ -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, @@ -29,12 +31,6 @@ from .registry import ApplicationRegistry, LazyRegistry, UnitRegistry 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 @@ -138,6 +134,7 @@ __all__ = ( "UnitRegistry", "PintError", "DefinitionSyntaxError", + "LogarithmicUnitCalculusError", "DimensionalityError", "OffsetUnitCalculusError", "RedefinitionError", diff --git a/pint/default_en.txt b/pint/default_en.txt index b63d9fe..164ff2d 100644 --- a/pint/default_en.txt +++ b/pint/default_en.txt @@ -475,9 +475,9 @@ bohr_magneton = e * hbar / (2 * m_e) = µ_B = mu_B nuclear_magneton = e * hbar / (2 * m_p) = µ_N = mu_N # Logaritmic Unit Definition -# Unit = scale; logbase; logfactor +# Unit = scale; logbase; logfactor # x_dB = [logfactor] * log( x_lin / [scale] ) / log( [logbase] ) - + # Logaritmic Units of dimensionless quantity: [ https://en.wikipedia.org/wiki/Level_(logarithmic_quantity) ] decibelmilliwatt = 1e-3 watt; logbase: 10; logfactor: 10 = dBm diff --git a/pint/measurement.py b/pint/measurement.py index 1c5292b..01262eb 100644 --- a/pint/measurement.py +++ b/pint/measurement.py @@ -83,6 +83,9 @@ class Measurement(Quantity): 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 diff --git a/pint/numpy_func.py b/pint/numpy_func.py index 38aab1a..5c48e5a 100644 --- a/pint/numpy_func.py +++ b/pint/numpy_func.py @@ -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 diff --git a/pint/quantity.py b/pint/quantity.py index f11c790..1305a56 100644 --- a/pint/quantity.py +++ b/pint/quantity.py @@ -9,7 +9,6 @@ from __future__ import annotations import bisect -import contextlib import copy import datetime import functools @@ -168,20 +167,6 @@ def check_dask_array(f): 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") @@ -413,7 +398,9 @@ class Quantity(PrettyIPython, SharedRegistryObject, Generic[_MagnitudeType]): 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>") @@ -440,7 +427,7 @@ class Quantity(PrettyIPython, SharedRegistryObject, Generic[_MagnitudeType]): 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", "") @@ -763,6 +750,23 @@ class Quantity(PrettyIPython, SharedRegistryObject, Generic[_MagnitudeType]): return self.__class__(magnitude, other) + def _get_reduced_units(self, units): + # loop through individual units and compare to each other unit + # can we do better than a nested loop here? + for unit1, exp in units.items(): + # make sure it wasn't already reduced to zero exponent on prior pass + 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: + units = units.add(unit2, exp / power).remove([unit1]) + break + return units + def ito_reduced_units(self) -> None: """Return Quantity scaled in place to reduced units, i.e. one unit per dimension. This will not reduce compound units (e.g., 'J/kg' will not @@ -775,21 +779,10 @@ class Quantity(PrettyIPython, SharedRegistryObject, Generic[_MagnitudeType]): if len(self._units) == 1: return None - newunits = self._units.copy() - # loop through individual units and compare to each other unit - # can we do better than a nested loop here? - for unit1, exp in self._units.items(): - # make sure it wasn't already reduced to zero exponent on prior pass - if unit1 not in newunits: - continue - for unit2 in newunits: - if unit1 != unit2: - power = self._REGISTRY._get_dimensionality_ratio(unit1, unit2) - if power: - newunits = newunits.add(unit2, exp / power).remove([unit1]) - break + units = self._units.copy() + new_units = self._get_reduced_units(units) - return self.ito(newunits) + return self.ito(new_units) def to_reduced_units(self) -> Quantity[_MagnitudeType]: """Return Quantity scaled in place to reduced units, i.e. one unit per @@ -797,10 +790,16 @@ class Quantity(PrettyIPython, SharedRegistryObject, Generic[_MagnitudeType]): can it make use of contexts at this time. """ - # can we make this more efficient? - newq = copy.copy(self) - newq.ito_reduced_units() - return newq + # shortcuts in case we're dimensionless or only a single unit + if self.dimensionless: + return self.to({}) + if len(self._units) == 1: + return self + + units = self._units.copy() + new_units = self._get_reduced_units(units) + + return self.to(new_units) def to_compact(self, unit=None) -> Quantity[_MagnitudeType]: """ "Return Quantity rescaled to compact, human-readable units. diff --git a/pint/registry.py b/pint/registry.py index 6bf4bb0..77f167d 100644 --- a/pint/registry.py +++ b/pint/registry.py @@ -71,7 +71,7 @@ from . import registry_helpers, systems 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, @@ -1218,6 +1218,10 @@ class BaseRegistry(metaclass=RegistryMeta): if token_type == NAME: if token_text == "dimensionless": return 1 * self.dimensionless + elif token_text.lower() in ("inf", "infinity"): + return float("inf") + elif token_text.lower() == "nan": + return float("nan") elif token_text in values: return self.Quantity(values[token_text]) else: @@ -1459,10 +1463,10 @@ class NonMultiplicativeRegistry(BaseRegistry): 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 @@ -1530,13 +1534,13 @@ class NonMultiplicativeRegistry(BaseRegistry): 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) diff --git a/pint/registry_helpers.py b/pint/registry_helpers.py index 7f6ee7f..026edf9 100644 --- a/pint/registry_helpers.py +++ b/pint/registry_helpers.py @@ -186,8 +186,8 @@ def _apply_defaults(func, args, kwargs): def wraps( ureg: "UnitRegistry", - ret: Union[str, "Unit", Iterable[str], Iterable["Unit"], None], - args: Union[str, "Unit", Iterable[str], Iterable["Unit"], None], + ret: Union[str, "Unit", Iterable[Union[str, "Unit", None]], None], + args: Union[str, "Unit", Iterable[Union[str, "Unit", None]], None], strict: bool = True, ) -> Callable[[Callable[..., T]], Callable[..., Quantity[T]]]: """Wraps a function to become pint-aware. @@ -204,9 +204,9 @@ def wraps( ---------- ureg : pint.UnitRegistry a UnitRegistry instance. - ret : str, pint.Unit, iterable of str, or iterable of pint.Unit + ret : str, pint.Unit, or iterable of str or pint.Unit Units of each of the return values. Use `None` to skip argument conversion. - args : str, pint.Unit, iterable of str, or iterable of pint.Unit + args : str, pint.Unit, or iterable of str or pint.Unit Units of each of the input arguments. Use `None` to skip argument conversion. strict : bool Indicates that only quantities are accepted. (Default value = True) @@ -220,7 +220,7 @@ def wraps( ------ TypeError if the number of given arguments does not match the number of function parameters. - if the any of the provided arguments is not a unit a string or Quantity + if any of the provided arguments is not a unit a string or Quantity """ diff --git a/pint/testsuite/test_issues.py b/pint/testsuite/test_issues.py index ed781b7..55723c3 100644 --- a/pint/testsuite/test_issues.py +++ b/pint/testsuite/test_issues.py @@ -717,6 +717,17 @@ class TestIssues(QuantityTestCase): 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 diff --git a/pint/testsuite/test_measurement.py b/pint/testsuite/test_measurement.py index 323140f..61427cd 100644 --- a/pint/testsuite/test_measurement.py +++ b/pint/testsuite/test_measurement.py @@ -156,6 +156,28 @@ class TestMeasurement(QuantityTestCase): 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 ± 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 ± 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") diff --git a/pint/testsuite/test_numpy.py b/pint/testsuite/test_numpy.py index ce337b2..5e9915b 100644 --- a/pint/testsuite/test_numpy.py +++ b/pint/testsuite/test_numpy.py @@ -329,6 +329,16 @@ class TestNumpyMathematicalFunctions(TestNumpyMethods): 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) diff --git a/pint/testsuite/test_quantity.py b/pint/testsuite/test_quantity.py index ddb242d..998a53d 100644 --- a/pint/testsuite/test_quantity.py +++ b/pint/testsuite/test_quantity.py @@ -617,6 +617,34 @@ class TestQuantity(QuantityTestCase): with self.ureg.context("sp"): assert a.is_compatible_with(b) + @pytest.mark.parametrize(["inf_str"], [("inf",), ("-infinity",), ("INFINITY",)]) + @pytest.mark.parametrize(["has_unit"], [(True,), (False,)]) + def test_infinity(self, inf_str, has_unit): + inf = float(inf_str) + ref = self.Q_(inf, "meter" if has_unit else None) + test = self.Q_(inf_str + (" meter" if has_unit else "")) + assert ref == test + + @pytest.mark.parametrize(["nan_str"], [("nan",), ("NAN",)]) + @pytest.mark.parametrize(["has_unit"], [(True,), (False,)]) + def test_nan(self, nan_str, has_unit): + nan = float(nan_str) + ref = self.Q_(nan, " meter" if has_unit else None) + test = self.Q_(nan_str + (" meter" if has_unit else "")) + assert ref.units == test.units + assert math.isnan(test.magnitude) + assert ref != test + + @helpers.requires_numpy + def test_to_reduced_units(self): + q = self.Q_([3, 4], "s * ms") + helpers.assert_quantity_equal( + 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): @@ -18,7 +18,6 @@ classifiers = Programming Language :: Python Topic :: Scientific/Engineering Topic :: Software Development :: Libraries - Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 @@ -27,17 +26,13 @@ classifiers = packages = pint zip_safe = True include_package_data = True -python_requires = >=3.7 -install_requires = - packaging - importlib-metadata; python_version < '3.8' +python_requires = >=3.8 setup_requires = setuptools; setuptools_scm -test_suite = pint.testsuite.testsuite scripts = pint/pint-convert [options.extras_require] -numpy = numpy >= 1.17 -uncertainties = uncertainties >= 3.1.4 +numpy = numpy >= 1.19.5 +uncertainties = uncertainties >= 3.1.6 test = pytest pytest-mpl |
