Skip to content

Commit 3a1fa08

Browse files
committed
Merge branch '30-composite-non-dimensionals'
2 parents 80acd01 + f1589e4 commit 3a1fa08

File tree

6 files changed

+115
-12
lines changed

6 files changed

+115
-12
lines changed

src/property_utils/tests/api/test_api.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,19 @@ def test_sub(self):
4242
@args({"p1": p(11, JOULE / KELVIN), "p2": p(10, JOULE / KELVIN), "op": ge})
4343
def test_greater_or_equal(self):
4444
self.assertResultTrue()
45+
46+
@args({"p1": p(1) ** 2, "p2": p(1), "op": add})
47+
def test_non_dimensionals_addition(self):
48+
self.assert_result("2.0 ")
49+
50+
@args({"p1": p(1) / p(1), "p2": p(1), "op": add})
51+
def test_non_dimensionals_addition_2(self):
52+
self.assert_result("2.0 ")
53+
54+
@args({"p1": p(1) / (p(1) ** 2), "p2": p(1), "op": add})
55+
def test_non_dimensionals_addition_3(self):
56+
self.assert_result("2.0 ")
57+
58+
@args({"p1": (p(1) ** 2) ** 3, "p2": p(1), "op": add})
59+
def test_non_dimensionals_addition_4(self):
60+
self.assert_result("2.0 ")

src/property_utils/tests/data.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,14 @@ def si(cls) -> "Unit8":
115115
return cls.h
116116

117117

118+
class Unit9(MeasurementUnit):
119+
NON_DIMENSIONAL = ""
120+
121+
@classmethod
122+
def is_non_dimensional(cls) -> bool:
123+
return True
124+
125+
118126
class UnregisteredConverter(AbsoluteUnitConverter): ...
119127

120128

@@ -262,6 +270,10 @@ def dimension_7(power: float = 1) -> Dimension:
262270
return Dimension(Unit7.G, power)
263271

264272

273+
def dimension_9(power: float = 1) -> Dimension:
274+
return Dimension(Unit9.NON_DIMENSIONAL, power)
275+
276+
265277
def generic_dimension_1(power: float = 1) -> GenericDimension:
266278
"""
267279
Unit1^power

src/property_utils/tests/units/test_descriptors.py

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,15 @@
2929
Unit5,
3030
Unit6,
3131
Unit7,
32+
Unit9,
3233
dimension_1,
3334
dimension_2,
3435
dimension_3,
3536
dimension_4,
3637
dimension_5,
3738
dimension_6,
3839
dimension_7,
40+
dimension_9,
3941
generic_dimension_1,
4042
generic_dimension_2,
4143
generic_dimension_3,
@@ -68,14 +70,12 @@
6870

6971
@add_to(MeasurementUnitMeta_test_suite)
7072
class TestMeasurementUnitMetaInverseGeneric(TestDescriptor):
71-
7273
def test_inverse_generic(self):
7374
self.assertSequenceEqual(str(Unit1.inverse_generic()), " / Unit1", str)
7475

7576

7677
@add_to(MeasurementUnitMeta_test_suite)
7778
class TestMeasurementUnitMetaMultiplication(TestDescriptorBinaryOperation):
78-
7979
operator = mul
8080
produced_type = GenericCompositeDimension
8181

@@ -116,7 +116,6 @@ def test_with_int(self):
116116

117117
@add_to(MeasurementUnitMeta_test_suite)
118118
class TestMeasurementUnitMetaDivision(TestDescriptorBinaryOperation):
119-
120119
operator = truediv
121120
produced_type = GenericCompositeDimension
122121

@@ -227,7 +226,6 @@ def test_with_generic_composite_dimension_same_numerator(self):
227226

228227
@add_to(MeasurementUnit_test_suite)
229228
class TestMeasurementUnitFromDescriptor(TestDescriptor):
230-
231229
def subject(self, descriptor):
232230
return MeasurementUnit.from_descriptor(descriptor)
233231

@@ -266,7 +264,6 @@ def test_with_generic_composite_dimension(self):
266264

267265
@add_to(MeasurementUnit_test_suite)
268266
class TestMeasurementUnitIsInstance(TestDescriptor):
269-
270267
def subject(self, generic):
271268
return Unit1.A.isinstance(generic)
272269

@@ -301,7 +298,6 @@ def test_with_generic_composite_dimension(self):
301298

302299
@add_to(MeasurementUnit_test_suite)
303300
class TestMeasurementUnitIsInstanceEquivalent(TestDescriptor):
304-
305301
def subject(self, descriptor):
306302
return Unit3.C.isinstance_equivalent(descriptor)
307303

@@ -472,6 +468,22 @@ def test_with_none(self):
472468
self.assertResultRaises(DescriptorExponentError)
473469

474470

471+
@add_to(MeasurementUnit_test_suite)
472+
class TestNonDimensionalMeasurementUnitExponentiation(TestDescriptor):
473+
produced_type = Dimension
474+
475+
def subject(self, value):
476+
return Unit9.NON_DIMENSIONAL**value
477+
478+
@args({"value": 2})
479+
def test_with_positive_int(self):
480+
self.assert_result("")
481+
482+
@args({"value": -10})
483+
def test_with_negative_int(self):
484+
self.assert_result("")
485+
486+
475487
@add_to(AliasMeasurementUnit_test_suite)
476488
class TestAliasMeasurementUnitFromDescriptor(TestDescriptor):
477489
def subject(self, descriptor):
@@ -1378,6 +1390,19 @@ def subject(self, value):
13781390
return dimension_1() ** value
13791391

13801392

1393+
@add_to(Dimension_test_suite)
1394+
class TestNonDimensionalDimensionExponentiation(
1395+
TestNonDimensionalMeasurementUnitExponentiation
1396+
):
1397+
"""
1398+
Repeat all tests in `TestNonDimensionalMeasurementUnitExponentiation` but with
1399+
dimension_9() as descriptor.
1400+
"""
1401+
1402+
def subject(self, value):
1403+
return dimension_9() ** value
1404+
1405+
13811406
@add_to(Dimension_test_suite)
13821407
class TestExponentiatedDimensionExponentiation(TestDescriptor):
13831408
produced_type = Dimension
@@ -2006,6 +2031,7 @@ def test_with_aliased_denominator_composite_dimension(self):
20062031
def test_with_fully_aliased_composite_dimension(self):
20072032
self.assertResultTrue()
20082033

2034+
20092035
@add_to(GenericCompositeDimension_test_suite)
20102036
class TestGenericCompositeDimensionHasNoUnits(TestDescriptor):
20112037
def test_with_measurement_units(self):
@@ -2547,6 +2573,14 @@ def test_same_denominator_dimensions(self):
25472573
def test_same_numerator_dimensions_zero_sum(self):
25482574
self.assert_result(" / C")
25492575

2576+
@args({"composite": CompositeDimension([dimension_9()], [dimension_9()])})
2577+
def test_non_dimensionals(self):
2578+
self.assert_result("")
2579+
2580+
@args({"composite": CompositeDimension([dimension_9(3)], [dimension_9()])})
2581+
def test_non_dimensionals_exponentiated(self):
2582+
self.assert_result("")
2583+
25502584

25512585
@add_to(CompositeDimension_test_suite)
25522586
class TestCompositeDimensionSimplified(TestCompositeDimensionSimplify):
@@ -2576,6 +2610,7 @@ def test_objects_are_not_persisted(self):
25762610
self.assertNotEqual(ids(composite.numerator), ids(inverse.denominator))
25772611
self.assertNotEqual(ids(composite.denominator), ids(inverse.numerator))
25782612

2613+
25792614
@add_to(CompositeDimension_test_suite)
25802615
class TestCompositeDimensionHasNoUnits(TestDescriptor):
25812616
def test_with_units(self):
@@ -2585,10 +2620,11 @@ def test_with_no_units(self):
25852620
self.assertTrue(CompositeDimension().has_no_units())
25862621

25872622
def test_with_same_unit_type(self):
2588-
self.assertFalse(CompositeDimension([Unit1.A],[Unit1.a]).has_no_units())
2623+
self.assertFalse(CompositeDimension([Unit1.A], [Unit1.a]).has_no_units())
25892624

25902625
def test_with_same_unit(self):
2591-
self.assertFalse(CompositeDimension([Unit1.A],[Unit1.A]).has_no_units())
2626+
self.assertFalse(CompositeDimension([Unit1.A], [Unit1.A]).has_no_units())
2627+
25922628

25932629
@add_to(CompositeDimension_test_suite)
25942630
class TestCompositeDimensionMultiplication(TestDescriptorBinaryOperation):

src/property_utils/units/converters.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from property_utils.units.descriptors import UnitDescriptor
1414
from property_utils.exceptions.units.converter_types import UnitConversionError
1515
from property_utils.units.units import (
16+
NonDimensionalUnit,
1617
RelativeTemperatureUnit,
1718
AbsoluteTemperatureUnit,
1819
LengthUnit,
@@ -94,9 +95,7 @@ def inverse(self) -> float:
9495

9596

9697
@register_converter(RelativeTemperatureUnit)
97-
class RelativeTemperatureUnitConverter(
98-
RelativeUnitConverter
99-
): # pylint: disable=too-few-public-methods
98+
class RelativeTemperatureUnitConverter(RelativeUnitConverter): # pylint: disable=too-few-public-methods
10099
"""
101100
Convert temperature units with this converter.
102101
@@ -155,6 +154,17 @@ def convert(
155154
return value * cls.get_factor(from_descriptor, to_descriptor)
156155

157156

157+
@register_converter(NonDimensionalUnit)
158+
class NonDimensionalUnitConverter(AbsoluteUnitConverter):
159+
"""
160+
This converter is needed for compatibility, i.e. for conversions to work from
161+
non-dimensional units to non-dimensional dimensions.
162+
"""
163+
164+
reference_unit = NonDimensionalUnit.NON_DIMENSIONAL
165+
conversion_map = {NonDimensionalUnit.NON_DIMENSIONAL: 1}
166+
167+
158168
@register_converter(LengthUnit)
159169
class LengthUnitConverter(AbsoluteUnitConverter):
160170
"""

src/property_utils/units/descriptors.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,22 @@ def si(cls) -> "MeasurementUnit":
320320
"""
321321
raise NotImplementedError
322322

323+
@classmethod
324+
def is_non_dimensional(cls) -> bool:
325+
"""
326+
Implement this function for defined measurement units that are non dimensional.
327+
328+
Examples:
329+
>>> class NonDimensionalUnit(MeasurementUnit):
330+
... NON_DIMENSIONAL = ""
331+
... @classmethod
332+
... def is_non_dimensional(cls) -> bool: return True
333+
334+
>>> NonDimensionalUnit.is_non_dimensional()
335+
True
336+
"""
337+
return False
338+
323339
@staticmethod
324340
def from_descriptor(descriptor: UnitDescriptor) -> "MeasurementUnit":
325341
"""
@@ -468,6 +484,9 @@ def __pow__(self, power: float) -> "Dimension":
468484
>>> LengthUnit.FEET**3
469485
<Dimension: ft^3>
470486
"""
487+
# always keep non dimensional units to the first power
488+
power = 1 if self.is_non_dimensional() else power
489+
471490
return Dimension(self, power)
472491

473492
def __hash__(self) -> int:
@@ -1009,7 +1028,10 @@ def __pow__(self, power: float) -> "Dimension":
10091028
f"invalid exponent: {{ value: {power}, type: {type(power)} }};"
10101029
" expected float or int. "
10111030
)
1012-
self.power *= power
1031+
if self.unit.is_non_dimensional():
1032+
self.power = 1
1033+
else:
1034+
self.power *= power
10131035
return self
10141036

10151037
def __eq__(self, dimension) -> bool:
@@ -1701,6 +1723,9 @@ def simplify(self) -> None:
17011723
numerator = []
17021724
denominator = []
17031725
for unit, exponent in exponents.items():
1726+
if unit.is_non_dimensional():
1727+
continue # do not add non dimensional units to the simplified composite
1728+
17041729
if exponent > 0:
17051730
numerator.append(Dimension(unit) ** exponent)
17061731
elif exponent < 0:

src/property_utils/units/units.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ class NonDimensionalUnit(MeasurementUnit):
3434
def si(cls) -> "NonDimensionalUnit":
3535
return cls.NON_DIMENSIONAL
3636

37+
@classmethod
38+
def is_non_dimensional(cls) -> bool:
39+
return True
40+
3741

3842
class RelativeTemperatureUnit(MeasurementUnit):
3943
CELCIUS = "°C"

0 commit comments

Comments
 (0)