Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplify composite non dimensionals #31

Merged
merged 9 commits into from
Sep 20, 2024
16 changes: 16 additions & 0 deletions src/property_utils/tests/api/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,19 @@ def test_sub(self):
@args({"p1": p(11, JOULE / KELVIN), "p2": p(10, JOULE / KELVIN), "op": ge})
def test_greater_or_equal(self):
self.assertResultTrue()

@args({"p1": p(1) ** 2, "p2": p(1), "op": add})
def test_non_dimensionals_addition(self):
self.assert_result("2.0 ")

@args({"p1": p(1) / p(1), "p2": p(1), "op": add})
def test_non_dimensionals_addition_2(self):
self.assert_result("2.0 ")

@args({"p1": p(1) / (p(1) ** 2), "p2": p(1), "op": add})
def test_non_dimensionals_addition_3(self):
self.assert_result("2.0 ")

@args({"p1": (p(1) ** 2) ** 3, "p2": p(1), "op": add})
def test_non_dimensionals_addition_4(self):
self.assert_result("2.0 ")
12 changes: 12 additions & 0 deletions src/property_utils/tests/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,14 @@ def si(cls) -> "Unit8":
return cls.h


class Unit9(MeasurementUnit):
NON_DIMENSIONAL = ""

@classmethod
def is_non_dimensional(cls) -> bool:
return True


class UnregisteredConverter(AbsoluteUnitConverter): ...


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


def dimension_9(power: float = 1) -> Dimension:
return Dimension(Unit9.NON_DIMENSIONAL, power)


def generic_dimension_1(power: float = 1) -> GenericDimension:
"""
Unit1^power
Expand Down
52 changes: 44 additions & 8 deletions src/property_utils/tests/units/test_descriptors.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,15 @@
Unit5,
Unit6,
Unit7,
Unit9,
dimension_1,
dimension_2,
dimension_3,
dimension_4,
dimension_5,
dimension_6,
dimension_7,
dimension_9,
generic_dimension_1,
generic_dimension_2,
generic_dimension_3,
Expand Down Expand Up @@ -68,14 +70,12 @@

@add_to(MeasurementUnitMeta_test_suite)
class TestMeasurementUnitMetaInverseGeneric(TestDescriptor):

def test_inverse_generic(self):
self.assertSequenceEqual(str(Unit1.inverse_generic()), " / Unit1", str)


@add_to(MeasurementUnitMeta_test_suite)
class TestMeasurementUnitMetaMultiplication(TestDescriptorBinaryOperation):

operator = mul
produced_type = GenericCompositeDimension

Expand Down Expand Up @@ -116,7 +116,6 @@ def test_with_int(self):

@add_to(MeasurementUnitMeta_test_suite)
class TestMeasurementUnitMetaDivision(TestDescriptorBinaryOperation):

operator = truediv
produced_type = GenericCompositeDimension

Expand Down Expand Up @@ -227,7 +226,6 @@ def test_with_generic_composite_dimension_same_numerator(self):

@add_to(MeasurementUnit_test_suite)
class TestMeasurementUnitFromDescriptor(TestDescriptor):

def subject(self, descriptor):
return MeasurementUnit.from_descriptor(descriptor)

Expand Down Expand Up @@ -266,7 +264,6 @@ def test_with_generic_composite_dimension(self):

@add_to(MeasurementUnit_test_suite)
class TestMeasurementUnitIsInstance(TestDescriptor):

def subject(self, generic):
return Unit1.A.isinstance(generic)

Expand Down Expand Up @@ -301,7 +298,6 @@ def test_with_generic_composite_dimension(self):

@add_to(MeasurementUnit_test_suite)
class TestMeasurementUnitIsInstanceEquivalent(TestDescriptor):

def subject(self, descriptor):
return Unit3.C.isinstance_equivalent(descriptor)

Expand Down Expand Up @@ -472,6 +468,22 @@ def test_with_none(self):
self.assertResultRaises(DescriptorExponentError)


@add_to(MeasurementUnit_test_suite)
class TestNonDimensionalMeasurementUnitExponentiation(TestDescriptor):
produced_type = Dimension

def subject(self, value):
return Unit9.NON_DIMENSIONAL**value

@args({"value": 2})
def test_with_positive_int(self):
self.assert_result("")

@args({"value": -10})
def test_with_negative_int(self):
self.assert_result("")


@add_to(AliasMeasurementUnit_test_suite)
class TestAliasMeasurementUnitFromDescriptor(TestDescriptor):
def subject(self, descriptor):
Expand Down Expand Up @@ -1378,6 +1390,19 @@ def subject(self, value):
return dimension_1() ** value


@add_to(Dimension_test_suite)
class TestNonDimensionalDimensionExponentiation(
TestNonDimensionalMeasurementUnitExponentiation
):
"""
Repeat all tests in `TestNonDimensionalMeasurementUnitExponentiation` but with
dimension_9() as descriptor.
"""

def subject(self, value):
return dimension_9() ** value


@add_to(Dimension_test_suite)
class TestExponentiatedDimensionExponentiation(TestDescriptor):
produced_type = Dimension
Expand Down Expand Up @@ -2006,6 +2031,7 @@ def test_with_aliased_denominator_composite_dimension(self):
def test_with_fully_aliased_composite_dimension(self):
self.assertResultTrue()


@add_to(GenericCompositeDimension_test_suite)
class TestGenericCompositeDimensionHasNoUnits(TestDescriptor):
def test_with_measurement_units(self):
Expand Down Expand Up @@ -2547,6 +2573,14 @@ def test_same_denominator_dimensions(self):
def test_same_numerator_dimensions_zero_sum(self):
self.assert_result(" / C")

@args({"composite": CompositeDimension([dimension_9()], [dimension_9()])})
def test_non_dimensionals(self):
self.assert_result("")

@args({"composite": CompositeDimension([dimension_9(3)], [dimension_9()])})
def test_non_dimensionals_exponentiated(self):
self.assert_result("")


@add_to(CompositeDimension_test_suite)
class TestCompositeDimensionSimplified(TestCompositeDimensionSimplify):
Expand Down Expand Up @@ -2576,6 +2610,7 @@ def test_objects_are_not_persisted(self):
self.assertNotEqual(ids(composite.numerator), ids(inverse.denominator))
self.assertNotEqual(ids(composite.denominator), ids(inverse.numerator))


@add_to(CompositeDimension_test_suite)
class TestCompositeDimensionHasNoUnits(TestDescriptor):
def test_with_units(self):
Expand All @@ -2585,10 +2620,11 @@ def test_with_no_units(self):
self.assertTrue(CompositeDimension().has_no_units())

def test_with_same_unit_type(self):
self.assertFalse(CompositeDimension([Unit1.A],[Unit1.a]).has_no_units())
self.assertFalse(CompositeDimension([Unit1.A], [Unit1.a]).has_no_units())

def test_with_same_unit(self):
self.assertFalse(CompositeDimension([Unit1.A],[Unit1.A]).has_no_units())
self.assertFalse(CompositeDimension([Unit1.A], [Unit1.A]).has_no_units())


@add_to(CompositeDimension_test_suite)
class TestCompositeDimensionMultiplication(TestDescriptorBinaryOperation):
Expand Down
16 changes: 13 additions & 3 deletions src/property_utils/units/converters.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from property_utils.units.descriptors import UnitDescriptor
from property_utils.exceptions.units.converter_types import UnitConversionError
from property_utils.units.units import (
NonDimensionalUnit,
RelativeTemperatureUnit,
AbsoluteTemperatureUnit,
LengthUnit,
Expand Down Expand Up @@ -94,9 +95,7 @@ def inverse(self) -> float:


@register_converter(RelativeTemperatureUnit)
class RelativeTemperatureUnitConverter(
RelativeUnitConverter
): # pylint: disable=too-few-public-methods
class RelativeTemperatureUnitConverter(RelativeUnitConverter): # pylint: disable=too-few-public-methods
"""
Convert temperature units with this converter.

Expand Down Expand Up @@ -155,6 +154,17 @@ def convert(
return value * cls.get_factor(from_descriptor, to_descriptor)


@register_converter(NonDimensionalUnit)
class NonDimensionalUnitConverter(AbsoluteUnitConverter):
"""
This converter is needed for compatibility, i.e. for conversions to work from
non-dimensional units to non-dimensional dimensions.
"""

reference_unit = NonDimensionalUnit.NON_DIMENSIONAL
conversion_map = {NonDimensionalUnit.NON_DIMENSIONAL: 1}


@register_converter(LengthUnit)
class LengthUnitConverter(AbsoluteUnitConverter):
"""
Expand Down
27 changes: 26 additions & 1 deletion src/property_utils/units/descriptors.py
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,22 @@ def si(cls) -> "MeasurementUnit":
"""
raise NotImplementedError

@classmethod
def is_non_dimensional(cls) -> bool:
"""
Implement this function for defined measurement units that are non dimensional.

Examples:
>>> class NonDimensionalUnit(MeasurementUnit):
... NON_DIMENSIONAL = ""
... @classmethod
... def is_non_dimensional(cls) -> bool: return True

>>> NonDimensionalUnit.is_non_dimensional()
True
"""
return False

@staticmethod
def from_descriptor(descriptor: UnitDescriptor) -> "MeasurementUnit":
"""
Expand Down Expand Up @@ -468,6 +484,9 @@ def __pow__(self, power: float) -> "Dimension":
>>> LengthUnit.FEET**3
<Dimension: ft^3>
"""
# always keep non dimensional units to the first power
power = 1 if self.is_non_dimensional() else power

return Dimension(self, power)

def __hash__(self) -> int:
Expand Down Expand Up @@ -1009,7 +1028,10 @@ def __pow__(self, power: float) -> "Dimension":
f"invalid exponent: {{ value: {power}, type: {type(power)} }};"
" expected float or int. "
)
self.power *= power
if self.unit.is_non_dimensional():
self.power = 1
else:
self.power *= power
return self

def __eq__(self, dimension) -> bool:
Expand Down Expand Up @@ -1701,6 +1723,9 @@ def simplify(self) -> None:
numerator = []
denominator = []
for unit, exponent in exponents.items():
if unit.is_non_dimensional():
continue # do not add non dimensional units to the simplified composite

if exponent > 0:
numerator.append(Dimension(unit) ** exponent)
elif exponent < 0:
Expand Down
4 changes: 4 additions & 0 deletions src/property_utils/units/units.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ class NonDimensionalUnit(MeasurementUnit):
def si(cls) -> "NonDimensionalUnit":
return cls.NON_DIMENSIONAL

@classmethod
def is_non_dimensional(cls) -> bool:
return True


class RelativeTemperatureUnit(MeasurementUnit):
CELCIUS = "°C"
Expand Down
Loading