From 6be06c6175c53040f34b492a137ae52c41955dd0 Mon Sep 17 00:00:00 2001 From: andrewgsavage Date: Sun, 12 May 2024 21:41:21 +0900 Subject: [PATCH 01/17] Skip failing benchmark test (#1981) --- pint/testsuite/benchmarks/test_10_registry.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pint/testsuite/benchmarks/test_10_registry.py b/pint/testsuite/benchmarks/test_10_registry.py index 09264fa44..3a1d42da5 100644 --- a/pint/testsuite/benchmarks/test_10_registry.py +++ b/pint/testsuite/benchmarks/test_10_registry.py @@ -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""" From d28efac6a0a029c21cd15e8fec2c8cd6ddbc1779 Mon Sep 17 00:00:00 2001 From: andrewgsavage Date: Sun, 12 May 2024 21:46:10 +0900 Subject: [PATCH 02/17] avoid calling str on array (#1959) --- pint/facets/plain/quantity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pint/facets/plain/quantity.py b/pint/facets/plain/quantity.py index 2727a7da3..a18919273 100644 --- a/pint/facets/plain/quantity.py +++ b/pint/facets/plain/quantity.py @@ -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) == "": + if str(type(self.magnitude)) == "NAType": return 0 return self.magnitude.ndim From e5b04b4d7dd3a399a4d57dbeb7d10e2454eedcbf Mon Sep 17 00:00:00 2001 From: David Linke Date: Sun, 12 May 2024 14:47:21 +0200 Subject: [PATCH 03/17] Document defaults of pint.UnitRegistry (#1919) This updates the doc-string to include the defaults for all parameters. --- pint/registry.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/pint/registry.py b/pint/registry.py index 210ea9112..ceb9b62d1 100644 --- a/pint/registry.py +++ b/pint/registry.py @@ -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 From be4e15b05a024766c50fbf8289d4c54b9f21ab1b Mon Sep 17 00:00:00 2001 From: andrewgsavage Date: Mon, 13 May 2024 00:12:43 +0900 Subject: [PATCH 04/17] Fix doctests (#1982) --- docs/advanced/pitheorem.rst | 6 ++++-- docs/api/facets.rst | 2 +- docs/getting/tutorial.rst | 8 ++++---- docs/user/angular_frequency.rst | 2 +- docs/user/defining-quantities.rst | 2 +- docs/user/formatting.rst | 5 +++-- pint/facets/plain/qto.py | 4 ++-- 7 files changed, 16 insertions(+), 13 deletions(-) diff --git a/docs/advanced/pitheorem.rst b/docs/advanced/pitheorem.rst index cd3716528..06409d8b5 100644 --- a/docs/advanced/pitheorem.rst +++ b/docs/advanced/pitheorem.rst @@ -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: diff --git a/docs/api/facets.rst b/docs/api/facets.rst index f4b6a54e8..d835f5cea 100644 --- a/docs/api/facets.rst +++ b/docs/api/facets.rst @@ -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 diff --git a/docs/getting/tutorial.rst b/docs/getting/tutorial.rst index bb3505b51..d675860f2 100644 --- a/docs/getting/tutorial.rst +++ b/docs/getting/tutorial.rst @@ -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: @@ -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`. diff --git a/docs/user/angular_frequency.rst b/docs/user/angular_frequency.rst index 58e126a9c..61bdf1614 100644 --- a/docs/user/angular_frequency.rst +++ b/docs/user/angular_frequency.rst @@ -2,7 +2,7 @@ Angles and Angular Frequency -================= +============================= Angles ------ diff --git a/docs/user/defining-quantities.rst b/docs/user/defining-quantities.rst index e40b08cf9..a7405151a 100644 --- a/docs/user/defining-quantities.rst +++ b/docs/user/defining-quantities.rst @@ -134,7 +134,7 @@ For example, the units of .. doctest:: >>> Q_('3 l / 100 km') - + may be unexpected at first but, are a consequence of applying this rule. Use brackets to get the expected result: diff --git a/docs/user/formatting.rst b/docs/user/formatting.rst index f17939a86..d45fc1e13 100644 --- a/docs/user/formatting.rst +++ b/docs/user/formatting.rst @@ -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. @@ -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!' diff --git a/pint/facets/plain/qto.py b/pint/facets/plain/qto.py index 9de541584..22176491d 100644 --- a/pint/facets/plain/qto.py +++ b/pint/facets/plain/qto.py @@ -184,7 +184,7 @@ def to_preferred( >>> (1*ureg.acre).to_preferred([ureg.meters]) >>> (1*(ureg.force_pound*ureg.m)).to_preferred([ureg.W]) - + """ units = _get_preferred(quantity, preferred_units) @@ -204,7 +204,7 @@ def ito_preferred( >>> (1*ureg.acre).to_preferred([ureg.meters]) >>> (1*(ureg.force_pound*ureg.m)).to_preferred([ureg.W]) - + """ units = _get_preferred(quantity, preferred_units) From 449697e505d35b103e5db874747eca65d37c2526 Mon Sep 17 00:00:00 2001 From: Toon Verstraelen Date: Sun, 12 May 2024 17:20:14 +0200 Subject: [PATCH 05/17] Fix siunitx format of integer powers with non_int_type=decimal.Decimal (#1977) --- .gitignore | 2 ++ CHANGES | 1 + pint/delegates/formatter/latex.py | 4 ++-- pint/testsuite/test_issues.py | 17 +++++++++++++++++ 4 files changed, 22 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index ae702bac3..69fd3338d 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,8 @@ MANIFEST .mypy_cache pip-wheel-metadata pint/testsuite/dask-worker-space +venv +.envrc # WebDAV file system cache files .DAV/ diff --git a/CHANGES b/CHANGES index 048765ec0..c27473af5 100644 --- a/CHANGES +++ b/CHANGES @@ -7,6 +7,7 @@ Pint Changelog - Add `dim_sort` function to _formatter_helpers. - Add `dim_order` and `default_sort_func` properties to FullFormatter. (PR #1926, fixes Issue #1841) +- Fix LaTeX siuntix formatting when using non_int_type=decimal.Decimal. 0.23 (2023-12-08) diff --git a/pint/delegates/formatter/latex.py b/pint/delegates/formatter/latex.py index 476997b84..468a65fa4 100644 --- a/pint/delegates/formatter/latex.py +++ b/pint/delegates/formatter/latex.py @@ -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: diff --git a/pint/testsuite/test_issues.py b/pint/testsuite/test_issues.py index dc63ececd..06ca4c322 100644 --- a/pint/testsuite/test_issues.py +++ b/pint/testsuite/test_issues.py @@ -1201,3 +1201,20 @@ def test_issues_1841_xfail(): # this prints "2*pi hour * radian", not "2*pi radian * hour" unless sort_dims is True # print(q) + + +@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 From 2b4a8b7d322a40eb1f9a9d656bac0a72f51ca47c Mon Sep 17 00:00:00 2001 From: Bhavin Patel <15210802+bpatel2107@users.noreply.github.com> Date: Sun, 12 May 2024 16:23:50 +0100 Subject: [PATCH 06/17] Implement numpy roll (#1968) --- CHANGES | 1 + pint/facets/numpy/numpy_func.py | 2 ++ pint/testsuite/test_numpy.py | 5 +++++ 3 files changed, 8 insertions(+) diff --git a/CHANGES b/CHANGES index c27473af5..e45cd50e6 100644 --- a/CHANGES +++ b/CHANGES @@ -4,6 +4,7 @@ Pint Changelog 0.24 (unreleased) ----------------- +- 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) diff --git a/pint/facets/numpy/numpy_func.py b/pint/facets/numpy/numpy_func.py index 29724837f..fe80727d4 100644 --- a/pint/facets/numpy/numpy_func.py +++ b/pint/facets/numpy/numpy_func.py @@ -413,6 +413,7 @@ def implementation(*args, **kwargs): "take", "trace", "transpose", + "roll", "ceil", "floor", "hypot", @@ -850,6 +851,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), diff --git a/pint/testsuite/test_numpy.py b/pint/testsuite/test_numpy.py index 69c8128c0..b58be1791 100644 --- a/pint/testsuite/test_numpy.py +++ b/pint/testsuite/test_numpy.py @@ -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 From feaa945ab346971a101ca670d1a96fe3112ed8e0 Mon Sep 17 00:00:00 2001 From: Ryan May Date: Sun, 12 May 2024 11:29:41 -0600 Subject: [PATCH 07/17] MNT: Handle trapz for numpy>=2 (#1971) trapz has been deprecated in favor of the newly available trapezoid function. This wraps the new function and avoids a DeprecationWarning on numpy>=2. --- pint/facets/numpy/numpy_func.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pint/facets/numpy/numpy_func.py b/pint/facets/numpy/numpy_func.py index fe80727d4..ac702014c 100644 --- a/pint/facets/numpy/numpy_func.py +++ b/pint/facets/numpy/numpy_func.py @@ -741,8 +741,11 @@ def _base_unit_if_needed(a): raise OffsetUnitCalculusError(a.units) +# 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: @@ -750,13 +753,13 @@ def _trapz(y, x=None, dx=1.0, **kwargs): 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) From cbdd79e3e1ec8d179c365db3b9c4dcf0383930d0 Mon Sep 17 00:00:00 2001 From: David Linke Date: Sun, 12 May 2024 20:25:58 +0200 Subject: [PATCH 08/17] Fix converting to offset units of higher dimension e.g. gauge pressure (#1952) --- CHANGES | 2 ++ pint/facets/nonmultiplicative/registry.py | 7 ++++++- pint/testsuite/test_issues.py | 12 +++++++++++- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index e45cd50e6..fe3f0b48b 100644 --- a/CHANGES +++ b/CHANGES @@ -10,6 +10,8 @@ Pint Changelog (PR #1926, fixes Issue #1841) - Fix LaTeX siuntix formatting when using non_int_type=decimal.Decimal. +- Fix converting to offset units of higher dimension e.g. gauge pressure (#1949). +- 0.23 (2023-12-08) ----------------- diff --git a/pint/facets/nonmultiplicative/registry.py b/pint/facets/nonmultiplicative/registry.py index 4985ba51b..d476cc676 100644 --- a/pint/facets/nonmultiplicative/registry.py +++ b/pint/facets/nonmultiplicative/registry.py @@ -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 @@ -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 diff --git a/pint/testsuite/test_issues.py b/pint/testsuite/test_issues.py index 06ca4c322..69909c1a4 100644 --- a/pint/testsuite/test_issues.py +++ b/pint/testsuite/test_issues.py @@ -1147,7 +1147,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) @@ -1203,6 +1203,16 @@ def test_issues_1841_xfail(): # 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", [ From cb0ec94a34b88028bb000e1c0b061c16295a3324 Mon Sep 17 00:00:00 2001 From: andrewgsavage Date: Mon, 13 May 2024 03:32:04 +0900 Subject: [PATCH 09/17] numpy2 support (#1985) --- .github/workflows/ci.yml | 2 +- CHANGES | 7 ++++--- pint/facets/numpy/numpy_func.py | 2 +- pint/testsuite/test_numpy.py | 15 ++++++++++++++- 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d445a2970..0797decb5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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: diff --git a/CHANGES b/CHANGES index fe3f0b48b..50b524949 100644 --- a/CHANGES +++ b/CHANGES @@ -4,14 +4,15 @@ 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) - Fix LaTeX siuntix formatting when using non_int_type=decimal.Decimal. - -- Fix converting to offset units of higher dimension e.g. gauge pressure (#1949). -- +- Fix converting to offset units of higher dimension e.g. gauge pressure + (PR #1949) 0.23 (2023-12-08) ----------------- diff --git a/pint/facets/numpy/numpy_func.py b/pint/facets/numpy/numpy_func.py index ac702014c..0d03af74e 100644 --- a/pint/facets/numpy/numpy_func.py +++ b/pint/facets/numpy/numpy_func.py @@ -741,7 +741,7 @@ def _base_unit_if_needed(a): raise OffsetUnitCalculusError(a.units) -# Can remove trapz wrapping when we only support numpy>=2 +# 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): diff --git a/pint/testsuite/test_numpy.py b/pint/testsuite/test_numpy.py index b58be1791..486102124 100644 --- a/pint/testsuite/test_numpy.py +++ b/pint/testsuite/test_numpy.py @@ -438,6 +438,7 @@ 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( @@ -445,6 +446,15 @@ def test_trapz(self): 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( @@ -758,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) From 754a7ff722f3480486431fd727ac0533a6882163 Mon Sep 17 00:00:00 2001 From: Peter Kraus Date: Sun, 12 May 2024 20:35:51 +0200 Subject: [PATCH 10/17] Add RIU to default_en.txt (#1816) --- CHANGES | 3 +++ pint/default_en.txt | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/CHANGES b/CHANGES index 50b524949..3b011a177 100644 --- a/CHANGES +++ b/CHANGES @@ -11,8 +11,11 @@ Pint Changelog - Add `dim_order` and `default_sort_func` properties to FullFormatter. (PR #1926, fixes Issue #1841) - Fix LaTeX siuntix formatting when using non_int_type=decimal.Decimal. +- Added refractive index units. + (PR #1816) - Fix converting to offset units of higher dimension e.g. gauge pressure (PR #1949) + 0.23 (2023-12-08) ----------------- diff --git a/pint/default_en.txt b/pint/default_en.txt index 5fc7f8265..83ea967f6 100644 --- a/pint/default_en.txt +++ b/pint/default_en.txt @@ -494,6 +494,10 @@ buckingham = debye * angstrom bohr_magneton = e * hbar / (2 * m_e) = µ_B = mu_B nuclear_magneton = e * hbar / (2 * m_p) = µ_N = mu_N +# Refractive index +[refractive_index] = [] +refractive_index_unit = [] = RIU + # Logaritmic Unit Definition # Unit = scale; logbase; logfactor # x_dB = [logfactor] * log( x_lin / [scale] ) / log( [logbase] ) From 7262388fb498ab1497cdc37c61baadf816590b8d Mon Sep 17 00:00:00 2001 From: andrewgsavage Date: Mon, 13 May 2024 03:50:22 +0900 Subject: [PATCH 11/17] Array ufunc multiplication (#1677) --- CHANGES | 3 +++ pint/facets/numpy/numpy_func.py | 11 +++++++++++ pint/testsuite/test_issues.py | 20 ++++++++++++++++++++ 3 files changed, 34 insertions(+) diff --git a/CHANGES b/CHANGES index 3b011a177..d1200e5e3 100644 --- a/CHANGES +++ b/CHANGES @@ -10,6 +10,9 @@ Pint Changelog - 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. - Added refractive index units. (PR #1816) diff --git a/pint/facets/numpy/numpy_func.py b/pint/facets/numpy/numpy_func.py index 0d03af74e..138414553 100644 --- a/pint/facets/numpy/numpy_func.py +++ b/pint/facets/numpy/numpy_func.py @@ -284,6 +284,17 @@ def implement_func(func_type, func_str, input_units=None, output_unit=None): @implements(func_str, func_type) def implementation(*args, **kwargs): + if func_str in ["multiply", "true_divide", "divide", "floor_divide"] and any( + [ + not _is_quantity(arg) and _is_sequence_with_quantity_elements(arg) + for arg in args + ] + ): + # the sequence may contain different units, so fall back to element-wise + return np.array( + [func(*func_args) for func_args in zip(*args)], dtype=object + ) + first_input_units = _get_first_input_units(args, kwargs) if input_units == "all_consistent": # Match all input args/kwargs to same units diff --git a/pint/testsuite/test_issues.py b/pint/testsuite/test_issues.py index 69909c1a4..1e0497e4a 100644 --- a/pint/testsuite/test_issues.py +++ b/pint/testsuite/test_issues.py @@ -888,6 +888,26 @@ def test_issue_1300(self): m = module_registry.Measurement(1, 0.1, "meter") assert m.default_format == "~P" + @helpers.requires_numpy() + def test_issue1674(self, module_registry): + Q_ = module_registry.Quantity + arr_of_q = np.array([Q_(2, "m"), Q_(4, "m")], dtype="object") + q_arr = Q_(np.array([1, 2]), "m") + + helpers.assert_quantity_equal( + arr_of_q * q_arr, np.array([Q_(2, "m^2"), Q_(8, "m^2")], dtype="object") + ) + helpers.assert_quantity_equal( + arr_of_q / q_arr, np.array([Q_(2, ""), Q_(2, "")], dtype="object") + ) + + arr_of_q = np.array([Q_(2, "m"), Q_(4, "s")], dtype="object") + q_arr = Q_(np.array([1, 2]), "m") + + helpers.assert_quantity_equal( + arr_of_q * q_arr, np.array([Q_(2, "m^2"), Q_(8, "m s")], dtype="object") + ) + @helpers.requires_babel() def test_issue_1400(self, sess_registry): q1 = 3.1 * sess_registry.W From c0501ff53f05c61796c05f5b713c3f899ee935c6 Mon Sep 17 00:00:00 2001 From: Jonas Neubert Date: Sun, 12 May 2024 13:39:33 -0600 Subject: [PATCH 12/17] Fix TypeError when combining auto_reduce_dimensions=True and non_int_type=Decimal (#1853) --- CHANGES | 4 +++- pint/testsuite/test_issues.py | 7 +++++++ pint/util.py | 9 +++++++-- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index d1200e5e3..66281cc6d 100644 --- a/CHANGES +++ b/CHANGES @@ -18,7 +18,9 @@ Pint Changelog (PR #1816) - Fix converting to offset units of higher dimension e.g. gauge pressure (PR #1949) - +- Fix unhandled TypeError when auto_reduce_dimensions=True and non_int_type=Decimal + (PR #1853) + 0.23 (2023-12-08) ----------------- diff --git a/pint/testsuite/test_issues.py b/pint/testsuite/test_issues.py index 1e0497e4a..2a0b7edf6 100644 --- a/pint/testsuite/test_issues.py +++ b/pint/testsuite/test_issues.py @@ -1179,6 +1179,13 @@ def test_issue1505(): ) # unexpected fail (magnitude should be a decimal) +def test_issue_1845(): + ur = UnitRegistry(auto_reduce_dimensions=True, non_int_type=decimal.Decimal) + # before issue 1845 these inputs would have resulted in a TypeError + assert ur("km / h * m").units == ur.Quantity("meter ** 2 / hour") + assert ur("kW / min * W").units == ur.Quantity("watts ** 2 / minute") + + @pytest.mark.parametrize( "units,spec,expected", [ diff --git a/pint/util.py b/pint/util.py index 0c40c5187..c7a7ec10c 100644 --- a/pint/util.py +++ b/pint/util.py @@ -495,7 +495,7 @@ def add(self: Self, key: str, value: Number) -> Self: UnitsContainer A copy of this container. """ - newval = self._d[key] + value + newval = self._d[key] + self._normalize_nonfloat_value(value) new = self.copy() if newval: new._d[key] = newval @@ -656,7 +656,7 @@ def __truediv__(self, other: Any): new = self.copy() for key, value in other.items(): - new._d[key] -= value + new._d[key] -= self._normalize_nonfloat_value(value) if new._d[key] == 0: del new._d[key] @@ -670,6 +670,11 @@ def __rtruediv__(self, other: Any): return self**-1 + def _normalize_nonfloat_value(self, value: Scalar) -> Scalar: + if not isinstance(value, int) and not isinstance(value, self._non_int_type): + return self._non_int_type(value) # type: ignore[no-any-return] + return value + class ParserHelper(UnitsContainer): """The ParserHelper stores in place the product of variables and From 24dd23705897f75889073c364941e08f9b6e00d5 Mon Sep 17 00:00:00 2001 From: tristannew Date: Sun, 12 May 2024 20:43:21 +0100 Subject: [PATCH 13/17] Detailed Error Message for `get_dimensionality()` (#1874) --- CHANGES | 2 ++ pint/facets/plain/registry.py | 7 ++++++- pint/testsuite/test_errors.py | 10 ++++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 66281cc6d..9ec58b72c 100644 --- a/CHANGES +++ b/CHANGES @@ -49,6 +49,8 @@ Pint Changelog (PR #1819) - Add numpy.linalg.norm implementation. (PR #1251) +- Improved error message in `get_dimensionality()` when non existent units are passed. + (PR #1874, Issue #1716) 0.22 (2023-05-25) ----------------- diff --git a/pint/facets/plain/registry.py b/pint/facets/plain/registry.py index 277a6f7a2..09fd220ee 100644 --- a/pint/facets/plain/registry.py +++ b/pint/facets/plain/registry.py @@ -736,7 +736,12 @@ def _get_dimensionality_recurse( for key in ref: exp2 = exp * ref[key] if _is_dim(key): - reg = self._dimensions[key] + try: + reg = self._dimensions[key] + except KeyError: + raise ValueError( + f"{key} is not defined as dimension in the pint UnitRegistry" + ) if isinstance(reg, DerivedDimensionDefinition): self._get_dimensionality_recurse(reg.reference, exp2, accumulator) else: diff --git a/pint/testsuite/test_errors.py b/pint/testsuite/test_errors.py index 370ccfc9d..e0c4ec3f4 100644 --- a/pint/testsuite/test_errors.py +++ b/pint/testsuite/test_errors.py @@ -144,3 +144,13 @@ def test_pickle_definition_syntax_error(self, subtests): with pytest.raises(PintError): raise ex + + def test_dimensionality_error_message(self): + ureg = UnitRegistry(system="SI") + with pytest.raises(ValueError) as error: + ureg.get_dimensionality("[bilbo]") + + assert ( + str(error.value) + == "[bilbo] is not defined as dimension in the pint UnitRegistry" + ) From ea3a5d1006e44f64625f92054eec39405b8e4840 Mon Sep 17 00:00:00 2001 From: andrewgsavage Date: Mon, 13 May 2024 06:55:41 +0900 Subject: [PATCH 14/17] move a change to 0.24 (#1986) --- CHANGES | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 9ec58b72c..c13d76334 100644 --- a/CHANGES +++ b/CHANGES @@ -20,6 +20,8 @@ Pint Changelog (PR #1949) - Fix unhandled TypeError when auto_reduce_dimensions=True and non_int_type=Decimal (PR #1853) +- Improved error message in `get_dimensionality()` when non existent units are passed. + (PR #1874, Issue #1716) 0.23 (2023-12-08) @@ -49,8 +51,6 @@ Pint Changelog (PR #1819) - Add numpy.linalg.norm implementation. (PR #1251) -- Improved error message in `get_dimensionality()` when non existent units are passed. - (PR #1874, Issue #1716) 0.22 (2023-05-25) ----------------- From 5206fac8adc64514d58daea8d41e75fd7f28a34c Mon Sep 17 00:00:00 2001 From: Matt Ettus Date: Sun, 12 May 2024 15:03:54 -0700 Subject: [PATCH 15/17] Add dBW, decibel watts (#1292) --- CHANGES | 1 + pint/default_en.txt | 1 + pint/testsuite/test_log_units.py | 8 ++++++++ 3 files changed, 10 insertions(+) diff --git a/CHANGES b/CHANGES index c13d76334..8250fb9df 100644 --- a/CHANGES +++ b/CHANGES @@ -4,6 +4,7 @@ Pint Changelog 0.24 (unreleased) ----------------- +- Added dBW, decibel Watts, which is used in RF high power applications - NumPy 2.0 support (PR #1985, #1971) - Implement numpy roll (Related to issue #981) diff --git a/pint/default_en.txt b/pint/default_en.txt index 83ea967f6..45f241f18 100644 --- a/pint/default_en.txt +++ b/pint/default_en.txt @@ -504,6 +504,7 @@ refractive_index_unit = [] = RIU # Logaritmic Units of dimensionless quantity: [ https://en.wikipedia.org/wiki/Level_(logarithmic_quantity) ] +decibelwatt = watt; logbase: 10; logfactor: 10 = dBW decibelmilliwatt = 1e-3 watt; logbase: 10; logfactor: 10 = dBm decibelmicrowatt = 1e-6 watt; logbase: 10; logfactor: 10 = dBu diff --git a/pint/testsuite/test_log_units.py b/pint/testsuite/test_log_units.py index c3b7b2c5a..5f1b0be49 100644 --- a/pint/testsuite/test_log_units.py +++ b/pint/testsuite/test_log_units.py @@ -65,6 +65,11 @@ def test_log_convert(self): helpers.assert_quantity_almost_equal( self.Q_(0.0, "dBm"), self.Q_(29.999999999999996, "dBu"), atol=1e-7 ) + # ## Test dB to dB units dBm - dBW + # 0 dBW = 1W = 1e3 mW = 30 dBm + helpers.assert_quantity_almost_equal( + self.Q_(0.0, "dBW"), self.Q_(29.999999999999996, "dBm"), atol=1e-7 + ) def test_mix_regular_log_units(self): # Test regular-logarithmic mixed definition, such as dB/km or dB/cm @@ -84,6 +89,8 @@ def test_mix_regular_log_units(self): log_unit_names = [ + "decibelwatt", + "dBW", "decibelmilliwatt", "dBm", "decibelmicrowatt", @@ -135,6 +142,7 @@ def test_quantity_by_multiplication(module_registry_auto_offset, unit_name, mag) @pytest.mark.parametrize( "unit1,unit2", [ + ("decibelwatt", "dBW"), ("decibelmilliwatt", "dBm"), ("decibelmicrowatt", "dBu"), ("decibel", "dB"), From 9a93a68291f5eaa5e6ae2d0a733392ee27d186b4 Mon Sep 17 00:00:00 2001 From: David Linke Date: Mon, 13 May 2024 00:25:18 +0200 Subject: [PATCH 16/17] Add check for delta unit to convert (#1905) --- CHANGES | 1 + pint/facets/nonmultiplicative/registry.py | 7 ++++++- pint/testsuite/test_unit.py | 2 ++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 8250fb9df..c0b99ef49 100644 --- a/CHANGES +++ b/CHANGES @@ -4,6 +4,7 @@ Pint Changelog 0.24 (unreleased) ----------------- +- Fix detection of invalid conversion between offset and delta units. (PR #1905) - Added dBW, decibel Watts, which is used in RF high power applications - NumPy 2.0 support (PR #1985, #1971) diff --git a/pint/facets/nonmultiplicative/registry.py b/pint/facets/nonmultiplicative/registry.py index d476cc676..7f58d060c 100644 --- a/pint/facets/nonmultiplicative/registry.py +++ b/pint/facets/nonmultiplicative/registry.py @@ -119,7 +119,7 @@ def _is_multiplicative(self, unit_name: str) -> bool: Raises ------ UndefinedUnitError - If the unit is not in the registyr. + If the unit is not in the registry. """ if unit_name in self._units: return self._units[unit_name].is_multiplicative @@ -254,6 +254,7 @@ def _convert( src, dst, extra_msg=f" - In destination units, {ex}" ) + # convert if no offset units are present if not (src_offset_unit or dst_offset_unit): return super()._convert(value, src, dst, inplace) @@ -267,6 +268,8 @@ def _convert( # clean src from offset units by converting to reference if src_offset_unit: + if any(u.startswith("delta_") for u in dst): + raise DimensionalityError(src, dst) value = self._units[src_offset_unit].converter.to_reference(value, inplace) src = src.remove([src_offset_unit]) # Add reference unit for multiplicative section @@ -274,6 +277,8 @@ def _convert( # clean dst units from offset units if dst_offset_unit: + if any(u.startswith("delta_") for u in src): + raise DimensionalityError(src, dst) dst = dst.remove([dst_offset_unit]) # Add reference unit for multiplicative section dst = self._add_ref_of_log_or_offset_unit(dst_offset_unit, dst) diff --git a/pint/testsuite/test_unit.py b/pint/testsuite/test_unit.py index 5b5f69a0c..2156bbafd 100644 --- a/pint/testsuite/test_unit.py +++ b/pint/testsuite/test_unit.py @@ -989,6 +989,8 @@ class TestConvertWithOffset(QuantityTestCase): (({"degC": 2}, {"kelvin": 2}), "error"), (({"degC": 1, "degF": 1}, {"kelvin": 2}), "error"), (({"degC": 1, "kelvin": 1}, {"kelvin": 2}), "error"), + (({"delta_degC": 1}, {"degF": 1}), "error"), + (({"delta_degC": 1}, {"degC": 1}), "error"), ] @pytest.mark.parametrize(("input_tuple", "expected"), convert_with_offset) From ad11d3a23aaf621af5ec3ee8c2080c896f6c7ab6 Mon Sep 17 00:00:00 2001 From: andrewgsavage Date: Mon, 13 May 2024 07:25:38 +0900 Subject: [PATCH 17/17] Avoid looping on large numpy arrays (#1987) --- pint/facets/numpy/numpy_func.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pint/facets/numpy/numpy_func.py b/pint/facets/numpy/numpy_func.py index 138414553..6bccd409f 100644 --- a/pint/facets/numpy/numpy_func.py +++ b/pint/facets/numpy/numpy_func.py @@ -52,6 +52,10 @@ def _is_sequence_with_quantity_elements(obj): ------- True if obj is a sequence and at least one element is a Quantity; False otherwise """ + if np is not None and isinstance(obj, np.ndarray) and not obj.dtype.hasobject: + # If obj is a numpy array, avoid looping on all elements + # if dtype does not have objects + return False return ( iterable(obj) and sized(obj)