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,624 @@
1
+ # Pyomo core
2
+ from pyomo.environ import (
3
+ Constraint,
4
+ Expression,
5
+ NonNegativeReals,
6
+ Suffix,
7
+ Var,
8
+ value,
9
+ units as UNIT,
10
+ )
11
+ from pyomo.core.base.reference import Reference
12
+ from pyomo.common.config import ConfigBlock, ConfigValue, Bool
13
+
14
+ # IDAES core
15
+ from idaes.core import (
16
+ declare_process_block_class,
17
+ UnitModelBlockData,
18
+ useDefault,
19
+ StateBlock,
20
+ )
21
+ from idaes.core.util import scaling
22
+ from idaes.core.util.config import is_physical_parameter_block
23
+ from idaes.core.util.tables import create_stream_table_dataframe
24
+ from idaes.core.solvers import get_solver
25
+ from idaes.core.initialization import ModularInitializerBase
26
+
27
+ # Other
28
+ from property_packages.build_package import build_package
29
+
30
+ # Logger
31
+ import idaes.logger as idaeslog
32
+
33
+ # Typing
34
+ from typing import List
35
+
36
+
37
+ __author__ = "Ahuora Centre for Smart Energy Systems, University of Waikato, New Zealand"
38
+
39
+ # Set up logger
40
+ _log = idaeslog.getLogger(__name__)
41
+
42
+ class DesuperheaterInitializer(ModularInitializerBase):
43
+ """Initializer for ``Desuperheater``.
44
+
45
+ Parameters
46
+ ----------
47
+ blk : Desuperheater
48
+ The unit model block to initialize.
49
+ solver : optional
50
+ A Pyomo/IDAES solver instance. Defaults to :func:`idaes.core.solvers.get_solver`.
51
+ solver_options : dict, optional
52
+ Options to set on the solver, e.g. tolerances.
53
+ outlvl : int, optional
54
+ IDAES log level (e.g. :data:`idaes.logger.WARNING`).
55
+
56
+ Returns
57
+ -------
58
+ pyomo.opt.results.results_.SolverResults
59
+ Result from the final solve.
60
+ """
61
+
62
+ def initialize(self, blk, **kwargs):
63
+ # --- Solver setup
64
+ solver = kwargs.get("solver", None) or get_solver()
65
+ solver_options = kwargs.get("solver_options", {})
66
+ for k, v in solver_options.items():
67
+ solver.options[k] = v
68
+
69
+ outlvl = kwargs.get("outlvl", idaeslog.WARNING)
70
+ log = idaeslog.getLogger(__name__)
71
+
72
+ # --- Time index
73
+ t0 = blk.flowsheet().time.first()
74
+
75
+ # --- 1) Initialize inlet state blocks
76
+ inlet_blocks = list(blk.inlet_blocks)
77
+
78
+ inlet_steam, inlet_water = inlet_blocks
79
+ inlet_water[t0].pressure.set_value(
80
+ inlet_steam[t0].pressure
81
+ )
82
+ inlet_water[t0].enth_mol.set_value(
83
+ blk.water.htpx(
84
+ blk.bfw_temperature[t0],
85
+ inlet_water[t0].pressure,
86
+ )
87
+ )
88
+ for sb in inlet_blocks:
89
+ if hasattr(sb, "initialize"):
90
+ sb.initialize(outlvl=outlvl)
91
+
92
+ # --- 2) Seed satuarate inlet-related state
93
+ sb = blk._int_sat_vap_state
94
+ sb[t0].pressure.set_value(
95
+ inlet_steam[t0].pressure
96
+ )
97
+ sb[t0].enth_mol.set_value(
98
+ inlet_steam[t0].enth_mol_sat_phase["Vap"]
99
+ )
100
+ if hasattr(sb, "initialize"):
101
+ sb.initialize(outlvl=outlvl)
102
+
103
+ # --- 3) Aggregate inlet info for seeding mixed state block
104
+ ms = blk.outlet_state
105
+ ms[t0].pressure.set_value(
106
+ inlet_steam[t0].pressure
107
+ )
108
+ if value(blk.deltaT_superheat[t0]) > 0:
109
+ ms[t0].enth_mol.set_value(
110
+ blk.water.htpx(
111
+ T=blk._int_sat_vap_state[t0].temperature + blk.deltaT_superheat[t0],
112
+ p=inlet_steam[t0].pressure,
113
+ )
114
+ )
115
+ else:
116
+ ms[t0].enth_mol.set_value(
117
+ blk._int_sat_vap_state[t0].enth_mol
118
+ )
119
+
120
+ if value(inlet_steam[t0].enth_mol) > value(ms[t0].enth_mol) > value(inlet_water[t0].enth_mol):
121
+ ms[t0].flow_mol.set_value(
122
+ inlet_steam[t0].flow_mol * (inlet_steam[t0].enth_mol - ms[t0].enth_mol) / (ms[t0].enth_mol - inlet_water[t0].enth_mol)
123
+ )
124
+ else:
125
+ ms[t0].flow_mol.set_value(
126
+ inlet_steam[t0].flow_mol
127
+ )
128
+ if hasattr(ms, "initialize"):
129
+ ms.initialize(outlvl=outlvl)
130
+
131
+ # --- 4) Solve
132
+ res = solver.solve(blk, tee=False)
133
+ log.info(f"Desuperheater init status: {res.solver.termination_condition}")
134
+ return res
135
+
136
+ def _make_config_block(config):
137
+ """Declare configuration options for the Desuperheater unit.
138
+
139
+ Declares property package references and integer counts for inlets and outlets.
140
+
141
+ Args:
142
+ config (ConfigBlock): The mutable configuration block to populate.
143
+ """
144
+ config.declare(
145
+ "property_package",
146
+ ConfigValue(
147
+ default=useDefault,
148
+ domain=is_physical_parameter_block,
149
+ description="Property package to use for control volume",
150
+ ),
151
+ )
152
+ config.declare(
153
+ "property_package_args",
154
+ ConfigBlock(
155
+ implicit=True,
156
+ description="Arguments to use for constructing property packages",
157
+ ),
158
+ )
159
+
160
+ @declare_process_block_class("Desuperheater")
161
+ class DesuperheaterData(UnitModelBlockData):
162
+ """Desuperheater unit operation.
163
+
164
+ The Desuperheater injects a small water flow into a superheated steam flow to reduce superheat.
165
+ Desuperheating is common before using steam. Heat loss and pressure loss may be defined.
166
+ An intermediate saturated state is used for a key reference point.
167
+
168
+ Key features:
169
+ - Material, energy, and momentum balances around the desuperheater
170
+ - User-specified target amount of superheat at the exit of the desuperheater
171
+ - Optional heat and pressure losses.
172
+
173
+ Attributes:
174
+ inlet_list (list[str]): Names for inlet ports.
175
+ outlet_list (list[str]): Names for outlet ports (incl. condensate/ and vent).
176
+ inlet_blocks (list): StateBlocks for all inlets.
177
+ outlet_blocks (list): StateBlocks for all outlets.
178
+ _int_sat_vap_state: Intermediate saturated vapour StateBlock.
179
+
180
+ State variables:
181
+ deltaT_superheat (Var): Target degree of superheat in the steam at the exit of the process.
182
+ bfw_temperature (Var): Temperature of the inlet boiler feed water flow for desuperheating (degC).
183
+ heat_loss (Var): Heat loss from the header (W).
184
+ pressure_loss (Var): Pressure drop from inlet minimum to mixed state (Pa).
185
+ """
186
+
187
+ default_initializer=DesuperheaterInitializer
188
+ CONFIG = UnitModelBlockData.CONFIG()
189
+ _make_config_block(CONFIG)
190
+
191
+ def build(self) -> None:
192
+ """Build the unit model structure (ports, states, constraints)."""
193
+ # 1. Inherit standard UnitModelBlockData properties and functions
194
+ super().build()
195
+
196
+ # 2. Validate input parameters are valid
197
+ self._validate_model_config()
198
+
199
+ # 3. Create lists of ports with state blocks to add
200
+ self.inlet_list = self._create_inlet_port_name_list()
201
+ self.outlet_list = self._create_outlet_port_name_list()
202
+
203
+ # 4. Declare ports, state blocks and state property bounds
204
+ self.inlet_blocks = self._add_ports_with_state_blocks(
205
+ stream_list=self.inlet_list,
206
+ is_inlet=True,
207
+ has_phase_equilibrium=False,
208
+ is_defined_state=True,
209
+ )
210
+ self.outlet_blocks = self._add_ports_with_state_blocks(
211
+ stream_list=self.outlet_list,
212
+ is_inlet=False,
213
+ has_phase_equilibrium=False,
214
+ is_defined_state=False
215
+ )
216
+ self._internal_blocks = self._add_internal_state_blocks()
217
+ self._add_bounds_to_state_properties()
218
+
219
+ # 4. Declare references, variables and expressions for external and internal use
220
+ self._create_references()
221
+ self._create_variables()
222
+ self._create_expressions()
223
+
224
+ # 5. Set balance equations
225
+ self._add_material_balances()
226
+ self._add_energy_balances()
227
+ self._add_momentum_balances()
228
+ self._add_additional_constraints()
229
+
230
+ # 6. Other
231
+ self.scaling_factor = Suffix(direction=Suffix.EXPORT)
232
+
233
+ # ------------------------------------------------------------------
234
+ # Helpers & construction utilities
235
+ # ------------------------------------------------------------------
236
+ def _validate_model_config(self) -> bool:
237
+ """Validate configuration for inlet and outlet counts.
238
+
239
+ Raises:
240
+ ValueError: If ``property_package is None``.
241
+ """
242
+ if self.config.property_package is None:
243
+ raise ValueError("Desuperheater: Property package not defined.")
244
+ return True
245
+
246
+ def _create_inlet_port_name_list(self) -> List[str]:
247
+ """Build ordered inlet port names.
248
+
249
+ Returns:
250
+ list[str]: Names
251
+ """
252
+
253
+ return (
254
+ [
255
+ "inlet_steam", "inlet_water"
256
+ ]
257
+ )
258
+
259
+ def _create_outlet_port_name_list(self) -> List[str]:
260
+ """Build ordered outlet port names.
261
+
262
+ Returns:
263
+ list[str]: Names
264
+ """
265
+ return [
266
+ "outlet",
267
+ ]
268
+
269
+ def _add_ports_with_state_blocks(self,
270
+ stream_list: List[str],
271
+ is_inlet: List[str],
272
+ has_phase_equilibrium: bool=False,
273
+ is_defined_state: bool=None,
274
+ ) -> List[StateBlock]:
275
+ """Construct StateBlocks and expose them as ports.
276
+
277
+ Creates a StateBlock per named stream and attaches a corresponding inlet or
278
+ outlet Port. Inlet blocks are defined states; outlet blocks are calculated states.
279
+
280
+ Args:
281
+ stream_list (list[str]): Port/StateBlock base names to create.
282
+ is_inlet (bool): If True, create inlet ports with ``defined_state=True``;
283
+ otherwise create outlet ports with ``defined_state=False``.
284
+ has_phase_equilibrium (bool)
285
+
286
+ Returns:
287
+ list: The created StateBlocks, in the same order as ``stream_list``.
288
+ """
289
+ # Create empty list to hold StateBlocks for return
290
+ state_block_ls = []
291
+
292
+ # Setup StateBlock argument dict
293
+ tmp_dict = dict(**self.config.property_package_args)
294
+ tmp_dict["has_phase_equilibrium"] = has_phase_equilibrium
295
+ if is_defined_state == None:
296
+ tmp_dict["defined_state"] = True if is_inlet else False
297
+ else:
298
+ tmp_dict["defined_state"] = is_defined_state
299
+
300
+ # Create an instance of StateBlock for all streams
301
+ for s in stream_list:
302
+ sb = self.config.property_package.build_state_block(
303
+ self.flowsheet().time, doc=f"Thermophysical properties at {s}", **tmp_dict
304
+ )
305
+ setattr(
306
+ self, s + "_state",
307
+ sb
308
+ )
309
+ state_block_ls.append(sb)
310
+ add_fn = self.add_inlet_port if is_inlet else self.add_outlet_port
311
+ add_fn(
312
+ name=s,
313
+ block=sb,
314
+ )
315
+
316
+ return state_block_ls
317
+
318
+ def _add_internal_state_blocks(self) -> List[StateBlock]:
319
+ """Create the intermediate StateBlock(s)."""
320
+ # The _int_sat_vap_state:
321
+ # - Has phase equilibrium enabled.
322
+ # - Is not a defined state (solved from balances).
323
+ tmp_dict = dict(**self.config.property_package_args)
324
+ tmp_dict["has_phase_equilibrium"] = True
325
+ tmp_dict["defined_state"] = False
326
+ self._int_sat_vap_state = self.config.property_package.build_state_block(
327
+ self.flowsheet().time,
328
+ doc="Thermophysical properties internal saturated vapour state.",
329
+ **tmp_dict
330
+ )
331
+ self._int_sat_vap_state[:].flow_mol.fix(1)
332
+
333
+ return [
334
+ self._int_sat_vap_state,
335
+ ]
336
+
337
+ def _add_bounds_to_state_properties(self) -> None:
338
+ """Add lower and/or upper bounds to state properties.
339
+
340
+ - Set nonnegativity lower bounds on all inlet/intermediate/outlet flows.
341
+ """
342
+ for sb in (self.inlet_blocks + self.outlet_blocks):
343
+ for t in sb:
344
+ sb[t].flow_mol.setlb(0.0)
345
+
346
+ def _create_references(self) -> None:
347
+ """Create convenient References.
348
+
349
+ Creates references to _int_mixed_inlet_state properties:
350
+ - ``bfw_flow_mass``
351
+ - ``bfw_flow_mol``
352
+ """
353
+ self.bfw_flow_mass = Reference(
354
+ self.inlet_water_state[:].flow_mass
355
+ )
356
+ self.bfw_flow_mol = Reference(
357
+ self.inlet_water_state[:].flow_mol
358
+ )
359
+ self.inlet_water_state[:].flow_mol.unfix()
360
+ self.water = build_package("helmholtz", ["water"], ["Liq"])
361
+
362
+ def _create_variables(self) -> None:
363
+ """Declare decision/parameter variables for the unit.
364
+
365
+ Creates:
366
+ - ``heat_loss``
367
+ - ``pressure_loss``
368
+ - ``bfw_temperature``
369
+ - ``deltaT_superheat``
370
+ """
371
+ # Get units consistent with the property package
372
+ units_meta = self.config.property_package.get_metadata()
373
+
374
+ # User defined: Heat and pressure losses
375
+ self.heat_loss = Var(
376
+ self.flowsheet().time,
377
+ domain=NonNegativeReals,
378
+ doc="Heat loss. Default: 0 kW.",
379
+ units=units_meta.get_derived_units("power")
380
+ )
381
+ self.heat_loss.fix(
382
+ 0 # Default fixed value
383
+ )
384
+ self.pressure_loss = Var(
385
+ self.flowsheet().time,
386
+ domain=NonNegativeReals,
387
+ doc="Pressure loss. Default: 0 Pa.",
388
+ units=units_meta.get_derived_units("pressure")
389
+ )
390
+ self.pressure_loss.fix(
391
+ 0 # Default fixed value
392
+ )
393
+ # User defined: Boiler feed water temperature entering the desuperheater
394
+ self.bfw_temperature = Var(
395
+ self.flowsheet().time,
396
+ domain=NonNegativeReals,
397
+ doc="The target amount of subcooling of the condensate after process heating. Default: 0 K.",
398
+ units=units_meta.get_derived_units("temperature"),
399
+ )
400
+ self.bfw_temperature.fix(
401
+ (110 + 273.15) * UNIT.K # Default fixed value
402
+ )
403
+ # User defined: Target degree of superheat at the outlet of the desuperheater
404
+ self.deltaT_superheat = Var(
405
+ self.flowsheet().time,
406
+ domain=NonNegativeReals,
407
+ doc="The target amount of superheat present in the steam after desuperheating before use. Default: 0 K.",
408
+ units=units_meta.get_derived_units("temperature"),
409
+ )
410
+ self.deltaT_superheat.fix(
411
+ 0 # Default fixed value
412
+ )
413
+
414
+ def _create_expressions(self) -> None:
415
+ """Create helper Expressions.
416
+
417
+ Creates:
418
+ - ``flow_ratio``
419
+ """
420
+ # Calculated, always show
421
+ self.flow_ratio = Expression(
422
+ self.flowsheet().time,
423
+ rule=lambda b, t: (
424
+ b.inlet_water_state[t].flow_mol
425
+ /
426
+ (b.inlet_steam_state[t].flow_mol + 1e-9)
427
+ ),
428
+ doc="Ratio of water to steam flows.",
429
+ )
430
+
431
+ # ------------------------------------------------------------------
432
+ # Balances
433
+ # ------------------------------------------------------------------
434
+ def _add_material_balances(self) -> None:
435
+ """Material balance equations summary.
436
+
437
+ Balances / Constraints:
438
+ - ``overall_material_balance``
439
+ """
440
+ @self.Constraint(
441
+ self.flowsheet().time,
442
+ doc="Overall material balance",
443
+ )
444
+ def overall_material_balance(b, t):
445
+ return (
446
+ sum(
447
+ o[t].flow_mol
448
+ for o in b.outlet_blocks
449
+ )
450
+ ==
451
+ sum(
452
+ i[t].flow_mol
453
+ for i in b.inlet_blocks
454
+ )
455
+ )
456
+
457
+ def _add_energy_balances(self) -> None:
458
+ """Energy balance equations summary.
459
+
460
+ Balances / Constraints:
461
+ - ``overall_energy_balance``
462
+ - ``saturated_vap_enthalpy_eq``
463
+ """
464
+ @self.Constraint(
465
+ self.flowsheet().time,
466
+ doc="Overall energy balance",
467
+ )
468
+ def overall_energy_balance(b, t):
469
+ return (
470
+ sum(
471
+ i[t].flow_mol * i[t].enth_mol
472
+ for i in b.inlet_blocks
473
+ )
474
+ ==
475
+ sum(
476
+ i[t].flow_mol * i[t].enth_mol
477
+ for i in b.outlet_blocks
478
+ )
479
+ +
480
+ b.heat_loss[t]
481
+ )
482
+ @self.Constraint(
483
+ self.flowsheet().time,
484
+ doc="Saturated vapour enthalpy",
485
+ )
486
+ def saturated_vap_enthalpy_eq(b, t):
487
+ return (
488
+ b.outlet_state[t].enth_mol_sat_phase["Vap"]
489
+ ==
490
+ b._int_sat_vap_state[t].enth_mol
491
+ )
492
+
493
+ def _add_momentum_balances(self) -> None:
494
+ """Momentum balance equations summary.
495
+
496
+ Balances / Constraints:
497
+ - ``overall_momentum_balance``
498
+ - ``intlet_water_momentum_balance``
499
+ - ``saturated_vap_pressure_eq``
500
+ """
501
+ @self.Constraint(
502
+ self.flowsheet().time,
503
+ doc="Overall momentum balance",
504
+ )
505
+ def overall_momentum_balance(b, t):
506
+ return (
507
+ b.inlet_steam_state[t].pressure
508
+ ==
509
+ b.outlet_state[t].pressure
510
+ +
511
+ b.pressure_loss[t]
512
+ )
513
+ @self.Constraint(
514
+ self.flowsheet().time,
515
+ doc="Inlet water momentum balance",
516
+ )
517
+ def intlet_water_momentum_balance(b, t):
518
+ return (
519
+ b.inlet_water_state[t].pressure
520
+ ==
521
+ b.inlet_steam_state[t].pressure
522
+ )
523
+ @self.Constraint(
524
+ self.flowsheet().time,
525
+ doc="Saturated vapour pressure",
526
+ )
527
+ def saturated_vap_pressure_eq(b, t):
528
+ return (
529
+ b._int_sat_vap_state[t].pressure
530
+ ==
531
+ b.outlet_state[t].pressure
532
+ )
533
+
534
+ def _add_additional_constraints(self) -> None:
535
+ """Add auxiliary constraints and bounds.
536
+
537
+ Constraints:
538
+ - ``inlet_water_temperature_eq``
539
+ - ``desuperheating_temperature_eq``
540
+ """
541
+ @self.Constraint(
542
+ self.flowsheet().time,
543
+ doc="Inlet water temperature",
544
+ )
545
+ def inlet_water_temperature_eq(b, t):
546
+ return (
547
+ b.inlet_water_state[t].temperature
548
+ ==
549
+ b.bfw_temperature[t]
550
+ )
551
+ @self.Constraint(
552
+ self.flowsheet().time,
553
+ doc="Temperature after desuperheating",
554
+ )
555
+ def desuperheating_temperature_eq(b, t):
556
+ return (
557
+ b.outlet_state[t].temperature
558
+ ==
559
+ b._int_sat_vap_state[t].temperature + b.deltaT_superheat[t]
560
+ )
561
+
562
+ def calculate_scaling_factors(self):
563
+ """Assign scaling factors to improve numerical conditioning.
564
+
565
+ Sets scaling factors for performance and auxiliary variables.
566
+ """
567
+ super().calculate_scaling_factors()
568
+ scaling.set_scaling_factor(self.heat_loss, 1e-3) # kW scale
569
+ scaling.set_scaling_factor(self.pressure_loss, 1e-3) # kPa scale
570
+
571
+ def _get_stream_table_contents(self, time_point=0):
572
+ """Create a stream table for all inlets and outlets.
573
+
574
+ Args:
575
+ time_point (int | float): Time index at which to extract stream data.
576
+
577
+ Returns:
578
+ pandas.DataFrame: A tabular view suitable for reporting via
579
+ ``create_stream_table_dataframe``.
580
+ """
581
+ io_dict = {}
582
+
583
+ for inlet_name in self.inlet_list:
584
+ io_dict[inlet_name] = getattr(self, inlet_name)
585
+
586
+ for outlet_name in self.outlet_list:
587
+ io_dict[outlet_name] = getattr(self, outlet_name)
588
+
589
+ return create_stream_table_dataframe(io_dict, time_point=time_point)
590
+
591
+ def _get_performance_contents(self, time_point=0):
592
+ """Collect performance variables for reporting.
593
+
594
+ Args:
595
+ time_point (int | float): Time index at which to report values.
596
+
597
+ Returns:
598
+ dict: Mapping used by IDAES reporters, containing human-friendly labels
599
+ to Vars/References (e.g., heat/pressure loss, mixed-state properties).
600
+ """
601
+ return {
602
+ "vars": {
603
+ "BFW temperature [K]": self.bfw_temperature[time_point],
604
+ "Degree of superheat target [K]": self.deltaT_superheat[time_point],
605
+ "Heat loss [W]": self.heat_loss[time_point],
606
+ "Pressure loss [Pa]": self.pressure_loss[time_point],
607
+ },
608
+ "exprs": {
609
+ "Water-to-steam flow ratio": self.flow_ratio[time_point],
610
+ },
611
+ }
612
+
613
+ def initialize(self, *args, **kwargs):
614
+ """Initialize the Desuperheater unit using :class:`DesuperheaterInitializer`.
615
+
616
+ Args:
617
+ *args: Forwarded to ``DesuperheaterInitializer.initialize``.
618
+ **kwargs: Forwarded to ``DesuperheaterInitializer.initialize`` (e.g., solver, options).
619
+
620
+ Returns:
621
+ pyomo.opt.results.results_.SolverResults: Results from the initializer's solve.
622
+ """
623
+ init = DesuperheaterInitializer()
624
+ return init.initialize(self, *args, **kwargs)