From 02788b5167c3442bcb918698a0a2fec32d0b2ea8 Mon Sep 17 00:00:00 2001 From: Jacob Portukalian Date: Thu, 17 Apr 2025 10:13:00 -0700 Subject: [PATCH 1/2] fixed adding dB + dB and dB + dBm --- pint/facets/plain/quantity.py | 76 +++++++++++++++++++++++++------- pint/testsuite/test_log_units.py | 7 ++- 2 files changed, 65 insertions(+), 18 deletions(-) diff --git a/pint/facets/plain/quantity.py b/pint/facets/plain/quantity.py index 101a4c84b..b88513797 100644 --- a/pint/facets/plain/quantity.py +++ b/pint/facets/plain/quantity.py @@ -166,24 +166,22 @@ def __reduce__(self) -> tuple[type, Magnitude, UnitsContainer]: @overload def __new__( cls, value: MagnitudeT, units: UnitLike | None = None - ) -> PlainQuantity[MagnitudeT]: - ... + ) -> PlainQuantity[MagnitudeT]: ... @overload - def __new__(cls, value: str, units: UnitLike | None = None) -> PlainQuantity[Any]: - ... + def __new__( + cls, value: str, units: UnitLike | None = None + ) -> PlainQuantity[Any]: ... @overload def __new__( # type: ignore[misc] cls, value: Sequence[ScalarT], units: UnitLike | None = None - ) -> PlainQuantity[Any]: - ... + ) -> PlainQuantity[Any]: ... @overload def __new__( cls, value: PlainQuantity[Any], units: UnitLike | None = None - ) -> PlainQuantity[Any]: - ... + ) -> PlainQuantity[Any]: ... def __new__(cls, value, units=None): if is_upcast_type(type(value)): @@ -747,18 +745,56 @@ def _add_sub(self, other, op): raise DimensionalityError(self._units, "dimensionless") return self.__class__(magnitude, units) + # Special case for logarithmic units: dB can be added to dBm, dBW, etc. + # Get non-multiplicative units before checking dimensionality + self_non_mul_units = self._get_non_multiplicative_units() + other_non_mul_units = other._get_non_multiplicative_units() + + # Check if we're dealing with logarithmic units that can be added (dB + dBm, etc.) + if ( + len(self_non_mul_units) == 1 + and len(other_non_mul_units) == 1 + and hasattr( + self._get_unit_definition(self_non_mul_units[0]), "is_logarithmic" + ) + and hasattr( + self._get_unit_definition(other_non_mul_units[0]), "is_logarithmic" + ) + and self._get_unit_definition(self_non_mul_units[0]).is_logarithmic + and self._get_unit_definition(other_non_mul_units[0]).is_logarithmic + ): + # Case 1: both are the same logarithmic unit (dB + dB) + if self_non_mul_units[0] == other_non_mul_units[0]: + # Add the magnitudes directly when the same logarithmic unit + magnitude = op(self._magnitude, other._magnitude) + return self.__class__(magnitude, self._units) + + # Case 2: Special handling for adding dimensionless dB to other logarithmic units + elif ( + other_non_mul_units[0] == "decibel" + and self.dimensionality != other.dimensionality + ): + # Add the magnitude directly - this assumes both are in logarithmic scale + magnitude = op(self._magnitude, other._magnitude) + return self.__class__(magnitude, self._units) + elif ( + self_non_mul_units[0] == "decibel" + and self.dimensionality != other.dimensionality + ): + # Add the magnitude directly - this assumes both are in logarithmic scale + magnitude = op(other._magnitude, self._magnitude) + return self.__class__(magnitude, other._units) if not self.dimensionality == other.dimensionality: raise DimensionalityError( self._units, other._units, self.dimensionality, other.dimensionality ) # Next we define some variables to make if-clauses more readable. - self_non_mul_units = self._get_non_multiplicative_units() + # We already have self_non_mul_units and other_non_mul_units from our logarithmic check is_self_multiplicative = len(self_non_mul_units) == 0 + is_other_multiplicative = len(other_non_mul_units) == 0 if len(self_non_mul_units) == 1: self_non_mul_unit = self_non_mul_units[0] - other_non_mul_units = other._get_non_multiplicative_units() - is_other_multiplicative = len(other_non_mul_units) == 0 if len(other_non_mul_units) == 1: other_non_mul_unit = other_non_mul_units[0] @@ -831,8 +867,7 @@ def __iadd__(self, other: datetime.datetime) -> datetime.timedelta: # type: ign ... @overload - def __iadd__(self, other) -> PlainQuantity[MagnitudeT]: - ... + def __iadd__(self, other) -> PlainQuantity[MagnitudeT]: ... def __iadd__(self, other): if isinstance(other, datetime.datetime): @@ -1401,10 +1436,17 @@ def compare(self, other, op): ) return op(self.to_root_units().magnitude, other.to_root_units().magnitude) - __lt__ = lambda self, other: self.compare(other, op=operator.lt) - __le__ = lambda self, other: self.compare(other, op=operator.le) - __ge__ = lambda self, other: self.compare(other, op=operator.ge) - __gt__ = lambda self, other: self.compare(other, op=operator.gt) + def __lt__(self, other): + return self.compare(other, op=operator.lt) + + def __le__(self, other): + return self.compare(other, op=operator.le) + + def __ge__(self, other): + return self.compare(other, op=operator.ge) + + def __gt__(self, other): + return self.compare(other, op=operator.gt) def __bool__(self) -> bool: # Only cast when non-ambiguous (when multiplicative unit) diff --git a/pint/testsuite/test_log_units.py b/pint/testsuite/test_log_units.py index 5f1b0be49..725d18701 100644 --- a/pint/testsuite/test_log_units.py +++ b/pint/testsuite/test_log_units.py @@ -255,7 +255,12 @@ def test_compound_log_unit_parse_expr(module_registry_auto_offset): assert canonical_def == parse_def -@pytest.mark.xfail +def test_db_db_addition(module_registry_auto_offset): + """Test a dB value can be added to a dB and the answer is correct.""" + ratio = (5 * module_registry_auto_offset.dB) + (10 * module_registry_auto_offset.dB) + assert ratio.to("dB").magnitude == pytest.approx(15) + + def test_dbm_db_addition(module_registry_auto_offset): """Test a dB value can be added to a dBm and the answer is correct.""" power = (5 * module_registry_auto_offset.dBm) + ( From 9a917a27deac1a67e2e82bacd61a09f0cfc092a6 Mon Sep 17 00:00:00 2001 From: Jacob Portukalian Date: Thu, 17 Apr 2025 12:56:48 -0700 Subject: [PATCH 2/2] added to changelist --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 51080320a..f0d5ac464 100644 --- a/CHANGES +++ b/CHANGES @@ -19,7 +19,7 @@ Pint Changelog - Add membrane filtration flux and permeability dimensionality, and shorthand "LMH". (#2116) - Fix find_shortest_path to use breadth first search (#2146) - Fix typo in ``pyproject.toml``: rename ``AS_MIP`` to ``HAS_MIP`` so that MIP support is correctly detected. (#2152) - +- Fix ability to add dB units, and to add dB (dimensionless) to referenced dB units, such as dBm or dBW (#1160) 0.24.4 (2024-11-07) -------------------