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,998 @@
1
+ import pytest
2
+
3
+ from ahuora_builder.custom.thermal_utility_systems.header import simple_header
4
+ from ahuora_builder.custom.custom_heater import DynamicHeater
5
+
6
+ import pyomo.environ as pyo
7
+ from idaes.core import FlowsheetBlock
8
+ from idaes.core.util.model_statistics import degrees_of_freedom
9
+ from property_packages.build_package import build_package
10
+ from pyomo.network import Arc, SequentialDecomposition
11
+ import idaes.logger as idaeslog
12
+
13
+
14
+ @pytest.fixture
15
+ def steam_header_base_case():
16
+ def _make_case(num_inlets=2, num_outlets=3):
17
+ # This defines the base case for all tests
18
+ m = pyo.ConcreteModel()
19
+ m.fs = FlowsheetBlock(dynamic=False)
20
+ m.fs.water = build_package("helmholtz", ["water"], ["Liq", "Vap"])
21
+ # Create Customsimple_header with 2 inlets and 3 outlets
22
+ m.fs.header = simple_header(
23
+ property_package=m.fs.water,
24
+ num_inlets=num_inlets,
25
+ num_outlets=num_outlets,
26
+ )
27
+
28
+ # Set inlet conditions for inlet_1
29
+ m.fs.header.inlet_1.flow_mol.fix(1000) # mol/s
30
+ m.fs.header.inlet_1.pressure.fix(201325) # Pa
31
+ m.fs.header.inlet_1.enth_mol.fix(m.fs.water.htpx((300 + 273.15) * pyo.units.K, m.fs.header.inlet_1.pressure[0])) # J/mol
32
+
33
+ return m
34
+ return _make_case
35
+
36
+ def assert_header_solution(
37
+ m,
38
+ expected_total_flow,
39
+ expected_vent_flow,
40
+ expected_makeup_flow,
41
+ expected_liquid_flow,
42
+ has_heat_loss,
43
+ has_pressure_loss,
44
+ ):
45
+ eps_rel = 1e-4
46
+ eps_abs = 0.1
47
+ # Check degrees of freedom
48
+ assert degrees_of_freedom(m) == 0
49
+
50
+ # Initialize the model
51
+ m.fs.header.initialize()
52
+
53
+ # Solve the model
54
+ opt = pyo.SolverFactory("ipopt")
55
+ results = opt.solve(m, tee=False)
56
+
57
+ # Check that solve was successful
58
+ assert results.solver.termination_condition == pyo.TerminationCondition.optimal
59
+
60
+ # Verify mass balance: total inlet flow = total vapor outlet flow + liquid outlet flow
61
+ balance_flow = pyo.value(m.fs.header.balance_flow_mol[0])
62
+ makeup_flow = max(-balance_flow, 0)
63
+ total_inlet_flow = sum([pyo.value(inlet_sb[0].flow_mol) for inlet_sb in m.fs.header.inlet_blocks]) + makeup_flow
64
+ total_outlet_flow = sum([pyo.value(outlet_sb[0].flow_mol) for outlet_sb in m.fs.header.outlet_blocks])
65
+ total_outlet_supply = sum([pyo.value(outlet_sb[0].flow_mol) for outlet_sb in m.fs.header._outlet_supply_blocks])
66
+ condensate_flow = pyo.value(m.fs.header.outlet_condensate_state[0].flow_mol)
67
+ vent_flow = pyo.value(m.fs.header.outlet_vent_state[0].flow_mol)
68
+
69
+ assert expected_total_flow == pytest.approx(total_outlet_flow, rel=eps_rel, abs=eps_abs)
70
+ assert expected_total_flow == pytest.approx(total_inlet_flow, rel=eps_rel, abs=eps_abs)
71
+
72
+ assert expected_liquid_flow == pytest.approx(condensate_flow, rel=eps_rel, abs=0.02)
73
+ assert expected_vent_flow == pytest.approx(vent_flow, rel=eps_rel, abs=0.02)
74
+ assert expected_makeup_flow == pytest.approx(makeup_flow, rel=eps_rel, abs=0.02)
75
+
76
+ # Verify pressure drop is applied correctly
77
+ if pyo.value(m.fs.header.inlet_1.pressure[0]) > eps_rel:
78
+ expected_min_inlet_pressure = pyo.value(m.fs.header.inlet_1.pressure[0])
79
+ else:
80
+ expected_min_inlet_pressure = pyo.value(m.fs.header.inlet_1.pressure[0])
81
+ expected_outlet_pressure = expected_min_inlet_pressure - pyo.value(m.fs.header.pressure_loss[0]) # Accounts for pressure drop
82
+
83
+ assert pyo.value(m.fs.header.outlet_1.pressure[0]) == pytest.approx(expected_outlet_pressure, rel=eps_rel, abs=eps_abs)
84
+ assert pyo.value(m.fs.header.outlet_vent.pressure[0]) == pytest.approx(expected_outlet_pressure, rel=eps_rel, abs=eps_abs)
85
+
86
+ assert pyo.value(m.fs.header.outlet_1.pressure[0]) == pytest.approx(pyo.value(m.fs.header.outlet_condensate.pressure[0]), rel=eps_rel, abs=eps_abs)
87
+ assert pyo.value(m.fs.header.outlet_vent.pressure[0]) == pytest.approx(pyo.value(m.fs.header.outlet_condensate.pressure[0]), rel=eps_rel, abs=eps_abs)
88
+
89
+ # Verify molar enthalpy relationships
90
+ if m.fs.header.config.is_liquid_header:
91
+ assert pyo.value(m.fs.header.outlet_1.enth_mol[0]) == pytest.approx(pyo.value(m.fs.header.outlet_condensate.enth_mol[0]), rel=eps_rel, abs=eps_abs)
92
+ else:
93
+ assert pyo.value(m.fs.header.outlet_1.enth_mol[0]) == pytest.approx(pyo.value(m.fs.header.outlet_vent.enth_mol[0]), rel=eps_rel, abs=eps_abs)
94
+
95
+ hV = pyo.value(m.fs.header.outlet_1.enth_mol[0]) # J/mol
96
+ hL = pyo.value(m.fs.header.outlet_condensate.enth_mol[0]) # J/mol
97
+ if pyo.value(m.fs.header.outlet_condensate.flow_mol[0]) > eps_abs:
98
+ assert hV >= hL
99
+
100
+ # Verify cooling effect
101
+ inlet_total_energy = pyo.value(
102
+ sum(
103
+ i[0].flow_mol * i[0].enth_mol
104
+ for i in m.fs.header.inlet_blocks
105
+ )
106
+ *
107
+ (m.fs.header.mixed_state[0].flow_mol + makeup_flow) / m.fs.header.mixed_state[0].flow_mol
108
+ )
109
+ outlet_total_energy = pyo.value(
110
+ sum(
111
+ i[0].flow_mol * i[0].enth_mol
112
+ for i in m.fs.header.outlet_blocks
113
+ )
114
+ +
115
+ m.fs.header.heat_loss[0]
116
+ )
117
+ assert outlet_total_energy == pytest.approx(inlet_total_energy, rel=eps_rel, abs=eps_abs)
118
+
119
+ # Test that ports exist
120
+ assert hasattr(m.fs.header, 'outlet_condensate')
121
+ assert hasattr(m.fs.header, 'inlet_1')
122
+ assert hasattr(m.fs.header, 'outlet_1')
123
+ assert hasattr(m.fs.header, 'outlet_vent')
124
+
125
+ inlet_mass_list = [pyo.value(i[0].flow_mass) for i in m.fs.header.inlet_blocks] + [ pyo.value(makeup_flow * 0.01801528)] # convert mol/s to kg/s
126
+ outlet_mass_list = [pyo.value(i[0].flow_mass) for i in m.fs.header.outlet_blocks]
127
+ # Test all mass flows are non-zero and sum of inlet and outlet masses is approx same
128
+ assert sum(inlet_mass_list) == pytest.approx(sum(outlet_mass_list), rel=eps_rel, abs=eps_abs)
129
+
130
+ min_inlet_pressure = min([pyo.value(i[0].pressure) for i in m.fs.header.inlet_blocks])
131
+ min_outlet_pressure = min([pyo.value(i[0].pressure) for i in m.fs.header.outlet_blocks])
132
+ if total_outlet_flow > eps_abs:
133
+ assert min_outlet_pressure == pytest.approx(min_inlet_pressure - pyo.value(m.fs.header.pressure_loss[0]), rel=eps_rel, abs=eps_abs)
134
+
135
+ # Test minimum input pressure is >= outlet pressure and all pressure are non-zero
136
+ assert min_inlet_pressure >= min_outlet_pressure - eps_abs
137
+ assert min_inlet_pressure > eps_abs
138
+ assert min_outlet_pressure > eps_abs
139
+
140
+ def test_header_with_inlet_greater_than_outlet_flow(steam_header_base_case):
141
+ # Case where there is zero liquid flow and there is more known inlet than outlet flows
142
+ m = steam_header_base_case(num_inlets=1, num_outlets=1)
143
+
144
+ # Set user steam demand
145
+ m.fs.header.outlet_1.flow_mol.fix(800) # mol/s
146
+ # Set cooler specifications
147
+ m.fs.header.heat_loss[0].fix(1000) # W -> heat loss
148
+ m.fs.header.pressure_loss[0].fix(1000) # Pa -> pressure loss
149
+
150
+ assert_header_solution(
151
+ m,
152
+ expected_total_flow=1000,
153
+ expected_vent_flow=200,
154
+ expected_makeup_flow=0,
155
+ expected_liquid_flow=0,
156
+ has_heat_loss=True if pyo.value(m.fs.header.heat_loss[0]) > 0 else False,
157
+ has_pressure_loss=True if pyo.value(m.fs.header.pressure_loss[0]) > 0 else False,
158
+ )
159
+
160
+ def test_header_with_condensate_in_inlet_flow(steam_header_base_case):
161
+ # Case where there is zero liquid flow and there is more known inlet than outlet flows
162
+ m = steam_header_base_case(num_inlets=1, num_outlets=1)
163
+
164
+ h_val = pyo.value(m.fs.water.htpx(p=201325 * pyo.units.Pa, x=0.9))
165
+ m.fs.header.inlet_1.enth_mol.fix(h_val) # J/mol
166
+
167
+ # Set user steam demand
168
+ m.fs.header.outlet_1.flow_mol.fix(800) # mol/s
169
+ # Set cooler specifications
170
+ m.fs.header.heat_loss[0].fix(0) # W -> heat loss
171
+ m.fs.header.pressure_loss[0].fix(0) # Pa -> pressure loss
172
+
173
+ assert_header_solution(
174
+ m,
175
+ expected_total_flow=1000,
176
+ expected_vent_flow=100,
177
+ expected_makeup_flow=0,
178
+ expected_liquid_flow=100,
179
+ has_heat_loss=True if pyo.value(m.fs.header.heat_loss[0]) > 0 else False,
180
+ has_pressure_loss=True if pyo.value(m.fs.header.pressure_loss[0]) > 0 else False,
181
+ )
182
+
183
+ def test_header_with_inlet_equal_to_outlet_flow(steam_header_base_case):
184
+ # Case where there is zero liquid flow and the header is balanced
185
+ m = steam_header_base_case(num_inlets=1, num_outlets=1)
186
+ # Set user steam demand
187
+ m.fs.header.outlet_1.flow_mol.fix(1000) # mol/s
188
+ # Set cooler specifications
189
+ m.fs.header.heat_loss[0].fix(0) # W -> heat loss
190
+ m.fs.header.pressure_loss[0].fix(0) # Pa -> pressure loss
191
+
192
+ assert_header_solution(
193
+ m,
194
+ expected_total_flow=1000,
195
+ expected_vent_flow=0,
196
+ expected_makeup_flow=0,
197
+ expected_liquid_flow=0,
198
+ has_heat_loss=True if pyo.value(m.fs.header.heat_loss[0]) > 0 else False,
199
+ has_pressure_loss=True if pyo.value(m.fs.header.pressure_loss[0]) > 0 else False,
200
+ )
201
+
202
+ def test_header_with_inlet_less_than_outlet_flow(steam_header_base_case):
203
+ # Case where there is zero liquid flow and the header is balanced
204
+ m = steam_header_base_case(num_inlets=1, num_outlets=1)
205
+ # Set user steam demand
206
+ m.fs.header.outlet_1.flow_mol.fix(1200) # mol/s
207
+ # Set cooler specifications
208
+ m.fs.header.heat_loss[0].fix(0) # W -> heat loss
209
+ m.fs.header.pressure_loss[0].fix(0) # Pa -> pressure loss
210
+
211
+ assert_header_solution(
212
+ m,
213
+ expected_total_flow=1200,
214
+ expected_vent_flow=0,
215
+ expected_makeup_flow=200,
216
+ expected_liquid_flow=0,
217
+ has_heat_loss=True if pyo.value(m.fs.header.heat_loss[0]) > 0 else False,
218
+ has_pressure_loss=True if pyo.value(m.fs.header.pressure_loss[0]) > 0 else False,
219
+ )
220
+
221
+ def test_header_with_zero_flow(steam_header_base_case):
222
+ # Case where there is zero liquid flow and the header is balanced
223
+ m = steam_header_base_case(num_inlets=1, num_outlets=1)
224
+ m.fs.header.inlet_1.flow_mol.fix(0) # mol/s
225
+ # Set user steam demand
226
+ m.fs.header.outlet_1.flow_mol.fix(0) # mol/s
227
+ # Set cooler specifications
228
+ m.fs.header.heat_loss[0].fix(0) # W -> heat loss
229
+ m.fs.header.pressure_loss[0].fix(0) # Pa -> pressure loss
230
+
231
+ assert_header_solution(
232
+ m,
233
+ expected_total_flow=0,
234
+ expected_vent_flow=0,
235
+ expected_makeup_flow=0,
236
+ expected_liquid_flow=0,
237
+ has_heat_loss=True if pyo.value(m.fs.header.heat_loss[0]) > 0 else False,
238
+ has_pressure_loss=True if pyo.value(m.fs.header.pressure_loss[0]) > 0 else False,
239
+ )
240
+
241
+ def test_header_with_inlet_greater_than_three_defined_outlet_flows(steam_header_base_case):
242
+ # Case where there is zero liquid flow and there is more known inlet than outlet flows
243
+ m = steam_header_base_case(num_inlets=1, num_outlets=3)
244
+
245
+ # Set user steam demand
246
+ m.fs.header.outlet_1.flow_mol.fix(500) # mol/s
247
+ m.fs.header.outlet_2.flow_mol.fix(100) # mol/s
248
+ m.fs.header.outlet_3.flow_mol.fix(200) # mol/s
249
+
250
+ # Set cooler specifications
251
+ m.fs.header.heat_loss[0].fix(1000) # W -> heat loss
252
+ m.fs.header.pressure_loss[0].fix(1000) # Pa -> pressure loss
253
+
254
+ assert_header_solution(
255
+ m,
256
+ expected_total_flow=1000,
257
+ expected_vent_flow=200,
258
+ expected_makeup_flow=0,
259
+ expected_liquid_flow=0,
260
+ has_heat_loss=True if pyo.value(m.fs.header.heat_loss[0]) > 0 else False,
261
+ has_pressure_loss=True if pyo.value(m.fs.header.pressure_loss[0]) > 0 else False,
262
+ )
263
+
264
+ def test_header_with_inlet_less_than_two_defined_outlet_flows(steam_header_base_case):
265
+ # Case where there is zero liquid flow and the header is balanced
266
+ m = steam_header_base_case(num_inlets=1, num_outlets=2)
267
+ # Set user steam demand
268
+ m.fs.header.outlet_1.flow_mol.fix(600) # mol/s
269
+ m.fs.header.outlet_2.flow_mol.fix(600) # mol/s
270
+ # Set cooler specifications
271
+ m.fs.header.heat_loss[0].fix(0) # W -> heat loss
272
+ m.fs.header.pressure_loss[0].fix(0) # Pa -> pressure loss
273
+
274
+ assert_header_solution(
275
+ m,
276
+ expected_total_flow=1200,
277
+ expected_vent_flow=0,
278
+ expected_makeup_flow=200,
279
+ expected_liquid_flow=0,
280
+ has_heat_loss=True if pyo.value(m.fs.header.heat_loss[0]) > 0 else False,
281
+ has_pressure_loss=True if pyo.value(m.fs.header.pressure_loss[0]) > 0 else False,
282
+ )
283
+
284
+ def test_header_with_two_defined_inlet_less_than_two_defined_outlet_flows(steam_header_base_case):
285
+ # Case where there is zero liquid flow and the header is balanced
286
+ m = steam_header_base_case(num_inlets=2, num_outlets=2)
287
+ m.fs.header.inlet_2.flow_mol.fix(100) # mol/s
288
+ m.fs.header.inlet_2.pressure.fix(221325) # Pa
289
+ m.fs.header.inlet_2.enth_mol.fix(m.fs.water.htpx((310 + 273.15) * pyo.units.K, m.fs.header.inlet_1.pressure[0])) # J/mol
290
+
291
+ # Set user steam demand
292
+ m.fs.header.outlet_1.flow_mol.fix(600) # mol/s
293
+ m.fs.header.outlet_2.flow_mol.fix(600) # mol/s
294
+ # Set cooler specifications
295
+ m.fs.header.heat_loss[0].fix(0) # W -> heat loss
296
+ m.fs.header.pressure_loss[0].fix(0) # Pa -> pressure loss
297
+
298
+ assert_header_solution(
299
+ m,
300
+ expected_total_flow=1200,
301
+ expected_vent_flow=0,
302
+ expected_makeup_flow=100,
303
+ expected_liquid_flow=0,
304
+ has_heat_loss=True if pyo.value(m.fs.header.heat_loss[0]) > 0 else False,
305
+ has_pressure_loss=True if pyo.value(m.fs.header.pressure_loss[0]) > 0 else False,
306
+ )
307
+
308
+ def test_sequential_decomposition():
309
+ m = pyo.ConcreteModel()
310
+ m.fs = FlowsheetBlock(dynamic=False)
311
+ m.fs.water = build_package("helmholtz", ["water"], ["Liq", "Vap"])
312
+
313
+ m.fs.heater = DynamicHeater(property_package=m.fs.water)
314
+ m.fs.heater.inlet.flow_mol.fix(100)
315
+ m.fs.heater.inlet.enth_mol.fix(
316
+ m.fs.water.htpx((25 + 273.15) * pyo.units.K, 201325 * pyo.units.Pa)
317
+ ) # J/mol
318
+ m.fs.heater.inlet.pressure.fix(101325)
319
+ m.fs.heater.heat_duty.fix(5000)
320
+
321
+ # Create Customsimple_header with 2 inlets and 3 outlets
322
+ m.fs.header = simple_header(
323
+ property_package=m.fs.water,
324
+ num_inlets=1,
325
+ num_outlets=1,
326
+ )
327
+
328
+ # Set user steam demand
329
+ m.fs.header.outlet_1.flow_mol.fix(600) # mol/s
330
+ # Set cooler specifications
331
+ m.fs.header.heat_loss[0].fix(0) # W -> heat loss
332
+ m.fs.header.pressure_loss[0].fix(0) # Pa -> pressure loss
333
+
334
+ m.fs.heater_to_header = Arc(
335
+ source=m.fs.heater.outlet, destination=m.fs.header.inlet_1
336
+ )
337
+
338
+ pyo.TransformationFactory("network.expand_arcs").apply_to(m)
339
+
340
+ assert degrees_of_freedom(m) == 0
341
+
342
+ # create Sequential Decomposition object
343
+ seq = SequentialDecomposition()
344
+ seq.options.select_tear_method = "heuristic"
345
+ seq.options.tear_method = "Wegstein"
346
+ seq.options.iterLim = 1
347
+
348
+ # create computation graph
349
+ G = seq.create_graph(m)
350
+ heuristic_tear_set = seq.tear_set_arcs(G, method="heuristic")
351
+ # get calculation order
352
+ order = seq.calculation_order(G)
353
+
354
+ for o in heuristic_tear_set:
355
+ print(o.name)
356
+
357
+ print("Initialization order:")
358
+ for o in order:
359
+ print(o[0].name)
360
+
361
+ # define unit initialisation function
362
+ def init_unit(unit):
363
+ unit.initialize()
364
+
365
+ # run sequential decomposition
366
+ seq.run(m, init_unit)
367
+
368
+ def test_header_with_inlet_steam_generator():
369
+ m = pyo.ConcreteModel()
370
+ m.fs = FlowsheetBlock(dynamic=False)
371
+ m.fs.water = build_package("helmholtz", ["water"], ["Liq", "Vap"])
372
+
373
+ # Add a heater before the header
374
+ m.fs.boiler = DynamicHeater(
375
+ property_package=m.fs.water,
376
+ has_pressure_change=True,
377
+ )
378
+ # Create Customsimple_header with 2 inlets and 3 outlets
379
+ m.fs.header = simple_header(
380
+ property_package=m.fs.water,
381
+ num_inlets=1,
382
+ num_outlets=1
383
+ )
384
+ # Connect heater outlet to header inlet_1
385
+ m.boiler_to_header_arc = Arc(
386
+ source=m.fs.boiler.outlet, destination=m.fs.header.inlet_1
387
+ )
388
+ pyo.TransformationFactory("network.expand_arcs").apply_to(m)
389
+
390
+ # Set inlet conditions for inlet_1
391
+ m.fs.boiler.inlet.flow_mol.fix(1000) # mol/s
392
+ m.fs.boiler.inlet.pressure.fix(200 * 1000) # Pa
393
+ m.fs.boiler.inlet.enth_mol.fix(
394
+ m.fs.water.htpx(
395
+ (15 + 273.15) * pyo.units.K,
396
+ m.fs.boiler.inlet.pressure[0]
397
+ )
398
+ ) # J/mol
399
+ m.fs.boiler.heat_duty.fix(
400
+ 50.577 * 1000 * 1000
401
+ )
402
+ m.fs.boiler.deltaP.fix(
403
+ 0.0
404
+ )
405
+ m.fs.header.outlet_1.flow_mol.fix(500)
406
+ m.fs.header.heat_loss.fix(
407
+ 0.0
408
+ )
409
+ m.fs.header.pressure_loss.fix(
410
+ 0.0
411
+ )
412
+
413
+ # Initialize the heater first
414
+ seq = SequentialDecomposition()
415
+ seq.options.select_tear_method = "heuristic"
416
+ seq.options.tear_method = "Wegstein"
417
+ seq.options.iterLim = 1
418
+
419
+ # create computation graph
420
+ G = seq.create_graph(m)
421
+ heuristic_tear_set = seq.tear_set_arcs(G, method="heuristic")
422
+ # get calculation order
423
+ order = seq.calculation_order(G)
424
+
425
+ for o in heuristic_tear_set:
426
+ print(o.name)
427
+
428
+ for o in order:
429
+ print(o[0].name)
430
+
431
+ # define unit initialisation function
432
+ def init_unit(unit):
433
+ unit.initialize()
434
+
435
+ # run sequential decomposition
436
+ seq.run(m, init_unit)
437
+
438
+ dof = degrees_of_freedom(m)
439
+
440
+ opt = pyo.SolverFactory("ipopt_v2")
441
+ results = opt.solve(m, tee=False)
442
+
443
+ assert results.solver.termination_condition == pyo.TerminationCondition.optimal
444
+
445
+ assert_header_solution(
446
+ m=m,
447
+ expected_total_flow=1000,
448
+ expected_vent_flow=500,
449
+ expected_makeup_flow=0,
450
+ expected_liquid_flow=0,
451
+ has_heat_loss=0,
452
+ has_pressure_loss=0,
453
+ )
454
+
455
+ def test_header_with_inlet_steam_generator_with_insufficent_flow():
456
+ m = pyo.ConcreteModel()
457
+ m.fs = FlowsheetBlock(dynamic=False)
458
+ m.fs.water = build_package("helmholtz", ["water"], ["Liq", "Vap"])
459
+
460
+ # Add a heater before the header
461
+ m.fs.boiler = DynamicHeater(
462
+ property_package=m.fs.water,
463
+ has_pressure_change=True,
464
+ )
465
+ # Create Customsimple_header with 2 inlets and 3 outlets
466
+ m.fs.header = simple_header(
467
+ property_package=m.fs.water,
468
+ num_inlets=1,
469
+ num_outlets=1
470
+ )
471
+ # Connect heater outlet to header inlet_1
472
+ m.boiler_to_header_arc = Arc(
473
+ source=m.fs.boiler.outlet, destination=m.fs.header.inlet_1
474
+ )
475
+ pyo.TransformationFactory("network.expand_arcs").apply_to(m)
476
+
477
+ # Set inlet conditions for inlet_1
478
+ m.fs.boiler.inlet.flow_mol.fix(1000) # mol/s
479
+ m.fs.boiler.inlet.pressure.fix(200 * 1000) # Pa
480
+ m.fs.boiler.inlet.enth_mol.fix(
481
+ m.fs.water.htpx(
482
+ (15 + 273.15) * pyo.units.K,
483
+ m.fs.boiler.inlet.pressure[0]
484
+ )
485
+ ) # J/mol
486
+ m.fs.boiler.heat_duty.fix(
487
+ 50.577 * 1000 * 1000
488
+ )
489
+ m.fs.boiler.deltaP.fix(
490
+ 0.0
491
+ )
492
+ m.fs.header.outlet_1.flow_mol.fix(1500)
493
+ m.fs.header.heat_loss.fix(
494
+ 0.0
495
+ )
496
+ m.fs.header.pressure_loss.fix(
497
+ 0.0
498
+ )
499
+
500
+ # Initialize the heater first
501
+ seq = SequentialDecomposition()
502
+ seq.options.select_tear_method = "heuristic"
503
+ seq.options.tear_method = "Wegstein"
504
+ seq.options.iterLim = 1
505
+
506
+ # create computation graph
507
+ G = seq.create_graph(m)
508
+ heuristic_tear_set = seq.tear_set_arcs(G, method="heuristic")
509
+ # get calculation order
510
+ order = seq.calculation_order(G)
511
+
512
+ for o in heuristic_tear_set:
513
+ print(o.name)
514
+
515
+ for o in order:
516
+ print(o[0].name)
517
+
518
+ # define unit initialisation function
519
+ def init_unit(unit):
520
+ unit.initialize()
521
+
522
+ # run sequential decomposition
523
+ seq.run(m, init_unit)
524
+
525
+ opt = pyo.SolverFactory("ipopt_v2")
526
+ results = opt.solve(m, tee=False)
527
+
528
+ assert results.solver.termination_condition == pyo.TerminationCondition.optimal
529
+
530
+ assert_header_solution(
531
+ m=m,
532
+ expected_total_flow=1500,
533
+ expected_vent_flow=0,
534
+ expected_makeup_flow=500,
535
+ expected_liquid_flow=0,
536
+ has_heat_loss=0,
537
+ has_pressure_loss=0,
538
+ )
539
+
540
+ def test_header_with_inlet_steam_generator_with_losses():
541
+ m = pyo.ConcreteModel()
542
+ m.fs = FlowsheetBlock(dynamic=False)
543
+ m.fs.water = build_package("helmholtz", ["water"], ["Liq", "Vap"])
544
+
545
+ # Add a heater before the header
546
+ m.fs.boiler = DynamicHeater(
547
+ property_package=m.fs.water,
548
+ has_pressure_change=True,
549
+ )
550
+ # Create Customsimple_header with 2 inlets and 3 outlets
551
+ m.fs.header = simple_header(
552
+ property_package=m.fs.water,
553
+ num_inlets=1,
554
+ num_outlets=1
555
+ )
556
+ # Connect heater outlet to header inlet_1
557
+ m.boiler_to_header_arc = Arc(
558
+ source=m.fs.boiler.outlet, destination=m.fs.header.inlet_1
559
+ )
560
+ pyo.TransformationFactory("network.expand_arcs").apply_to(m)
561
+
562
+ # Set inlet conditions for inlet_1
563
+ m.fs.boiler.inlet.flow_mol.fix(1000) # mol/s
564
+ m.fs.boiler.inlet.pressure.fix(200 * 1000) # Pa
565
+ m.fs.boiler.inlet.enth_mol.fix(
566
+ m.fs.water.htpx(
567
+ (15 + 273.15) * pyo.units.K,
568
+ m.fs.boiler.inlet.pressure[0]
569
+ )
570
+ ) # J/mol
571
+ m.fs.boiler.heat_duty.fix(
572
+ 50.577 * 1000 * 1000
573
+ )
574
+ m.fs.boiler.deltaP.fix(
575
+ 0.0
576
+ )
577
+ m.fs.header.outlet_1.flow_mol.fix(500)
578
+ m.fs.header.heat_loss.fix(
579
+ 10.0
580
+ )
581
+ m.fs.header.pressure_loss.fix(
582
+ 5000.0
583
+ )
584
+
585
+ # Initialize the heater first
586
+ seq = SequentialDecomposition()
587
+ seq.options.select_tear_method = "heuristic"
588
+ seq.options.tear_method = "Wegstein"
589
+ seq.options.iterLim = 1
590
+
591
+ # create computation graph
592
+ G = seq.create_graph(m)
593
+ heuristic_tear_set = seq.tear_set_arcs(G, method="heuristic")
594
+ # get calculation order
595
+ order = seq.calculation_order(G)
596
+
597
+ for o in heuristic_tear_set:
598
+ print(o.name)
599
+
600
+ for o in order:
601
+ print(o[0].name)
602
+
603
+ # define unit initialisation function
604
+ def init_unit(unit):
605
+ unit.initialize()
606
+
607
+ # run sequential decomposition
608
+ seq.run(m, init_unit)
609
+
610
+ opt = pyo.SolverFactory("ipopt_v2")
611
+ results = opt.solve(m, tee=False)
612
+
613
+ assert results.solver.termination_condition == pyo.TerminationCondition.optimal
614
+
615
+ assert_header_solution(
616
+ m=m,
617
+ expected_total_flow=1000,
618
+ expected_vent_flow=500,
619
+ expected_makeup_flow=0,
620
+ expected_liquid_flow=0,
621
+ has_heat_loss=True,
622
+ has_pressure_loss=True,
623
+ )
624
+
625
+ def test_header_with_inlet_steam_generator_with_wet_steam():
626
+ m = pyo.ConcreteModel()
627
+ m.fs = FlowsheetBlock(dynamic=False)
628
+ m.fs.water = build_package("helmholtz", ["water"], ["Liq", "Vap"])
629
+
630
+ # Add a heater before the header
631
+ m.fs.boiler = DynamicHeater(
632
+ property_package=m.fs.water,
633
+ has_pressure_change=True,
634
+ )
635
+ # Create Customsimple_header with 2 inlets and 3 outlets
636
+ m.fs.header = simple_header(
637
+ property_package=m.fs.water,
638
+ num_inlets=1,
639
+ num_outlets=1
640
+ )
641
+ # Connect heater outlet to header inlet_1
642
+ m.boiler_to_header_arc = Arc(
643
+ source=m.fs.boiler.outlet, destination=m.fs.header.inlet_1
644
+ )
645
+ pyo.TransformationFactory("network.expand_arcs").apply_to(m)
646
+
647
+ # Set inlet conditions for inlet_1
648
+ m.fs.boiler.inlet.flow_mol.fix(1000) # mol/s
649
+ m.fs.boiler.inlet.pressure.fix(200 * 1000) # Pa
650
+ m.fs.boiler.inlet.enth_mol.fix(
651
+ m.fs.water.htpx(
652
+ (15 + 273.15) * pyo.units.K,
653
+ m.fs.boiler.inlet.pressure[0]
654
+ )
655
+ ) # J/mol
656
+ m.fs.boiler.heat_duty.fix(
657
+ 27784964.63
658
+ )
659
+ m.fs.boiler.heat_duty.unfix()
660
+ m.fs.boiler.outlet.enth_mol.fix(
661
+ m.fs.water.htpx(
662
+ x=0.5,
663
+ p=m.fs.boiler.inlet.pressure[0]
664
+ )
665
+ )
666
+ m.fs.boiler.deltaP.fix(
667
+ 0.0
668
+ )
669
+ m.fs.header.outlet_1.flow_mol.fix(500)
670
+ m.fs.header.heat_loss.fix(
671
+ 0.0
672
+ )
673
+ m.fs.header.pressure_loss.fix(
674
+ 0.0
675
+ )
676
+
677
+ # Initialize the heater first
678
+ seq = SequentialDecomposition()
679
+ seq.options.select_tear_method = "heuristic"
680
+ seq.options.tear_method = "Wegstein"
681
+ seq.options.iterLim = 1
682
+
683
+ # create computation graph
684
+ G = seq.create_graph(m)
685
+ heuristic_tear_set = seq.tear_set_arcs(G, method="heuristic")
686
+ # get calculation order
687
+ order = seq.calculation_order(G)
688
+
689
+ for o in heuristic_tear_set:
690
+ print(o.name)
691
+
692
+ for o in order:
693
+ print(o[0].name)
694
+
695
+ # define unit initialisation function
696
+ def init_unit(unit):
697
+ unit.initialize()
698
+
699
+ # run sequential decomposition
700
+ seq.run(m, init_unit)
701
+
702
+ opt = pyo.SolverFactory("ipopt_v2")
703
+ results = opt.solve(m, tee=False)
704
+
705
+ assert results.solver.termination_condition == pyo.TerminationCondition.optimal
706
+
707
+ assert_header_solution(
708
+ m=m,
709
+ expected_total_flow=1000,
710
+ expected_vent_flow=0,
711
+ expected_makeup_flow=0.0,
712
+ expected_liquid_flow=500,
713
+ has_heat_loss=0,
714
+ has_pressure_loss=0,
715
+ )
716
+
717
+
718
+ """ TESTS FOR USE AS LIQUID HEADER """
719
+ @pytest.fixture
720
+ def liquid_header_base_case():
721
+ def _make_case(num_inlets=2, num_outlets=3):
722
+ # This defines the base case for all tests
723
+ m = pyo.ConcreteModel()
724
+ m.fs = FlowsheetBlock(dynamic=False)
725
+ m.fs.water = build_package("helmholtz", ["water"], ["Liq", "Vap"])
726
+
727
+ m.fs.header = simple_header(
728
+ property_package=m.fs.water,
729
+ num_inlets=num_inlets,
730
+ num_outlets=num_outlets,
731
+ is_liquid_header=True,
732
+ )
733
+
734
+ # Set inlet conditions for inlet_1
735
+ m.fs.header.inlet_1.flow_mol.fix(1000) # mol/s
736
+ m.fs.header.inlet_1.pressure.fix(201325) # Pa
737
+ m.fs.header.inlet_1.enth_mol.fix(m.fs.water.htpx((50 + 273.15) * pyo.units.K, m.fs.header.inlet_1.pressure[0])) # J/mol
738
+
739
+ return m
740
+ return _make_case
741
+
742
+ def test_liq_header_with_inlet_and_outlet_and_water_boiler(liquid_header_base_case):
743
+ m = liquid_header_base_case(num_inlets=1, num_outlets=1)
744
+
745
+ # Add a heater before the header
746
+ m.fs.boiler = DynamicHeater(
747
+ property_package=m.fs.water,
748
+ has_pressure_change=True,
749
+ )
750
+ # Connect heater outlet to header inlet_1
751
+ m.boiler_to_header_arc = Arc(
752
+ source=m.fs.boiler.outlet, destination=m.fs.header.inlet_1
753
+ )
754
+ pyo.TransformationFactory("network.expand_arcs").apply_to(m)
755
+
756
+ m.fs.header.inlet_1.flow_mol.unfix() # mol/s
757
+ m.fs.header.inlet_1.pressure.unfix() # Pa
758
+ m.fs.header.inlet_1.enth_mol.unfix() # J/mol
759
+
760
+ # Set inlet conditions for inlet_1
761
+ m.fs.boiler.inlet.flow_mol.fix(1000) # mol/s
762
+ m.fs.boiler.inlet.pressure.fix(200 * 1000) # Pa
763
+ m.fs.boiler.inlet.enth_mol.fix(
764
+ m.fs.water.htpx(
765
+ (15 + 273.15) * pyo.units.K,
766
+ m.fs.boiler.inlet.pressure[0]
767
+ )
768
+ ) # J/mol
769
+ m.fs.boiler.heat_duty.fix(
770
+ 0
771
+ )
772
+ m.fs.boiler.heat_duty.unfix()
773
+ m.fs.boiler.outlet.enth_mol.fix(
774
+ m.fs.water.htpx(
775
+ x=0.1,
776
+ p=m.fs.boiler.inlet.pressure[0]
777
+ )
778
+ )
779
+ m.fs.boiler.deltaP.fix(
780
+ 0.0
781
+ )
782
+ m.fs.header.outlet_1.flow_mol.fix(500)
783
+ m.fs.header.heat_loss.fix(
784
+ 0.0
785
+ )
786
+ m.fs.header.pressure_loss.fix(
787
+ 0.0
788
+ )
789
+
790
+ # Initialize the heater first
791
+ seq = SequentialDecomposition()
792
+ seq.options.select_tear_method = "heuristic"
793
+ seq.options.tear_method = "Wegstein"
794
+ seq.options.iterLim = 1
795
+
796
+ # create computation graph
797
+ G = seq.create_graph(m)
798
+ heuristic_tear_set = seq.tear_set_arcs(G, method="heuristic")
799
+ # get calculation order
800
+ order = seq.calculation_order(G)
801
+
802
+ for o in heuristic_tear_set:
803
+ print(o.name)
804
+
805
+ for o in order:
806
+ print(o[0].name)
807
+
808
+ # define unit initialisation function
809
+ def init_unit(unit):
810
+ unit.initialize()
811
+
812
+ # run sequential decomposition
813
+ seq.run(m, init_unit)
814
+
815
+ opt = pyo.SolverFactory("ipopt_v2")
816
+ results = opt.solve(m, tee=False)
817
+
818
+ assert results.solver.termination_condition == pyo.TerminationCondition.optimal
819
+
820
+ assert_header_solution(
821
+ m=m,
822
+ expected_total_flow=1000,
823
+ expected_vent_flow=100,
824
+ expected_makeup_flow=0.0,
825
+ expected_liquid_flow=400,
826
+ has_heat_loss=0,
827
+ has_pressure_loss=0,
828
+ )
829
+
830
+ def test_liq_header_with_inlet_greater_than_outlet_flow(liquid_header_base_case):
831
+ # Case where there is zero liquid flow and there is more known inlet than outlet flows
832
+ m = liquid_header_base_case(num_inlets=1, num_outlets=1)
833
+
834
+ # Set user steam demand
835
+ m.fs.header.outlet_1.flow_mol.fix(800) # mol/s
836
+ # Set cooler specifications
837
+ m.fs.header.heat_loss[0].fix(5000) # W -> heat loss
838
+ m.fs.header.pressure_loss[0].fix(1000) # Pa -> pressure loss
839
+
840
+ assert_header_solution(
841
+ m,
842
+ expected_total_flow=1000,
843
+ expected_vent_flow=0,
844
+ expected_makeup_flow=0,
845
+ expected_liquid_flow=200,
846
+ has_heat_loss=True if pyo.value(m.fs.header.heat_loss[0]) > 0 else False,
847
+ has_pressure_loss=True if pyo.value(m.fs.header.pressure_loss[0]) > 0 else False,
848
+ )
849
+
850
+ def test_liq_header_with_vapour_in_inlet_flow(liquid_header_base_case):
851
+ # Case where there is zero liquid flow and there is more known inlet than outlet flows
852
+ m = liquid_header_base_case(num_inlets=1, num_outlets=1)
853
+
854
+ h_val = pyo.value(m.fs.water.htpx(p=201325 * pyo.units.Pa, x=0.15))
855
+ m.fs.header.inlet_1.enth_mol.fix(h_val) # J/mol
856
+
857
+ # Set user steam demand
858
+ m.fs.header.outlet_1.flow_mol.fix(800) # mol/s
859
+ # Set cooler specifications
860
+ m.fs.header.heat_loss[0].fix(0) # W -> heat loss
861
+ m.fs.header.pressure_loss[0].fix(0) # Pa -> pressure loss
862
+
863
+ assert_header_solution(
864
+ m,
865
+ expected_total_flow=1000,
866
+ expected_vent_flow=150,
867
+ expected_makeup_flow=0,
868
+ expected_liquid_flow=50,
869
+ has_heat_loss=True if pyo.value(m.fs.header.heat_loss[0]) > 0 else False,
870
+ has_pressure_loss=True if pyo.value(m.fs.header.pressure_loss[0]) > 0 else False,
871
+ )
872
+
873
+ def test_liq_header_with_inlet_equal_to_outlet_flow(liquid_header_base_case):
874
+ # Case where there is zero liquid flow and the header is balanced
875
+ m = liquid_header_base_case(num_inlets=1, num_outlets=1)
876
+ # Set user steam demand
877
+ m.fs.header.outlet_1.flow_mol.fix(1000) # mol/s
878
+ # Set cooler specifications
879
+ m.fs.header.heat_loss[0].fix(0) # W -> heat loss
880
+ m.fs.header.pressure_loss[0].fix(0) # Pa -> pressure loss
881
+
882
+ assert_header_solution(
883
+ m,
884
+ expected_total_flow=1000,
885
+ expected_vent_flow=0,
886
+ expected_makeup_flow=0,
887
+ expected_liquid_flow=0,
888
+ has_heat_loss=True if pyo.value(m.fs.header.heat_loss[0]) > 0 else False,
889
+ has_pressure_loss=True if pyo.value(m.fs.header.pressure_loss[0]) > 0 else False,
890
+ )
891
+
892
+ def test_liq_header_with_inlet_less_than_outlet_flow(liquid_header_base_case):
893
+ # Case where there is zero liquid flow and the header is balanced
894
+ m = liquid_header_base_case(num_inlets=1, num_outlets=1)
895
+ # Set user steam demand
896
+ m.fs.header.outlet_1.flow_mol.fix(1200) # mol/s
897
+ # Set cooler specifications
898
+ m.fs.header.heat_loss[0].fix(0) # W -> heat loss
899
+ m.fs.header.pressure_loss[0].fix(0) # Pa -> pressure loss
900
+
901
+ assert_header_solution(
902
+ m,
903
+ expected_total_flow=1200,
904
+ expected_vent_flow=0,
905
+ expected_makeup_flow=200,
906
+ expected_liquid_flow=0,
907
+ has_heat_loss=True if pyo.value(m.fs.header.heat_loss[0]) > 0 else False,
908
+ has_pressure_loss=True if pyo.value(m.fs.header.pressure_loss[0]) > 0 else False,
909
+ )
910
+
911
+ def test_liq_header_with_zero_flow(liquid_header_base_case):
912
+ # Case where there is zero liquid flow and the header is balanced
913
+ m = liquid_header_base_case(num_inlets=1, num_outlets=1)
914
+ m.fs.header.inlet_1.flow_mol.fix(0) # mol/s
915
+ # Set user steam demand
916
+ m.fs.header.outlet_1.flow_mol.fix(0) # mol/s
917
+ # Set cooler specifications
918
+ m.fs.header.heat_loss[0].fix(0) # W -> heat loss
919
+ m.fs.header.pressure_loss[0].fix(0) # Pa -> pressure loss
920
+
921
+ assert_header_solution(
922
+ m,
923
+ expected_total_flow=0,
924
+ expected_vent_flow=0,
925
+ expected_makeup_flow=0,
926
+ expected_liquid_flow=0,
927
+ has_heat_loss=True if pyo.value(m.fs.header.heat_loss[0]) > 0 else False,
928
+ has_pressure_loss=True if pyo.value(m.fs.header.pressure_loss[0]) > 0 else False,
929
+ )
930
+
931
+ def test_liq_header_with_inlet_greater_than_three_defined_outlet_flows(liquid_header_base_case):
932
+ # Case where there is zero liquid flow and there is more known inlet than outlet flows
933
+ m = liquid_header_base_case(num_inlets=1, num_outlets=3)
934
+
935
+ # Set user steam demand
936
+ m.fs.header.outlet_1.flow_mol.fix(500) # mol/s
937
+ m.fs.header.outlet_2.flow_mol.fix(150) # mol/s
938
+ m.fs.header.outlet_3.flow_mol.fix(200) # mol/s
939
+
940
+ # Set cooler specifications
941
+ m.fs.header.heat_loss[0].fix(10000) # W -> heat loss
942
+ m.fs.header.pressure_loss[0].fix(1000) # Pa -> pressure loss
943
+
944
+ assert_header_solution(
945
+ m,
946
+ expected_total_flow=1000,
947
+ expected_vent_flow=0,
948
+ expected_makeup_flow=0,
949
+ expected_liquid_flow=150,
950
+ has_heat_loss=True if pyo.value(m.fs.header.heat_loss[0]) > 0 else False,
951
+ has_pressure_loss=True if pyo.value(m.fs.header.pressure_loss[0]) > 0 else False,
952
+ )
953
+
954
+ def test_liq_header_with_inlet_less_than_two_defined_outlet_flows(liquid_header_base_case):
955
+ # Case where there is zero liquid flow and the header is balanced
956
+ m = liquid_header_base_case(num_inlets=1, num_outlets=2)
957
+ # Set user steam demand
958
+ m.fs.header.outlet_1.flow_mol.fix(600) # mol/s
959
+ m.fs.header.outlet_2.flow_mol.fix(600) # mol/s
960
+ # Set cooler specifications
961
+ m.fs.header.heat_loss[0].fix(0) # W -> heat loss
962
+ m.fs.header.pressure_loss[0].fix(0) # Pa -> pressure loss
963
+
964
+ assert_header_solution(
965
+ m,
966
+ expected_total_flow=1200,
967
+ expected_vent_flow=0,
968
+ expected_makeup_flow=200,
969
+ expected_liquid_flow=0,
970
+ has_heat_loss=True if pyo.value(m.fs.header.heat_loss[0]) > 0 else False,
971
+ has_pressure_loss=True if pyo.value(m.fs.header.pressure_loss[0]) > 0 else False,
972
+ )
973
+
974
+ def test_header_with_two_defined_inlet_less_than_two_defined_outlet_flows(liquid_header_base_case):
975
+ # Case where there is zero liquid flow and the header is balanced
976
+ m = liquid_header_base_case(num_inlets=2, num_outlets=2)
977
+ m.fs.header.inlet_2.flow_mol.fix(100) # mol/s
978
+ m.fs.header.inlet_2.pressure.fix(221325) # Pa
979
+ m.fs.header.inlet_2.enth_mol.fix(m.fs.water.htpx((90 + 273.15) * pyo.units.K, m.fs.header.inlet_1.pressure[0])) # J/mol
980
+
981
+ # Set user steam demand
982
+ m.fs.header.outlet_1.flow_mol.fix(600) # mol/s
983
+ m.fs.header.outlet_2.flow_mol.fix(600) # mol/s
984
+ # Set cooler specifications
985
+ m.fs.header.heat_loss[0].fix(0) # W -> heat loss
986
+ m.fs.header.pressure_loss[0].fix(0) # Pa -> pressure loss
987
+
988
+ assert_header_solution(
989
+ m,
990
+ expected_total_flow=1200,
991
+ expected_vent_flow=0,
992
+ expected_makeup_flow=100,
993
+ expected_liquid_flow=0,
994
+ has_heat_loss=True if pyo.value(m.fs.header.heat_loss[0]) > 0 else False,
995
+ has_pressure_loss=True if pyo.value(m.fs.header.pressure_loss[0]) > 0 else False,
996
+ )
997
+
998
+