Coverage for backend/idaes_service/solver/custom/SimpleEffectivenessHX_DH.py: 84%

207 statements  

« prev     ^ index     » next       coverage.py v7.10.7, created at 2025-11-06 23:27 +0000

1################################################################################# 

2# The Institute for the Design of Advanced Energy Systems Integrated Platform 

3# Framework (IDAES IP) was produced under the DOE Institute for the 

4# Design of Advanced Energy Systems (IDAES). 

5# 

6# Copyright (c) 2018-2024 by the software owners: The Regents of the 

7# University of California, through Lawrence Berkeley National Laboratory, 

8# National Technology & Engineering Solutions of Sandia, LLC, Carnegie Mellon 

9# University, West Virginia University Research Corporation, et al. 

10# All rights reserved. Please see the files COPYRIGHT.md and LICENSE.md 

11# for full copyright and license information. 

12################################################################################# 

13""" 

14Ben Lincolns heat exchanger model using effectiveness 

15 

16""" 

17 

18# Import Pyomo libraries 

19from pyomo.environ import ( 

20 Block, 

21 check_optimal_termination, 

22 Constraint, 

23 Expression, 

24 Param, 

25 PositiveReals, 

26 Reference, 

27 units as pyunits, 

28 Var, 

29) 

30from pyomo.common.config import Bool, ConfigBlock, ConfigValue, In 

31 

32# Import IDAES cores 

33from idaes.core import ( 

34 ControlVolume0DBlock, 

35 declare_process_block_class, 

36 MaterialBalanceType, 

37 EnergyBalanceType, 

38 MomentumBalanceType, 

39 UnitModelBlockData, 

40 useDefault, 

41) 

42from idaes.models.unit_models.heat_exchanger import hx_process_config, add_hx_references 

43from idaes.core.util.config import is_physical_parameter_block 

44from idaes.core.util.tables import create_stream_table_dataframe 

45from idaes.core.util.math import smooth_min, smooth_max 

46from idaes.core.solvers import get_solver 

47from idaes.core.util.exceptions import InitializationError 

48import idaes.logger as idaeslog 

49from idaes.core.initialization import SingleControlVolumeUnitInitializer 

50from idaes.core.util.model_statistics import degrees_of_freedom 

51from .inverted import add_inverted, initialise_inverted 

52 

53__author__ = "Paul Akula, Andrew Lee, Ben Lincoln" 

54 

55 

56# Set up logger 

57_log = idaeslog.getLogger(__name__) 

58 

59 

60class HXEFFInitializer(SingleControlVolumeUnitInitializer): 

61 """ 

62 Initializer for NTU Heat Exchanger units. 

63 

64 """ 

65 

66 def initialization_routine( 

67 self, 

68 model: Block, 

69 plugin_initializer_args: dict = None, 

70 copy_inlet_state: bool = False, 

71 duty=1000 * pyunits.W, 

72 ): 

73 """ 

74 Common initialization routine for NTU Heat Exchangers. 

75 

76 This routine starts by initializing the hot and cold side properties. Next, the heat 

77 transfer between the two sides is fixed to an initial guess for the heat duty (provided by the duty 

78 argument), the associated constraint deactivated, and the model is then solved. Finally, the heat 

79 duty is unfixed and the heat transfer constraint reactivated followed by a final solve of the model. 

80 

81 Args: 

82 model: Pyomo Block to be initialized 

83 plugin_initializer_args: dict-of-dicts containing arguments to be passed to plug-in Initializers. 

84 Keys should be submodel components. 

85 copy_inlet_state: bool (default=False). Whether to copy inlet state to other states or not 

86 (0-D control volumes only). Copying will generally be faster, but inlet states may not contain 

87 all properties required elsewhere. 

88 duty: initial guess for heat duty to assist with initialization. Can be a Pyomo expression with units. 

89 

90 Returns: 

91 Pyomo solver results object 

92 """ 

93 return super(SingleControlVolumeUnitInitializer, self).initialization_routine( 

94 model=model, 

95 plugin_initializer_args=plugin_initializer_args, 

96 copy_inlet_state=copy_inlet_state, 

97 duty=duty, 

98 ) 

99 

100 def initialize_main_model( 

101 self, 

102 model: Block, 

103 copy_inlet_state: bool = False, 

104 duty=1000 * pyunits.W, 

105 ): 

106 """ 

107 Initialization routine for main NTU HX models. 

108 

109 Args: 

110 model: Pyomo Block to be initialized. 

111 copy_inlet_state: bool (default=False). Whether to copy inlet state to other states or not 

112 (0-D control volumes only). Copying will generally be faster, but inlet states may not contain 

113 all properties required elsewhere. 

114 duty: initial guess for heat duty to assist with initialization. Can be a Pyomo expression with units. 

115 

116 Returns: 

117 Pyomo solver results object. 

118 

119 """ 

120 initialise_inverted(model.hot_side,"deltaP") 

121 initialise_inverted(model.cold_side,"deltaP") 

122 # TODO: Aside from one differences in constraint names, this is 

123 # identical to the Initializer for the 0D HX unit. 

124 # Set solver options 

125 init_log = idaeslog.getInitLogger( 

126 model.name, self.get_output_level(), tag="unit" 

127 ) 

128 solve_log = idaeslog.getSolveLogger( 

129 model.name, self.get_output_level(), tag="unit" 

130 ) 

131 

132 # Create solver 

133 solver = self._get_solver() 

134 

135 self.initialize_control_volume(model.hot_side, copy_inlet_state) 

136 init_log.info_high("Initialization Step 1a (hot side) Complete.") 

137 

138 self.initialize_control_volume(model.cold_side, copy_inlet_state) 

139 init_log.info_high("Initialization Step 1b (cold side) Complete.") 

140 

141 # --------------------------------------------------------------------- 

142 # Solve unit without heat transfer equation 

143 model.energy_balance_constraint.deactivate() 

144 

145 model.cold_side.heat.fix(duty) 

146 for i in model.hot_side.heat: 

147 model.hot_side.heat[i].set_value(-duty) 

148 

149 with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc: 

150 res = solver.solve(model, tee=slc.tee) 

151 

152 init_log.info_high("Initialization Step 2 {}.".format(idaeslog.condition(res))) 

153 

154 model.cold_side.heat.unfix() 

155 model.energy_balance_constraint.activate() 

156 # --------------------------------------------------------------------- 

157 # Solve unit 

158 with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc: 

159 res = solver.solve(model, tee=slc.tee) 

160 init_log.info("Initialization Completed, {}".format(idaeslog.condition(res))) 

161 

162 return res 

163 

164 

165@declare_process_block_class("HeatExchangerEffectiveness") 

166class HeatExchangerEFFData(UnitModelBlockData): 

167 """Heat Exchanger Unit Model using NTU method.""" 

168 

169 default_initializer = HXEFFInitializer 

170 

171 CONFIG = UnitModelBlockData.CONFIG(implicit=True) 

172 

173 # Configuration template for fluid specific arguments 

174 _SideCONFIG = ConfigBlock() 

175 _SideCONFIG.declare( 

176 "has_phase_equilibrium", 

177 ConfigValue( 

178 default=False, 

179 domain=Bool, 

180 description="Phase equilibrium construction flag", 

181 doc="""Indicates whether terms for phase equilibrium should be 

182constructed, **default** = False. 

183**Valid values:** { 

184**True** - include phase equilibrium terms 

185**False** - exclude phase equilibrium terms.}""", 

186 ), 

187 ) 

188 _SideCONFIG.declare( 

189 "material_balance_type", 

190 ConfigValue( 

191 default=MaterialBalanceType.useDefault, 

192 domain=In(MaterialBalanceType), 

193 description="Material balance construction flag", 

194 doc="""Indicates what type of mass balance should be constructed, 

195**default** - MaterialBalanceType.useDefault. 

196**Valid values:** { 

197**MaterialBalanceType.useDefault - refer to property package for default 

198balance type 

199**MaterialBalanceType.none** - exclude material balances, 

200**MaterialBalanceType.componentPhase** - use phase component balances, 

201**MaterialBalanceType.componentTotal** - use total component balances, 

202**MaterialBalanceType.elementTotal** - use total element balances, 

203**MaterialBalanceType.total** - use total material balance.}""", 

204 ), 

205 ) 

206 _SideCONFIG.declare( 

207 "energy_balance_type", 

208 ConfigValue( 

209 default=EnergyBalanceType.useDefault, 

210 domain=In(EnergyBalanceType), 

211 description="Energy balance construction flag", 

212 doc="""Indicates what type of energy balance should be constructed, 

213**default** - EnergyBalanceType.useDefault. 

214**Valid values:** { 

215**EnergyBalanceType.useDefault - refer to property package for default 

216balance type 

217**EnergyBalanceType.none** - exclude energy balances, 

218**EnergyBalanceType.enthalpyTotal** - single enthalpy balance for material, 

219**EnergyBalanceType.enthalpyPhase** - enthalpy balances for each phase, 

220**EnergyBalanceType.energyTotal** - single energy balance for material, 

221**EnergyBalanceType.energyPhase** - energy balances for each phase.}""", 

222 ), 

223 ) 

224 _SideCONFIG.declare( 

225 "momentum_balance_type", 

226 ConfigValue( 

227 default=MomentumBalanceType.pressureTotal, 

228 domain=In(MomentumBalanceType), 

229 description="Momentum balance construction flag", 

230 doc="""Indicates what type of momentum balance should be constructed, 

231**default** - MomentumBalanceType.pressureTotal. 

232**Valid values:** { 

233**MomentumBalanceType.none** - exclude momentum balances, 

234**MomentumBalanceType.pressureTotal** - single pressure balance for material, 

235**MomentumBalanceType.pressurePhase** - pressure balances for each phase, 

236**MomentumBalanceType.momentumTotal** - single momentum balance for material, 

237**MomentumBalanceType.momentumPhase** - momentum balances for each phase.}""", 

238 ), 

239 ) 

240 _SideCONFIG.declare( 

241 "has_pressure_change", 

242 ConfigValue( 

243 default=False, 

244 domain=Bool, 

245 description="Pressure change term construction flag", 

246 doc="""Indicates whether terms for pressure change should be 

247constructed, 

248**default** - False. 

249**Valid values:** { 

250**True** - include pressure change terms, 

251**False** - exclude pressure change terms.}""", 

252 ), 

253 ) 

254 _SideCONFIG.declare( 

255 "property_package", 

256 ConfigValue( 

257 default=useDefault, 

258 domain=is_physical_parameter_block, 

259 description="Property package to use ", 

260 doc="""Property parameter object used to define property calculations 

261 **default** - useDefault. 

262 **Valid values:** { 

263 **useDefault** - use default package from parent model or flowsheet, 

264 **PhysicalParameterObject** - a PhysicalParameterBlock object.}""", 

265 ), 

266 ) 

267 _SideCONFIG.declare( 

268 "property_package_args", 

269 ConfigBlock( 

270 implicit=True, 

271 description="Arguments to use for constructing property package", 

272 doc="""A ConfigBlock with arguments to be passed to 

273 property block(s) and used when constructing these, 

274 **default** - None. 

275 **Valid values:** { 

276 see property package for documentation.}""", 

277 ), 

278 ) 

279 

280 # Create individual config blocks for hot and cold sides 

281 CONFIG.declare("hot_side", _SideCONFIG(doc="Hot fluid config arguments")) 

282 CONFIG.declare("cold_side", _SideCONFIG(doc="Cold fluid config arguments")) 

283 CONFIG.declare( 

284 "hot_side_name", 

285 ConfigValue( 

286 default=None, 

287 domain=str, 

288 doc="Hot side name, sets control volume and inlet and outlet names", 

289 ), 

290 ) 

291 CONFIG.declare( 

292 "cold_side_name", 

293 ConfigValue( 

294 default=None, 

295 domain=str, 

296 doc="Cold side name, sets control volume and inlet and outlet names", 

297 ), 

298 ) 

299 

300 def build(self): 

301 # Call UnitModel.build to setup model 

302 super().build() 

303 hx_process_config(self) 

304 

305 # --------------------------------------------------------------------- 

306 # Build hot-side control volume 

307 self.hot_side = ControlVolume0DBlock( 

308 dynamic=self.config.dynamic, 

309 has_holdup=self.config.has_holdup, 

310 property_package=self.config.hot_side.property_package, 

311 property_package_args=self.config.hot_side.property_package_args, 

312 ) 

313 

314 # TODO : Add support for phase equilibrium? 

315 self.hot_side.add_state_blocks(has_phase_equilibrium=self.config.hot_side.has_phase_equilibrium) 

316 

317 self.hot_side.add_material_balances( 

318 balance_type=self.config.hot_side.material_balance_type, 

319 has_phase_equilibrium=self.config.hot_side.has_phase_equilibrium, 

320 ) 

321 

322 self.hot_side.add_energy_balances( 

323 balance_type=self.config.hot_side.energy_balance_type, 

324 has_heat_transfer=True, 

325 ) 

326 

327 self.hot_side.add_momentum_balances( 

328 balance_type=self.config.hot_side.momentum_balance_type, 

329 has_pressure_change=self.config.hot_side.has_pressure_change, 

330 ) 

331 

332 # --------------------------------------------------------------------- 

333 # Build cold-side control volume 

334 self.cold_side = ControlVolume0DBlock( 

335 dynamic=self.config.dynamic, 

336 has_holdup=self.config.has_holdup, 

337 property_package=self.config.cold_side.property_package, 

338 property_package_args=self.config.cold_side.property_package_args, 

339 ) 

340 

341 self.cold_side.add_state_blocks(has_phase_equilibrium=self.config.cold_side.has_phase_equilibrium) 

342 

343 self.cold_side.add_material_balances( 

344 balance_type=self.config.cold_side.material_balance_type, 

345 has_phase_equilibrium=self.config.cold_side.has_phase_equilibrium, 

346 ) 

347 

348 self.cold_side.add_energy_balances( 

349 balance_type=self.config.cold_side.energy_balance_type, 

350 has_heat_transfer=True, 

351 ) 

352 

353 self.cold_side.add_momentum_balances( 

354 balance_type=self.config.cold_side.momentum_balance_type, 

355 has_pressure_change=self.config.cold_side.has_pressure_change, 

356 ) 

357 

358 # --------------------------------------------------------------------- 

359 # Add Ports to control volumes 

360 self.add_inlet_port( 

361 name="hot_side_inlet", block=self.hot_side, doc="Hot side inlet port" 

362 ) 

363 self.add_outlet_port( 

364 name="hot_side_outlet", block=self.hot_side, doc="Hot side outlet port" 

365 ) 

366 

367 self.add_inlet_port( 

368 name="cold_side_inlet", block=self.cold_side, doc="Cold side inlet port" 

369 ) 

370 self.add_outlet_port( 

371 name="cold_side_outlet", block=self.cold_side, doc="Cold side outlet port" 

372 ) 

373 

374 # --------------------------------------------------------------------- 

375 # Add unit level References 

376 # Set references to balance terms at unit level 

377 self.heat_duty = Reference(self.cold_side.heat[:]) 

378 

379 # Add references to the user provided aliases (if applicable). 

380 add_hx_references(self) 

381 

382 # --------------------------------------------------------------------- 

383 # Add performance equations 

384 # All units of measurement will be based on hot side 

385 hunits = self.hot_side.config.property_package.get_metadata().get_derived_units 

386 

387 # Overall energy balance 

388 def rule_energy_balance(blk, t): 

389 return blk.hot_side.heat[t] == -pyunits.convert( 

390 blk.cold_side.heat[t], to_units=hunits("power") 

391 ) 

392 

393 self.energy_balance_constraint = Constraint( 

394 self.flowsheet().time, rule=rule_energy_balance 

395 ) 

396 

397 # Add e-NTU variables 

398 self.effectiveness = Var( 

399 self.flowsheet().time, 

400 initialize=1, 

401 units=pyunits.dimensionless, 

402 domain=PositiveReals, 

403 doc="Effectiveness factor", 

404 ) 

405 

406 # Minimum heat capacitance ratio for e-NTU method 

407 self.eps_cmin = Param( 

408 initialize=1e-3, 

409 mutable=True, 

410 units=hunits("power") / hunits("temperature"), 

411 doc="Epsilon parameter for smooth Cmin and Cmax", 

412 ) 

413 

414########### 

415 

416 # Todo: Will not work for a pure component system in the two phase region 

417 

418########### 

419 

420 #Build hotside State Block 

421 tmp_dict = dict(**self.config.hot_side.property_package_args) 

422 tmp_dict["has_phase_equilibrium"] = self.config.hot_side.has_phase_equilibrium 

423 tmp_dict["defined_state"] = False 

424 

425 self.properties_hotside = self.config.hot_side.property_package.build_state_block( 

426 self.flowsheet().time, doc="Hot side properties at outlet under Qmax", **tmp_dict 

427 ) 

428 #Connect Properties to Hot Side  

429 @self.Constraint( 

430 self.flowsheet().time, doc="Hot Side Temperature at Qmax = Inlet Cold Temp") 

431 def constraint_hotside_temp(self, t): 

432 return self.properties_hotside[t].temperature == self.cold_side.properties_in[t].temperature 

433 @self.Constraint( 

434 self.flowsheet().time, doc="Hot Side Pressure at Qmax = Inlet Hot Pressure") 

435 def constraint_hotside_pres(self, t): 

436 return self.properties_hotside[t].pressure == self.hot_side.properties_in[t].pressure 

437 @self.Constraint( 

438 self.flowsheet().time, doc="Hot Side Flowrate at Qmax = Inlet Hot Flowrate") 

439 def constraint_hotside_flow(self, t): 

440 return self.properties_hotside[t].flow_mol == self.hot_side.properties_in[t].flow_mol 

441 

442 #Build Coldside State Block 

443 tmp_dict = dict(**self.config.cold_side.property_package_args) 

444 tmp_dict["has_phase_equilibrium"] = self.config.cold_side.has_phase_equilibrium 

445 tmp_dict["defined_state"] = False 

446 

447 self.properties_coldside = self.config.cold_side.property_package.build_state_block( 

448 self.flowsheet().time, doc="Cold side properties at outlet under Qmax", **tmp_dict 

449 ) 

450 

451######## 

452 

453 # Todo: Will not work for a pure component system in the two phase region 

454 

455########### 

456 

457 #Connect Properties to Cold Side 

458 @self.Constraint( 

459 self.flowsheet().time, doc="Cold Side Temperature at Qmax = Inlet Hot Temp") 

460 def constraint_coldside_temp(self, t): 

461 return self.properties_coldside[t].temperature == self.hot_side.properties_in[t].temperature 

462 @self.Constraint( 

463 self.flowsheet().time, doc="Cold Side Pressure at Qmax = Inlet Cold Pressure") 

464 def constraint_coldside_pres(self, t): 

465 return self.properties_coldside[t].pressure == self.cold_side.properties_in[t].pressure 

466 @self.Constraint( 

467 self.flowsheet().time, doc="Cold Side Flowrate at Qmax = Inlet Cold Flowrate") 

468 def constraint_coldside_flow(self, t): 

469 return self.properties_coldside[t].flow_mol == self.cold_side.properties_in[t].flow_mol 

470 

471 # Delta h hot at Qmax 

472 def rule_deltah_hot_qmax(blk, t): 

473 return blk.hot_side.properties_in[t].enth_mol - blk.properties_hotside[t].enth_mol 

474 

475 self.delta_h_hot_qmax = Expression( 

476 self.flowsheet().time, rule=rule_deltah_hot_qmax, doc="Delta h Hot Side at Qmax" 

477 ) 

478 # Delta h cold at Qmax 

479 def rule_deltah_cold_qmax(blk, t): 

480 return blk.properties_coldside[t].enth_mol - blk.cold_side.properties_in[t].enth_mol 

481 self.delta_h_cold_qmax = Expression( 

482 self.flowsheet().time, rule=rule_deltah_cold_qmax, doc="Delta h Cold Side at Qmax" 

483 ) 

484 

485 # TODO : Support both mass and mole based flows 

486 # Minimum heat transfer rate 

487 def rule_Hmin(blk, t): 

488 Hhot = pyunits.convert( 

489 blk.hot_side.properties_in[t].flow_mol 

490 * blk.delta_h_hot_qmax[t], 

491 to_units=hunits("power"), 

492 ) 

493 Hcold = pyunits.convert( 

494 blk.cold_side.properties_in[t].flow_mol 

495 * blk.delta_h_cold_qmax[t], 

496 to_units=hunits("power"), 

497 ) 

498 return smooth_min(Hhot, Hcold, eps=blk.eps_cmin) 

499 

500 self.Qmax = Expression( 

501 self.flowsheet().time, rule=rule_Hmin, doc="Max heat transfer rate" 

502 ) 

503 #Max heat transfer rate 

504 def rule_Hmax(blk, t): 

505 Hhot = pyunits.convert( 

506 blk.hot_side.properties_in[t].flow_mol 

507 * blk.delta_h_hot_qmax[t], 

508 to_units=hunits("power"), 

509 ) 

510 Hcold = pyunits.convert( 

511 blk.cold_side.properties_in[t].flow_mol 

512 * blk.delta_h_cold_qmax[t], 

513 to_units=hunits("power"), 

514 ) 

515 return smooth_max(Hhot, Hcold, eps=blk.eps_cmin) 

516 self.Hmax_theory = Expression( 

517 self.flowsheet().time, 

518 rule=rule_Hmax, doc="Maximum heat transfer rate" 

519 ) 

520 

521# Add effectiveness relation 

522 def rule_effectiveness(blk, t): 

523 return blk.hot_side.heat[t] == -( 

524 blk.effectiveness[t] 

525 * blk.Qmax[t] 

526 ) 

527 

528 self.heat_duty_constraint = Constraint(self.flowsheet().time, rule=rule_effectiveness) 

529 

530 add_inverted(self.hot_side,"deltaP") 

531 add_inverted(self.cold_side,"deltaP") 

532 # TODO : Add scaling methods 

533 

534 def initialize_build( 

535 self, 

536 hot_side_state_args=None, 

537 cold_side_state_args=None, 

538 outlvl=idaeslog.NOTSET, 

539 solver=None, 

540 optarg=None, 

541 duty=None, 

542 ): 

543 """ 

544 Heat exchanger initialization method. 

545 

546 Args: 

547 hot_side_state_args : a dict of arguments to be passed to the 

548 property initialization for the hot side (see documentation of 

549 the specific property package) (default = None). 

550 cold_side_state_args : a dict of arguments to be passed to the 

551 property initialization for the cold side (see documentation of 

552 the specific property package) (default = None). 

553 outlvl : sets output level of initialization routine 

554 optarg : solver options dictionary object (default=None, use 

555 default solver options) 

556 solver : str indicating which solver to use during 

557 initialization (default = None, use default solver) 

558 duty : an initial guess for the amount of heat transferred. This 

559 should be a tuple in the form (value, units), (default 

560 = (1000 J/s)) 

561 

562 Returns: 

563 None 

564 

565 """ 

566 initialise_inverted(self.hot_side,"deltaP") 

567 initialise_inverted(self.cold_side,"deltaP") 

568 # Set solver options 

569 init_log = idaeslog.getInitLogger(self.name, outlvl, tag="unit") 

570 solve_log = idaeslog.getSolveLogger(self.name, outlvl, tag="unit") 

571 

572 hot_side = self.hot_side 

573 cold_side = self.cold_side 

574 hot_side_max = self.properties_hotside 

575 cold_side_max = self.properties_coldside 

576 

577 

578 # Create solver 

579 opt = get_solver(solver, optarg) 

580 

581 flags1 = hot_side.initialize( 

582 outlvl=outlvl, optarg=optarg, solver=solver, state_args=hot_side_state_args 

583 ) 

584 

585 init_log.info_high("Initialization Step 1a (hot side) Complete.") 

586 

587 flags2 = cold_side.initialize( 

588 outlvl=outlvl, optarg=optarg, solver=solver, state_args=cold_side_state_args 

589 ) 

590 

591 init_log.info_high("Initialization Step 1b (cold side) Complete.") 

592 

593 # cold_side_max[0].pressure.set_value(cold_side.properties_in[0].pressure.value) 

594 # hot_side_max[0].pressure.set_value(hot_side.properties_in[0].pressure.value) 

595 

596 # cold_side_max[0].flow_mol.set_value(cold_side.properties_in[0].flow_mol.value) 

597 # hot_side_max[0].flow_mol.set_value(hot_side.properties_in[0].flow_mol.value) 

598 

599 # cold_side_max[0].enth_mol.set_value(cold_side.properties_in[0].enth_mol.value) 

600 # hot_side_max[0].enth_mol.set_value(hot_side.properties_in[0].enth_mol.value) 

601 

602 flags3 = hot_side_max.initialize( 

603 outlvl=outlvl 

604 ) 

605 init_log.info_high("Initialization Step 1c (hot side max) Complete.") 

606 

607 flags4 = cold_side_max.initialize( 

608 outlvl=outlvl 

609 ) 

610 init_log.info_high("Initialization Step 1d (cold side max) Complete.") 

611 

612 hot_side_max[0].display() 

613 cold_side_max[0].display() 

614 

615 

616 #---------------------------------------------------------------------- 

617 

618 self.energy_balance_constraint.deactivate() 

619 self.heat_duty_constraint.deactivate() 

620 self.hot_side.enthalpy_balances.deactivate() 

621 self.cold_side.enthalpy_balances.deactivate() 

622 self.cold_side.material_balances.deactivate() 

623 self.hot_side.material_balances.deactivate() 

624 self.hot_side.properties_out.deactivate() 

625 self.cold_side.properties_out.deactivate() 

626 

627 print("DOF:", degrees_of_freedom(self)) 

628 from idaes.core.util import DiagnosticsToolbox 

629 dt = DiagnosticsToolbox(self) 

630 dt.display_underconstrained_set() 

631 dt.display_overconstrained_set() 

632 dt.report_structural_issues() 

633 dt.report_numerical_issues() 

634 dt.display_constraints_with_large_residuals() 

635 #dt.compute_infeasibility_explanation() 

636 dt.display_variables_at_or_outside_bounds() 

637 

638 with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc: 

639 res = opt.solve(self, tee=slc.tee) 

640 

641 

642 init_log.info_high("Initialization Step 1.5 {}.".format(idaeslog.condition(res))) 

643 

644 # --------------------------------------------------------------------- 

645 # Solve unit without heat transfer equation 

646 self.heat_duty_constraint.activate() 

647 self.heat_duty.unfix() 

648 self.hot_side.enthalpy_balances.activate() 

649 self.cold_side.enthalpy_balances.activate() 

650 self.cold_side.material_balances.activate() 

651 self.hot_side.material_balances.activate() 

652 self.hot_side.properties_out.activate() 

653 self.cold_side.properties_out.activate() 

654 

655 # self.Qmax[0].set_value(1000) 

656 

657 # Get side 1 and side 2 heat units, and convert duty as needed 

658 s1_units = hot_side.heat.get_units() 

659 s2_units = cold_side.heat.get_units() 

660 

661 if duty is None: 661 ↛ 676line 661 didn't jump to line 676 because the condition on line 661 was always true

662 # Assume 1000 J/s and check for unitless properties 

663 if s1_units is None and s2_units is None: 663 ↛ 665line 663 didn't jump to line 665 because the condition on line 663 was never true

664 # Backwards compatibility for unitless properties 

665 s1_duty = -1000 

666 s2_duty = 1000 

667 else: 

668 s1_duty = pyunits.convert_value( 

669 -1000, from_units=pyunits.W, to_units=s1_units 

670 ) 

671 s2_duty = pyunits.convert_value( 

672 1000, from_units=pyunits.W, to_units=s2_units 

673 ) 

674 else: 

675 # Duty provided with explicit units 

676 s1_duty = -pyunits.convert_value( 

677 duty[0], from_units=duty[1], to_units=s1_units 

678 ) 

679 s2_duty = pyunits.convert_value( 

680 duty[0], from_units=duty[1], to_units=s2_units 

681 ) 

682 

683 cold_side.heat.fix(s2_duty) 

684 for i in hot_side.heat: 

685 hot_side.heat[i].value = s1_duty 

686 

687 print("DOF:", degrees_of_freedom(self)) 

688 

689 with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc: 

690 res = opt.solve(self, tee=slc.tee) 

691 

692 init_log.info_high("Initialization Step 2 {}.".format(idaeslog.condition(res))) 

693 

694 cold_side.heat.unfix() 

695 self.energy_balance_constraint.activate() 

696 

697 # --------------------------------------------------------------------- 

698 # Solve unit 

699 with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc: 

700 res = opt.solve(self, tee=slc.tee) 

701 init_log.info_high("Initialization Step 3 {}.".format(idaeslog.condition(res))) 

702 

703 # --------------------------------------------------------------------- 

704 # Release Inlet state 

705 hot_side.release_state(flags1, outlvl=outlvl) 

706 cold_side.release_state(flags2, outlvl=outlvl) 

707 

708 init_log.info("Initialization Completed, {}".format(idaeslog.condition(res))) 

709 

710 if not check_optimal_termination(res): 710 ↛ 711line 710 didn't jump to line 711 because the condition on line 710 was never true

711 raise InitializationError( 

712 f"{self.name} failed to initialize successfully. Please check " 

713 f"the output logs for more information." 

714 ) 

715 

716 return res 

717 

718 def _get_stream_table_contents(self, time_point=0): 

719 return create_stream_table_dataframe( 

720 { 

721 "Hot Inlet": self.hot_side_inlet, 

722 "Hot Outlet": self.hot_side_outlet, 

723 "Cold Inlet": self.cold_side_inlet, 

724 "Cold Outlet": self.cold_side_outlet, 

725 }, 

726 time_point=time_point, 

727 )