Coverage for backend/idaes_service/solver/custom/thermal_utility_systems/willans_turbine.py: 66%

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

14Standard IDAES pressure changer model. 

15""" 

16# TODO: Missing docstrings 

17# pylint: disable=missing-function-docstring 

18 

19# Changing existing config block attributes 

20# pylint: disable=protected-access 

21 

22# Import Python libraries 

23 

24 

25# Import Pyomo libraries 

26from pyomo.environ import ( 

27 Block, 

28 value, 

29 Var, 

30 Expression, 

31 Constraint, 

32 Reference, 

33 check_optimal_termination, 

34 Reals, 

35 Param, 

36) 

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

38 

39# Import IDAES cores 

40from idaes.core import ( 

41 ControlVolume0DBlock, 

42 declare_process_block_class, 

43 EnergyBalanceType, 

44 MomentumBalanceType, 

45 MaterialBalanceType, 

46 ProcessBlockData, 

47 UnitModelBlockData, 

48 useDefault, 

49) 

50from idaes.core.util.exceptions import PropertyNotSupportedError, InitializationError 

51from idaes.core.util.config import is_physical_parameter_block 

52import idaes.logger as idaeslog 

53from idaes.core.util import scaling as iscale 

54from idaes.core.solvers import get_solver 

55from idaes.core.initialization import SingleControlVolumeUnitInitializer 

56from idaes.core.util import to_json, from_json, StoreSpec 

57from idaes.core.util.math import smooth_max, safe_sqrt, sqrt, smooth_min 

58from pyomo.environ import units as pyunits 

59 

60 

61__author__ = "Emmanuel Ogbe, Andrew Lee" 

62_log = idaeslog.getLogger(__name__) 

63 

64 

65@declare_process_block_class("TurbineBase") 

66class TurbineBaseData(UnitModelBlockData): 

67 """ 

68 Standard Compressor/Expander Unit Model Class 

69 """ 

70 

71 CONFIG = UnitModelBlockData.CONFIG() 

72 

73 CONFIG.declare( 

74 "material_balance_type", 

75 ConfigValue( 

76 default=MaterialBalanceType.useDefault, 

77 domain=In(MaterialBalanceType), 

78 description="Material balance construction flag", 

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

80**default** - MaterialBalanceType.useDefault. 

81**Valid values:** { 

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

83balance type 

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

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

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

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

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

89 ), 

90 ) 

91 CONFIG.declare( 

92 "energy_balance_type", 

93 ConfigValue( 

94 default=EnergyBalanceType.useDefault, 

95 domain=In(EnergyBalanceType), 

96 description="Energy balance construction flag", 

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

98**default** - EnergyBalanceType.useDefault. 

99**Valid values:** { 

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

101balance type 

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

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

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

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

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

107 ), 

108 ) 

109 CONFIG.declare( 

110 "momentum_balance_type", 

111 ConfigValue( 

112 default=MomentumBalanceType.pressureTotal, 

113 domain=In(MomentumBalanceType), 

114 description="Momentum balance construction flag", 

115 doc="""Indicates what type of momentum balance should be 

116constructed, **default** - MomentumBalanceType.pressureTotal. 

117**Valid values:** { 

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

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

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

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

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

123 ), 

124 ) 

125 CONFIG.declare( 

126 "has_phase_equilibrium", 

127 ConfigValue( 

128 default=False, 

129 domain=Bool, 

130 description="Phase equilibrium construction flag", 

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

132constructed, **default** = False. 

133**Valid values:** { 

134**True** - include phase equilibrium terms 

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

136 ), 

137 ) 

138 CONFIG.declare( 

139 "property_package", 

140 ConfigValue( 

141 default=useDefault, 

142 domain=is_physical_parameter_block, 

143 description="Property package to use for control volume", 

144 doc="""Property parameter object used to define property 

145calculations, **default** - useDefault. 

146**Valid values:** { 

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

148**PropertyParameterObject** - a PropertyParameterBlock object.}""", 

149 ), 

150 ) 

151 CONFIG.declare( 

152 "property_package_args", 

153 ConfigBlock( 

154 implicit=True, 

155 description="Arguments to use for constructing property packages", 

156 doc="""A ConfigBlock with arguments to be passed to a property 

157block(s) and used when constructing these, 

158**default** - None. 

159**Valid values:** { 

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

161 ), 

162 ) 

163 CONFIG.declare( 

164 "calculation_method", 

165 ConfigValue( 

166 default='isentropic', 

167 domain=In(["isentropic", "simple_willans", "part_load_willans", "Tsat_willans", "BPST_willans", "CT_willans"]), 

168 description="Calculation method used to model mechanical work", 

169 doc="""Property parameter object used to define property 

170calculations, **default** - 'isentropic'. 

171**Valid values:** { 

172**isentropic** - default method, uses isentropic efficiency to determine work 

173**simple_willans** - simple willans line requring slope, intercept and max molar flow. 

174**part_load_willans** - willans line with part load correction, a, b, c and max molar flow as parameters 

175**Tsat_willans** - willans line with part load correction using saturation temperature. Requires only max molar flow 

176**BPST_willans** - back pressure willans line with part load correction using pressure difference, requires max molar flow. 

177**CT_willans** - condensing turbine willans line with part load correction using pressure difference, requires max molar flow. 

178}""", 

179 ), 

180 ) 

181 

182 def build(self): 

183 """ 

184 

185 Args: 

186 None 

187 

188 Returns: 

189 None 

190 """ 

191 # Call UnitModel.build 

192 super().build() 

193 

194 # Add a control volume to the unit including setting up dynamics. 

195 self.control_volume = ControlVolume0DBlock( 

196 dynamic=self.config.dynamic, 

197 has_holdup=self.config.has_holdup, 

198 property_package=self.config.property_package, 

199 property_package_args=self.config.property_package_args, 

200 ) 

201 

202 # Add geometry variables to control volume 

203 if self.config.has_holdup: 203 ↛ 204line 203 didn't jump to line 204 because the condition on line 203 was never true

204 self.control_volume.add_geometry() 

205 

206 # Add inlet and outlet state blocks to control volume 

207 self.control_volume.add_state_blocks( 

208 has_phase_equilibrium=self.config.has_phase_equilibrium 

209 ) 

210 

211 # Add mass balance 

212 # Set has_equilibrium is False for now 

213 # TO DO; set has_equilibrium to True 

214 self.control_volume.add_material_balances( 

215 balance_type=self.config.material_balance_type, 

216 has_phase_equilibrium=self.config.has_phase_equilibrium, 

217 ) 

218 

219 # Add energy balance 

220 eb = self.control_volume.add_energy_balances( 

221 balance_type=self.config.energy_balance_type, has_work_transfer=True 

222 ) 

223 

224 # add momentum balance 

225 self.control_volume.add_momentum_balances( 

226 balance_type=self.config.momentum_balance_type, has_pressure_change=True 

227 ) 

228 

229 # Add Ports 

230 self.add_inlet_port() 

231 self.add_outlet_port() 

232 

233 # Set Unit Geometry and holdup Volume 

234 if self.config.has_holdup is True: 234 ↛ 235line 234 didn't jump to line 235 because the condition on line 234 was never true

235 self.volume = Reference(self.control_volume.volume[:]) 

236 

237 self.work_mechanical = Reference(self.control_volume.work[:]) 

238 # self.work_mechanical.fix(-1000e3) 

239 # self.work_mechanical.unfix() 

240 

241 

242 # Add Momentum balance variable 'deltaP' 

243 self.deltaP = Reference(self.control_volume.deltaP[:]) 

244 

245 # Performance Variables 

246 self.ratioP = Var(self.flowsheet().time, initialize=1.0, doc="Pressure Ratio") 

247 

248 # Pressure Ratio 

249 @self.Constraint(self.flowsheet().time, doc="Pressure ratio constraint") 

250 def ratioP_calculation(self, t): 

251 return ( 

252 self.ratioP[t] * self.control_volume.properties_in[t].pressure 

253 == self.control_volume.properties_out[t].pressure 

254 ) 

255 

256 units_meta = self.control_volume.config.property_package.get_metadata() 

257 

258 # Get indexing sets from control volume 

259 # Add isentropic variables 

260 self.efficiency_isentropic = Var( 

261 self.flowsheet().time, 

262 initialize=0.5, 

263 doc="Efficiency with respect to an isentropic process [-]", 

264 ) 

265 

266 # self.delta_h_is = Var( 

267 # self.flowsheet().time, 

268 # initialize=-100e3, 

269 # doc="Enthalpy difference input to unit if isentropic process", 

270 # units=units_meta.get_derived_units("energy") / units_meta.get_derived_units("amount"), 

271 # ) 

272 # self.delta_h_act = Var( 

273 # self.flowsheet().time, 

274 # initialize=-100e3, 

275 # doc="Enthalpy difference input to unit for actual process", 

276 # units=units_meta.get_derived_units("energy") / units_meta.get_derived_units("amount"), 

277 # ) 

278 # self.work_isentropic = Var( 

279 # self.flowsheet().time, 

280 # initialize=-100e3, 

281 # doc="Work input to unit if isentropic process", 

282 # units=units_meta.get_derived_units("power"), 

283 # ) 

284 

285 # Add motor/electrical work and efficiency variable 

286 self.efficiency_motor = Var( 

287 self.flowsheet().time, 

288 initialize=1.0, 

289 doc="Motor efficiency converting shaft work to electrical work [-]", 

290 ) 

291 

292 self.work_electrical = Var( 

293 self.flowsheet().time, 

294 initialize=1.0, 

295 doc="Electrical work of a turbine [-]", 

296 units=units_meta.get_derived_units("power") 

297 ) 

298 

299 # Add willans line parameters 

300 if 'willans' in self.config.calculation_method: 

301 self.willans_slope = Var( 

302 self.flowsheet().time, 

303 initialize=100, 

304 doc="Slope of willans line", 

305 units=units_meta.get_derived_units("energy") / units_meta.get_derived_units("amount"), 

306 ) 

307 

308 self.willans_intercept = Var( 

309 self.flowsheet().time, 

310 initialize=-100, 

311 doc="Intercept of willans line", 

312 units=units_meta.get_derived_units("power"), 

313 ) 

314 

315 self.willans_max_mol = Var( 

316 self.flowsheet().time, 

317 initialize=1.0, 

318 doc="Max molar flow of willans line", 

319 units=units_meta.get_derived_units("amount") / units_meta.get_derived_units("time"), 

320 ) 

321 

322 if self.config.calculation_method in ["part_load_willans", "Tsat_willans", "BPST_willans", "CT_willans"]: 

323 self.willans_a = Var( 

324 self.flowsheet().time, 

325 initialize=1.0, 

326 doc="Willans a coefficient", 

327 ) 

328 

329 self.willans_b = Var( 

330 self.flowsheet().time, 

331 initialize=1.0, 

332 doc="Willans b coefficient", 

333 units=units_meta.get_derived_units("power") 

334 ) 

335 

336 self.willans_efficiency = Var( 

337 self.flowsheet().time, 

338 initialize=1.0, 

339 doc="Willans efficiency", 

340 ) 

341 

342 # Build isentropic state block 

343 tmp_dict = dict(**self.config.property_package_args) 

344 tmp_dict["has_phase_equilibrium"] = self.config.has_phase_equilibrium 

345 tmp_dict["defined_state"] = False 

346 

347 self.properties_isentropic = self.config.property_package.build_state_block( 

348 self.flowsheet().time, doc="isentropic properties at outlet", **tmp_dict 

349 ) 

350 

351 # Connect isentropic state block properties 

352 @self.Constraint( 

353 self.flowsheet().time, doc="Pressure for isentropic calculations" 

354 ) 

355 def isentropic_pressure(self, t): 

356 return ( 

357 self.properties_isentropic[t].pressure 

358 == self.control_volume.properties_out[t].pressure 

359 ) 

360 

361 # This assumes isentropic composition is the same as outlet 

362 self.add_state_material_balances( 

363 self.config.material_balance_type, 

364 self.properties_isentropic, 

365 self.control_volume.properties_out, 

366 ) 

367 

368 # This assumes isentropic entropy is the same as inlet 

369 @self.Constraint(self.flowsheet().time, doc="Isentropic assumption") 

370 def isentropic(self, t): 

371 return ( 

372 self.properties_isentropic[t].entr_mol 

373 == self.control_volume.properties_in[t].entr_mol 

374 ) 

375 

376 self.add_isentropic_work_definition() 

377 

378 if 'willans' in self.config.calculation_method: 

379 # Write isentropic efficiency eqn 

380 self.add_willans_line_relationship() 

381 

382 if self.config.calculation_method in ["part_load_willans", "Tsat_willans", "BPST_willans", "CT_willans"]: 

383 if self.config.calculation_method == "Tsat_willans": # use published values and dTsat to calculate willans a,b,c 

384 self.calculate_Tsat_willans_parameters() 

385 

386 elif self.config.calculation_method == "BPST_willans": 

387 self.calculate_BPST_willans_parameters() 

388 

389 elif self.config.calculation_method == "CT_willans": 

390 self.calculate_CT_willans_parameters() 

391 

392 # calculate slope and intercept 

393 self.calculate_willans_coefficients() 

394 

395 self.add_mechanical_and_isentropic_work_definition() 

396 self.add_electrical_work_definition() 

397 

398 def calculate_CT_willans_parameters(self): 

399 # a parameter 

400 @self.Constraint( 

401 self.flowsheet().time, doc="Willans CT a calculation" 

402 ) 

403 def willans_CT_a_calculation(self, t): 

404 return self.willans_a[t] == 1.288464 - 0.0015185 * (self.control_volume.properties_in[t].pressure / 1e5) / pyunits.Pa - 0.33415834 * (self.control_volume.properties_out[t].pressure / 1e5) / pyunits.Pa 

405 

406 # b parameter 

407 @self.Constraint( 

408 self.flowsheet().time, doc="Willans CT b calculation" 

409 ) 

410 def willans_CT_b_calculation(self, t): 

411 return self.willans_b[t] == (-437.7746025 + 29.00736723 * (self.control_volume.properties_in[t].pressure / 1e5) / pyunits.Pa + 10.35902331 * (self.control_volume.properties_out[t].pressure / 1e5) / pyunits.Pa) * 1000 * pyunits.W 

412 

413 # c parameter 

414 @self.Constraint( 

415 self.flowsheet().time, doc="Willans CT efficiency calculation" 

416 ) 

417 def willans_CT_efficiency_calculation(self, t): 

418 return self.willans_efficiency[t] == 1 / ((0.07886297 + 0.000528327 * (self.control_volume.properties_in[t].pressure / 1e5) / pyunits.Pa - 0.703153891 * (self.control_volume.properties_out[t].pressure / 1e5) / pyunits.Pa) + 1) 

419 

420 def calculate_BPST_willans_parameters(self): 

421 # a parameter 

422 @self.Constraint( 

423 self.flowsheet().time, doc="Willans BPST a calculation" 

424 ) 

425 def willans_BPST_a_calculation(self, t): 

426 return self.willans_a[t] == 1.18795366 - 0.00029564 * (self.control_volume.properties_in[t].pressure / 1e5) / pyunits.Pa + 0.004647288 * (self.control_volume.properties_out[t].pressure / 1e5) / pyunits.Pa 

427 

428 

429 # b parameter 

430 @self.Constraint( 

431 self.flowsheet().time, doc="Willans BPST b calculation" 

432 ) 

433 def willans_BPST_b_calculation(self, t): 

434 return self.willans_b[t] == (449.9767142 + 5.670176939 * (self.control_volume.properties_in[t].pressure / 1e5) / pyunits.Pa - 11.5045814 * (self.control_volume.properties_out[t].pressure / 1e5) / pyunits.Pa) * 1000 * pyunits.W 

435 

436 

437 # c parameter 

438 @self.Constraint( 

439 self.flowsheet().time, doc="Willans BPST c calculation" 

440 ) 

441 def willans_BPST_efficiency_calculation(self, t): 

442 return self.willans_efficiency[t] == 1 / ((0.205149333 - 0.000695171 * (self.control_volume.properties_in[t].pressure / 1e5) / pyunits.Pa + 0.002844611 * (self.control_volume.properties_out[t].pressure / 1e5) / pyunits.Pa) + 1) 

443 

444 def calculate_Tsat_willans_parameters(self): 

445 # a parameter 

446 @self.Constraint( 

447 self.flowsheet().time, doc="Willans Tsat a calculation" 

448 ) 

449 def willans_Tsat_a_calculation(self, t): 

450 return self.willans_a[t] == (1.155 + 0.000538 * (self.control_volume.properties_in[t].temperature_sat - self.control_volume.properties_out[t].temperature_sat) / pyunits.K) 

451 

452 # b parameter 

453 @self.Constraint( 

454 self.flowsheet().time, doc="Willans Tsat b calculation" 

455 ) 

456 def willans_Tsat_b_calculation(self, t): 

457 return self.willans_b[t] == (0 + 4.23 * (self.control_volume.properties_in[t].temperature_sat - self.control_volume.properties_out[t].temperature_sat) / pyunits.K)*1000 * pyunits.W 

458 

459 # c parameter 

460 @self.Constraint( 

461 self.flowsheet().time, doc="Willans Tsat efficiency calculation" 

462 ) 

463 def willans_Tsat_c_calculation(self, t): 

464 return self.willans_efficiency[t] == 0.83333 

465 

466 def calculate_willans_coefficients(self): 

467 # Calculate willans coefficients 

468 @self.Constraint( 

469 self.flowsheet().time, doc="Willans slope calculation" 

470 ) 

471 def willans_slope_calculation(self, t): 

472 return self.willans_slope[t] == 1 / (self.willans_efficiency[t] * self.willans_a[t]) * ((self.control_volume.properties_in[t].enth_mol - self.properties_isentropic[t].enth_mol) - self.willans_b[t] / self.willans_max_mol[t]) 

473 

474 

475 @self.Constraint( 

476 self.flowsheet().time, doc="Willans intercept calculation" 

477 ) 

478 def willans_intercept_calculation(self, t): 

479 return self.willans_intercept[t] == ((1 - self.willans_efficiency[t]) / (self.willans_efficiency[t] * self.willans_a[t])) * ((self.control_volume.properties_in[t].enth_mol - self.properties_isentropic[t].enth_mol) * self.willans_max_mol[t] - self.willans_b[t]) 

480 

481 def add_mechanical_and_isentropic_work_definition(self): 

482 

483 if 'willans' in self.config.calculation_method: 

484 self.efficiency_isentropic = Expression( 

485 self.flowsheet().time, 

486 rule=lambda b, t: ( 

487 b.delta_h_act[t] / b.delta_h_is[t] 

488 ) 

489 ) 

490 else: 

491 # Mechanical work 

492 @self.Constraint( 

493 self.flowsheet().time, doc="Isentropic and mechanical work relationship" 

494 ) 

495 def isentropic_and_mechanical_work_eq(self, t): 

496 return self.work_mechanical[t] == ( 

497 self.delta_h_is[t] * self.control_volume.properties_in[t].flow_mol * self.efficiency_isentropic[t] 

498 ) 

499 

500 def add_willans_line_relationship(self): 

501 @self.Constraint( 

502 self.flowsheet().time, doc="Willans line and mechanical work relationship" 

503 ) 

504 def willans_line_eq(self, t): 

505 eps = 0.0001 # smoothing parameter; smaller = closer to exact max, larger = smoother 

506 return self.work_mechanical[t] == smooth_min( 

507 -(self.willans_slope[t] * self.control_volume.properties_in[t].flow_mol - self.willans_intercept[t]) / (self.willans_slope[t] * self.willans_max_mol[t]), 

508 0.0, 

509 eps 

510 ) * (self.willans_slope[t] * self.willans_max_mol[t]) 

511 

512 def add_electrical_work_definition(self): 

513 # Electrical work 

514 @self.Constraint( 

515 self.flowsheet().time, doc="Calculate electrical work of turbine" 

516 ) 

517 def electrical_energy_balance(self, t): 

518 return self.work_electrical[t] == self.work_mechanical[t] * self.efficiency_motor[t] 

519 

520 def add_isentropic_work_definition(self): 

521 self.delta_h_is = Expression( 

522 self.flowsheet().time, 

523 rule=lambda b, t: ( 

524 b.properties_isentropic[t].enth_mol - b.control_volume.properties_in[t].enth_mol 

525 ) 

526 ) 

527 self.delta_h_act = Expression( 

528 self.flowsheet().time, 

529 rule=lambda b, t: ( 

530 b.control_volume.properties_out[t].enth_mol - b.control_volume.properties_in[t].enth_mol 

531 ) 

532 ) 

533 # # Isentropic work 

534 # @self.Constraint( 

535 # self.flowsheet().time, doc="Calculate work of isentropic process" 

536 # ) 

537 # def isentropic_energy_balance(self, t): 

538 # return self.work_isentropic[t] == ( self.properties_isentropic[t].enth_mol - self.control_volume.properties_in[t].enth_mol ) * self.control_volume.properties_in[t].flow_mol 

539 

540 def initialize_build( 

541 blk, 

542 state_args=None, 

543 routine=None, 

544 outlvl=idaeslog.NOTSET, 

545 solver=None, 

546 optarg=None, 

547 ): 

548 """ 

549 General wrapper for pressure changer initialization routines 

550 

551 Keyword Arguments: 

552 routine : str stating which initialization routine to execute 

553 * None - use routine matching thermodynamic_assumption 

554 * 'isentropic' - use isentropic initialization routine 

555 * 'isothermal' - use isothermal initialization routine 

556 state_args : a dict of arguments to be passed to the property 

557 package(s) to provide an initial state for 

558 initialization (see documentation of the specific 

559 property package) (default = {}). 

560 outlvl : sets output level of initialization routine 

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

562 default solver options) 

563 solver : str indicating which solver to use during 

564 initialization (default = None, use default solver) 

565 

566 Returns: 

567 None 

568 """ 

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

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

571 

572 # Create solver 

573 opt = get_solver(solver, optarg) 

574 

575 cv = blk.control_volume 

576 t0 = blk.flowsheet().time.first() 

577 state_args_out = {} 

578 

579 if state_args is None: 579 ↛ 592line 579 didn't jump to line 592 because the condition on line 579 was always true

580 state_args = {} 

581 state_dict = cv.properties_in[t0].define_port_members() 

582 

583 for k in state_dict.keys(): 

584 if state_dict[k].is_indexed(): 

585 state_args[k] = {} 

586 for m in state_dict[k].keys(): 

587 state_args[k][m] = state_dict[k][m].value 

588 else: 

589 state_args[k] = state_dict[k].value 

590 

591 # Get initialisation guesses for outlet and isentropic states 

592 for k in state_args: 

593 if k == "pressure" and k not in state_args_out: 

594 # Work out how to estimate outlet pressure 

595 if cv.properties_out[t0].pressure.fixed: 

596 # Fixed outlet pressure, use this value 

597 state_args_out[k] = value(cv.properties_out[t0].pressure) 

598 elif blk.deltaP[t0].fixed: 598 ↛ 599line 598 didn't jump to line 599 because the condition on line 598 was never true

599 state_args_out[k] = value(state_args[k] + blk.deltaP[t0]) 

600 elif blk.ratioP[t0].fixed: 600 ↛ 601line 600 didn't jump to line 601 because the condition on line 600 was never true

601 state_args_out[k] = value(state_args[k] * blk.ratioP[t0]) 

602 else: 

603 # Not obvious what to do, use inlet state 

604 state_args_out[k] = state_args[k] 

605 elif k not in state_args_out: 605 ↛ 592line 605 didn't jump to line 592 because the condition on line 605 was always true

606 state_args_out[k] = state_args[k] 

607 

608 # Initialize state blocks 

609 flags = cv.properties_in.initialize( 

610 outlvl=outlvl, 

611 optarg=optarg, 

612 solver=solver, 

613 hold_state=True, 

614 state_args=state_args, 

615 ) 

616 cv.properties_out.initialize( 

617 outlvl=outlvl, 

618 optarg=optarg, 

619 solver=solver, 

620 hold_state=False, 

621 state_args=state_args_out, 

622 ) 

623 

624 init_log.info_high("Initialization Step 1 Complete.") 

625 # --------------------------------------------------------------------- 

626 # Initialize Isentropic block 

627 

628 blk.properties_isentropic.initialize( 

629 outlvl=outlvl, 

630 optarg=optarg, 

631 solver=solver, 

632 state_args=state_args_out, 

633 ) 

634 

635 init_log.info_high("Initialization Step 2 Complete.") 

636 

637 # Skipping step 3 because Isothermal had problems. 

638 

639 # --------------------------------------------------------------------- 

640 # Solve unit 

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

642 res = opt.solve(blk, tee=slc.tee) 

643 init_log.info_high("Initialization Step 4 {}.".format(idaeslog.condition(res))) 

644 

645 

646 # --------------------------------------------------------------------- 

647 # Release Inlet state 

648 blk.control_volume.release_state(flags, outlvl) 

649 

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

651 raise InitializationError( 

652 f"{blk.name} failed to initialize successfully. Please check " 

653 f"the output logs for more information." 

654 ) 

655 

656 init_log.info(f"Initialization Complete: {idaeslog.condition(res)}") 

657 

658 def _get_performance_contents(self, time_point=0): 

659 var_dict = {} 

660 if hasattr(self, "deltaP"): 660 ↛ 662line 660 didn't jump to line 662 because the condition on line 660 was always true

661 var_dict["Mechanical Work"] = self.work_mechanical[time_point] 

662 if hasattr(self, "deltaP"): 662 ↛ 664line 662 didn't jump to line 664 because the condition on line 662 was always true

663 var_dict["Electrical Work"] = self.work_electrical[time_point] 

664 if hasattr(self, "deltaP"): 664 ↛ 666line 664 didn't jump to line 666 because the condition on line 664 was always true

665 var_dict["Pressure Change"] = self.deltaP[time_point] 

666 if hasattr(self, "ratioP"): 666 ↛ 671line 666 didn't jump to line 671 because the condition on line 666 was always true

667 var_dict["Pressure Ratio"] = self.ratioP[time_point] 

668 # if hasattr(self, "efficiency_isentropic"): 

669 # var_dict["Isentropic Efficiency"] = self.efficiency_isentropic[time_point] 

670 

671 return {"vars": var_dict} 

672 

673 def calculate_scaling_factors(self): 

674 super().calculate_scaling_factors() 

675 

676 if hasattr(self, "work_fluid"): 

677 for t, v in self.work_fluid.items(): 

678 iscale.set_scaling_factor( 

679 v, 

680 iscale.get_scaling_factor( 

681 self.control_volume.work[t], default=1, warning=True 

682 ), 

683 ) 

684 

685 if hasattr(self, "work_mechanical"): 

686 for t, v in self.work_mechanical.items(): 

687 iscale.set_scaling_factor( 

688 v, 

689 iscale.get_scaling_factor( 

690 self.control_volume.work[t], default=1, warning=True 

691 ), 

692 ) 

693 

694 if hasattr(self, "work_isentropic"): 

695 for t, v in self.work_isentropic.items(): 

696 iscale.set_scaling_factor( 

697 v, 

698 iscale.get_scaling_factor( 

699 self.control_volume.work[t], default=1, warning=True 

700 ), 

701 ) 

702 

703 if hasattr(self, "ratioP_calculation"): 

704 for t, c in self.ratioP_calculation.items(): 

705 iscale.constraint_scaling_transform( 

706 c, 

707 iscale.get_scaling_factor( 

708 self.control_volume.properties_in[t].pressure, 

709 default=1, 

710 warning=True, 

711 ), 

712 overwrite=False, 

713 ) 

714 

715 if hasattr(self, "fluid_work_calculation"): 

716 for t, c in self.fluid_work_calculation.items(): 

717 iscale.constraint_scaling_transform( 

718 c, 

719 iscale.get_scaling_factor( 

720 self.control_volume.deltaP[t], default=1, warning=True 

721 ), 

722 overwrite=False, 

723 ) 

724 

725 if hasattr(self, "actual_work"): 

726 for t, c in self.actual_work.items(): 

727 iscale.constraint_scaling_transform( 

728 c, 

729 iscale.get_scaling_factor( 

730 self.control_volume.work[t], default=1, warning=True 

731 ), 

732 overwrite=False, 

733 ) 

734 

735 if hasattr(self, "isentropic_pressure"): 

736 for t, c in self.isentropic_pressure.items(): 

737 iscale.constraint_scaling_transform( 

738 c, 

739 iscale.get_scaling_factor( 

740 self.control_volume.properties_in[t].pressure, 

741 default=1, 

742 warning=True, 

743 ), 

744 overwrite=False, 

745 ) 

746 

747 if hasattr(self, "isentropic"): 

748 for t, c in self.isentropic.items(): 

749 iscale.constraint_scaling_transform( 

750 c, 

751 iscale.get_scaling_factor( 

752 self.control_volume.properties_in[t].entr_mol, 

753 default=1, 

754 warning=True, 

755 ), 

756 overwrite=False, 

757 ) 

758 

759 if hasattr(self, "isentropic_energy_balance"): 

760 for t, c in self.isentropic_energy_balance.items(): 

761 iscale.constraint_scaling_transform( 

762 c, 

763 iscale.get_scaling_factor( 

764 self.control_volume.work[t], default=1, warning=True 

765 ), 

766 overwrite=False, 

767 ) 

768 

769 if hasattr(self, "zero_work_equation"): 

770 for t, c in self.zero_work_equation.items(): 

771 iscale.constraint_scaling_transform( 

772 c, 

773 iscale.get_scaling_factor( 

774 self.control_volume.work[t], default=1, warning=True 

775 ), 

776 ) 

777 

778 if hasattr(self, "state_material_balances"): 

779 cvol = self.control_volume 

780 phase_list = cvol.properties_in.phase_list 

781 phase_component_set = cvol.properties_in.phase_component_set 

782 mb_type = cvol._constructed_material_balance_type 

783 if mb_type == MaterialBalanceType.componentPhase: 

784 for (t, p, j), c in self.state_material_balances.items(): 

785 sf = iscale.get_scaling_factor( 

786 cvol.properties_in[t].get_material_flow_terms(p, j), 

787 default=1, 

788 warning=True, 

789 ) 

790 iscale.constraint_scaling_transform(c, sf) 

791 elif mb_type == MaterialBalanceType.componentTotal: 

792 for (t, j), c in self.state_material_balances.items(): 

793 sf = iscale.min_scaling_factor( 

794 [ 

795 cvol.properties_in[t].get_material_flow_terms(p, j) 

796 for p in phase_list 

797 if (p, j) in phase_component_set 

798 ] 

799 ) 

800 iscale.constraint_scaling_transform(c, sf) 

801 else: 

802 # There are some other material balance types but they create 

803 # constraints with different names. 

804 _log.warning(f"Unknown material balance type {mb_type}")