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 committed May 12, 2024
2 parents da0058d + cb0ec94 commit 02f0209
Show file tree
Hide file tree
Showing 18 changed files with 103 additions and 27 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12"]
numpy: [null, "numpy>=1.23,<2.0.0"]
numpy: [null, "numpy>=1.23,<2.0.0", "numpy>=2.0.0rc1"]
uncertainties: [null, "uncertainties==3.1.6", "uncertainties>=3.1.6,<4.0.0"]
extras: [null]
include:
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ MANIFEST
.mypy_cache
pip-wheel-metadata
pint/testsuite/dask-worker-space
venv
.envrc

# WebDAV file system cache files
.DAV/
Expand Down
6 changes: 6 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,18 @@ Pint Changelog
0.24 (unreleased)
-----------------

- NumPy 2.0 support
(PR #1985, #1971)
- Implement numpy roll (Related to issue #981)
- Add `dim_sort` function to _formatter_helpers.
- Add `dim_order` and `default_sort_func` properties to FullFormatter.
(PR #1926, fixes Issue #1841)
- Fixed bug causing operations between arrays of quantity scalars and quantity holding
array resulting in incorrect units.
(PR #1677)
- Fix LaTeX siuntix formatting when using non_int_type=decimal.Decimal.
- Fix converting to offset units of higher dimension e.g. gauge pressure
(PR #1949)

0.23 (2023-12-08)
-----------------
Expand Down
6 changes: 4 additions & 2 deletions docs/advanced/pitheorem.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,10 @@ Which can be pretty printed using the `Pint` formatter:

>>> from pint import formatter
>>> result = pi_theorem({'V': '[length]/[time]', 'T': '[time]', 'L': '[length]'})
>>> print(formatter(result[0].items()))
T * V / L
>>> numerator = [item for item in result[0].items() if item[1]>0]
>>> denominator = [item for item in result[0].items() if item[1]<0]
>>> print(formatter(numerator, denominator))
V * T / L

You can also apply the Buckingham π theorem associated to a Registry. In this case,
you can use derived dimensions such as speed:
Expand Down
2 changes: 1 addition & 1 deletion docs/api/facets.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ The default UnitRegistry inherits from all of them.
:members:
:exclude-members: Quantity, Unit, Measurement, Group, Context, System

.. automodule:: pint.facets.formatting
.. automodule:: pint.delegates.formatter
:members:
:exclude-members: Quantity, Unit, Measurement, Group, Context, System

Expand Down
8 changes: 4 additions & 4 deletions docs/getting/tutorial.rst
Original file line number Diff line number Diff line change
Expand Up @@ -428,7 +428,7 @@ If Babel_ is installed you can translate unit names to any language
.. doctest::

>>> ureg.formatter.format_quantity(accel, locale='fr_FR')
'1,3 mètres/secondes²'
'1,3 mètres par seconde²'

You can also specify the format locale at the registry level either at creation:

Expand All @@ -449,11 +449,11 @@ and by doing that, string formatting is now localized:
>>> ureg.default_format = 'P'
>>> accel = 1.3 * ureg.parse_units('meter/second**2')
>>> str(accel)
'1,3 mètres/secondes²'
'1,3 mètres par seconde²'
>>> "%s" % accel
'1,3 mètres/secondes²'
'1,3 mètres par seconde²'
>>> "{}".format(accel)
'1,3 mètres/secondes²'
'1,3 mètres par seconde²'

If you want to customize string formatting, take a look at :ref:`formatting`.

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 @@ -2,7 +2,7 @@


Angles and Angular Frequency
=================
=============================

Angles
------
Expand Down
2 changes: 1 addition & 1 deletion docs/user/defining-quantities.rst
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ For example, the units of
.. doctest::

>>> Q_('3 l / 100 km')
<Quantity(0.03, 'kilometer * liter')>
<Quantity(0.03, 'liter * kilometer')>

may be unexpected at first but, are a consequence of applying this rule. Use
brackets to get the expected result:
Expand Down
5 changes: 3 additions & 2 deletions docs/user/formatting.rst
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ formats:
... def format_unit_simple(unit, registry, **options):
... return " * ".join(f"{u} ** {p}" for u, p in unit.items())
>>> f"{q:Z}"
'2.3e-06 meter ** 3 * second ** -2 * kilogram ** -1'
'2.3e-06 kilogram ** -1 * meter ** 3 * second ** -2'

where ``unit`` is a :py:class:`dict` subclass containing the unit names and
their exponents.
Expand All @@ -111,10 +111,11 @@ following methods: `format_magnitude`, `format_unit`, `format_quantity`, `format
...
... default_format = ""
...
... def format_unit(self, unit, uspec: str = "", **babel_kwds) -> str:
... def format_unit(self, unit, uspec, sort_func, **babel_kwds) -> str:
... return "ups!"
...
>>> ureg.formatter = MyFormatter()
>>> ureg.formatter._registry = ureg
>>> str(q)
'2.3e-06 ups!'

Expand Down
4 changes: 2 additions & 2 deletions pint/delegates/formatter/latex.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,8 @@ def siunitx_format_unit(
) -> str:
"""Returns LaTeX code for the unit that can be put into an siunitx command."""

def _tothe(power: int | float) -> str:
if isinstance(power, int) or (isinstance(power, float) and power.is_integer()):
def _tothe(power) -> str:
if power == int(power):
if power == 1:
return ""
elif power == 2:
Expand Down
7 changes: 6 additions & 1 deletion pint/facets/nonmultiplicative/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ def _add_ref_of_log_or_offset_unit(
self, offset_unit: str, all_units: UnitsContainer
) -> UnitsContainer:
slct_unit = self._units[offset_unit]
if slct_unit.is_logarithmic or (not slct_unit.is_multiplicative):
if slct_unit.is_logarithmic:
# Extract reference unit
slct_ref = slct_unit.reference

Expand All @@ -204,6 +204,11 @@ def _add_ref_of_log_or_offset_unit(
(u, e) = [(u, e) for u, e in slct_ref.items()].pop()
# Add it back to the unit list
return all_units.add(u, e)

if not slct_unit.is_multiplicative: # is offset unit
# Extract reference unit
return slct_unit.reference

# Otherwise, return the units unmodified
return all_units

Expand Down
9 changes: 7 additions & 2 deletions pint/facets/numpy/numpy_func.py
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,7 @@ def implementation(*args, **kwargs):
"take",
"trace",
"transpose",
"roll",
"ceil",
"floor",
"hypot",
Expand Down Expand Up @@ -751,22 +752,25 @@ def _base_unit_if_needed(a):
raise OffsetUnitCalculusError(a.units)


# NP2 Can remove trapz wrapping when we only support numpy>=2
@implements("trapz", "function")
@implements("trapezoid", "function")
def _trapz(y, x=None, dx=1.0, **kwargs):
trapezoid = np.trapezoid if hasattr(np, "trapezoid") else np.trapz
y = _base_unit_if_needed(y)
units = y.units
if x is not None:
if hasattr(x, "units"):
x = _base_unit_if_needed(x)
units *= x.units
x = x._magnitude
ret = np.trapz(y._magnitude, x, **kwargs)
ret = trapezoid(y._magnitude, x, **kwargs)
else:
if hasattr(dx, "units"):
dx = _base_unit_if_needed(dx)
units *= dx.units
dx = dx._magnitude
ret = np.trapz(y._magnitude, dx=dx, **kwargs)
ret = trapezoid(y._magnitude, dx=dx, **kwargs)

return y.units._REGISTRY.Quantity(ret, units)

Expand Down Expand Up @@ -861,6 +865,7 @@ def implementation(*args, **kwargs):
("median", "a", True),
("nanmedian", "a", True),
("transpose", "a", True),
("roll", "a", True),
("copy", "a", True),
("average", "a", True),
("nanmean", "a", True),
Expand Down
4 changes: 2 additions & 2 deletions pint/facets/plain/qto.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ def to_preferred(
>>> (1*ureg.acre).to_preferred([ureg.meters])
<Quantity(4046.87261, 'meter ** 2')>
>>> (1*(ureg.force_pound*ureg.m)).to_preferred([ureg.W])
<Quantity(4.44822162, 'second * watt')>
<Quantity(4.44822162, 'watt * second')>
"""

units = _get_preferred(quantity, preferred_units)
Expand All @@ -204,7 +204,7 @@ def ito_preferred(
>>> (1*ureg.acre).to_preferred([ureg.meters])
<Quantity(4046.87261, 'meter ** 2')>
>>> (1*(ureg.force_pound*ureg.m)).to_preferred([ureg.W])
<Quantity(4.44822162, 'second * watt')>
<Quantity(4.44822162, 'watt * second')>
"""

units = _get_preferred(quantity, preferred_units)
Expand Down
2 changes: 1 addition & 1 deletion pint/facets/plain/quantity.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ class PlainQuantity(Generic[MagnitudeT], PrettyIPython, SharedRegistryObject):
def ndim(self) -> int:
if isinstance(self.magnitude, numbers.Number):
return 0
if str(self.magnitude) == "<NA>":
if str(type(self.magnitude)) == "NAType":
return 0
return self.magnitude.ndim

Expand Down
17 changes: 12 additions & 5 deletions pint/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,31 +69,38 @@ class UnitRegistry(GenericUnitRegistry[Quantity, Unit]):
----------
filename :
path of the units definition file to load or line-iterable object.
Empty to load the default definition file.
Empty string to load the default definition file. (default)
None to leave the UnitRegistry empty.
force_ndarray : bool
convert any input, scalar or not to a numpy.ndarray.
(Default: False)
force_ndarray_like : bool
convert all inputs other than duck arrays to a numpy.ndarray.
(Default: False)
default_as_delta :
In the context of a multiplication of units, interpret
non-multiplicative units as their *delta* counterparts.
(Default: False)
autoconvert_offset_to_baseunit :
If True converts offset units in quantities are
converted to their plain units in multiplicative
context. If False no conversion happens.
context. If False no conversion happens. (Default: False)
on_redefinition : str
action to take in case a unit is redefined.
'warn', 'raise', 'ignore'
'warn', 'raise', 'ignore' (Default: 'raise')
auto_reduce_dimensions :
If True, reduce dimensionality on appropriate operations.
(Default: False)
autoconvert_to_preferred :
If True, converts preferred units on appropriate operations.
(Default: False)
preprocessors :
list of callables which are iteratively ran on any input expression
or unit string
or unit string or None for no preprocessor.
(Default=None)
fmt_locale :
locale identifier string, used in `format_babel`. Default to None
locale identifier string, used in `format_babel` or None.
(Default=None)
case_sensitive : bool, optional
Control default case sensitivity of unit parsing. (Default: True)
cache_folder : str or pathlib.Path or None, optional
Expand Down
3 changes: 3 additions & 0 deletions pint/testsuite/benchmarks/test_10_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,9 @@ def test_load_definitions_stage_1(benchmark, cache_folder, use_cache_folder):
benchmark(pint.UnitRegistry, None, cache_folder=use_cache_folder)


@pytest.mark.skip(
"Test failing ValueError: Group USCSLengthInternational already present in registry"
)
@pytest.mark.parametrize("use_cache_folder", (None, True))
def test_load_definitions_stage_2(benchmark, cache_folder, use_cache_folder):
"""empty registry creation + parsing default files + definition object loading"""
Expand Down
29 changes: 28 additions & 1 deletion pint/testsuite/test_issues.py
Original file line number Diff line number Diff line change
Expand Up @@ -1167,7 +1167,7 @@ def test_issue1725(registry_empty):
assert registry_empty.get_compatible_units("dollar") == set()


def test_issues_1505():
def test_issue1505():
ur = UnitRegistry(non_int_type=decimal.Decimal)

assert isinstance(ur.Quantity("1m/s").magnitude, decimal.Decimal)
Expand Down Expand Up @@ -1221,3 +1221,30 @@ def test_issues_1841_xfail():

# this prints "2*pi hour * radian", not "2*pi radian * hour" unless sort_dims is True
# print(q)


def test_issue1949(registry_empty):
ureg = UnitRegistry()
ureg.define(
"in_Hg_gauge = 3386389 * gram / metre / second ** 2; offset:101325000 = inHg_g = in_Hg_g = inHg_gauge"
)
q = ureg.Quantity("1 atm").to("inHg_gauge")
assert q.units == ureg.in_Hg_gauge
assert_equal(q.magnitude, 0.0)


@pytest.mark.parametrize(
"given,expected",
[
(
"8.989e9 newton * meter^2 / coulomb^2",
r"\SI[]{8.989E+9}{\meter\squared\newton\per\coulomb\squared}",
),
("5 * meter / second", r"\SI[]{5}{\meter\per\second}"),
("2.2 * meter^4", r"\SI[]{2.2}{\meter\tothe{4}}"),
("2.2 * meter^-4", r"\SI[]{2.2}{\per\meter\tothe{4}}"),
],
)
def test_issue1772(given, expected):
ureg = UnitRegistry(non_int_type=decimal.Decimal)
assert f"{ureg(given):Lx}" == expected
20 changes: 19 additions & 1 deletion pint/testsuite/test_numpy.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,11 @@ def test_broadcast_arrays(self):
result = np.broadcast_arrays(x, y, subok=True)
helpers.assert_quantity_equal(result, expected)

def test_roll(self):
helpers.assert_quantity_equal(
np.roll(self.q, 1), [[4, 1], [2, 3]] * self.ureg.m
)


class TestNumpyMathematicalFunctions(TestNumpyMethods):
# https://www.numpy.org/devdocs/reference/routines.math.html
Expand Down Expand Up @@ -433,13 +438,23 @@ def test_cross(self):
np.cross(a, b), [[-15, -2, 39]] * self.ureg.kPa * self.ureg.m**2
)

# NP2: Remove this when we only support np>=2.0
@helpers.requires_array_function_protocol()
def test_trapz(self):
helpers.assert_quantity_equal(
np.trapz([1.0, 2.0, 3.0, 4.0] * self.ureg.J, dx=1 * self.ureg.m),
7.5 * self.ureg.J * self.ureg.m,
)

@helpers.requires_array_function_protocol()
def test_trapezoid(self):
# NP2: Remove this when we only support np>=2.0
if np.lib.NumpyVersion(np.__version__) >= "2.0.0b1":
helpers.assert_quantity_equal(
np.trapezoid([1.0, 2.0, 3.0, 4.0] * self.ureg.J, dx=1 * self.ureg.m),
7.5 * self.ureg.J * self.ureg.m,
)

@helpers.requires_array_function_protocol()
def test_dot(self):
helpers.assert_quantity_equal(
Expand Down Expand Up @@ -753,9 +768,12 @@ def test_minimum(self):
np.minimum(self.q, self.Q_([0, 5], "m")), self.Q_([[0, 2], [0, 4]], "m")
)

# NP2: Can remove Q_(arr).ptp test when we only support numpy>=2
def test_ptp(self):
assert self.q.ptp() == 3 * self.ureg.m
if not np.lib.NumpyVersion(np.__version__) >= "2.0.0b1":
assert self.q.ptp() == 3 * self.ureg.m

# NP2: Keep this test for numpy>=2, it's only arr.ptp() that is deprecated
@helpers.requires_array_function_protocol()
def test_ptp_numpy_func(self):
helpers.assert_quantity_equal(np.ptp(self.q, axis=0), [2, 2] * self.ureg.m)
Expand Down

0 comments on commit 02f0209

Please sign in to comment.