Coverage for backend/idaes_service/solver/custom/custom_heat_exchanger.py: 79%
74 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
3# Import Pyomo libraries
4from pyomo.environ import (
5 Block,
6 Var,
7 Param,
8 log,
9 Reference,
10 PositiveReals,
11 ExternalFunction,
12 units as pyunits,
13 check_optimal_termination,
14)
15from pyomo.common.config import ConfigBlock, ConfigValue, In
17# Import IDAES cores
18from idaes.core import (
19 declare_process_block_class,
20 UnitModelBlockData,
21)
23import idaes.logger as idaeslog
24from idaes.core.util.functions import functions_lib
25from idaes.core.util.tables import create_stream_table_dataframe
26from idaes.models.unit_models.heater import (
27 _make_heater_config_block,
28 _make_heater_control_volume,
29)
31from idaes.core.util.misc import add_object_reference
32from idaes.core.util import scaling as iscale
33from idaes.core.solvers import get_solver
34from idaes.core.util.exceptions import ConfigurationError, InitializationError
35from idaes.core.initialization import SingleControlVolumeUnitInitializer
36from idaes.models.unit_models.heat_exchanger import HX0DInitializer, _make_heat_exchanger_config, HeatExchangerData
37from .inverted import add_inverted, initialise_inverted
38_log = idaeslog.getLogger(__name__)
41@declare_process_block_class("CustomHeatExchanger", doc="Simple 0D heat exchanger model.")
42class CustomHeatExchangerData(HeatExchangerData):
44 def build(self,*args,**kwargs) -> None:
45 """
46 Begin building model.
47 """
48 super().build(*args,**kwargs)
49 # Add an inverted DeltaP
50 add_inverted(self.hot_side, "deltaP")
51 add_inverted(self.cold_side, "deltaP")
53 def initialize_build(
54 self,
55 state_args_1=None,
56 state_args_2=None,
57 outlvl=idaeslog.NOTSET,
58 solver=None,
59 optarg=None,
60 duty=None,
61 ):
62 """
63 Heat exchanger initialization method.
65 Args:
66 state_args_1 : a dict of arguments to be passed to the property
67 initialization for the hot side (see documentation of the specific
68 property package) (default = {}).
69 state_args_2 : a dict of arguments to be passed to the property
70 initialization for the cold side (see documentation of the specific
71 property package) (default = {}).
72 outlvl : sets output level of initialization routine
73 optarg : solver options dictionary object (default=None, use
74 default solver options)
75 solver : str indicating which solver to use during
76 initialization (default = None, use default solver)
77 duty : an initial guess for the amount of heat transferred. This
78 should be a tuple in the form (value, units), (default
79 = (1000 J/s))
81 Returns:
82 None
84 """
85 # So, when solving with a correct area, there can be problems
86 # That's because if the area's even slightly too large, it becomes infeasible
87 if not self.area.fixed: 87 ↛ 88line 87 didn't jump to line 88 because the condition on line 87 was never true
88 self.area.value = self.area.value * 0.8
90 initialise_inverted(self.hot_side, "deltaP")
91 initialise_inverted(self.cold_side, "deltaP")
93 # Set solver options
94 init_log = idaeslog.getInitLogger(self.name, outlvl, tag="unit")
95 solve_log = idaeslog.getSolveLogger(self.name, outlvl, tag="unit")
97 # Create solver
98 opt = get_solver(solver, optarg)
100 flags1 = self.hot_side.initialize(
101 outlvl=outlvl, optarg=optarg, solver=solver, state_args=state_args_1
102 )
104 init_log.info_high("Initialization Step 1a (hot side) Complete.")
106 flags2 = self.cold_side.initialize(
107 outlvl=outlvl, optarg=optarg, solver=solver, state_args=state_args_2
108 )
109 init_log.info_high("Initialization Step 1b (cold side) Complete.")
110 # ---------------------------------------------------------------------
111 # Solve unit without heat transfer equation
112 self.heat_transfer_equation.deactivate()
113 if hasattr( self.cold_side.properties_out[0], "constraints"): 113 ↛ 115line 113 didn't jump to line 115 because the condition on line 113 was always true
114 self.cold_side.properties_out[0].constraints.deactivate()
115 if hasattr( self.hot_side.properties_out[0], "constraints"): 115 ↛ 119line 115 didn't jump to line 119 because the condition on line 115 was always true
116 self.hot_side.properties_out[0].constraints.deactivate()
118 # Get side 1 and side 2 heat units, and convert duty as needed
119 s1_units = self.hot_side.heat.get_units()
120 s2_units = self.cold_side.heat.get_units()
122 # Check to see if heat duty is fixed
123 # WE will assume that if the first point is fixed, it is fixed at all points
124 if not self.cold_side.heat[self.flowsheet().time.first()].fixed: 124 ↛ 152line 124 didn't jump to line 152 because the condition on line 124 was always true
125 cs_fixed = False
126 if duty is None: 126 ↛ 141line 126 didn't jump to line 141 because the condition on line 126 was always true
127 # Assume 1000 J/s and check for unitless properties
128 if s1_units is None and s2_units is None: 128 ↛ 130line 128 didn't jump to line 130 because the condition on line 128 was never true
129 # Backwards compatibility for unitless properties
130 s1_duty = -1000
131 s2_duty = 1000
132 else:
133 s1_duty = pyunits.convert_value(
134 -1000, from_units=pyunits.W, to_units=s1_units
135 )
136 s2_duty = pyunits.convert_value(
137 1000, from_units=pyunits.W, to_units=s2_units
138 )
139 else:
140 # Duty provided with explicit units
141 s1_duty = -pyunits.convert_value(
142 duty[0], from_units=duty[1], to_units=s1_units
143 )
144 s2_duty = pyunits.convert_value(
145 duty[0], from_units=duty[1], to_units=s2_units
146 )
148 self.cold_side.heat.fix(s2_duty)
149 for i in self.hot_side.heat:
150 self.hot_side.heat[i].value = s1_duty
151 else:
152 cs_fixed = True
153 for i in self.hot_side.heat:
154 self.hot_side.heat[i].set_value(self.cold_side.heat[i])
155 with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc:
156 res = opt.solve(self, tee=slc.tee)
157 init_log.info_high("Initialization Step 2 {}.".format(idaeslog.condition(res)))
158 if not cs_fixed: 158 ↛ 160line 158 didn't jump to line 160 because the condition on line 158 was always true
159 self.cold_side.heat.unfix()
160 if hasattr( self.cold_side.properties_out[0], "constraints"): 160 ↛ 162line 160 didn't jump to line 162 because the condition on line 160 was always true
161 self.cold_side.properties_out[0].constraints.activate()
162 if hasattr( self.hot_side.properties_out[0], "constraints"): 162 ↛ 164line 162 didn't jump to line 164 because the condition on line 162 was always true
163 self.hot_side.properties_out[0].constraints.activate()
164 self.heat_transfer_equation.activate()
166 # ---------------------------------------------------------------------
167 # Solve unit
168 with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc:
169 res = opt.solve(self, tee=slc.tee)
170 init_log.info_high("Initialization Step 3 {}.".format(idaeslog.condition(res)))
171 # ---------------------------------------------------------------------
173 # Release Inlet state
174 self.hot_side.release_state(flags1, outlvl=outlvl)
175 self.cold_side.release_state(flags2, outlvl=outlvl)
177 init_log.info("Initialization Completed, {}".format(idaeslog.condition(res)))
179 if not check_optimal_termination(res): 179 ↛ 180line 179 didn't jump to line 180 because the condition on line 179 was never true
180 raise InitializationError(
181 f"{self.name} failed to initialize successfully. Please check "
182 f"the output logs for more information."
183 )