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

1# units are provided by `pint` library 

2# see https://github.com/hgrecco/pint/ 

3 

4from typing import cast 

5from pint import UnitRegistry, Unit, Quantity 

6from ahuora_builder_types.units import register_project_units 

7 

8pint_registry = UnitRegistry() 

9register_project_units(pint_registry.define) 

10 

11 

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) 

24 

25 

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 

42 

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)}") 

54 

55def is_offset_unit(quantity:Quantity ) -> bool: 

56 """ 

57 Check if a unit is an offset unit (e.g. °C, °F) 

58 

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) 

64 

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 

78 

79 from_unit = pint_registry(from_unit) 

80 to_unit = pint_registry(to_unit) 

81 

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) 

86 

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)