ahuora-builder 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. ahuora_builder/__init__.py +0 -0
  2. ahuora_builder/arc_manager.py +33 -0
  3. ahuora_builder/build_state.py +57 -0
  4. ahuora_builder/custom/PIDController.py +494 -0
  5. ahuora_builder/custom/PySMOModel.py +178 -0
  6. ahuora_builder/custom/SimpleEffectivenessHX_DH.py +727 -0
  7. ahuora_builder/custom/__init__.py +0 -0
  8. ahuora_builder/custom/add_initial_dynamics.py +35 -0
  9. ahuora_builder/custom/custom_compressor.py +107 -0
  10. ahuora_builder/custom/custom_cooler.py +33 -0
  11. ahuora_builder/custom/custom_heat_exchanger.py +183 -0
  12. ahuora_builder/custom/custom_heat_exchanger_1d.py +258 -0
  13. ahuora_builder/custom/custom_heater.py +41 -0
  14. ahuora_builder/custom/custom_pressure_changer.py +34 -0
  15. ahuora_builder/custom/custom_pump.py +107 -0
  16. ahuora_builder/custom/custom_separator.py +371 -0
  17. ahuora_builder/custom/custom_tank.py +133 -0
  18. ahuora_builder/custom/custom_turbine.py +132 -0
  19. ahuora_builder/custom/custom_valve.py +300 -0
  20. ahuora_builder/custom/custom_variable.py +29 -0
  21. ahuora_builder/custom/direct_steam_injection.py +371 -0
  22. ahuora_builder/custom/energy/__init__.py +0 -0
  23. ahuora_builder/custom/energy/acBus.py +280 -0
  24. ahuora_builder/custom/energy/ac_property_package.py +279 -0
  25. ahuora_builder/custom/energy/battery.py +170 -0
  26. ahuora_builder/custom/energy/bus.py +182 -0
  27. ahuora_builder/custom/energy/energy_mixer.py +195 -0
  28. ahuora_builder/custom/energy/energy_splitter.py +228 -0
  29. ahuora_builder/custom/energy/grid.py +173 -0
  30. ahuora_builder/custom/energy/hydro.py +169 -0
  31. ahuora_builder/custom/energy/link.py +137 -0
  32. ahuora_builder/custom/energy/load.py +155 -0
  33. ahuora_builder/custom/energy/mainDistributionBoard.py +257 -0
  34. ahuora_builder/custom/energy/power_property_package.py +253 -0
  35. ahuora_builder/custom/energy/solar.py +176 -0
  36. ahuora_builder/custom/energy/storage.py +230 -0
  37. ahuora_builder/custom/energy/storage_wrapper +0 -0
  38. ahuora_builder/custom/energy/tests/__init__.py +0 -0
  39. ahuora_builder/custom/energy/tests/test_bus.py +44 -0
  40. ahuora_builder/custom/energy/tests/test_energy_mixer.py +46 -0
  41. ahuora_builder/custom/energy/tests/test_mdb.py +49 -0
  42. ahuora_builder/custom/energy/transformer.py +187 -0
  43. ahuora_builder/custom/energy/transformer_property_package.py +267 -0
  44. ahuora_builder/custom/energy/transmissionLine.py +228 -0
  45. ahuora_builder/custom/energy/wind.py +206 -0
  46. ahuora_builder/custom/hda_ideal_VLE.py +1341 -0
  47. ahuora_builder/custom/hda_reaction.py +182 -0
  48. ahuora_builder/custom/heat_exchanger_1d_wrapper.py +31 -0
  49. ahuora_builder/custom/integration_block.py +106 -0
  50. ahuora_builder/custom/inverted.py +81 -0
  51. ahuora_builder/custom/performance_curves.py +1 -0
  52. ahuora_builder/custom/reactions/__init__.py +0 -0
  53. ahuora_builder/custom/reactions/hda_stoich.py +10 -0
  54. ahuora_builder/custom/simple_separator.py +680 -0
  55. ahuora_builder/custom/tests/__init__.py +0 -0
  56. ahuora_builder/custom/tests/test_SimpleEffectivenessHX_DH.py +91 -0
  57. ahuora_builder/custom/tests/test_custom_tank.py +70 -0
  58. ahuora_builder/custom/tests/test_direct_steam_injection.py +41 -0
  59. ahuora_builder/custom/tests/test_simple_separator.py +46 -0
  60. ahuora_builder/custom/tests/test_waterpipe.py +46 -0
  61. ahuora_builder/custom/thermal_utility_systems/desuperheater.py +624 -0
  62. ahuora_builder/custom/thermal_utility_systems/header.py +889 -0
  63. ahuora_builder/custom/thermal_utility_systems/simple_heat_pump.py +567 -0
  64. ahuora_builder/custom/thermal_utility_systems/steam_header.py +353 -0
  65. ahuora_builder/custom/thermal_utility_systems/steam_user.py +944 -0
  66. ahuora_builder/custom/thermal_utility_systems/temp.py +349 -0
  67. ahuora_builder/custom/thermal_utility_systems/tests/test_desuperheater.py +142 -0
  68. ahuora_builder/custom/thermal_utility_systems/tests/test_header.py +998 -0
  69. ahuora_builder/custom/thermal_utility_systems/tests/test_ntu_hx.py +129 -0
  70. ahuora_builder/custom/thermal_utility_systems/tests/test_simple_heat_pump.py +120 -0
  71. ahuora_builder/custom/thermal_utility_systems/tests/test_steam_header.py +703 -0
  72. ahuora_builder/custom/thermal_utility_systems/tests/test_steam_user.py +277 -0
  73. ahuora_builder/custom/thermal_utility_systems/tests/test_waterpipe.py +36 -0
  74. ahuora_builder/custom/thermal_utility_systems/tests/test_willans_turbine.py +253 -0
  75. ahuora_builder/custom/thermal_utility_systems/willans_turbine.py +804 -0
  76. ahuora_builder/custom/translator.py +129 -0
  77. ahuora_builder/custom/updated_pressure_changer.py +1404 -0
  78. ahuora_builder/custom/valve_wrapper.py +38 -0
  79. ahuora_builder/custom/water_tank_with_units.py +456 -0
  80. ahuora_builder/diagnostics/__init__.py +0 -0
  81. ahuora_builder/diagnostics/infeasibilities.py +40 -0
  82. ahuora_builder/diagnostics/tests/__init__.py +0 -0
  83. ahuora_builder/diagnostics/tests/test_infeasibilities.py +28 -0
  84. ahuora_builder/flowsheet_manager.py +542 -0
  85. ahuora_builder/flowsheet_manager_type.py +20 -0
  86. ahuora_builder/generate_python_file.py +440 -0
  87. ahuora_builder/methods/BlockContext.py +84 -0
  88. ahuora_builder/methods/__init__.py +0 -0
  89. ahuora_builder/methods/adapter.py +355 -0
  90. ahuora_builder/methods/adapter_library.py +549 -0
  91. ahuora_builder/methods/adapter_methods.py +80 -0
  92. ahuora_builder/methods/expression_parsing.py +105 -0
  93. ahuora_builder/methods/load_unit_model.py +147 -0
  94. ahuora_builder/methods/slice_manipulation.py +7 -0
  95. ahuora_builder/methods/tests/__init__.py +0 -0
  96. ahuora_builder/methods/tests/test_expression_parsing.py +15 -0
  97. ahuora_builder/methods/units_handler.py +129 -0
  98. ahuora_builder/ml_wizard.py +101 -0
  99. ahuora_builder/port_manager.py +20 -0
  100. ahuora_builder/properties_manager.py +44 -0
  101. ahuora_builder/property_package_manager.py +78 -0
  102. ahuora_builder/solver.py +38 -0
  103. ahuora_builder/tear_manager.py +98 -0
  104. ahuora_builder/tests/__init__.py +0 -0
  105. ahuora_builder/tests/test_generate_python_file/__init__.py +0 -0
  106. ahuora_builder/tests/test_generate_python_file/configurations/compressor_generated.py +63 -0
  107. ahuora_builder/tests/test_generate_python_file/configurations/heat_exchanger_generated.py +70 -0
  108. ahuora_builder/tests/test_generate_python_file/configurations/pump_generated.py +84 -0
  109. ahuora_builder/tests/test_generate_python_file/configurations/recycle_generated.py +73 -0
  110. ahuora_builder/tests/test_generate_python_file/test_generate_python_file.py +108 -0
  111. ahuora_builder/tests/test_solver/__init__.py +0 -0
  112. ahuora_builder/tests/test_solver/configurations/BT_PR.json +59 -0
  113. ahuora_builder/tests/test_solver/configurations/BT_PR_solved.json +59 -0
  114. ahuora_builder/tests/test_solver/configurations/bus.json +99 -0
  115. ahuora_builder/tests/test_solver/configurations/bus_solved.json +50 -0
  116. ahuora_builder/tests/test_solver/configurations/compound_separator.json +377 -0
  117. ahuora_builder/tests/test_solver/configurations/compound_separator_solved.json +374 -0
  118. ahuora_builder/tests/test_solver/configurations/compressor.json +38 -0
  119. ahuora_builder/tests/test_solver/configurations/compressor_solved.json +68 -0
  120. ahuora_builder/tests/test_solver/configurations/constraints.json +44 -0
  121. ahuora_builder/tests/test_solver/configurations/constraints_solved.json +59 -0
  122. ahuora_builder/tests/test_solver/configurations/control.json +39 -0
  123. ahuora_builder/tests/test_solver/configurations/control_solved.json +68 -0
  124. ahuora_builder/tests/test_solver/configurations/dynamic_tank.json +733 -0
  125. ahuora_builder/tests/test_solver/configurations/dynamic_tank_solved.json +846 -0
  126. ahuora_builder/tests/test_solver/configurations/elimination.json +39 -0
  127. ahuora_builder/tests/test_solver/configurations/elimination_solved.json +68 -0
  128. ahuora_builder/tests/test_solver/configurations/expressions.json +68 -0
  129. ahuora_builder/tests/test_solver/configurations/expressions_solved.json +104 -0
  130. ahuora_builder/tests/test_solver/configurations/header.json +1192 -0
  131. ahuora_builder/tests/test_solver/configurations/header_solved.json +761 -0
  132. ahuora_builder/tests/test_solver/configurations/heat_exchanger.json +63 -0
  133. ahuora_builder/tests/test_solver/configurations/heat_exchanger_solved.json +104 -0
  134. ahuora_builder/tests/test_solver/configurations/heat_pump.json +137 -0
  135. ahuora_builder/tests/test_solver/configurations/heat_pump_solved.json +104 -0
  136. ahuora_builder/tests/test_solver/configurations/machine_learning.json +2156 -0
  137. ahuora_builder/tests/test_solver/configurations/machine_learning_solved.json +266 -0
  138. ahuora_builder/tests/test_solver/configurations/mass_flow_tear.json +77 -0
  139. ahuora_builder/tests/test_solver/configurations/mass_flow_tear_solved.json +68 -0
  140. ahuora_builder/tests/test_solver/configurations/milk_heater.json +521 -0
  141. ahuora_builder/tests/test_solver/configurations/milk_heater_solved.json +311 -0
  142. ahuora_builder/tests/test_solver/configurations/mixer.json +44 -0
  143. ahuora_builder/tests/test_solver/configurations/mixer_solved.json +86 -0
  144. ahuora_builder/tests/test_solver/configurations/optimization.json +62 -0
  145. ahuora_builder/tests/test_solver/configurations/optimization_solved.json +59 -0
  146. ahuora_builder/tests/test_solver/configurations/propane_heat_pump.json +167 -0
  147. ahuora_builder/tests/test_solver/configurations/propane_heat_pump_solved.json +158 -0
  148. ahuora_builder/tests/test_solver/configurations/propane_recycle.json +141 -0
  149. ahuora_builder/tests/test_solver/configurations/propane_recycle_solved.json +104 -0
  150. ahuora_builder/tests/test_solver/configurations/pump.json +64 -0
  151. ahuora_builder/tests/test_solver/configurations/pump_solved.json +59 -0
  152. ahuora_builder/tests/test_solver/configurations/pump_unit_conversions.json +63 -0
  153. ahuora_builder/tests/test_solver/configurations/recycle.json +49 -0
  154. ahuora_builder/tests/test_solver/configurations/recycle_solved.json +50 -0
  155. ahuora_builder/tests/test_solver/configurations/sb_vapor_frac.json +29 -0
  156. ahuora_builder/tests/test_solver/configurations/sb_vapor_frac_solved.json +29 -0
  157. ahuora_builder/tests/test_solver/configurations/solar.json +67 -0
  158. ahuora_builder/tests/test_solver/configurations/solar_solved.json +50 -0
  159. ahuora_builder/tests/test_solver/configurations/vapor_frac_target.json +67 -0
  160. ahuora_builder/tests/test_solver/configurations/vapor_frac_target_solved.json +68 -0
  161. ahuora_builder/tests/test_solver/test_solve_models.py +250 -0
  162. ahuora_builder/timing.py +65 -0
  163. ahuora_builder/types/__init__.py +1 -0
  164. ahuora_builder/unit_model_manager.py +48 -0
  165. ahuora_builder-0.1.0.dist-info/METADATA +14 -0
  166. ahuora_builder-0.1.0.dist-info/RECORD +167 -0
  167. ahuora_builder-0.1.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,727 @@
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
+ """
14
+ Ben Lincolns heat exchanger model using effectiveness
15
+
16
+ """
17
+
18
+ # Import Pyomo libraries
19
+ from 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
+ )
30
+ from pyomo.common.config import Bool, ConfigBlock, ConfigValue, In
31
+
32
+ # Import IDAES cores
33
+ from idaes.core import (
34
+ ControlVolume0DBlock,
35
+ declare_process_block_class,
36
+ MaterialBalanceType,
37
+ EnergyBalanceType,
38
+ MomentumBalanceType,
39
+ UnitModelBlockData,
40
+ useDefault,
41
+ )
42
+ from idaes.models.unit_models.heat_exchanger import hx_process_config, add_hx_references
43
+ from idaes.core.util.config import is_physical_parameter_block
44
+ from idaes.core.util.tables import create_stream_table_dataframe
45
+ from idaes.core.util.math import smooth_min, smooth_max
46
+ from idaes.core.solvers import get_solver
47
+ from idaes.core.util.exceptions import InitializationError
48
+ import idaes.logger as idaeslog
49
+ from idaes.core.initialization import SingleControlVolumeUnitInitializer
50
+ from idaes.core.util.model_statistics import degrees_of_freedom
51
+ from .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
+
60
+ class 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")
166
+ class 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
182
+ constructed, **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
198
+ balance 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
216
+ balance 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
247
+ constructed,
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:
662
+ # Assume 1000 J/s and check for unitless properties
663
+ if s1_units is None and s2_units is None:
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):
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
+ )