summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.coveragerc2
-rw-r--r--.github/pull_request_template.md (renamed from pull_request_template.md)0
-rw-r--r--.github/workflows/ci.yml15
-rw-r--r--.github/workflows/docs.yml1
-rw-r--r--.github/workflows/lint.yml2
-rw-r--r--.pre-commit-config.yaml11
-rw-r--r--AUTHORS1
-rw-r--r--CHANGES15
-rw-r--r--README.rst6
-rw-r--r--docs/_templates/sidebarintro.html2
-rw-r--r--docs/_themes/flask/static/flasky.css_t50
-rw-r--r--docs/_themes/flask/theme.conf2
-rw-r--r--docs/defining-quantities.rst14
-rw-r--r--docs/getting.rst2
-rw-r--r--docs/index.rst2
-rw-r--r--docs/numpy.ipynb2
-rw-r--r--docs/performance.rst12
-rw-r--r--pint/__init__.py9
-rw-r--r--pint/default_en.txt4
-rw-r--r--pint/measurement.py3
-rw-r--r--pint/numpy_func.py65
-rw-r--r--pint/quantity.py69
-rw-r--r--pint/registry.py14
-rw-r--r--pint/registry_helpers.py10
-rw-r--r--pint/testsuite/test_issues.py11
-rw-r--r--pint/testsuite/test_measurement.py22
-rw-r--r--pint/testsuite/test_numpy.py10
-rw-r--r--pint/testsuite/test_quantity.py28
-rw-r--r--setup.cfg11
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
-
diff --git a/AUTHORS b/AUTHORS
index 8a5b5ad..e74dc67 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -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>
diff --git a/CHANGES b/CHANGES
index 6446c13..8d729ed 100644
--- a/CHANGES
+++ b/CHANGES
@@ -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)
diff --git a/README.rst b/README.rst
index e43930f..86c8f77 100644
--- a/README.rst
+++ b/README.rst
@@ -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 &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")
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):
diff --git a/setup.cfg b/setup.cfg
index e8a4193..939fe09 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -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