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
« 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
16"""
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
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
53__author__ = "Paul Akula, Andrew Lee, Ben Lincoln"
56# Set up logger
57_log = idaeslog.getLogger(__name__)
60class HXEFFInitializer(SingleControlVolumeUnitInitializer):
61 """
62 Initializer for NTU Heat Exchanger units.
64 """
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.
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.
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.
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 )
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.
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.
116 Returns:
117 Pyomo solver results object.
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 )
132 # Create solver
133 solver = self._get_solver()
135 self.initialize_control_volume(model.hot_side, copy_inlet_state)
136 init_log.info_high("Initialization Step 1a (hot side) Complete.")
138 self.initialize_control_volume(model.cold_side, copy_inlet_state)
139 init_log.info_high("Initialization Step 1b (cold side) Complete.")
141 # ---------------------------------------------------------------------
142 # Solve unit without heat transfer equation
143 model.energy_balance_constraint.deactivate()
145 model.cold_side.heat.fix(duty)
146 for i in model.hot_side.heat:
147 model.hot_side.heat[i].set_value(-duty)
149 with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc:
150 res = solver.solve(model, tee=slc.tee)
152 init_log.info_high("Initialization Step 2 {}.".format(idaeslog.condition(res)))
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)))
162 return res
165@declare_process_block_class("HeatExchangerEffectiveness")
166class HeatExchangerEFFData(UnitModelBlockData):
167 """Heat Exchanger Unit Model using NTU method."""
169 default_initializer = HXEFFInitializer
171 CONFIG = UnitModelBlockData.CONFIG(implicit=True)
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 )
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 )
300 def build(self):
301 # Call UnitModel.build to setup model
302 super().build()
303 hx_process_config(self)
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 )
314 # TODO : Add support for phase equilibrium?
315 self.hot_side.add_state_blocks(has_phase_equilibrium=self.config.hot_side.has_phase_equilibrium)
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 )
322 self.hot_side.add_energy_balances(
323 balance_type=self.config.hot_side.energy_balance_type,
324 has_heat_transfer=True,
325 )
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 )
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 )
341 self.cold_side.add_state_blocks(has_phase_equilibrium=self.config.cold_side.has_phase_equilibrium)
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 )
348 self.cold_side.add_energy_balances(
349 balance_type=self.config.cold_side.energy_balance_type,
350 has_heat_transfer=True,
351 )
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 )
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 )
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 )
374 # ---------------------------------------------------------------------
375 # Add unit level References
376 # Set references to balance terms at unit level
377 self.heat_duty = Reference(self.cold_side.heat[:])
379 # Add references to the user provided aliases (if applicable).
380 add_hx_references(self)
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
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 )
393 self.energy_balance_constraint = Constraint(
394 self.flowsheet().time, rule=rule_energy_balance
395 )
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 )
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 )
414###########
416 # Todo: Will not work for a pure component system in the two phase region
418###########
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
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
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
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 )
451########
453 # Todo: Will not work for a pure component system in the two phase region
455###########
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
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
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 )
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)
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 )
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 )
528 self.heat_duty_constraint = Constraint(self.flowsheet().time, rule=rule_effectiveness)
530 add_inverted(self.hot_side,"deltaP")
531 add_inverted(self.cold_side,"deltaP")
532 # TODO : Add scaling methods
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.
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))
562 Returns:
563 None
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")
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
578 # Create solver
579 opt = get_solver(solver, optarg)
581 flags1 = hot_side.initialize(
582 outlvl=outlvl, optarg=optarg, solver=solver, state_args=hot_side_state_args
583 )
585 init_log.info_high("Initialization Step 1a (hot side) Complete.")
587 flags2 = cold_side.initialize(
588 outlvl=outlvl, optarg=optarg, solver=solver, state_args=cold_side_state_args
589 )
591 init_log.info_high("Initialization Step 1b (cold side) Complete.")
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)
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)
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)
602 flags3 = hot_side_max.initialize(
603 outlvl=outlvl
604 )
605 init_log.info_high("Initialization Step 1c (hot side max) Complete.")
607 flags4 = cold_side_max.initialize(
608 outlvl=outlvl
609 )
610 init_log.info_high("Initialization Step 1d (cold side max) Complete.")
612 hot_side_max[0].display()
613 cold_side_max[0].display()
616 #----------------------------------------------------------------------
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()
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()
638 with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc:
639 res = opt.solve(self, tee=slc.tee)
642 init_log.info_high("Initialization Step 1.5 {}.".format(idaeslog.condition(res)))
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()
655 # self.Qmax[0].set_value(1000)
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()
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 )
683 cold_side.heat.fix(s2_duty)
684 for i in hot_side.heat:
685 hot_side.heat[i].value = s1_duty
687 print("DOF:", degrees_of_freedom(self))
689 with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc:
690 res = opt.solve(self, tee=slc.tee)
692 init_log.info_high("Initialization Step 2 {}.".format(idaeslog.condition(res)))
694 cold_side.heat.unfix()
695 self.energy_balance_constraint.activate()
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)))
703 # ---------------------------------------------------------------------
704 # Release Inlet state
705 hot_side.release_state(flags1, outlvl=outlvl)
706 cold_side.release_state(flags2, outlvl=outlvl)
708 init_log.info("Initialization Completed, {}".format(idaeslog.condition(res)))
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 )
716 return res
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 )