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,703 @@
1
+ from ahuora_builder.custom.thermal_utility_systems.steam_header import SteamHeader
2
+ from ahuora_builder.custom.custom_heater import DynamicHeater
3
+ import pytest
4
+ import pyomo.environ as pyo
5
+ from idaes.core import FlowsheetBlock
6
+ from idaes.core.util.model_statistics import degrees_of_freedom
7
+ from property_packages.build_package import build_package
8
+ from pyomo.network import Arc, SequentialDecomposition
9
+ import idaes.logger as idaeslog
10
+
11
+
12
+
13
+ @pytest.fixture
14
+ def steam_header_base_case():
15
+ def _make_case(num_inlets=2, num_outlets=3):
16
+ # This defines the base case for all tests
17
+ m = pyo.ConcreteModel()
18
+ m.fs = FlowsheetBlock(dynamic=False)
19
+ m.fs.water = build_package("helmholtz", ["water"], ["Liq", "Vap"])
20
+ # Create CustomHeader with 2 inlets and 3 outlets
21
+ m.fs.header = SteamHeader(property_package=m.fs.water, num_inlets=num_inlets, num_outlets=num_outlets)
22
+
23
+ # Set inlet conditions for inlet_1
24
+ m.fs.header.mixer.inlet_1.flow_mol.fix(1000) # mol/s
25
+ m.fs.header.mixer.inlet_1.pressure.fix(201325) # Pa
26
+ m.fs.header.mixer.inlet_1.enth_mol.fix(m.fs.water.htpx((300 + 273.15) * pyo.units.K, m.fs.header.mixer.inlet_1.pressure[0])) # J/mol
27
+
28
+ # Initialise outlet flow for the let-down line (i.e., outlet_2 in this case)
29
+ vent = m.fs.header.splitter.vent
30
+ vent.flow_mol.fix(0)
31
+ vent.flow_mol.unfix()
32
+ return m
33
+ return _make_case
34
+
35
+ def assert_header_solution(
36
+ m,
37
+ expected_total_flow,
38
+ expected_vent_flow,
39
+ expected_makeup_flow,
40
+ expected_liquid_flow,
41
+ has_heat_loss,
42
+ has_pressure_loss,
43
+ ):
44
+ eps_rel = 1e-4
45
+ eps_abs = 1e-4
46
+ # Check degrees of freedom
47
+ assert degrees_of_freedom(m) == 0
48
+
49
+ # Initialize the model
50
+ m.fs.header.initialize()
51
+
52
+ # Solve the model
53
+ opt = pyo.SolverFactory("ipopt")
54
+ results = opt.solve(m, tee=False)
55
+
56
+ # Check that solve was successful
57
+ assert results.solver.termination_condition == pyo.TerminationCondition.optimal
58
+
59
+ # Verify mass balance: total inlet flow = total vapor outlet flow + liquid outlet flow
60
+ total_inlet_flow = sum([pyo.value(inlet_sb[0].flow_mol) for inlet_sb in m.fs.header.inlet_blocks]) + pyo.value(m.fs.header.makeup_flow_mol[0])
61
+ total_outlet_flow = sum([pyo.value(outlet_sb[0].flow_mol) for outlet_sb in m.fs.header.outlet_blocks])
62
+ condensate_flow = pyo.value(m.fs.header.phase_separator.condensate_outlet.flow_mol[0])
63
+ assert expected_total_flow == pytest.approx(total_outlet_flow + condensate_flow, rel=eps_rel, abs=eps_abs)
64
+ assert expected_total_flow == pytest.approx(total_inlet_flow, rel=eps_rel, abs=eps_abs)
65
+
66
+ assert expected_liquid_flow == pytest.approx(condensate_flow, rel=eps_rel, abs=eps_abs)
67
+ assert expected_vent_flow == pytest.approx(pyo.value(m.fs.header.splitter.vent.flow_mol[0]), rel=eps_rel, abs=0.02)
68
+ assert expected_makeup_flow == pytest.approx(pyo.value(m.fs.header.makeup_flow_mol[0]), rel=eps_rel, abs=0.02)
69
+
70
+ # Verify pressure drop is applied correctly
71
+ if pyo.value(m.fs.header.mixer.inlet_1.pressure[0]) > eps_rel:
72
+ expected_min_inlet_pressure = pyo.value(m.fs.header.mixer.inlet_1.pressure[0])
73
+ else:
74
+ expected_min_inlet_pressure = pyo.value(m.fs.header.mixer.inlet_1.pressure[0])
75
+ expected_outlet_pressure = expected_min_inlet_pressure + pyo.value(m.fs.header.deltaP[0]) # Accounts for pressure drop
76
+
77
+ assert pyo.value(m.fs.header.splitter.outlet_1.pressure[0]) == pytest.approx(expected_outlet_pressure, rel=eps_rel, abs=eps_abs)
78
+ assert pyo.value(m.fs.header.splitter.vent.pressure[0]) == pytest.approx(expected_outlet_pressure, rel=eps_rel, abs=eps_abs)
79
+
80
+ assert pyo.value(m.fs.header.splitter.outlet_1.pressure[0]) == pytest.approx(pyo.value(m.fs.header.phase_separator.condensate_outlet.pressure[0]), rel=eps_rel, abs=eps_abs)
81
+ assert pyo.value(m.fs.header.splitter.vent.pressure[0]) == pytest.approx(pyo.value(m.fs.header.phase_separator.condensate_outlet.pressure[0]), rel=eps_rel, abs=eps_abs)
82
+
83
+ cooler_inlet_pressure = pyo.value(m.fs.header.mixer.mixed_state[0].pressure)
84
+ cooler_outlet_pressure = pyo.value(m.fs.header.pressure[0])
85
+ cooler_deltaP = -pyo.value(m.fs.header.deltaP[0])
86
+ if total_outlet_flow > eps_abs:
87
+ assert cooler_outlet_pressure == pytest.approx(cooler_inlet_pressure - cooler_deltaP, rel=eps_rel, abs=eps_abs)
88
+
89
+ # Verify molar enthalpy relationships
90
+ assert pyo.value(m.fs.header.splitter.outlet_1.enth_mol[0]) == pytest.approx(pyo.value(m.fs.header.splitter.vent.enth_mol[0]), rel=eps_rel, abs=eps_abs)
91
+
92
+ hV = pyo.value(m.fs.header.phase_separator.vapor_outlet.enth_mol[0]) # J/mol
93
+ hL = pyo.value(m.fs.header.phase_separator.condensate_outlet.enth_mol[0]) # J/mol
94
+ if pyo.value(m.fs.header.phase_separator.condensate_outlet.flow_mol[0]) > eps_abs:
95
+ assert hV >= hL
96
+
97
+ # Verify cooling effect
98
+ cooler_inlet_temperature = pyo.value(m.fs.header.mixer.mixed_state[0].temperature)
99
+ cooler_outlet_temperature = pyo.value(m.fs.header.temperature[0])
100
+ if total_outlet_flow > eps_abs:
101
+ if has_heat_loss:
102
+ assert cooler_outlet_temperature < cooler_inlet_temperature
103
+ else:
104
+ assert cooler_outlet_temperature == pytest.approx(cooler_inlet_temperature, rel=eps_rel, abs=eps_abs)
105
+
106
+ cooler_inlet_total_energy = pyo.value(m.fs.header.mixer.mixed_state[0].enth_mol * m.fs.header.mixer.mixed_state[0].flow_mol)
107
+ cooler_outlet_total_energy = pyo.value(m.fs.header.enth_mol[0] * m.fs.header.total_flow_mol[0])
108
+ cooler_duty = -pyo.value(m.fs.header.heat_duty[0])
109
+ assert cooler_outlet_total_energy == pytest.approx(cooler_inlet_total_energy - cooler_duty, rel=eps_rel, abs=eps_abs)
110
+
111
+ # Test that the unit has the expected structure
112
+ assert hasattr(m.fs.header, 'mixer')
113
+ assert hasattr(m.fs.header, 'cooler')
114
+ assert hasattr(m.fs.header, 'phase_separator')
115
+ assert hasattr(m.fs.header, 'splitter')
116
+ assert hasattr(m.fs.header.phase_separator, 'condensate_outlet')
117
+
118
+ # Test that ports exist
119
+ assert hasattr(m.fs.header.mixer, 'inlet_1')
120
+ assert hasattr(m.fs.header.splitter, 'outlet_1')
121
+ assert hasattr(m.fs.header.splitter, 'vent')
122
+
123
+ inlet_mass_list = [pyo.value(i[0].flow_mass) for i in m.fs.header.inlet_blocks] + [ pyo.value(m.fs.header.makeup_flow_mol[0] * 0.01801528)] # convert mol/s to kg/s
124
+ outlet_mass_list = [pyo.value(i[0].flow_mass) for i in m.fs.header.outlet_blocks]
125
+ condensate_mass = pyo.value(m.fs.header.phase_separator.condensate_outlet_state[0].flow_mass)
126
+ # Test all mass flows are non-zero and sum of inlet and outlet masses is approx same
127
+ assert sum(inlet_mass_list) == pytest.approx(sum(outlet_mass_list) + condensate_mass, rel=eps_rel, abs=eps_abs)
128
+
129
+ min_inlet_pressure = min([pyo.value(i[0].pressure) for i in m.fs.header.inlet_blocks])
130
+ min_outlet_pressure = min([pyo.value(i[0].pressure) for i in m.fs.header.outlet_blocks])
131
+ min_outlet_pressure = min(pyo.value(m.fs.header.phase_separator.condensate_outlet.pressure[0]), min_outlet_pressure)
132
+
133
+ # Test minimum input pressure is >= outlet pressure and all pressure are non-zero
134
+ assert min_inlet_pressure >= min_outlet_pressure - eps_abs
135
+ assert min_inlet_pressure > eps_abs
136
+ assert min_outlet_pressure > eps_abs
137
+
138
+ def test_header_with_inlet_greater_than_outlet_flow(steam_header_base_case):
139
+ # Case where there is zero liquid flow and there is more known inlet than outlet flows
140
+ m = steam_header_base_case(num_inlets=1, num_outlets=1)
141
+
142
+ # Set user steam demand
143
+ m.fs.header.splitter.outlet_1.flow_mol.fix(800) # mol/s
144
+ # Set cooler specifications
145
+ m.fs.header.heat_duty[0].fix(-1000) # W -> heat loss
146
+ m.fs.header.deltaP[0].fix(-1000) # Pa -> pressure loss
147
+
148
+ assert_header_solution(
149
+ m,
150
+ expected_total_flow=1000,
151
+ expected_vent_flow=200,
152
+ expected_makeup_flow=0,
153
+ expected_liquid_flow=0,
154
+ has_heat_loss=True if pyo.value(m.fs.header.heat_duty[0]) < 0 else False,
155
+ has_pressure_loss=True if pyo.value(m.fs.header.deltaP[0]) < 0 else False,
156
+ )
157
+
158
+ def test_header_with_condensate_in_inlet_flow(steam_header_base_case):
159
+ # Case where there is zero liquid flow and there is more known inlet than outlet flows
160
+ m = steam_header_base_case(num_inlets=1, num_outlets=1)
161
+
162
+ h_val = pyo.value(m.fs.water.htpx(p=201325 * pyo.units.Pa, x=0.9))
163
+ m.fs.header.mixer.inlet_1.enth_mol.fix(h_val) # J/mol
164
+
165
+ # Set user steam demand
166
+ m.fs.header.splitter.outlet_1.flow_mol.fix(800) # mol/s
167
+ # Set cooler specifications
168
+ m.fs.header.heat_duty[0].fix(0) # W -> heat loss
169
+ m.fs.header.deltaP[0].fix(0) # Pa -> pressure loss
170
+
171
+ assert_header_solution(
172
+ m,
173
+ expected_total_flow=1000,
174
+ expected_vent_flow=100,
175
+ expected_makeup_flow=0,
176
+ expected_liquid_flow=100,
177
+ has_heat_loss=True if pyo.value(m.fs.header.heat_duty[0]) < 0 else False,
178
+ has_pressure_loss=True if pyo.value(m.fs.header.deltaP[0]) < 0 else False,
179
+ )
180
+
181
+ def test_header_with_inlet_equal_to_outlet_flow(steam_header_base_case):
182
+ # Case where there is zero liquid flow and the header is balanced
183
+ m = steam_header_base_case(num_inlets=1, num_outlets=1)
184
+ # Set user steam demand
185
+ m.fs.header.splitter.outlet_1.flow_mol.fix(1000) # mol/s
186
+ # Set cooler specifications
187
+ m.fs.header.heat_duty[0].fix(0) # W -> heat loss
188
+ m.fs.header.deltaP[0].fix(0) # Pa -> pressure loss
189
+
190
+ assert_header_solution(
191
+ m,
192
+ expected_total_flow=1000,
193
+ expected_vent_flow=0,
194
+ expected_makeup_flow=0,
195
+ expected_liquid_flow=0,
196
+ has_heat_loss=True if pyo.value(m.fs.header.heat_duty[0]) < 0 else False,
197
+ has_pressure_loss=True if pyo.value(m.fs.header.deltaP[0]) < 0 else False,
198
+ )
199
+
200
+ def test_header_with_inlet_less_than_outlet_flow(steam_header_base_case):
201
+ # Case where there is zero liquid flow and the header is balanced
202
+ m = steam_header_base_case(num_inlets=1, num_outlets=1)
203
+ # Set user steam demand
204
+ m.fs.header.splitter.outlet_1.flow_mol.fix(1200) # mol/s
205
+ # Set cooler specifications
206
+ m.fs.header.heat_duty[0].fix(0) # W -> heat loss
207
+ m.fs.header.deltaP[0].fix(0) # Pa -> pressure loss
208
+
209
+ assert_header_solution(
210
+ m,
211
+ expected_total_flow=1200,
212
+ expected_vent_flow=0,
213
+ expected_makeup_flow=200,
214
+ expected_liquid_flow=0,
215
+ has_heat_loss=True if pyo.value(m.fs.header.heat_duty[0]) < 0 else False,
216
+ has_pressure_loss=True if pyo.value(m.fs.header.deltaP[0]) < 0 else False,
217
+ )
218
+
219
+ def test_header_with_zero_flow(steam_header_base_case):
220
+ # Case where there is zero liquid flow and the header is balanced
221
+ m = steam_header_base_case(num_inlets=1, num_outlets=1)
222
+ m.fs.header.mixer.inlet_1.flow_mol.fix(0) # mol/s
223
+ # Set user steam demand
224
+ m.fs.header.splitter.outlet_1.flow_mol.fix(0) # mol/s
225
+ # Set cooler specifications
226
+ m.fs.header.heat_duty[0].fix(0) # W -> heat loss
227
+ m.fs.header.deltaP[0].fix(0) # Pa -> pressure loss
228
+
229
+ assert_header_solution(
230
+ m,
231
+ expected_total_flow=0,
232
+ expected_vent_flow=0,
233
+ expected_makeup_flow=0,
234
+ expected_liquid_flow=0,
235
+ has_heat_loss=True if pyo.value(m.fs.header.heat_duty[0]) < 0 else False,
236
+ has_pressure_loss=True if pyo.value(m.fs.header.deltaP[0]) < 0 else False,
237
+ )
238
+
239
+ def test_header_with_inlet_greater_than_three_defined_outlet_flows(steam_header_base_case):
240
+ # Case where there is zero liquid flow and there is more known inlet than outlet flows
241
+ m = steam_header_base_case(num_inlets=1, num_outlets=3)
242
+
243
+ # Set user steam demand
244
+ m.fs.header.splitter.outlet_1.flow_mol.fix(500) # mol/s
245
+ m.fs.header.splitter.outlet_2.flow_mol.fix(100) # mol/s
246
+ m.fs.header.splitter.outlet_3.flow_mol.fix(200) # mol/s
247
+
248
+ # Set cooler specifications
249
+ m.fs.header.heat_duty[0].fix(-1000) # W -> heat loss
250
+ m.fs.header.deltaP[0].fix(-1000) # Pa -> pressure loss
251
+
252
+ assert_header_solution(
253
+ m,
254
+ expected_total_flow=1000,
255
+ expected_vent_flow=200,
256
+ expected_makeup_flow=0,
257
+ expected_liquid_flow=0,
258
+ has_heat_loss=True if pyo.value(m.fs.header.heat_duty[0]) < 0 else False,
259
+ has_pressure_loss=True if pyo.value(m.fs.header.deltaP[0]) < 0 else False,
260
+ )
261
+
262
+ def test_header_with_inlet_less_than_two_defined_outlet_flows(steam_header_base_case):
263
+ # Case where there is zero liquid flow and the header is balanced
264
+ m = steam_header_base_case(num_inlets=1, num_outlets=2)
265
+ # Set user steam demand
266
+ m.fs.header.splitter.outlet_1.flow_mol.fix(600) # mol/s
267
+ m.fs.header.splitter.outlet_2.flow_mol.fix(600) # mol/s
268
+ # Set cooler specifications
269
+ m.fs.header.heat_duty[0].fix(0) # W -> heat loss
270
+ m.fs.header.deltaP[0].fix(0) # Pa -> pressure loss
271
+
272
+ assert_header_solution(
273
+ m,
274
+ expected_total_flow=1200,
275
+ expected_vent_flow=0,
276
+ expected_makeup_flow=200,
277
+ expected_liquid_flow=0,
278
+ has_heat_loss=True if pyo.value(m.fs.header.heat_duty[0]) < 0 else False,
279
+ has_pressure_loss=True if pyo.value(m.fs.header.deltaP[0]) < 0 else False,
280
+ )
281
+
282
+ def test_header_with_two_defined_inlet_less_than_two_defined_outlet_flows(steam_header_base_case):
283
+ # Case where there is zero liquid flow and the header is balanced
284
+ m = steam_header_base_case(num_inlets=2, num_outlets=2)
285
+ m.fs.header.mixer.inlet_2.flow_mol.fix(100) # mol/s
286
+ m.fs.header.mixer.inlet_2.pressure.fix(221325) # Pa
287
+ m.fs.header.mixer.inlet_2.enth_mol.fix(m.fs.water.htpx((310 + 273.15) * pyo.units.K, m.fs.header.mixer.inlet_1.pressure[0])) # J/mol
288
+
289
+ # Set user steam demand
290
+ m.fs.header.splitter.outlet_1.flow_mol.fix(600) # mol/s
291
+ m.fs.header.splitter.outlet_2.flow_mol.fix(600) # mol/s
292
+ # Set cooler specifications
293
+ m.fs.header.heat_duty[0].fix(0) # W -> heat loss
294
+ m.fs.header.deltaP[0].fix(0) # Pa -> pressure loss
295
+
296
+ assert_header_solution(
297
+ m,
298
+ expected_total_flow=1200,
299
+ expected_vent_flow=0,
300
+ expected_makeup_flow=100,
301
+ expected_liquid_flow=0,
302
+ has_heat_loss=True if pyo.value(m.fs.header.heat_duty[0]) < 0 else False,
303
+ has_pressure_loss=True if pyo.value(m.fs.header.deltaP[0]) < 0 else False,
304
+ )
305
+
306
+ def test_sequential_decomposition(steam_header_base_case):
307
+ m = steam_header_base_case(num_inlets=1, num_outlets=1)
308
+
309
+ m.fs.heater = DynamicHeater(property_package=m.fs.water)
310
+
311
+ # Set user steam demand
312
+ m.fs.header.splitter.outlet_1.flow_mol.fix(600) # mol/s
313
+ # Set cooler specifications
314
+ m.fs.header.heat_duty[0].fix(0) # W -> heat loss
315
+ m.fs.header.deltaP[0].fix(0) # Pa -> pressure loss
316
+
317
+ m.fs.heater.inlet.flow_mol.fix(100)
318
+ m.fs.heater.inlet.enth_mol.fix(m.fs.water.htpx((300 + 273.15) * pyo.units.K, m.fs.header.mixer.inlet_1.pressure[0])) # J/mol
319
+ m.fs.heater.inlet.pressure.fix(101325)
320
+
321
+ m.fs.heater.heat_duty.fix(0)
322
+
323
+ m.fs.header.mixer.inlet_1.flow_mol.unfix()
324
+ m.fs.header.mixer.inlet_1.enth_mol.unfix()
325
+ m.fs.header.mixer.inlet_1.pressure.unfix()
326
+
327
+ m.fs.heater_to_header = Arc(
328
+ source=m.fs.heater.outlet, destination=m.fs.header.mixer.inlet_1
329
+ )
330
+
331
+ pyo.TransformationFactory("network.expand_arcs").apply_to(m)
332
+
333
+ assert degrees_of_freedom(m) == 0
334
+
335
+ # create Sequential Decomposition object
336
+ seq = SequentialDecomposition()
337
+ seq.options.select_tear_method = "heuristic"
338
+ seq.options.tear_method = "Wegstein"
339
+ seq.options.iterLim = 1
340
+
341
+ # create computation graph
342
+ G = seq.create_graph(m)
343
+ heuristic_tear_set = seq.tear_set_arcs(G, method="heuristic")
344
+ # get calculation order
345
+ order = seq.calculation_order(G)
346
+
347
+ for o in heuristic_tear_set:
348
+ print(o.name)
349
+
350
+ print("Initialization order:")
351
+ for o in order:
352
+ print(o[0].name)
353
+
354
+ # define unit initialisation function
355
+ def init_unit(unit):
356
+ unit.initialize()
357
+
358
+ # run sequential decomposition
359
+ seq.run(m, init_unit)
360
+
361
+ def test_header_with_inlet_steam_generator():
362
+ m = pyo.ConcreteModel()
363
+ m.fs = FlowsheetBlock(dynamic=False)
364
+ m.fs.water = build_package("helmholtz", ["water"], ["Liq", "Vap"])
365
+
366
+ # Add a heater before the header
367
+ m.fs.boiler = DynamicHeater(
368
+ property_package=m.fs.water,
369
+ has_pressure_change=True,
370
+ )
371
+ # Create CustomHeader with 2 inlets and 3 outlets
372
+ m.fs.header = SteamHeader(
373
+ property_package=m.fs.water,
374
+ num_inlets=1,
375
+ num_outlets=1
376
+ )
377
+ # Connect heater outlet to header inlet_1
378
+ m.boiler_to_header_arc = Arc(
379
+ source=m.fs.boiler.outlet, destination=m.fs.header.mixer.inlet_1
380
+ )
381
+ pyo.TransformationFactory("network.expand_arcs").apply_to(m)
382
+
383
+ # Set inlet conditions for inlet_1
384
+ m.fs.boiler.inlet.flow_mol.fix(1000) # mol/s
385
+ m.fs.boiler.inlet.pressure.fix(200 * 1000) # Pa
386
+ m.fs.boiler.inlet.enth_mol.fix(
387
+ m.fs.water.htpx(
388
+ (15 + 273.15) * pyo.units.K,
389
+ m.fs.boiler.inlet.pressure[0]
390
+ )
391
+ ) # J/mol
392
+ m.fs.boiler.heat_duty.fix(
393
+ 50.577 * 1000 * 1000
394
+ )
395
+ m.fs.boiler.deltaP.fix(
396
+ 0.0
397
+ )
398
+ m.fs.header.splitter.outlet_1.flow_mol.fix(500)
399
+ m.fs.header.heat_duty.fix(
400
+ 0.0
401
+ )
402
+ m.fs.header.deltaP.fix(
403
+ 0.0
404
+ )
405
+
406
+ # Initialize the heater first
407
+ seq = SequentialDecomposition()
408
+ seq.options.select_tear_method = "heuristic"
409
+ seq.options.tear_method = "Wegstein"
410
+ seq.options.iterLim = 1
411
+
412
+ # create computation graph
413
+ G = seq.create_graph(m)
414
+ heuristic_tear_set = seq.tear_set_arcs(G, method="heuristic")
415
+ # get calculation order
416
+ order = seq.calculation_order(G)
417
+
418
+ for o in heuristic_tear_set:
419
+ print(o.name)
420
+
421
+ for o in order:
422
+ print(o[0].name)
423
+
424
+ # define unit initialisation function
425
+ def init_unit(unit):
426
+ unit.initialize()
427
+
428
+ # run sequential decomposition
429
+ seq.run(m, init_unit)
430
+
431
+ dof = degrees_of_freedom(m)
432
+
433
+ opt = pyo.SolverFactory("ipopt_v2")
434
+ results = opt.solve(m, tee=False)
435
+
436
+ assert results.solver.termination_condition == pyo.TerminationCondition.optimal
437
+
438
+ assert_header_solution(
439
+ m=m,
440
+ expected_total_flow=1000,
441
+ expected_vent_flow=500,
442
+ expected_makeup_flow=0,
443
+ expected_liquid_flow=0,
444
+ has_heat_loss=0,
445
+ has_pressure_loss=0,
446
+ )
447
+
448
+ def test_header_with_inlet_steam_generator_with_insufficent_flow():
449
+ m = pyo.ConcreteModel()
450
+ m.fs = FlowsheetBlock(dynamic=False)
451
+ m.fs.water = build_package("helmholtz", ["water"], ["Liq", "Vap"])
452
+
453
+ # Add a heater before the header
454
+ m.fs.boiler = DynamicHeater(
455
+ property_package=m.fs.water,
456
+ has_pressure_change=True,
457
+ )
458
+ # Create CustomHeader with 2 inlets and 3 outlets
459
+ m.fs.header = SteamHeader(
460
+ property_package=m.fs.water,
461
+ num_inlets=1,
462
+ num_outlets=1
463
+ )
464
+ # Connect heater outlet to header inlet_1
465
+ m.boiler_to_header_arc = Arc(
466
+ source=m.fs.boiler.outlet, destination=m.fs.header.mixer.inlet_1
467
+ )
468
+ pyo.TransformationFactory("network.expand_arcs").apply_to(m)
469
+
470
+ # Set inlet conditions for inlet_1
471
+ m.fs.boiler.inlet.flow_mol.fix(1000) # mol/s
472
+ m.fs.boiler.inlet.pressure.fix(200 * 1000) # Pa
473
+ m.fs.boiler.inlet.enth_mol.fix(
474
+ m.fs.water.htpx(
475
+ (15 + 273.15) * pyo.units.K,
476
+ m.fs.boiler.inlet.pressure[0]
477
+ )
478
+ ) # J/mol
479
+ m.fs.boiler.heat_duty.fix(
480
+ 50.577 * 1000 * 1000
481
+ )
482
+ m.fs.boiler.deltaP.fix(
483
+ 0.0
484
+ )
485
+ m.fs.header.splitter.outlet_1.flow_mol.fix(1500)
486
+ m.fs.header.heat_duty.fix(
487
+ 0.0
488
+ )
489
+ m.fs.header.deltaP.fix(
490
+ 0.0
491
+ )
492
+
493
+ # Initialize the heater first
494
+ seq = SequentialDecomposition()
495
+ seq.options.select_tear_method = "heuristic"
496
+ seq.options.tear_method = "Wegstein"
497
+ seq.options.iterLim = 1
498
+
499
+ # create computation graph
500
+ G = seq.create_graph(m)
501
+ heuristic_tear_set = seq.tear_set_arcs(G, method="heuristic")
502
+ # get calculation order
503
+ order = seq.calculation_order(G)
504
+
505
+ for o in heuristic_tear_set:
506
+ print(o.name)
507
+
508
+ for o in order:
509
+ print(o[0].name)
510
+
511
+ # define unit initialisation function
512
+ def init_unit(unit):
513
+ unit.initialize()
514
+
515
+ # run sequential decomposition
516
+ seq.run(m, init_unit)
517
+
518
+ opt = pyo.SolverFactory("ipopt_v2")
519
+ results = opt.solve(m, tee=False)
520
+
521
+ assert results.solver.termination_condition == pyo.TerminationCondition.optimal
522
+
523
+ assert_header_solution(
524
+ m=m,
525
+ expected_total_flow=1500,
526
+ expected_vent_flow=0,
527
+ expected_makeup_flow=500,
528
+ expected_liquid_flow=0,
529
+ has_heat_loss=0,
530
+ has_pressure_loss=0,
531
+ )
532
+
533
+ def test_header_with_inlet_steam_generator_with_losses():
534
+ m = pyo.ConcreteModel()
535
+ m.fs = FlowsheetBlock(dynamic=False)
536
+ m.fs.water = build_package("helmholtz", ["water"], ["Liq", "Vap"])
537
+
538
+ # Add a heater before the header
539
+ m.fs.boiler = DynamicHeater(
540
+ property_package=m.fs.water,
541
+ has_pressure_change=True,
542
+ )
543
+ # Create CustomHeader with 2 inlets and 3 outlets
544
+ m.fs.header = SteamHeader(
545
+ property_package=m.fs.water,
546
+ num_inlets=1,
547
+ num_outlets=1
548
+ )
549
+ # Connect heater outlet to header inlet_1
550
+ m.boiler_to_header_arc = Arc(
551
+ source=m.fs.boiler.outlet, destination=m.fs.header.mixer.inlet_1
552
+ )
553
+ pyo.TransformationFactory("network.expand_arcs").apply_to(m)
554
+
555
+ # Set inlet conditions for inlet_1
556
+ m.fs.boiler.inlet.flow_mol.fix(1000) # mol/s
557
+ m.fs.boiler.inlet.pressure.fix(200 * 1000) # Pa
558
+ m.fs.boiler.inlet.enth_mol.fix(
559
+ m.fs.water.htpx(
560
+ (15 + 273.15) * pyo.units.K,
561
+ m.fs.boiler.inlet.pressure[0]
562
+ )
563
+ ) # J/mol
564
+ m.fs.boiler.heat_duty.fix(
565
+ 50.577 * 1000 * 1000
566
+ )
567
+ m.fs.boiler.deltaP.fix(
568
+ 0.0
569
+ )
570
+ m.fs.header.splitter.outlet_1.flow_mol.fix(500)
571
+ m.fs.header.heat_duty.fix(
572
+ -10.0
573
+ )
574
+ m.fs.header.deltaP.fix(
575
+ -5000.0
576
+ )
577
+
578
+ # Initialize the heater first
579
+ seq = SequentialDecomposition()
580
+ seq.options.select_tear_method = "heuristic"
581
+ seq.options.tear_method = "Wegstein"
582
+ seq.options.iterLim = 1
583
+
584
+ # create computation graph
585
+ G = seq.create_graph(m)
586
+ heuristic_tear_set = seq.tear_set_arcs(G, method="heuristic")
587
+ # get calculation order
588
+ order = seq.calculation_order(G)
589
+
590
+ for o in heuristic_tear_set:
591
+ print(o.name)
592
+
593
+ for o in order:
594
+ print(o[0].name)
595
+
596
+ # define unit initialisation function
597
+ def init_unit(unit):
598
+ unit.initialize()
599
+
600
+ # run sequential decomposition
601
+ seq.run(m, init_unit)
602
+
603
+ opt = pyo.SolverFactory("ipopt_v2")
604
+ results = opt.solve(m, tee=False)
605
+
606
+ assert results.solver.termination_condition == pyo.TerminationCondition.optimal
607
+
608
+ assert_header_solution(
609
+ m=m,
610
+ expected_total_flow=1000,
611
+ expected_vent_flow=500,
612
+ expected_makeup_flow=0,
613
+ expected_liquid_flow=0,
614
+ has_heat_loss=True,
615
+ has_pressure_loss=True,
616
+ )
617
+
618
+ def test_header_with_inlet_steam_generator_with_wet_steam():
619
+ m = pyo.ConcreteModel()
620
+ m.fs = FlowsheetBlock(dynamic=False)
621
+ m.fs.water = build_package("helmholtz", ["water"], ["Liq", "Vap"])
622
+
623
+ # Add a heater before the header
624
+ m.fs.boiler = DynamicHeater(
625
+ property_package=m.fs.water,
626
+ has_pressure_change=True,
627
+ )
628
+ # Create CustomHeader with 2 inlets and 3 outlets
629
+ m.fs.header = SteamHeader(
630
+ property_package=m.fs.water,
631
+ num_inlets=1,
632
+ num_outlets=1
633
+ )
634
+ # Connect heater outlet to header inlet_1
635
+ m.boiler_to_header_arc = Arc(
636
+ source=m.fs.boiler.outlet, destination=m.fs.header.mixer.inlet_1
637
+ )
638
+ pyo.TransformationFactory("network.expand_arcs").apply_to(m)
639
+
640
+ # Set inlet conditions for inlet_1
641
+ m.fs.boiler.inlet.flow_mol.fix(1000) # mol/s
642
+ m.fs.boiler.inlet.pressure.fix(200 * 1000) # Pa
643
+ m.fs.boiler.inlet.enth_mol.fix(
644
+ m.fs.water.htpx(
645
+ (15 + 273.15) * pyo.units.K,
646
+ m.fs.boiler.inlet.pressure[0]
647
+ )
648
+ ) # J/mol
649
+ m.fs.boiler.heat_duty.fix(
650
+ 27784964.63
651
+ )
652
+ m.fs.boiler.deltaP.fix(
653
+ 0.0
654
+ )
655
+ m.fs.header.splitter.outlet_1.flow_mol.fix(500)
656
+ m.fs.header.heat_duty.fix(
657
+ 0.0
658
+ )
659
+ m.fs.header.deltaP.fix(
660
+ 0.0
661
+ )
662
+
663
+ # Initialize the heater first
664
+ seq = SequentialDecomposition()
665
+ seq.options.select_tear_method = "heuristic"
666
+ seq.options.tear_method = "Wegstein"
667
+ seq.options.iterLim = 1
668
+
669
+ # create computation graph
670
+ G = seq.create_graph(m)
671
+ heuristic_tear_set = seq.tear_set_arcs(G, method="heuristic")
672
+ # get calculation order
673
+ order = seq.calculation_order(G)
674
+
675
+ for o in heuristic_tear_set:
676
+ print(o.name)
677
+
678
+ for o in order:
679
+ print(o[0].name)
680
+
681
+ # define unit initialisation function
682
+ def init_unit(unit):
683
+ unit.initialize()
684
+
685
+ # run sequential decomposition
686
+ seq.run(m, init_unit)
687
+
688
+ opt = pyo.SolverFactory("ipopt_v2")
689
+ results = opt.solve(m, tee=False)
690
+
691
+ assert results.solver.termination_condition == pyo.TerminationCondition.optimal
692
+
693
+ assert_header_solution(
694
+ m=m,
695
+ expected_total_flow=1000,
696
+ expected_vent_flow=0,
697
+ expected_makeup_flow=0.0,
698
+ expected_liquid_flow=500,
699
+ has_heat_loss=0,
700
+ has_pressure_loss=0,
701
+ )
702
+
703
+