Coverage for backend/idaes_service/solver/methods/units_handler.py: 81%

63 statements  

« prev     ^ index     » next       coverage.py v7.10.7, created at 2025-11-06 23:27 +0000

1from typing import Any, cast, NewType 

2from pyomo.environ import value 

3from pyomo.core.base.units_container import units, _PyomoUnit 

4from pyomo.core.base.var import Var 

5from pyomo.core.base.expression import Expression, ExpressionData 

6from pyomo.core.expr import ExpressionBase, NPV_ProductExpression 

7from pint import UnitRegistry 

8 

9ValueWithUnits = NewType("ValueWithUnits", NPV_ProductExpression) 

10 

11 

12def _get_pint_unit(unit: str) -> Any: 

13 """ 

14 Get the pint unit object 

15 """ 

16 pint_unit = getattr(units.pint_registry, unit, None) 

17 if pint_unit is None: 17 ↛ 18line 17 didn't jump to line 18 because the condition on line 17 was never true

18 raise AttributeError(f"Unit `{unit}` not found.") 

19 return pint_unit 

20 

21 

22def get_unit(unit: str) -> _PyomoUnit: 

23 """ 

24 Get the pint unit object 

25 @unit: str unit type 

26 @return: unit object 

27 """ 

28 return _PyomoUnit(_get_pint_unit(unit), units.pint_registry) 

29 

30 

31def attach_unit(value: float, unit: str | None) -> ValueWithUnits: 

32 """ 

33 Attach the unit to the value 

34 @value: float value 

35 @unit: str unit 

36 @return: value with unit attached 

37 """ 

38 value, unit = idaes_specific_convert( 

39 value, unit 

40 ) # make sure the value is in a unit that idaes supports 

41 if unit is None: 

42 return value 

43 pyomo_unit = get_unit(unit) 

44 return value * pyomo_unit 

45 

46 

47def check_units_equivalent(unit1: _PyomoUnit, unit2: _PyomoUnit) -> bool: 

48 """ 

49 Check if two units are equivalent 

50 """ 

51 if unit1 is None: 51 ↛ 52line 51 didn't jump to line 52 because the condition on line 51 was never true

52 unit1 = units.dimensionless 

53 if unit2 is None: 53 ↛ 54line 53 didn't jump to line 54 because the condition on line 53 was never true

54 unit2 = units.dimensionless 

55 return ( 

56 unit1._get_pint_unit().dimensionality 

57 == unit2._get_pint_unit().dimensionality 

58 ) 

59 

60 

61 

62def idaes_specific_convert(value: float, unit: str | None) -> tuple[float, str | None]: 

63 """ 

64 Convert the value to a unit that is specific to idaes 

65 (ie. idaes only supports K for temperature) 

66 @value: float value 

67 @unit: str unit 

68 @return: tuple: 

69 - (float) converted value 

70 - (str) new unit 

71 """ 

72 if unit in ["degC", "degR", "degF"]: # temperature units 

73 from_quantity = units.pint_registry.Quantity(value, unit) 

74 to_unit = units.pint_registry.K 

75 # can probably do the conversion/attachment in one step but haven't figured out how yet 

76 converted_value = from_quantity.to(to_unit) # pint.Quantity object 

77 return converted_value.magnitude, "K" 

78 if unit in ["percent"]: # dimensionless units 

79 from_quantity = units.pint_registry.Quantity(value, unit) 

80 to_unit = units.pint_registry.dimensionless 

81 converted_value = from_quantity.to(to_unit) 

82 return converted_value.magnitude, None 

83 if unit in [None, ""]: 

84 return value, None 

85 return value, unit 

86 

87 

88def get_attached_unit( 

89 var: Var | Expression | ExpressionBase | ExpressionData | float, 

90) -> _PyomoUnit | None: 

91 """ 

92 Get the unit of a variable. 

93 """ 

94 if isinstance(var, float): 94 ↛ 96line 94 didn't jump to line 96 because the condition on line 94 was never true

95 # no attached unit 

96 return units.dimensionless 

97 if isinstance(var, ExpressionData): 

98 var = var.parent_component() 

99 if var.is_indexed(): 

100 # we will get the unit from the first item in the indexed variable 

101 var = var[var.index_set().first()] 

102 if isinstance(var, (ExpressionBase, Expression, ExpressionData)): 

103 # handle expressions 

104 return units.get_units(var) 

105 else: 

106 # handle variables 

107 return var.get_units() 

108 

109 

110def get_attached_unit_str(var: Var | Expression | ExpressionBase) -> str: 

111 """ 

112 Get the unit of a variable as a string. 

113 """ 

114 unit = get_attached_unit(var) 

115 return str(unit) if unit is not None else "dimensionless" 

116 

117 

118def get_value(var: Var | Expression | ExpressionBase) -> float | dict: 

119 if var.is_indexed(): 119 ↛ 129line 119 didn't jump to line 129 because the condition on line 119 was always true

120 if isinstance(var, Var): 120 ↛ 124line 120 didn't jump to line 124 because the condition on line 120 was always true

121 # can get values directly 

122 return cast(dict, var.get_values()) 

123 else: 

124 data = {} 

125 for index in var.index_set(): 

126 data[index] = value(var[index]) 

127 return data 

128 else: 

129 return float(value(var))