Coverage for backend/ahuora-builder/src/ahuora_builder/tear_manager.py: 91%

62 statements  

« prev     ^ index     » next       coverage.py v7.10.7, created at 2026-07-22 05:22 +0000

1from pyomo.environ import value 

2from pyomo.network import Arc 

3from idaes.core.util.tables import _get_state_from_port 

4from pyomo.core.base.expression import ScalarExpression 

5from ahuora_builder_types.arc_schema import ArcSchema, TearGuessSchema 

6from .flowsheet_manager_type import FlowsheetManager 

7from ahuora_builder_types import PortId 

8from .methods.adapter import fix_var 

9 

10 

11class TearManager: 

12 """ 

13 Manages the tears in the flowsheet 

14 """ 

15 

16 def __init__(self, flowsheet_manager: FlowsheetManager): 

17 """ 

18 Create a new tear manager 

19 """ 

20 self._flowsheet_manager = flowsheet_manager 

21 self._tears: list[Arc] = [] 

22 

23 def load(self): 

24 """ 

25 Load all the tears (from the recycle unitops) 

26 """ 

27 schema = self._flowsheet_manager.schema 

28 

29 for arc_schema in schema.arcs: 

30 if arc_schema.tear_guess: 

31 self.add_tear(arc_schema) 

32 

33 def add_tear(self, arc_schema: ArcSchema): 

34 """ 

35 Add a tear to the flowsheet 

36 """ 

37 portId = arc_schema.destination 

38 guess = arc_schema.tear_guess 

39 

40 inlet_port = self._flowsheet_manager.ports.get_port(portId) 

41 arc = inlet_port.arcs()[0] 

42 self._tears.append(arc) 

43 

44 """ 

45 During model loading, we add in all the constraints and fix the variables 

46 for this port. Since it is a tear, things need to be deactivated/unfixed 

47 to ensure 0 degrees of freedom 

48 

49 If the state block is defined by constraints, we need to solve it to get the 

50 correct values for the state variables. Then we deactivate the constraints 

51 since we will be fixing the state variables instead (where applicable). 

52 """ 

53 

54 # hardcoding time indexes to [0] for now 

55 time_indexes = [0] 

56 

57 # need to get the correct value for state variables before running 

58 # sequential decomposition (since all state variables are fixed 

59 # during sequential decomposition). 

60 sb = _get_state_from_port(inlet_port, time_indexes[0]) 

61 for s_var_key in sb.define_state_vars(): 

62 s_var = getattr(sb, s_var_key) 

63 if isinstance(s_var, ScalarExpression): 63 ↛ 64line 63 didn't jump to line 64 because the condition on line 63 was never true

64 continue 

65 s_var.unfix() 

66 

67 var_map = { 

68 "flow_mol": "flow_mol", 

69 "flow_mass": "flow_mol", 

70 "temperature": "temperature", 

71 "pressure": "pressure", 

72 "mole_frac_comp": "mole_frac_comp", 

73 "enth_mol": "enth_mol", # some tests use this 

74 "vapor_frac": "temperature", # some tests us this too, but not really supported. 

75 } 

76 

77 inlet_port_schema = self._flowsheet_manager.ports.get_port_schema(portId) 

78 blk = sb.parent_component() 

79 blk.initialize() # initialise before sequential decomposition starts fixing state variables, to get better guesses. 

80 

81 for guess_key, is_fixed in guess.items(): 

82 property_schema = inlet_port_schema.properties.get(guess_key, None) 

83 if property_schema is None: 83 ↛ 84line 83 didn't jump to line 84 because the condition on line 83 was never true

84 raise ValueError(f"Property `{guess_key}` not found in port schema for port `{portId}`: in {blk}") 

85 

86 if is_fixed: 

87 state_var_key = var_map.get(guess_key, None) 

88 if state_var_key is None: 88 ↛ 89line 88 didn't jump to line 89 because the condition on line 88 was never true

89 raise ValueError(f"Cannot provide guess for variable `{guess_key}` at {blk}") 

90 if state_var_key not in sb.define_state_vars(): 

91 continue; # We allow implicitly skipping variables that are not in the state block (e.g mole_frac_comp in helmholtz where it is single-phase.) 

92 # deactivate the equality constraint (expanded arcs) 

93 expanded_arc = getattr( 

94 self._flowsheet_manager.model.fs, arc._name + "_expanded" 

95 ) 

96 equality_constraint = getattr(expanded_arc, state_var_key + "_equality") 

97 equality_constraint.deactivate() 

98 # fix the variable (Not needed, should already be fixed?) 

99 

100 for property_value in property_schema.data: 

101 property_value_id = property_value.id 

102 var_to_fix = self._flowsheet_manager.properties_map.get_constraint(property_value_id) 

103 if var_to_fix is None: 

104 continue # this is replaced by a constraint, so we don't need to fix it. 

105 for var in var_to_fix: 

106 var.fix() 

107 else: 

108 # unfix this variable 

109 for property_value in property_schema.data: 

110 property_value_id = property_value.id 

111 var_to_fix = self._flowsheet_manager.properties_map.get_constraint(property_value_id) 

112 if var_to_fix is None: 112 ↛ 113line 112 didn't jump to line 113 because the condition on line 112 was never true

113 continue # replaced by a constraint, should be fine? 

114 for var in var_to_fix: 

115 var.unfix()