Coverage for backend/django/idaes_factory/unit_conversion/unit_conversion.py: 82%
44 statements
« prev ^ index » next coverage.py v7.10.7, created at 2026-07-22 05:22 +0000
« prev ^ index » next coverage.py v7.10.7, created at 2026-07-22 05:22 +0000
1# units are provided by `pint` library
2# see https://github.com/hgrecco/pint/
4from typing import cast
5from pint import UnitRegistry, Unit, Quantity
6from ahuora_builder_types.units import register_project_units
8pint_registry = UnitRegistry()
9register_project_units(pint_registry.define)
12def get_unit(unit: str | None) -> Unit | None:
13 """
14 Get the pint unit object
15 @unit: str unit type
16 @return: unit object
17 """
18 if unit is None or unit == "":
19 return None
20 pint_unit = getattr(pint_registry, unit, None)
21 if pint_unit is None: 21 ↛ 22line 21 didn't jump to line 22 because the condition on line 21 was never true
22 raise AttributeError(f'Unit `{unit}` not found.')
23 return cast(Unit, pint_unit)
26def convert_value(
27 value: float,
28 from_unit: str | None = None,
29 to_unit: str | None = None,
30 ) -> float:
31 """
32 convert value from one unit to another
33 @value: float value in original units
34 @from_unit: str unit
35 @to_unit: str unit
36 @return: float value converted to new unit
37 """
38 if from_unit in (None, "") and to_unit in (None, ""): 38 ↛ 39line 38 didn't jump to line 39 because the condition on line 38 was never true
39 return value
40 if from_unit == to_unit:
41 return value
43 p_from_unit = get_unit(from_unit)
44 p_to_unit = get_unit(to_unit)
45 if p_from_unit is None or p_to_unit is None or p_from_unit == p_to_unit:
46 # no conversion needed
47 return value
48 try:
49 from_quantity = pint_registry.Quantity(value, p_from_unit)
50 to_quantity = from_quantity.to(p_to_unit)
51 return cast(float, to_quantity.magnitude)
52 except Exception as e:
53 raise ValueError(f"Could not perform unit conversion from {from_unit} to {to_unit}: {str(e)}")
55def is_offset_unit(quantity:Quantity ) -> bool:
56 """
57 Check if a unit is an offset unit (e.g. °C, °F)
59 These units are compatible but do not have the same zero point.
60 """
61 unit = quantity.units
62 # TODO: Support compound offset units such as degC/minute.
63 return (unit == pint_registry.degC or unit == pint_registry.degF or unit == pint_registry.degR or unit == pint_registry.K)
65def can_convert(
66 from_unit: str | None = None,
67 to_unit: str | None = None,
68 ) -> bool:
69 """
70 Check if pint can convert from one unit to another
71 """
72 if from_unit in (None, ""): 72 ↛ 73line 72 didn't jump to line 73 because the condition on line 72 was never true
73 from_unit = "dimensionless"
74 if to_unit in (None, ""):
75 to_unit = "dimensionless"
76 if from_unit == to_unit:
77 return True
79 from_unit = pint_registry(from_unit)
80 to_unit = pint_registry(to_unit)
82 # We can convert if:
83 # - they are both pint-compatible
84 # - they are either both offset units or both relative units
85 return is_offset_unit(from_unit) == is_offset_unit(to_unit) and from_unit.check(to_unit)
87def subtract_fraction(value: float, unit: str):
88 converted_value = convert_value(value, unit, "dimensionless")
89 calculated_value = 1 - converted_value
90 return convert_value(calculated_value, "dimensionless", unit)