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

1from idaes.models.unit_models.separator import SeparatorData, SplittingType 

2 

3from functools import partial 

4from pandas import DataFrame 

5 

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 

19 

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 

46 

47 

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. 

52 

53@declare_process_block_class("CustomSeparator") 

54class CustomSeparatorData(SeparatorData): 

55 

56 def initialize_build( 

57 blk, outlvl=idaeslog.NOTSET, optarg=None, solver=None, hold_state=False 

58 ): 

59 """ 

60 Initialization routine for separator 

61 

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. 

76 

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") 

83 

84 # Create solver 

85 opt = get_solver(solver, optarg) 

86 

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 ) 

98 

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() 

107 

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 ) 

114 

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() 

118 

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 

122 

123 # Initialize outlet StateBlocks 

124 outlet_list = blk.create_outlet_list() 

125 

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") 

135 

136 # Create dict to store fixed status of state variables 

137 o_flags = {} 

138 for t in blk.flowsheet().time: 

139 

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 

146 

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) 

336 

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 ) 

344 

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] 

351 

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) 

355 

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 ) 

361 

362 init_log.info( 

363 "Initialization Step 2 Complete: {}".format(idaeslog.condition(res)) 

364 ) 

365 else: 

366 init_log.info("Initialization Complete.") 

367 

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)