From 8e091d66de8e767f3d9d309f91c95d22e7bc7e5f Mon Sep 17 00:00:00 2001 From: Andrew Date: Fri, 12 Apr 2024 18:01:11 +0100 Subject: [PATCH 01/11] nonreducing --- pint/facets/plain/quantity.py | 2 ++ pint/facets/plain/registry.py | 5 ++++- pint/util.py | 24 +++++++++++++++++++++++- 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/pint/facets/plain/quantity.py b/pint/facets/plain/quantity.py index 2727a7da3..72990c5d0 100644 --- a/pint/facets/plain/quantity.py +++ b/pint/facets/plain/quantity.py @@ -206,6 +206,8 @@ def __new__(cls, value, units=None): if units is None: units = inst.UnitsContainer() else: + if isinstance(units, list): + units = inst._REGISTRY.NonReducingUnitContainer(units) if isinstance(units, (UnitsContainer, UnitDefinition)): units = units elif isinstance(units, str): diff --git a/pint/facets/plain/registry.py b/pint/facets/plain/registry.py index 277a6f7a2..e91c4d433 100644 --- a/pint/facets/plain/registry.py +++ b/pint/facets/plain/registry.py @@ -72,7 +72,7 @@ string_preprocessor, to_units_container, ) -from ...util import UnitsContainer as UnitsContainer +from ...util import UnitsContainer, NonReducingUnitContainer from .definitions import ( AliasDefinition, CommentDefinition, @@ -1401,6 +1401,9 @@ def UnitsContainer(self, *args: Any, **kwargs: Any) -> UnitsContainer: __call__ = parse_expression + def NonReducingUnitContainer(self, units) -> UnitsContainer: + return NonReducingUnitContainer(units) + class PlainRegistry(GenericPlainRegistry[PlainQuantity[Any], PlainUnit]): Quantity: TypeAlias = PlainQuantity[Any] diff --git a/pint/util.py b/pint/util.py index 0c40c5187..48ed935a0 100644 --- a/pint/util.py +++ b/pint/util.py @@ -671,6 +671,26 @@ def __rtruediv__(self, other: Any): return self**-1 +class NonReducingUnitContainer(UnitsContainer): + """ The NonReducingUnitContainer stores UnitsContainers without simplifying common units. + This is useful when it is desired to show a unit in the numerator and denominator, eg mm/mm. + """ + def __init__( + self, units: list[UnitsContainer] | list[tuple[QuantityOrUnitLike, Scalar]] + ) -> None: + if all(isinstance(u, tuple) for u in units): + units = [u[0]._units ** u[1] for u in units] + + self.non_reduced_units = units + + self.reduced_units = UnitsContainer() + for unit in units: + self.reduced_units *= unit + + self._d = self.reduced_units._d + self._hash = self.reduced_units._hash + + class ParserHelper(UnitsContainer): """The ParserHelper stores in place the product of variables and their respective exponent and implements the corresponding operations. @@ -766,13 +786,15 @@ def from_string(cls, input_string: str, non_int_type: type = float) -> ParserHel reps = False gen = pint_eval.tokenizer(input_string) + print(build_eval_tree(gen)) ret = build_eval_tree(gen).evaluate( partial(cls.eval_token, non_int_type=non_int_type) ) + print(ret) if isinstance(ret, Number): return cls(ret, non_int_type=non_int_type) - + if reps: ret = cls( ret.scale, From e8e257cec064031aa881ffb8a9b9724ce0991062 Mon Sep 17 00:00:00 2001 From: Andrew Date: Fri, 12 Apr 2024 18:14:42 +0100 Subject: [PATCH 02/11] nonreducing --- pint/util.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pint/util.py b/pint/util.py index 48ed935a0..91afaf644 100644 --- a/pint/util.py +++ b/pint/util.py @@ -786,15 +786,13 @@ def from_string(cls, input_string: str, non_int_type: type = float) -> ParserHel reps = False gen = pint_eval.tokenizer(input_string) - print(build_eval_tree(gen)) ret = build_eval_tree(gen).evaluate( partial(cls.eval_token, non_int_type=non_int_type) ) - print(ret) if isinstance(ret, Number): return cls(ret, non_int_type=non_int_type) - + if reps: ret = cls( ret.scale, From 11b3ee82ba34815157fa5b0da7b0260b9d07e718 Mon Sep 17 00:00:00 2001 From: andrewgsavage Date: Wed, 22 May 2024 08:06:36 +0200 Subject: [PATCH 03/11] lint --- pint/facets/plain/registry.py | 3 ++- pint/util.py | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/pint/facets/plain/registry.py b/pint/facets/plain/registry.py index ab440a2a5..6e9a43bc6 100644 --- a/pint/facets/plain/registry.py +++ b/pint/facets/plain/registry.py @@ -63,7 +63,9 @@ from ...errors import DimensionalityError, RedefinitionError, UndefinedUnitError from ...pint_eval import build_eval_tree from ...util import ( + NonReducingUnitContainer, ParserHelper, + UnitsContainer, _is_dim, create_class_with_registry, getattr_maybe_raise, @@ -72,7 +74,6 @@ string_preprocessor, to_units_container, ) -from ...util import UnitsContainer, NonReducingUnitContainer from .definitions import ( AliasDefinition, CommentDefinition, diff --git a/pint/util.py b/pint/util.py index ceead1463..f8e851451 100644 --- a/pint/util.py +++ b/pint/util.py @@ -677,9 +677,10 @@ def _normalize_nonfloat_value(self, value: Scalar) -> Scalar: class NonReducingUnitContainer(UnitsContainer): - """ The NonReducingUnitContainer stores UnitsContainers without simplifying common units. + """The NonReducingUnitContainer stores UnitsContainers without simplifying common units. This is useful when it is desired to show a unit in the numerator and denominator, eg mm/mm. """ + def __init__( self, units: list[UnitsContainer] | list[tuple[QuantityOrUnitLike, Scalar]] ) -> None: @@ -691,7 +692,7 @@ def __init__( self.reduced_units = UnitsContainer() for unit in units: self.reduced_units *= unit - + self._d = self.reduced_units._d self._hash = self.reduced_units._hash From 4b931fa30173f5772cee63663a706bc1473f4b01 Mon Sep 17 00:00:00 2001 From: andrewgsavage Date: Fri, 24 May 2024 21:16:34 +0200 Subject: [PATCH 04/11] attempt --- .../formatter/_compound_unit_helpers.py | 4 ++- pint/delegates/formatter/full.py | 9 +++++- pint/delegates/formatter/html.py | 2 ++ pint/facets/plain/registry.py | 6 ++-- pint/testsuite/test_unit.py | 13 ++++++++ pint/util.py | 32 +++++++++++++++---- 6 files changed, 55 insertions(+), 11 deletions(-) diff --git a/pint/delegates/formatter/_compound_unit_helpers.py b/pint/delegates/formatter/_compound_unit_helpers.py index 89bda87a2..34ffe146b 100644 --- a/pint/delegates/formatter/_compound_unit_helpers.py +++ b/pint/delegates/formatter/_compound_unit_helpers.py @@ -26,7 +26,7 @@ ) from ...compat import babel_parse -from ...util import UnitsContainer +from ...util import NonReducingUnitsContainer, UnitsContainer T = TypeVar("T") U = TypeVar("U") @@ -250,6 +250,8 @@ def prepare_compount_unit( if isinstance(unit, UnitsContainer): out = unit.items() + elif hasattr(unit, "_units") and isinstance(unit._units, NonReducingUnitsContainer): + out = unit._units.non_reduced_d_items elif hasattr(unit, "_units"): out = unit._units.items() else: diff --git a/pint/delegates/formatter/full.py b/pint/delegates/formatter/full.py index e6d0eee47..92609b713 100644 --- a/pint/delegates/formatter/full.py +++ b/pint/delegates/formatter/full.py @@ -16,7 +16,7 @@ from ..._typing import Magnitude from ...compat import Unpack, babel_parse -from ...util import iterable +from ...util import NonReducingUnitsContainer, iterable from ._compound_unit_helpers import BabelKwds, SortFunc, sort_by_unit_name from ._to_register import REGISTERED_FORMATTERS from .html import HTMLFormatter @@ -40,6 +40,11 @@ from ...registry import UnitRegistry +def _sort_func(items, registry): + # print([i for i in items]) + return items + + class FullFormatter(BaseFormatter): """A formatter that dispatch to other formatters. @@ -126,6 +131,8 @@ def format_unit( ) -> str: uspec = uspec or self.default_format sort_func = sort_func or self.default_sort_func + if isinstance(unit._units, NonReducingUnitsContainer): + sort_func = _sort_func return self.get_formatter(uspec).format_unit( unit, uspec, sort_func=sort_func, **babel_kwds ) diff --git a/pint/delegates/formatter/html.py b/pint/delegates/formatter/html.py index b8e3f517f..a29ffa5b1 100644 --- a/pint/delegates/formatter/html.py +++ b/pint/delegates/formatter/html.py @@ -104,7 +104,9 @@ def format_unit( division_fmt = localize_per(length, babel_kwds.get("locale"), "{}/{}") else: division_fmt = "{}/{}" + import pdb + pdb.set_trace() return formatter( numerator, denominator, diff --git a/pint/facets/plain/registry.py b/pint/facets/plain/registry.py index 6e9a43bc6..22898e1d6 100644 --- a/pint/facets/plain/registry.py +++ b/pint/facets/plain/registry.py @@ -63,7 +63,7 @@ from ...errors import DimensionalityError, RedefinitionError, UndefinedUnitError from ...pint_eval import build_eval_tree from ...util import ( - NonReducingUnitContainer, + NonReducingUnitsContainer, ParserHelper, UnitsContainer, _is_dim, @@ -1407,8 +1407,8 @@ def UnitsContainer(self, *args: Any, **kwargs: Any) -> UnitsContainer: __call__ = parse_expression - def NonReducingUnitContainer(self, units) -> UnitsContainer: - return NonReducingUnitContainer(units) + def NonReducingUnitsContainer(self, units) -> UnitsContainer: + return NonReducingUnitsContainer(units) class PlainRegistry(GenericPlainRegistry[PlainQuantity[Any], PlainUnit]): diff --git a/pint/testsuite/test_unit.py b/pint/testsuite/test_unit.py index 2156bbafd..d7c8f2034 100644 --- a/pint/testsuite/test_unit.py +++ b/pint/testsuite/test_unit.py @@ -832,6 +832,19 @@ def test_case_sensitivity(self): assert ureg.parse_units("j", case_sensitive=False) == UnitsContainer(joule=1) +class TestNonReducing(QuantityTestCase): + def test_init(self): + ureg = self.ureg + NRUC_ = ureg.NonReducingUnitsContainer + + mm_per_mm_unit = NRUC_([(ureg.mm, 1), (ureg.mm, -1)]) + assert mm_per_mm_unit.non_reduced_units == [ + ureg.UnitsContainer({"millimeter": 1}), + ureg.UnitsContainer({"millimeter": -1}), + ] + assert mm_per_mm_unit == ureg.dimensionless + + class TestCaseInsensitiveRegistry(QuantityTestCase): kwargs = dict(case_sensitive=False) diff --git a/pint/util.py b/pint/util.py index f8e851451..1f8e0da9d 100644 --- a/pint/util.py +++ b/pint/util.py @@ -676,26 +676,46 @@ def _normalize_nonfloat_value(self, value: Scalar) -> Scalar: return value -class NonReducingUnitContainer(UnitsContainer): - """The NonReducingUnitContainer stores UnitsContainers without simplifying common units. +class NonReducingUnitsContainer(UnitsContainer): + """The NonReducingUnitsContainer stores UnitsContainers without simplifying common units. This is useful when it is desired to show a unit in the numerator and denominator, eg mm/mm. """ def __init__( self, units: list[UnitsContainer] | list[tuple[QuantityOrUnitLike, Scalar]] ) -> None: - if all(isinstance(u, tuple) for u in units): - units = [u[0]._units ** u[1] for u in units] + self.non_reduced_units = [] - self.non_reduced_units = units + for u in units: + if isinstance(u, tuple): + u = u[0]._units ** u[1] + if hasattr(u, "_units"): + u = u._units + self.non_reduced_units.append(u) self.reduced_units = UnitsContainer() - for unit in units: + for unit in self.non_reduced_units: self.reduced_units *= unit self._d = self.reduced_units._d self._hash = self.reduced_units._hash + self.non_reduced_d_items = [ + (key, value) + for uc in self.non_reduced_units + for key, value in uc._d.items() + ] + self.i = 0 + + def __repr__(self) -> str: + tmp = "[%s]" % ", ".join( + [f"'{key}': {value}" for key, value in self.non_reduced_d_items] + ) + return f"" + + def unit_items(self) -> Iterable[tuple[str, Scalar]]: + return [items for _d in self.non_reduced_units for items in _d.items()] + class ParserHelper(UnitsContainer): """The ParserHelper stores in place the product of variables and From 6c89e09169825a805575561f1e36bb2063149721 Mon Sep 17 00:00:00 2001 From: andrewgsavage Date: Tue, 11 Jun 2024 20:36:20 +0100 Subject: [PATCH 05/11] format --- pint/delegates/formatter/_format_helpers.py | 5 ++++- pint/delegates/formatter/full.py | 6 +++++- pint/delegates/formatter/plain.py | 9 +++++++++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/pint/delegates/formatter/_format_helpers.py b/pint/delegates/formatter/_format_helpers.py index 995159e65..501c72c7a 100644 --- a/pint/delegates/formatter/_format_helpers.py +++ b/pint/delegates/formatter/_format_helpers.py @@ -161,6 +161,7 @@ def formatter( power_fmt: str = "{} ** {}", parentheses_fmt: str = "({0})", exp_call: FORMATTER = "{:n}".format, + empty_numerator_fmt = "1", ) -> str: """Format a list of (name, exponent) pairs. @@ -183,6 +184,8 @@ def formatter( the format used for parenthesis. (Default value = "({0})") exp_call : callable (Default value = lambda x: f"{x:n}") + empty_numerator_fmt : str + the format used for an empty numerator. (Default value = "1") Returns ------- @@ -218,7 +221,7 @@ def formatter( return join_u(product_fmt, pos_terms + neg_terms) # Show as Ratio: positive terms / negative terms - pos_ret = join_u(product_fmt, pos_terms) or "1" + pos_ret = join_u(product_fmt, pos_terms) or empty_numerator_fmt if not neg_terms: return pos_ret diff --git a/pint/delegates/formatter/full.py b/pint/delegates/formatter/full.py index 92609b713..c3bf49ef4 100644 --- a/pint/delegates/formatter/full.py +++ b/pint/delegates/formatter/full.py @@ -131,10 +131,14 @@ def format_unit( ) -> str: uspec = uspec or self.default_format sort_func = sort_func or self.default_sort_func + empty_numerator_fmt = "1" if isinstance(unit._units, NonReducingUnitsContainer): sort_func = _sort_func + empty_numerator_fmt = "" return self.get_formatter(uspec).format_unit( - unit, uspec, sort_func=sort_func, **babel_kwds + unit, uspec, sort_func=sort_func, + empty_numerator_fmt = empty_numerator_fmt, +**babel_kwds ) def format_quantity( diff --git a/pint/delegates/formatter/plain.py b/pint/delegates/formatter/plain.py index d40ec1ae0..d03c00959 100644 --- a/pint/delegates/formatter/plain.py +++ b/pint/delegates/formatter/plain.py @@ -81,6 +81,7 @@ def format_unit( unit: PlainUnit | Iterable[tuple[str, Any]], uspec: str = "", sort_func: SortFunc | None = None, + empty_numerator_fmt = "1", **babel_kwds: Unpack[BabelKwds], ) -> str: """Format a unit (can be compound) into string @@ -203,6 +204,7 @@ def format_unit( unit: PlainUnit | Iterable[tuple[str, Any]], uspec: str = "", sort_func: SortFunc | None = None, + empty_numerator_fmt = "1", **babel_kwds: Unpack[BabelKwds], ) -> str: numerator, denominator = prepare_compount_unit( @@ -225,6 +227,7 @@ def format_unit( division_fmt=division_fmt, power_fmt="{}**{}", parentheses_fmt=r"({})", + empty_numerator_fmt=empty_numerator_fmt, ) def format_quantity( @@ -313,12 +316,16 @@ def format_unit( unit: PlainUnit | Iterable[tuple[str, Any]], uspec: str = "", sort_func: SortFunc | None = None, + empty_numerator_fmt="1", + **babel_kwds: Unpack[BabelKwds], + ) -> str: numerator, denominator = prepare_compount_unit( unit, uspec, sort_func=sort_func, + **babel_kwds, registry=self._registry, ) @@ -339,6 +346,8 @@ def format_unit( power_fmt="{}{}", parentheses_fmt="({})", exp_call=pretty_fmt_exponent, + empty_numerator_fmt=empty_numerator_fmt, + ) def format_quantity( From a0f7a71e066d35c2817348e9d24ee9ad522e4d9b Mon Sep 17 00:00:00 2001 From: Andrew Date: Thu, 27 Jun 2024 11:50:55 +0100 Subject: [PATCH 06/11] add arg --- pint/delegates/formatter/_compound_unit_helpers.py | 1 + pint/delegates/formatter/plain.py | 12 ++++-------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/pint/delegates/formatter/_compound_unit_helpers.py b/pint/delegates/formatter/_compound_unit_helpers.py index c8ac1e2d9..46934c063 100644 --- a/pint/delegates/formatter/_compound_unit_helpers.py +++ b/pint/delegates/formatter/_compound_unit_helpers.py @@ -246,6 +246,7 @@ def prepare_compount_unit( locale: Locale | str | None = None, as_ratio: bool = True, registry: UnitRegistry | None = None, + empty_numerator_fmt="1", ) -> tuple[Iterable[tuple[str, T]], Iterable[tuple[str, T]]]: """Format compound unit into unit container given an spec and locale. diff --git a/pint/delegates/formatter/plain.py b/pint/delegates/formatter/plain.py index d03c00959..5cf77f3e0 100644 --- a/pint/delegates/formatter/plain.py +++ b/pint/delegates/formatter/plain.py @@ -81,7 +81,7 @@ def format_unit( unit: PlainUnit | Iterable[tuple[str, Any]], uspec: str = "", sort_func: SortFunc | None = None, - empty_numerator_fmt = "1", + empty_numerator_fmt="1", **babel_kwds: Unpack[BabelKwds], ) -> str: """Format a unit (can be compound) into string @@ -204,7 +204,7 @@ def format_unit( unit: PlainUnit | Iterable[tuple[str, Any]], uspec: str = "", sort_func: SortFunc | None = None, - empty_numerator_fmt = "1", + empty_numerator_fmt="1", **babel_kwds: Unpack[BabelKwds], ) -> str: numerator, denominator = prepare_compount_unit( @@ -316,16 +316,13 @@ def format_unit( unit: PlainUnit | Iterable[tuple[str, Any]], uspec: str = "", sort_func: SortFunc | None = None, - empty_numerator_fmt="1", - + empty_numerator_fmt="1", **babel_kwds: Unpack[BabelKwds], - ) -> str: numerator, denominator = prepare_compount_unit( unit, uspec, sort_func=sort_func, - **babel_kwds, registry=self._registry, ) @@ -346,8 +343,7 @@ def format_unit( power_fmt="{}{}", parentheses_fmt="({})", exp_call=pretty_fmt_exponent, - empty_numerator_fmt=empty_numerator_fmt, - + empty_numerator_fmt=empty_numerator_fmt, ) def format_quantity( From bcec387b40ff966148062de63306251bb158dd04 Mon Sep 17 00:00:00 2001 From: Andrew Date: Thu, 27 Jun 2024 11:53:19 +0100 Subject: [PATCH 07/11] lint --- pint/delegates/formatter/_format_helpers.py | 2 +- pint/delegates/formatter/full.py | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/pint/delegates/formatter/_format_helpers.py b/pint/delegates/formatter/_format_helpers.py index 065263126..429c0ba5f 100644 --- a/pint/delegates/formatter/_format_helpers.py +++ b/pint/delegates/formatter/_format_helpers.py @@ -163,7 +163,7 @@ def formatter( power_fmt: str = "{} ** {}", parentheses_fmt: str = "({0})", exp_call: FORMATTER = "{:n}".format, - empty_numerator_fmt = "1", + empty_numerator_fmt="1", ) -> str: """Format a list of (name, exponent) pairs. diff --git a/pint/delegates/formatter/full.py b/pint/delegates/formatter/full.py index 8a633b4c7..5133e0691 100644 --- a/pint/delegates/formatter/full.py +++ b/pint/delegates/formatter/full.py @@ -144,9 +144,11 @@ def format_unit( sort_func = _sort_func empty_numerator_fmt = "" return self.get_formatter(uspec).format_unit( - unit, uspec, sort_func=sort_func, - empty_numerator_fmt = empty_numerator_fmt, -**babel_kwds + unit, + uspec, + sort_func=sort_func, + empty_numerator_fmt=empty_numerator_fmt, + **babel_kwds, ) def format_quantity( From 315a9970ba52b435ad7f59c4db99902709baf3e0 Mon Sep 17 00:00:00 2001 From: Andrew Date: Thu, 27 Jun 2024 11:54:06 +0100 Subject: [PATCH 08/11] lint --- pint/delegates/formatter/html.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pint/delegates/formatter/html.py b/pint/delegates/formatter/html.py index a29ffa5b1..b8e3f517f 100644 --- a/pint/delegates/formatter/html.py +++ b/pint/delegates/formatter/html.py @@ -104,9 +104,7 @@ def format_unit( division_fmt = localize_per(length, babel_kwds.get("locale"), "{}/{}") else: division_fmt = "{}/{}" - import pdb - pdb.set_trace() return formatter( numerator, denominator, From 5fdf45def5998abf48b48ae43e7f6931abe1b708 Mon Sep 17 00:00:00 2001 From: Andrew Date: Sat, 29 Jun 2024 18:14:43 +0100 Subject: [PATCH 09/11] add reg flag, tests --- pint/facets/plain/registry.py | 23 ++++++++++++++++++----- pint/registry.py | 2 ++ pint/testsuite/test_unit.py | 12 ++++++++++++ pint/util.py | 33 +++++++++++++++++++++++++++------ 4 files changed, 59 insertions(+), 11 deletions(-) diff --git a/pint/facets/plain/registry.py b/pint/facets/plain/registry.py index 22898e1d6..7f5a499d3 100644 --- a/pint/facets/plain/registry.py +++ b/pint/facets/plain/registry.py @@ -218,6 +218,7 @@ def __init__( force_ndarray: bool = False, force_ndarray_like: bool = False, on_redefinition: str = "warn", + auto_reduce_units: bool = True, auto_reduce_dimensions: bool = False, autoconvert_to_preferred: bool = False, preprocessors: list[PreprocessorType] | None = None, @@ -262,6 +263,9 @@ def __init__( #: Action to take in case a unit is redefined. 'warn', 'raise', 'ignore' self._on_redefinition = on_redefinition + #: Determines if units should be reduced on appropriate operations. + self.auto_reduce_units = auto_reduce_units + #: Determines if dimensionality should be reduced on appropriate operations. self.auto_reduce_dimensions = auto_reduce_dimensions @@ -1259,7 +1263,6 @@ def _parse_units_as_container( if as_delta: cache[input_string] = ret - return ret def _eval_token( @@ -1403,12 +1406,22 @@ def _define_op(s: str): # TODO: Maybe in the future we need to change it to a more meaningful # non-colliding name. def UnitsContainer(self, *args: Any, **kwargs: Any) -> UnitsContainer: - return UnitsContainer(*args, non_int_type=self.non_int_type, **kwargs) + return UnitsContainer( + *args, + non_int_type=self.non_int_type, + auto_reduce_units=self.auto_reduce_units, + **kwargs, + ) - __call__ = parse_expression + def NonReducingUnitsContainer(self, *args: Any, **kwargs: Any) -> UnitsContainer: + return NonReducingUnitsContainer( + *args, + non_int_type=self.non_int_type, + auto_reduce_units=self.auto_reduce_units, + **kwargs, + ) - def NonReducingUnitsContainer(self, units) -> UnitsContainer: - return NonReducingUnitsContainer(units) + __call__ = parse_expression class PlainRegistry(GenericPlainRegistry[PlainQuantity[Any], PlainUnit]): diff --git a/pint/registry.py b/pint/registry.py index ceb9b62d1..c0c0e96ce 100644 --- a/pint/registry.py +++ b/pint/registry.py @@ -120,6 +120,7 @@ def __init__( autoconvert_offset_to_baseunit: bool = False, on_redefinition: str = "warn", system=None, + auto_reduce_units=True, auto_reduce_dimensions=False, autoconvert_to_preferred=False, preprocessors=None, @@ -136,6 +137,7 @@ def __init__( default_as_delta=default_as_delta, autoconvert_offset_to_baseunit=autoconvert_offset_to_baseunit, system=system, + auto_reduce_units=auto_reduce_units, auto_reduce_dimensions=auto_reduce_dimensions, autoconvert_to_preferred=autoconvert_to_preferred, preprocessors=preprocessors, diff --git a/pint/testsuite/test_unit.py b/pint/testsuite/test_unit.py index d7c8f2034..4921ef3ad 100644 --- a/pint/testsuite/test_unit.py +++ b/pint/testsuite/test_unit.py @@ -844,6 +844,18 @@ def test_init(self): ] assert mm_per_mm_unit == ureg.dimensionless + def test_ureg_auto_reduce_units(self): + ureg = UnitRegistry(auto_reduce_units=False) + NRUC_ = ureg.NonReducingUnitsContainer + + ureg.Unit("mm") / ureg.Unit("mm") == NRUC_([(ureg.mm, 1), (ureg.mm, -1)]) + + def test_formatting(self): + ureg = UnitRegistry(auto_reduce_units=False) + u = ureg.Unit("mm") / ureg.Unit("mm") + assert format(u, "~D") == "mm / mm" + assert format(u, "P") == "millimeter/millimeter" + class TestCaseInsensitiveRegistry(QuantityTestCase): kwargs = dict(case_sensitive=False) diff --git a/pint/util.py b/pint/util.py index 1f8e0da9d..9e2dc5b07 100644 --- a/pint/util.py +++ b/pint/util.py @@ -442,15 +442,20 @@ class UnitsContainer(Mapping[str, Scalar]): Numerical type used for non integer values. """ - __slots__ = ("_d", "_hash", "_one", "_non_int_type") + __slots__ = ("_d", "_hash", "_one", "_non_int_type", "_auto_reduce_units") _d: udict _hash: int | None _one: Scalar _non_int_type: type + _auto_reduce_units: bool def __init__( - self, *args: Any, non_int_type: type | None = None, **kwargs: Any + self, + *args: Any, + non_int_type: type | None = None, + auto_reduce_units: bool = True, + **kwargs: Any, ) -> None: if args and isinstance(args[0], UnitsContainer): default_non_int_type = args[0]._non_int_type @@ -458,6 +463,7 @@ def __init__( default_non_int_type = float self._non_int_type = non_int_type or default_non_int_type + self._auto_reduce_units = auto_reduce_units if self._non_int_type is float: self._one = 1 @@ -620,13 +626,20 @@ def __copy__(self): out._hash = self._hash out._non_int_type = self._non_int_type out._one = self._one + out._auto_reduce_units = self._auto_reduce_units return out + def __deepcopy__(self, memo): + return self.copy() + def __mul__(self, other: Any): - if not isinstance(other, self.__class__): + if not isinstance(other, UnitsContainer): err = "Cannot multiply UnitsContainer by {}" raise TypeError(err.format(type(other))) + if not self._auto_reduce_units: + return NonReducingUnitsContainer([self, other]) + new = self.copy() for key, value in other.items(): new._d[key] += value @@ -650,10 +663,13 @@ def __pow__(self, other: Any): return new def __truediv__(self, other: Any): - if not isinstance(other, self.__class__): + if not isinstance(other, UnitsContainer): err = "Cannot divide UnitsContainer by {}" raise TypeError(err.format(type(other))) + if not self._auto_reduce_units: + return NonReducingUnitsContainer([self, UnitsContainer({}) / other]) + new = self.copy() for key, value in other.items(): new._d[key] -= self._normalize_nonfloat_value(value) @@ -664,7 +680,7 @@ def __truediv__(self, other: Any): return new def __rtruediv__(self, other: Any): - if not isinstance(other, self.__class__) and other != 1: + if not isinstance(other, UnitsContainer) and other != 1: err = "Cannot divide {} by UnitsContainer" raise TypeError(err.format(type(other))) @@ -682,9 +698,14 @@ class NonReducingUnitsContainer(UnitsContainer): """ def __init__( - self, units: list[UnitsContainer] | list[tuple[QuantityOrUnitLike, Scalar]] + self, + units: list[UnitsContainer] | list[tuple[QuantityOrUnitLike, Scalar]], + non_int_type: type | None = None, + auto_reduce_units: bool = True, ) -> None: self.non_reduced_units = [] + self._non_int_type = non_int_type + self.auto_reduce_units = auto_reduce_units for u in units: if isinstance(u, tuple): From 3e5bfba1998989c2ecedd56452ffc85fb8ab342d Mon Sep 17 00:00:00 2001 From: Andrew Date: Sat, 29 Jun 2024 20:35:42 +0100 Subject: [PATCH 10/11] add reg flag, tests --- pint/testsuite/test_unit.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/pint/testsuite/test_unit.py b/pint/testsuite/test_unit.py index 4921ef3ad..7ce1a84e4 100644 --- a/pint/testsuite/test_unit.py +++ b/pint/testsuite/test_unit.py @@ -837,24 +837,29 @@ def test_init(self): ureg = self.ureg NRUC_ = ureg.NonReducingUnitsContainer - mm_per_mm_unit = NRUC_([(ureg.mm, 1), (ureg.mm, -1)]) - assert mm_per_mm_unit.non_reduced_units == [ + strain_unit_container = NRUC_([(ureg.mm, 1), (ureg.mm, -1)]) + assert strain_unit_container.non_reduced_units == [ ureg.UnitsContainer({"millimeter": 1}), ureg.UnitsContainer({"millimeter": -1}), ] - assert mm_per_mm_unit == ureg.dimensionless + assert strain_unit_container == ureg.dimensionless def test_ureg_auto_reduce_units(self): ureg = UnitRegistry(auto_reduce_units=False) NRUC_ = ureg.NonReducingUnitsContainer - ureg.Unit("mm") / ureg.Unit("mm") == NRUC_([(ureg.mm, 1), (ureg.mm, -1)]) + strain_unit = ureg.Unit("mm") / ureg.Unit("mm") + strain_unit == NRUC_([(ureg.mm, 1), (ureg.mm, -1)]) + strain_unit == ureg.Unit("dimensionless") + + strain_q = ureg.Quantity(1, "mm") / ureg.Quantity(1, "mm") + assert strain_q.units == strain_unit def test_formatting(self): ureg = UnitRegistry(auto_reduce_units=False) - u = ureg.Unit("mm") / ureg.Unit("mm") - assert format(u, "~D") == "mm / mm" - assert format(u, "P") == "millimeter/millimeter" + strain_unit = ureg.Unit("mm") / ureg.Unit("mm") + assert format(strain_unit, "~D") == "mm / mm" + assert format(strain_unit, "P") == "millimeter/millimeter" class TestCaseInsensitiveRegistry(QuantityTestCase): From af3cd64a5382d2ea67edafb1bb2acbe0de3982d7 Mon Sep 17 00:00:00 2001 From: andrewgsavage Date: Thu, 26 Feb 2026 21:28:35 +0000 Subject: [PATCH 11/11] Update pint/util.py Co-authored-by: mutricyl <118692416+mutricyl@users.noreply.github.com> --- pint/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pint/util.py b/pint/util.py index 9e2dc5b07..ed20aad99 100644 --- a/pint/util.py +++ b/pint/util.py @@ -705,7 +705,7 @@ def __init__( ) -> None: self.non_reduced_units = [] self._non_int_type = non_int_type - self.auto_reduce_units = auto_reduce_units + self._auto_reduce_units = auto_reduce_units for u in units: if isinstance(u, tuple):