Coverage for backend/idaes_service/solver/custom/custom_separator.py: 63%
108 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 idaes.models.unit_models.separator import SeparatorData, SplittingType
3from functools import partial
4from pandas import DataFrame
6from pyomo.environ import (
7 Block,
8 check_optimal_termination,
9 Constraint,
10 Param,
11 Reals,
12 Reference,
13 Set,
14 Var,
15 value,
16)
17from pyomo.network import Port
18from pyomo.common.config import ConfigBlock, ConfigValue, In, ListOf, Bool
20from idaes.core import (
21 declare_process_block_class,
22 UnitModelBlockData,
23 useDefault,
24 MaterialBalanceType,
25 MomentumBalanceType,
26 MaterialFlowBasis,
27 VarLikeExpression,
28)
29from idaes.core.util.config import (
30 is_physical_parameter_block,
31 is_state_block,
32)
33from idaes.core.util.exceptions import (
34 BurntToast,
35 ConfigurationError,
36 PropertyNotSupportedError,
37 InitializationError,
38)
39from idaes.core.solvers import get_solver
40from idaes.core.util.tables import create_stream_table_dataframe
41from idaes.core.util.model_statistics import degrees_of_freedom
42import idaes.logger as idaeslog
43import idaes.core.util.scaling as iscale
44from idaes.core.util.units_of_measurement import report_quantity
45from idaes.core.initialization import ModularInitializerBase
48# This only changes a couple of lines in the original SeparatorData class, to not fix state variables by default.
49# The state block initialisation already does this if needed, so we can just set their value.
50# This is because if the state block has extra constraints, such as for flow_mass, then fixing flow_mol will over-define the system.
51# It might be worth making this a pr to idaes.
53@declare_process_block_class("CustomSeparator")
54class CustomSeparatorData(SeparatorData):
56 def initialize_build(
57 blk, outlvl=idaeslog.NOTSET, optarg=None, solver=None, hold_state=False
58 ):
59 """
60 Initialization routine for separator
62 Keyword Arguments:
63 outlvl : sets output level of initialization routine
64 optarg : solver options dictionary object (default=None, use
65 default solver options)
66 solver : str indicating which solver to use during
67 initialization (default = None, use default solver)
68 hold_state : flag indicating whether the initialization routine
69 should unfix any state variables fixed during
70 initialization, **default** - False. **Valid values:**
71 **True** - states variables are not unfixed, and a dict of
72 returned containing flags for which states were fixed
73 during initialization, **False** - state variables are
74 unfixed after initialization by calling the release_state
75 method.
77 Returns:
78 If hold_states is True, returns a dict containing flags for which
79 states were fixed during initialization.
80 """
81 init_log = idaeslog.getInitLogger(blk.name, outlvl, tag="unit")
82 solve_log = idaeslog.getSolveLogger(blk.name, outlvl, tag="unit")
84 # Create solver
85 opt = get_solver(solver, optarg)
87 # Initialize mixed state block
88 if blk.config.mixed_state_block is not None: 88 ↛ 89line 88 didn't jump to line 89 because the condition on line 88 was never true
89 mblock = blk.config.mixed_state_block
90 else:
91 mblock = blk.mixed_state
92 flags = mblock.initialize(
93 outlvl=outlvl,
94 optarg=optarg,
95 solver=solver,
96 hold_state=True,
97 )
99 # Solve for split fractions only
100 component_status = {}
101 for c in blk.component_objects((Block, Constraint)):
102 for i in c:
103 if not c[i].local_name == "sum_split_frac": 103 ↛ 102line 103 didn't jump to line 102 because the condition on line 103 was always true
104 # Record current status of components to restore later
105 component_status[c[i]] = c[i].active
106 c[i].deactivate()
108 if degrees_of_freedom(blk) != 0: 108 ↛ 109line 108 didn't jump to line 109 because the condition on line 108 was never true
109 with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc:
110 res = opt.solve(blk, tee=slc.tee)
111 init_log.info(
112 "Initialization Step 1 Complete: {}".format(idaeslog.condition(res))
113 )
115 for c, s in component_status.items():
116 if s: 116 ↛ 115line 116 didn't jump to line 115 because the condition on line 116 was always true
117 c.activate()
119 if blk.config.ideal_separation: 119 ↛ 121line 119 didn't jump to line 121 because the condition on line 119 was never true
120 # If using ideal splitting, initialization should be complete
121 return flags
123 # Initialize outlet StateBlocks
124 outlet_list = blk.create_outlet_list()
126 # Premises for initializing outlet states:
127 # 1. Intensive states remain unchanged - this is either a valid premise
128 # or the actual state is impossible to calculate without solving the
129 # full separator model.
130 # 2. Extensive states are use split fractions if index matches, or
131 # average of split fractions for outlet otherwise
132 for o in outlet_list:
133 # Get corresponding outlet StateBlock
134 o_block = getattr(blk, o + "_state")
136 # Create dict to store fixed status of state variables
137 o_flags = {}
138 for t in blk.flowsheet().time:
140 # Calculate values for state variables
141 s_vars = o_block[t].define_state_vars()
142 for v in s_vars:
143 for k in s_vars[v]:
144 # Record whether variable was fixed or not
145 o_flags[t, v, k] = s_vars[v][k].fixed
147 # If fixed, use current value
148 # otherwise calculate guess from mixed state and fix
149 if not s_vars[v][k].fixed:
150 m_var = getattr(mblock[t], s_vars[v].local_name)
151 if "flow" in v:
152 # If a "flow" variable, is extensive
153 # Apply split fraction
154 if blk.config.split_basis == SplittingType.totalFlow:
155 # All flows split by outlet
156 s_vars[v][k].set_value(
157 value(m_var[k] * blk.split_fraction[(t, o)])
158 )
159 elif "_phase_comp" in v: 159 ↛ 161line 159 didn't jump to line 161 because the condition on line 159 was never true
160 # Need to match indices, but use split frac
161 if (
162 blk.config.split_basis
163 == SplittingType.phaseComponentFlow
164 ):
165 s_vars[v][k].set_value(
166 value(
167 m_var[k]
168 * blk.split_fraction[(t, o) + (k,)]
169 )
170 )
171 elif (
172 blk.config.split_basis
173 == SplittingType.phaseFlow
174 ):
175 s_vars[v][k].set_value(
176 value(
177 m_var[k]
178 * blk.split_fraction[(t, o) + (k[0],)]
179 )
180 )
181 elif (
182 blk.config.split_basis
183 == SplittingType.componentFlow
184 ):
185 s_vars[v][k].set_value(
186 value(
187 m_var[k]
188 * blk.split_fraction[(t, o) + (k[1],)]
189 )
190 )
191 else:
192 raise BurntToast(
193 "{} encountered unrecognised "
194 "SplittingType. This should not "
195 "occur - please send this bug to "
196 "the IDAES developers.".format(blk.name)
197 )
198 elif "_phase" in v: 198 ↛ 199line 198 didn't jump to line 199 because the condition on line 198 was never true
199 if (
200 blk.config.split_basis
201 == SplittingType.phaseComponentFlow
202 ):
203 # Need average split fraction
204 avg_split = value(
205 sum(
206 blk.split_fraction[t, o, k, j]
207 for j in mblock.component_list
208 )
209 / len(mblock.component_list)
210 )
211 s_vars[v][k].set_value(value(m_var[k] * avg_split))
212 elif (
213 blk.config.split_basis
214 == SplittingType.phaseFlow
215 ):
216 s_vars[v][k].set_value(
217 value(
218 m_var[k]
219 * blk.split_fraction[(t, o) + (k,)]
220 )
221 )
222 elif (
223 blk.config.split_basis
224 == SplittingType.componentFlow
225 ):
226 # Need average split fraction
227 avg_split = value(
228 sum(
229 blk.split_fraction[t, o, j]
230 for j in mblock.component_list
231 )
232 / len(mblock.component_list)
233 )
234 s_vars[v][k].set_value(value(m_var[k] * avg_split))
235 else:
236 raise BurntToast(
237 "{} encountered unrecognised "
238 "SplittingType. This should not "
239 "occur - please send this bug to "
240 "the IDAES developers.".format(blk.name)
241 )
242 elif "_comp" in v: 242 ↛ 243line 242 didn't jump to line 243 because the condition on line 242 was never true
243 if (
244 blk.config.split_basis
245 == SplittingType.phaseComponentFlow
246 ):
247 # Need average split fraction
248 avg_split = value(
249 sum(
250 blk.split_fraction[t, o, p, k]
251 for p in mblock.phase_list
252 )
253 / len(mblock.phase_list)
254 )
255 s_vars[v][k].set_value(value(m_var[k] * avg_split))
256 elif (
257 blk.config.split_basis
258 == SplittingType.phaseFlow
259 ):
260 # Need average split fraction
261 avg_split = value(
262 sum(
263 blk.split_fraction[t, o, p]
264 for p in mblock.phase_list
265 )
266 / len(mblock.phase_list)
267 )
268 s_vars[v][k].set_value(value(m_var[k] * avg_split))
269 elif (
270 blk.config.split_basis
271 == SplittingType.componentFlow
272 ):
273 s_vars[v][k].set_value(
274 value(
275 m_var[k]
276 * blk.split_fraction[(t, o) + (k,)]
277 )
278 )
279 else:
280 raise BurntToast(
281 "{} encountered unrecognised "
282 "SplittingType. This should not "
283 "occur - please send this bug to "
284 "the IDAES developers.".format(blk.name)
285 )
286 else:
287 # Assume unindexed extensive state
288 # Need average split
289 if ( 289 ↛ 294line 289 didn't jump to line 294 because the condition on line 289 was never true
290 blk.config.split_basis
291 == SplittingType.phaseComponentFlow
292 ):
293 # Need average split fraction
294 avg_split = value(
295 sum(
296 blk.split_fraction[t, o, p, j]
297 for (p, j) in mblock.phase_component_set
298 )
299 / len(mblock.phase_component_set)
300 )
301 elif (
302 blk.config.split_basis
303 == SplittingType.phaseFlow
304 ):
305 # Need average split fraction
306 avg_split = value(
307 sum(
308 blk.split_fraction[t, o, p]
309 for p in mblock.phase_list
310 )
311 / len(mblock.phase_list)
312 )
313 elif ( 313 ↛ 326line 313 didn't jump to line 326 because the condition on line 313 was always true
314 blk.config.split_basis
315 == SplittingType.componentFlow
316 ):
317 # Need average split fraction
318 avg_split = value(
319 sum(
320 blk.split_fraction[t, o, j]
321 for j in mblock.component_list
322 )
323 / len(mblock.component_list)
324 )
325 else:
326 raise BurntToast(
327 "{} encountered unrecognised "
328 "SplittingType. This should not "
329 "occur - please send this bug to "
330 "the IDAES developers.".format(blk.name)
331 )
332 s_vars[v][k].set_value(value(m_var[k] * avg_split))
333 else:
334 # Otherwise intensive, equate to mixed stream
335 s_vars[v][k].set_value(m_var[k].value)
337 # Call initialization routine for outlet StateBlock
338 o_block.initialize(
339 outlvl=outlvl,
340 optarg=optarg,
341 solver=solver,
342 hold_state=False,
343 )
345 # Revert fixed status of variables to what they were before
346 for t in blk.flowsheet().time:
347 s_vars = o_block[t].define_state_vars()
348 for v in s_vars:
349 for k in s_vars[v]:
350 s_vars[v][k].fixed = o_flags[t, v, k]
352 if blk.config.mixed_state_block is None: 352 ↛ 366line 352 didn't jump to line 366 because the condition on line 352 was always true
353 with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc:
354 res = opt.solve(blk, tee=slc.tee)
356 if not check_optimal_termination(res): 356 ↛ 357line 356 didn't jump to line 357 because the condition on line 356 was never true
357 raise InitializationError(
358 f"{blk.name} failed to initialize successfully. Please "
359 f"check the output logs for more information."
360 )
362 init_log.info(
363 "Initialization Step 2 Complete: {}".format(idaeslog.condition(res))
364 )
365 else:
366 init_log.info("Initialization Complete.")
368 if hold_state is True: 368 ↛ 369line 368 didn't jump to line 369 because the condition on line 368 was never true
369 return flags
370 else:
371 blk.release_state(flags, outlvl=outlvl)