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,349 @@
1
+ # TODO:
2
+ # backend/idaes_service/solver/custom/steam_header.py [DONE]
3
+ # backend/idaes_service/solver/custom/tests/test_steam_header.py [DONE]
4
+ # backend/idaes_service/solver/methods/adapter_library.py
5
+ # backend/idaes_factory/adapters/unit_models/header_adapter.py
6
+ # backend/idaes_factory/adapters/adapter_library.py
7
+ # backend/core/auxiliary/enums/unitOpData.py
8
+ # backend/flowsheetInternals/unitops/config/config_base.py
9
+ # backend/flowsheetInternals/unitops/config/objects/header_config.py
10
+ # frontend/src/data/objects.json
11
+
12
+ from pyomo.environ import Suffix, Var
13
+ from pyomo.common.config import ConfigBlock, ConfigValue
14
+ from pyomo.network import Arc
15
+ from pyomo.core.base.reference import Reference
16
+ import pyomo.environ as pyo
17
+
18
+ # Import IDAES cores
19
+ from idaes.core import (
20
+ declare_process_block_class,
21
+ UnitModelBlockData,
22
+ useDefault,
23
+ )
24
+ from idaes.core.util.config import is_physical_parameter_block
25
+ import idaes.logger as idaeslog
26
+ from idaes.core.util.tables import create_stream_table_dataframe
27
+ from idaes.models.unit_models.mixer import Mixer
28
+ from idaes.models.unit_models.heater import Heater
29
+
30
+ # Set up logger
31
+ _log = idaeslog.getLogger(__name__)
32
+
33
+
34
+ @declare_process_block_class("Desuperheater")
35
+ class DesuperheaterData(UnitModelBlockData):
36
+ """
37
+ Desuperheater unit operation:
38
+ Superheated steam + water -> Mixer -> Losses -> Saturated Vapour
39
+ Desuperheater aims to remove steam superhear by direct contact with boiler feed water.
40
+ Uses Sequential Decomposition for optimal initialization order.
41
+ """
42
+
43
+ CONFIG = UnitModelBlockData.CONFIG()
44
+ CONFIG.declare(
45
+ "property_package",
46
+ ConfigValue(
47
+ default=useDefault,
48
+ domain=is_physical_parameter_block,
49
+ description="Property package to use for control volume",
50
+ ),
51
+ )
52
+ CONFIG.declare(
53
+ "property_package_args",
54
+ ConfigBlock(
55
+ implicit=True,
56
+ description="Arguments to use for constructing property packages",
57
+ ),
58
+ )
59
+
60
+
61
+ def build(self):
62
+ super().build()
63
+ self.scaling_factor = Suffix(direction=Suffix.EXPORT)
64
+
65
+ # Create internal units
66
+ self.mixer = Mixer(
67
+ property_package=self.config.property_package,
68
+ property_package_args=self.config.property_package_args,
69
+ num_inlets=2
70
+ )
71
+ self.losses = Heater(
72
+ property_package=self.config.property_package,
73
+ property_package_args=self.config.property_package_args,
74
+ has_pressure_change=True,
75
+ )
76
+
77
+ # Build saturated vapour state block
78
+ tmp_dict = dict(**self.config.property_package_args)
79
+ tmp_dict["has_phase_equilibrium"] = True
80
+ tmp_dict["defined_state"] = False
81
+
82
+ self._properties_sat = self.config.property_package.build_state_block(
83
+ self.flowsheet().time,
84
+ doc="saturated vapour properties at outlet",
85
+ **tmp_dict
86
+ )
87
+
88
+ self.unit_ops = [self.mixer, self.losses, self._properties_sat]
89
+
90
+ # Create internal arcs
91
+ self.condenser_to_subcooler = Arc(
92
+ source=self.mixer.outlet,
93
+ destination=self.losses.inlet,
94
+ )
95
+
96
+ # Expand arcs
97
+ pyo.TransformationFactory("network.expand_arcs").apply_to(self)
98
+
99
+ # Identify the exposed ports
100
+ inlet_exposed_ls = [
101
+ (self.mixer, "steam_inlet", "inlet_1"),
102
+ (self.mixer, "water_inlet", "inlet_2"),
103
+ ]
104
+ outlet_exposed_ls = [
105
+ (self.losses, "steam_outlet", "outlet"),
106
+ ]
107
+
108
+ self.inlet_list, self.inlet_blocks = self._construct_exposed_ports(inlet_exposed_ls)
109
+ self.outlet_list, self.outlet_blocks = self._construct_exposed_ports(outlet_exposed_ls)
110
+
111
+ # Declare additional variables and provide alias of existing ones to expose to the user
112
+ self.deltaT_supheat = Var(
113
+ self.flowsheet().time,
114
+ domain=pyo.NonNegativeReals,
115
+ # initialize=0.0,
116
+ units=pyo.units.K,
117
+ doc="The degree of steam superheat remaining."
118
+ )
119
+ self.water_temperature = Reference(
120
+ self.water_inlet_state[:].temperature
121
+ )
122
+ self.water_flow_mol = Reference(
123
+ self.water_inlet_state[:].flow_mol
124
+ )
125
+ self.water_flow_mass = Reference(
126
+ self.water_inlet_state[:].flow_mass
127
+ )
128
+ self.heat_duty = Reference(
129
+ self.losses.heat_duty[:]
130
+ )
131
+ self.deltaP = Reference(
132
+ self.losses.deltaP[:]
133
+ )
134
+
135
+ # Declare internal variables and references for internal use
136
+ self._T_sat = Reference(
137
+ self._properties_sat[:].temperature
138
+ )
139
+
140
+ # Additional bounds and constraints
141
+ self._additional_constraints()
142
+
143
+ def _additional_constraints(self):
144
+ """
145
+ Additional constraints.
146
+ """
147
+
148
+ """
149
+ 1. Pressure equality for saturation state block
150
+ """
151
+ @self.Constraint(
152
+ self.flowsheet().time, doc="Pressure equality for saturation state block"
153
+ )
154
+ def outlet_pressure_eql(b, t):
155
+ return (
156
+ b._properties_sat[t].pressure == b.losses.control_volume.properties_out[t].pressure
157
+ )
158
+
159
+ """
160
+ 2. Vapour quality for saturation state block
161
+ """
162
+ @self.Constraint(
163
+ self.flowsheet().time, doc="Pressure equality for saturation state block"
164
+ )
165
+ def vapour_quality_eq(b, t):
166
+ return (
167
+ b._properties_sat[t].vapor_frac == 0.5
168
+ )
169
+
170
+ """
171
+ 3. Mixer pressure inlet equality for steam and water
172
+ """
173
+ @self.Constraint(
174
+ self.flowsheet().time, doc="Mixer pressure inlet equality for steam and water"
175
+ )
176
+ def mixer_inlet_pressure_eql(b, t):
177
+ return (
178
+ b.steam_inlet_state[t].pressure == b.water_inlet_state[t].pressure
179
+ )
180
+
181
+ """
182
+ 4. Superheat temperature balance
183
+ """
184
+ @self.Constraint(self.flowsheet().time, doc="Temperature superheat balance")
185
+ def superheat_temperature_eqn(b, t):
186
+ return (
187
+ b.deltaT_supheat[t] + 0.001 == b.steam_outlet_state[t].temperature - b._T_sat[t]
188
+ )
189
+
190
+ def _construct_exposed_ports(self, exposed_ls):
191
+ """
192
+ exposed_ls: list of [(unit, public_name, internal_basename)]
193
+ """
194
+ names, states = [], []
195
+
196
+ for unit, public_name, internal_basename in exposed_ls:
197
+ # 1) Locate the state block on the child unit
198
+ # Try <basename>_state (Mixer & many IDAES units)
199
+ state = getattr(unit, f"{internal_basename}_state", None)
200
+
201
+ # Fall back to control_volume properties for CV-based units (e.g., Heater)
202
+ if state is None and hasattr(unit, "control_volume"):
203
+ cv = unit.control_volume
204
+ if internal_basename.startswith("inlet") and hasattr(cv, "properties_in"):
205
+ state = cv.properties_in
206
+ elif internal_basename.startswith("outlet") and hasattr(cv, "properties_out"):
207
+ state = cv.properties_out
208
+
209
+ if state is None:
210
+ raise AttributeError(
211
+ f"{unit.name} has no state for '{internal_basename}'. "
212
+ f"Tried '{internal_basename}_state' and control_volume properties."
213
+ )
214
+
215
+ # 2) Expose the state on this wrapper
216
+ # (a) keep a convenient Reference to the time-indexed state block
217
+ setattr(self, f"{public_name}_state", Reference(state[:]))
218
+
219
+ # (b) create a public Port mapped to that state
220
+ # NOTE: UnitModelBlock.add_port builds a Port with the vars
221
+ # defined by the property package’s port members.
222
+ self.add_port(name=public_name, block=state)
223
+
224
+ names.append(public_name)
225
+ states.append(state)
226
+
227
+ return names, states
228
+
229
+ def calculate_scaling_factors(self):
230
+ super().calculate_scaling_factors()
231
+ for o in self.unit_ops:
232
+ if hasattr(o, "calculate_scaling_factors"):
233
+ o.calculate_scaling_factors()
234
+
235
+ def _get_stream_table_contents(self, time_point=0):
236
+ """
237
+ Create stream table showing all inlets and outlets
238
+ """
239
+ io_dict = {}
240
+
241
+ for inlet_name in self.inlet_list:
242
+ io_dict[inlet_name] = getattr(self, inlet_name)
243
+
244
+ for outlet_name in self.outlet_list:
245
+ io_dict[outlet_name] = getattr(self, outlet_name)
246
+
247
+ return create_stream_table_dataframe(io_dict, time_point=time_point)
248
+
249
+ def _copy_port_state(self, src_port, snk_port, t):
250
+ for name in ("flow_mol", "pressure", "enth_mol", "temperature", "enth_mass", "vapor_frac"):
251
+ if hasattr(src_port, name) and hasattr(snk_port, name):
252
+ src = getattr(src_port, name)
253
+ snk = getattr(snk_port, name)
254
+ if (t in src) and (t in snk) and pyo.is_variable_type(snk[t]):
255
+ try:
256
+ snk[t].set_value(pyo.value(src[t]))
257
+ except Exception:
258
+ pass
259
+
260
+ def _hold_state_vars(self, sb):
261
+ held = {}
262
+ if hasattr(sb, "define_state_vars"):
263
+ for k, v in sb.define_state_vars().items():
264
+ if pyo.is_variable_type(v) and not v.fixed:
265
+ v.fix()
266
+ held[k] = False
267
+ else:
268
+ held[k] = True
269
+ return held
270
+
271
+ def _release_state_vars(self, sb, held):
272
+ if hasattr(sb, "define_state_vars"):
273
+ for k, was_fixed in held.items():
274
+ v = sb.define_state_vars()[k]
275
+ if was_fixed is False and pyo.is_variable_type(v):
276
+ v.unfix()
277
+
278
+ def initialize_build(self, outlvl=idaeslog.NOTSET, **kwargs):
279
+ """
280
+ Initialize the Desuperheater without using state_args for sub-units.
281
+
282
+ Steps:
283
+ 1) Initialize Mixer (no state_args; it reads from connected inlet ports).
284
+ 2) Initialize Heater with temporary ΔP=0 and Q=0, then restore.
285
+ 3) Seed and (if supported) initialize saturated-vapor state block.
286
+ 4) Seed deltaT_supheat.
287
+ """
288
+ init_log = idaeslog.getInitLogger(self.name, outlvl, tag="unit")
289
+ solve_log = idaeslog.getSolveLogger(self.name, outlvl, tag="unit")
290
+
291
+ t0 = list(self.flowsheet().time)[0]
292
+
293
+ # 1) Mixer
294
+ init_log.info("Initializing Mixer (no state_args)...")
295
+ try:
296
+ # Inlet states should already be provided via self.steam_inlet / self.water_inlet
297
+ # and are wired to mixer.inlet_1 / mixer.inlet_2 via the exposed ports.
298
+ self.water_flow_mol[t0].fix(0.0)
299
+ self.water_flow_mol[t0].unfix()
300
+ self.mixer.inlet_2.pressure[t0].fix(
301
+ pyo.value(
302
+ self.mixer.inlet_1.pressure[t0]
303
+ )
304
+ )
305
+ self.mixer.inlet_2.pressure[t0].unfix()
306
+ self.mixer.initialize(outlvl=outlvl)
307
+ except Exception as err:
308
+ init_log.error(f"Mixer initialization failed: {err}")
309
+ raise
310
+
311
+ # 2) Heater (losses)
312
+ init_log.info("Initializing Heater (losses) with temporary ΔP=0 and Q=0...")
313
+ self._copy_port_state(
314
+ src_port=self.mixer.outlet,
315
+ snk_port=self.losses.inlet,
316
+ t=t0
317
+ )
318
+ held = self._hold_state_vars(self.losses.control_volume.properties_in[t0])
319
+ try:
320
+ self.losses.initialize(outlvl=outlvl)
321
+ finally:
322
+ self._release_state_vars(self.losses.control_volume.properties_in[t0], held)
323
+
324
+ # 3) Saturated-vapor state block seeding
325
+ init_log.info("Seeding saturated-vapor state block...")
326
+ try:
327
+ # Set seed values directly
328
+ self._properties_sat[t0].pressure.set_value(
329
+ pyo.value(self.losses.outlet.pressure[t0])
330
+ )
331
+ self._properties_sat[t0].enth_mol.set_value(
332
+ pyo.value(self.losses.outlet.enth_mol[t0])
333
+ )
334
+
335
+ # If the block exposes its own initialize(outlvl=...), call it
336
+ if hasattr(self._properties_sat[t0], "initialize"):
337
+ # Many property packages implement initialize on the *indexed* block,
338
+ # i.e. call on the parent indexed component:
339
+ try:
340
+ self._properties_sat.initialize(outlvl=outlvl)
341
+ except TypeError:
342
+ # Some implementations only support per-timepoint initialize
343
+ self._properties_sat[t0].initialize(outlvl=outlvl)
344
+
345
+ except Exception as err:
346
+ init_log.error(f"Saturation state initialization failed: {err}")
347
+ raise
348
+
349
+ init_log.info("Desuperheater initialization complete.")
@@ -0,0 +1,142 @@
1
+ import pytest
2
+
3
+ import pyomo.environ as pyo
4
+ from idaes.core import FlowsheetBlock
5
+ from idaes.core.util.model_statistics import degrees_of_freedom
6
+ from ahuora_builder.custom.thermal_utility_systems.desuperheater import Desuperheater
7
+ from property_packages.build_package import build_package
8
+
9
+ eps_rel = 1e-4
10
+ eps_abs = 1e-2
11
+
12
+ def desuperheater_base_case(deltaT_superheat, T_water, T_steam, P_steam, heat_loss, pressure_loss):
13
+ # This defines the base case for all tests
14
+ m = pyo.ConcreteModel()
15
+ m.fs = FlowsheetBlock(dynamic=False)
16
+ m.fs.water = build_package(
17
+ "helmholtz",
18
+ ["water"],
19
+ ["Liq", "Vap"]
20
+ )
21
+
22
+ # Create SteamUser
23
+ m.fs.desuperheater = Desuperheater(
24
+ property_package=m.fs.water
25
+ )
26
+
27
+ # Set inlet conditions for steam inlet
28
+ m.fs.desuperheater.inlet_steam.flow_mol.fix(1000) # mol/s
29
+ m.fs.desuperheater.inlet_steam.pressure.fix(P_steam) # Pa
30
+ m.fs.desuperheater.inlet_steam.enth_mol.fix(
31
+ m.fs.water.htpx((T_steam + 273.15) * pyo.units.K,
32
+ m.fs.desuperheater.inlet_steam.pressure[0]),
33
+ ) # J/mol
34
+
35
+ # Define the state variables
36
+ m.fs.desuperheater.deltaT_superheat.fix(
37
+ deltaT_superheat * pyo.units.K
38
+ ) # K
39
+ m.fs.desuperheater.bfw_temperature[:].fix(
40
+ (T_water + 273.15) * pyo.units.K
41
+ ) # K
42
+ m.fs.desuperheater.heat_loss[:].fix(heat_loss) # W -> heat loss
43
+ m.fs.desuperheater.pressure_loss[:].fix(pressure_loss) # Pa -> pressure loss
44
+
45
+ # Check degrees of freedom
46
+ assert degrees_of_freedom(m.fs) == 0
47
+
48
+ # Initialize the model
49
+ m.fs.desuperheater.initialize()
50
+
51
+ # Solve the model
52
+ opt = pyo.SolverFactory("ipopt_v2")
53
+ results = opt.solve(m, tee=False)
54
+
55
+ return m, results
56
+
57
+ def test_desuperheater_with_zero_superheat_in_outlet_flow():
58
+ P = 400*1000
59
+ T = 200
60
+ T_w = 80
61
+ dT_sup = 0
62
+ Q_loss = 0
63
+ dP = 0
64
+
65
+ m, results = desuperheater_base_case(
66
+ deltaT_superheat=dT_sup,
67
+ T_water=T_w,
68
+ T_steam=T,
69
+ heat_loss=Q_loss,
70
+ pressure_loss=dP,
71
+ P_steam=P,
72
+ )
73
+
74
+ assert results.solver.termination_condition == pyo.TerminationCondition.optimal
75
+ assert (pyo.value(m.fs.desuperheater.outlet_state[0].temperature) - pyo.value(m.fs.desuperheater._int_sat_vap_state[0].temperature)) == pytest.approx(dT_sup, rel=eps_rel, abs=eps_abs)
76
+ assert pyo.value(m.fs.desuperheater.outlet_state[0].pressure) == pytest.approx(P - dP, rel=eps_rel, abs=eps_abs)
77
+
78
+ def test_desuperheater_with_zero_superheat_in_outlet_flow_and_pressure_loss():
79
+ # Case where there is zero liquid flow and there is more known inlet than outlet flows
80
+ P = 400*1000
81
+ T = 200
82
+ T_w = 80
83
+ dT_sup = 0
84
+ Q_loss = 0
85
+ dP = 1000
86
+
87
+ m, results = desuperheater_base_case(
88
+ deltaT_superheat=dT_sup,
89
+ T_water=T_w,
90
+ T_steam=T,
91
+ heat_loss=Q_loss,
92
+ pressure_loss=dP,
93
+ P_steam=P,
94
+ )
95
+
96
+ assert results.solver.termination_condition == pyo.TerminationCondition.optimal
97
+ assert (pyo.value(m.fs.desuperheater.outlet_state[0].temperature) - pyo.value(m.fs.desuperheater._int_sat_vap_state[0].temperature)) == pytest.approx(dT_sup, rel=eps_rel, abs=eps_abs)
98
+ assert pyo.value(m.fs.desuperheater.outlet_state[0].pressure) == pytest.approx(P - dP, rel=eps_rel, abs=eps_abs)
99
+
100
+ def test_desuperheater_with_10_superheat_in_outlet_flow():
101
+ # Case where there is zero liquid flow and there is more known inlet than outlet flows
102
+ P = 400*1000
103
+ T = 200
104
+ T_w = 80
105
+ dT_sup = 10
106
+ Q_loss = 0
107
+ dP = 0
108
+
109
+ m, results = desuperheater_base_case(
110
+ deltaT_superheat=dT_sup,
111
+ T_water=T_w,
112
+ T_steam=T,
113
+ heat_loss=Q_loss,
114
+ pressure_loss=dP,
115
+ P_steam=P,
116
+ )
117
+
118
+ assert results.solver.termination_condition == pyo.TerminationCondition.optimal
119
+ assert (pyo.value(m.fs.desuperheater.outlet_state[0].temperature) - pyo.value(m.fs.desuperheater._int_sat_vap_state[0].temperature)) == pytest.approx(dT_sup, rel=eps_rel, abs=eps_abs)
120
+ assert pyo.value(m.fs.desuperheater.outlet_state[0].pressure) == pytest.approx(P - dP, rel=eps_rel, abs=eps_abs)
121
+
122
+ def test_desuperheater_with_5_superheat_in_outlet_flow_plus_losses():
123
+ # Case where there is zero liquid flow and there is more known inlet than outlet flows
124
+ P = 400*1000
125
+ T = 200
126
+ T_w = 80
127
+ dT_sup = 5
128
+ Q_loss = 10000
129
+ dP = 5000
130
+
131
+ m, results = desuperheater_base_case(
132
+ deltaT_superheat=dT_sup,
133
+ T_water=T_w,
134
+ T_steam=T,
135
+ heat_loss=Q_loss,
136
+ pressure_loss=dP,
137
+ P_steam=P,
138
+ )
139
+
140
+ assert results.solver.termination_condition == pyo.TerminationCondition.optimal
141
+ assert (pyo.value(m.fs.desuperheater.outlet_state[0].temperature) - pyo.value(m.fs.desuperheater._int_sat_vap_state[0].temperature)) == pytest.approx(dT_sup, rel=eps_rel, abs=eps_abs)
142
+ assert pyo.value(m.fs.desuperheater.outlet_state[0].pressure) == pytest.approx(P - dP, rel=eps_rel, abs=eps_abs)