|
2 | 2 | This module defines the Property class and property arithmetics. |
3 | 3 | """ |
4 | 4 |
|
5 | | -from dataclasses import dataclass |
| 5 | +from dataclasses import dataclass, replace |
6 | 6 | from typing import Type, Optional |
7 | 7 | from math import isclose |
8 | 8 |
|
@@ -209,8 +209,9 @@ def __mul__(self, other) -> "Property": |
209 | 209 | if isinstance(other, (float, int)): |
210 | 210 | return Property(self.value * other, self.unit) |
211 | 211 | if isinstance(other, Property): |
| 212 | + _other = self._unit_preconversion(other) |
212 | 213 | return Property( |
213 | | - self.value * other.value, (self.unit * other.unit).simplified() |
| 214 | + self.value * _other.value, (self.unit * _other.unit).simplified() |
214 | 215 | ) |
215 | 216 | raise PropertyBinaryOperationError( |
216 | 217 | f"cannot multiply {self} with {other}; " |
@@ -609,3 +610,114 @@ def _validate_comparison_input(self, other) -> None: |
609 | 610 | f"cannot compare ({other}) to ({self}); " |
610 | 611 | f"({other}) must have ({self.unit.to_generic()}) units. " |
611 | 612 | ) |
| 613 | + |
| 614 | + def _unit_preconversion(self, prop: "Property") -> "Property": |
| 615 | + """ |
| 616 | + Applies a conversion to the given property's units before it is multiplied or |
| 617 | + divided with this unit. |
| 618 | +
|
| 619 | + The preconversion is needed to produce simplified units from the multiplication/ |
| 620 | + division. |
| 621 | + For example, if you multiply 5 cm with 2.02 m you don't want to get the result |
| 622 | + in cm * m; in order to get the result in cm^2, 2.02 m (the right operand) is |
| 623 | + converted to cm first. |
| 624 | + """ |
| 625 | + if isinstance(prop.unit, CompositeDimension): |
| 626 | + return prop.to_unit(self._composite_unit_preconversion(prop.unit)) |
| 627 | + |
| 628 | + if isinstance(prop.unit, Dimension): |
| 629 | + return prop.to_unit(self._dimension_unit_preconversion(prop.unit)) |
| 630 | + |
| 631 | + if isinstance(prop.unit, MeasurementUnit): |
| 632 | + return prop.to_unit(self._simple_unit_preconversion(prop.unit)) |
| 633 | + |
| 634 | + return prop |
| 635 | + |
| 636 | + # pylint: disable=too-many-branches |
| 637 | + def _composite_unit_preconversion( |
| 638 | + self, unit: CompositeDimension |
| 639 | + ) -> CompositeDimension: |
| 640 | + """ |
| 641 | + Returns the composite dimension that the given dimension should be converted to |
| 642 | + before multiplication or division with this property. |
| 643 | + """ |
| 644 | + other = replace(unit).simplified() |
| 645 | + |
| 646 | + if isinstance(self.unit, CompositeDimension): |
| 647 | + self.unit.simplify() |
| 648 | + |
| 649 | + for i, num in enumerate(other.numerator): |
| 650 | + _n = self.unit.get_numerator(num.to_generic(), None) |
| 651 | + if _n is not None: |
| 652 | + other.numerator[i] = replace(_n) |
| 653 | + |
| 654 | + d = self.unit.get_denominator(num.to_generic(), None) |
| 655 | + if d is not None: |
| 656 | + other.numerator[i] = replace(d) |
| 657 | + |
| 658 | + for i, d in enumerate(other.denominator): |
| 659 | + _d = self.unit.get_denominator(d.to_generic(), None) |
| 660 | + if _d is not None: |
| 661 | + other.denominator[i] = replace(_d) |
| 662 | + |
| 663 | + n = self.unit.get_numerator(d.to_generic(), None) |
| 664 | + if n is not None: |
| 665 | + other.denominator[i] = replace(n) |
| 666 | + |
| 667 | + return other |
| 668 | + |
| 669 | + _self: UnitDescriptor |
| 670 | + if isinstance(self.unit, MeasurementUnit): |
| 671 | + _self = self.unit**1 |
| 672 | + elif isinstance(self.unit, Dimension): |
| 673 | + _self = replace(self.unit) |
| 674 | + else: |
| 675 | + _self = self.unit |
| 676 | + |
| 677 | + if isinstance(_self, Dimension): |
| 678 | + for i, n in enumerate(other.numerator): |
| 679 | + if n.unit.isinstance(_self.unit.to_generic()): |
| 680 | + other.numerator[i] = _self.unit ** other.numerator[i].power |
| 681 | + return other |
| 682 | + |
| 683 | + for i, d in enumerate(other.denominator): |
| 684 | + if d.unit.isinstance(_self.unit.to_generic()): |
| 685 | + other.denominator[i] = _self.unit ** other.denominator[i].power |
| 686 | + return other |
| 687 | + |
| 688 | + return unit |
| 689 | + |
| 690 | + def _dimension_unit_preconversion(self, unit: Dimension) -> Dimension: |
| 691 | + """ |
| 692 | + Returns the dimension that the given dimension should be converted to before |
| 693 | + multiplication or division with this property. |
| 694 | + """ |
| 695 | + if isinstance(self.unit, CompositeDimension): |
| 696 | + self.unit.simplify() |
| 697 | + |
| 698 | + for d in self.unit.denominator: |
| 699 | + if d.unit.isinstance(unit.unit.to_generic()): |
| 700 | + return d.unit**unit.power |
| 701 | + |
| 702 | + for n in self.unit.numerator: |
| 703 | + if n.unit.isinstance(unit.unit.to_generic()): |
| 704 | + return n.unit**unit.power |
| 705 | + |
| 706 | + _self: UnitDescriptor |
| 707 | + if isinstance(self.unit, Dimension): |
| 708 | + _self = self.unit.unit |
| 709 | + else: |
| 710 | + _self = self.unit |
| 711 | + |
| 712 | + if isinstance(_self, MeasurementUnit): |
| 713 | + if _self.isinstance(unit.unit.to_generic()): |
| 714 | + return _self**unit.power |
| 715 | + |
| 716 | + return unit |
| 717 | + |
| 718 | + def _simple_unit_preconversion(self, unit: MeasurementUnit) -> MeasurementUnit: |
| 719 | + """ |
| 720 | + Returns the unit that the given unit should be converted to before |
| 721 | + multiplication or division with this property. |
| 722 | + """ |
| 723 | + return self._dimension_unit_preconversion(unit**1).unit |
0 commit comments