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
« 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
9ValueWithUnits = NewType("ValueWithUnits", NPV_ProductExpression)
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
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)
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
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 )
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
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()
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"
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))