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
« 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
11class TearManager:
12 """
13 Manages the tears in the flowsheet
14 """
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] = []
23 def load(self):
24 """
25 Load all the tears (from the recycle unitops)
26 """
27 schema = self._flowsheet_manager.schema
29 for arc_schema in schema.arcs:
30 if arc_schema.tear_guess:
31 self.add_tear(arc_schema)
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
40 inlet_port = self._flowsheet_manager.ports.get_port(portId)
41 arc = inlet_port.arcs()[0]
42 self._tears.append(arc)
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
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 """
54 # hardcoding time indexes to [0] for now
55 time_indexes = [0]
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()
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 }
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.
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}")
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?)
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()