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.
- ahuora_builder/__init__.py +0 -0
- ahuora_builder/arc_manager.py +33 -0
- ahuora_builder/build_state.py +57 -0
- ahuora_builder/custom/PIDController.py +494 -0
- ahuora_builder/custom/PySMOModel.py +178 -0
- ahuora_builder/custom/SimpleEffectivenessHX_DH.py +727 -0
- ahuora_builder/custom/__init__.py +0 -0
- ahuora_builder/custom/add_initial_dynamics.py +35 -0
- ahuora_builder/custom/custom_compressor.py +107 -0
- ahuora_builder/custom/custom_cooler.py +33 -0
- ahuora_builder/custom/custom_heat_exchanger.py +183 -0
- ahuora_builder/custom/custom_heat_exchanger_1d.py +258 -0
- ahuora_builder/custom/custom_heater.py +41 -0
- ahuora_builder/custom/custom_pressure_changer.py +34 -0
- ahuora_builder/custom/custom_pump.py +107 -0
- ahuora_builder/custom/custom_separator.py +371 -0
- ahuora_builder/custom/custom_tank.py +133 -0
- ahuora_builder/custom/custom_turbine.py +132 -0
- ahuora_builder/custom/custom_valve.py +300 -0
- ahuora_builder/custom/custom_variable.py +29 -0
- ahuora_builder/custom/direct_steam_injection.py +371 -0
- ahuora_builder/custom/energy/__init__.py +0 -0
- ahuora_builder/custom/energy/acBus.py +280 -0
- ahuora_builder/custom/energy/ac_property_package.py +279 -0
- ahuora_builder/custom/energy/battery.py +170 -0
- ahuora_builder/custom/energy/bus.py +182 -0
- ahuora_builder/custom/energy/energy_mixer.py +195 -0
- ahuora_builder/custom/energy/energy_splitter.py +228 -0
- ahuora_builder/custom/energy/grid.py +173 -0
- ahuora_builder/custom/energy/hydro.py +169 -0
- ahuora_builder/custom/energy/link.py +137 -0
- ahuora_builder/custom/energy/load.py +155 -0
- ahuora_builder/custom/energy/mainDistributionBoard.py +257 -0
- ahuora_builder/custom/energy/power_property_package.py +253 -0
- ahuora_builder/custom/energy/solar.py +176 -0
- ahuora_builder/custom/energy/storage.py +230 -0
- ahuora_builder/custom/energy/storage_wrapper +0 -0
- ahuora_builder/custom/energy/tests/__init__.py +0 -0
- ahuora_builder/custom/energy/tests/test_bus.py +44 -0
- ahuora_builder/custom/energy/tests/test_energy_mixer.py +46 -0
- ahuora_builder/custom/energy/tests/test_mdb.py +49 -0
- ahuora_builder/custom/energy/transformer.py +187 -0
- ahuora_builder/custom/energy/transformer_property_package.py +267 -0
- ahuora_builder/custom/energy/transmissionLine.py +228 -0
- ahuora_builder/custom/energy/wind.py +206 -0
- ahuora_builder/custom/hda_ideal_VLE.py +1341 -0
- ahuora_builder/custom/hda_reaction.py +182 -0
- ahuora_builder/custom/heat_exchanger_1d_wrapper.py +31 -0
- ahuora_builder/custom/integration_block.py +106 -0
- ahuora_builder/custom/inverted.py +81 -0
- ahuora_builder/custom/performance_curves.py +1 -0
- ahuora_builder/custom/reactions/__init__.py +0 -0
- ahuora_builder/custom/reactions/hda_stoich.py +10 -0
- ahuora_builder/custom/simple_separator.py +680 -0
- ahuora_builder/custom/tests/__init__.py +0 -0
- ahuora_builder/custom/tests/test_SimpleEffectivenessHX_DH.py +91 -0
- ahuora_builder/custom/tests/test_custom_tank.py +70 -0
- ahuora_builder/custom/tests/test_direct_steam_injection.py +41 -0
- ahuora_builder/custom/tests/test_simple_separator.py +46 -0
- ahuora_builder/custom/tests/test_waterpipe.py +46 -0
- ahuora_builder/custom/thermal_utility_systems/desuperheater.py +624 -0
- ahuora_builder/custom/thermal_utility_systems/header.py +889 -0
- ahuora_builder/custom/thermal_utility_systems/simple_heat_pump.py +567 -0
- ahuora_builder/custom/thermal_utility_systems/steam_header.py +353 -0
- ahuora_builder/custom/thermal_utility_systems/steam_user.py +944 -0
- ahuora_builder/custom/thermal_utility_systems/temp.py +349 -0
- ahuora_builder/custom/thermal_utility_systems/tests/test_desuperheater.py +142 -0
- ahuora_builder/custom/thermal_utility_systems/tests/test_header.py +998 -0
- ahuora_builder/custom/thermal_utility_systems/tests/test_ntu_hx.py +129 -0
- ahuora_builder/custom/thermal_utility_systems/tests/test_simple_heat_pump.py +120 -0
- ahuora_builder/custom/thermal_utility_systems/tests/test_steam_header.py +703 -0
- ahuora_builder/custom/thermal_utility_systems/tests/test_steam_user.py +277 -0
- ahuora_builder/custom/thermal_utility_systems/tests/test_waterpipe.py +36 -0
- ahuora_builder/custom/thermal_utility_systems/tests/test_willans_turbine.py +253 -0
- ahuora_builder/custom/thermal_utility_systems/willans_turbine.py +804 -0
- ahuora_builder/custom/translator.py +129 -0
- ahuora_builder/custom/updated_pressure_changer.py +1404 -0
- ahuora_builder/custom/valve_wrapper.py +38 -0
- ahuora_builder/custom/water_tank_with_units.py +456 -0
- ahuora_builder/diagnostics/__init__.py +0 -0
- ahuora_builder/diagnostics/infeasibilities.py +40 -0
- ahuora_builder/diagnostics/tests/__init__.py +0 -0
- ahuora_builder/diagnostics/tests/test_infeasibilities.py +28 -0
- ahuora_builder/flowsheet_manager.py +542 -0
- ahuora_builder/flowsheet_manager_type.py +20 -0
- ahuora_builder/generate_python_file.py +440 -0
- ahuora_builder/methods/BlockContext.py +84 -0
- ahuora_builder/methods/__init__.py +0 -0
- ahuora_builder/methods/adapter.py +355 -0
- ahuora_builder/methods/adapter_library.py +549 -0
- ahuora_builder/methods/adapter_methods.py +80 -0
- ahuora_builder/methods/expression_parsing.py +105 -0
- ahuora_builder/methods/load_unit_model.py +147 -0
- ahuora_builder/methods/slice_manipulation.py +7 -0
- ahuora_builder/methods/tests/__init__.py +0 -0
- ahuora_builder/methods/tests/test_expression_parsing.py +15 -0
- ahuora_builder/methods/units_handler.py +129 -0
- ahuora_builder/ml_wizard.py +101 -0
- ahuora_builder/port_manager.py +20 -0
- ahuora_builder/properties_manager.py +44 -0
- ahuora_builder/property_package_manager.py +78 -0
- ahuora_builder/solver.py +38 -0
- ahuora_builder/tear_manager.py +98 -0
- ahuora_builder/tests/__init__.py +0 -0
- ahuora_builder/tests/test_generate_python_file/__init__.py +0 -0
- ahuora_builder/tests/test_generate_python_file/configurations/compressor_generated.py +63 -0
- ahuora_builder/tests/test_generate_python_file/configurations/heat_exchanger_generated.py +70 -0
- ahuora_builder/tests/test_generate_python_file/configurations/pump_generated.py +84 -0
- ahuora_builder/tests/test_generate_python_file/configurations/recycle_generated.py +73 -0
- ahuora_builder/tests/test_generate_python_file/test_generate_python_file.py +108 -0
- ahuora_builder/tests/test_solver/__init__.py +0 -0
- ahuora_builder/tests/test_solver/configurations/BT_PR.json +59 -0
- ahuora_builder/tests/test_solver/configurations/BT_PR_solved.json +59 -0
- ahuora_builder/tests/test_solver/configurations/bus.json +99 -0
- ahuora_builder/tests/test_solver/configurations/bus_solved.json +50 -0
- ahuora_builder/tests/test_solver/configurations/compound_separator.json +377 -0
- ahuora_builder/tests/test_solver/configurations/compound_separator_solved.json +374 -0
- ahuora_builder/tests/test_solver/configurations/compressor.json +38 -0
- ahuora_builder/tests/test_solver/configurations/compressor_solved.json +68 -0
- ahuora_builder/tests/test_solver/configurations/constraints.json +44 -0
- ahuora_builder/tests/test_solver/configurations/constraints_solved.json +59 -0
- ahuora_builder/tests/test_solver/configurations/control.json +39 -0
- ahuora_builder/tests/test_solver/configurations/control_solved.json +68 -0
- ahuora_builder/tests/test_solver/configurations/dynamic_tank.json +733 -0
- ahuora_builder/tests/test_solver/configurations/dynamic_tank_solved.json +846 -0
- ahuora_builder/tests/test_solver/configurations/elimination.json +39 -0
- ahuora_builder/tests/test_solver/configurations/elimination_solved.json +68 -0
- ahuora_builder/tests/test_solver/configurations/expressions.json +68 -0
- ahuora_builder/tests/test_solver/configurations/expressions_solved.json +104 -0
- ahuora_builder/tests/test_solver/configurations/header.json +1192 -0
- ahuora_builder/tests/test_solver/configurations/header_solved.json +761 -0
- ahuora_builder/tests/test_solver/configurations/heat_exchanger.json +63 -0
- ahuora_builder/tests/test_solver/configurations/heat_exchanger_solved.json +104 -0
- ahuora_builder/tests/test_solver/configurations/heat_pump.json +137 -0
- ahuora_builder/tests/test_solver/configurations/heat_pump_solved.json +104 -0
- ahuora_builder/tests/test_solver/configurations/machine_learning.json +2156 -0
- ahuora_builder/tests/test_solver/configurations/machine_learning_solved.json +266 -0
- ahuora_builder/tests/test_solver/configurations/mass_flow_tear.json +77 -0
- ahuora_builder/tests/test_solver/configurations/mass_flow_tear_solved.json +68 -0
- ahuora_builder/tests/test_solver/configurations/milk_heater.json +521 -0
- ahuora_builder/tests/test_solver/configurations/milk_heater_solved.json +311 -0
- ahuora_builder/tests/test_solver/configurations/mixer.json +44 -0
- ahuora_builder/tests/test_solver/configurations/mixer_solved.json +86 -0
- ahuora_builder/tests/test_solver/configurations/optimization.json +62 -0
- ahuora_builder/tests/test_solver/configurations/optimization_solved.json +59 -0
- ahuora_builder/tests/test_solver/configurations/propane_heat_pump.json +167 -0
- ahuora_builder/tests/test_solver/configurations/propane_heat_pump_solved.json +158 -0
- ahuora_builder/tests/test_solver/configurations/propane_recycle.json +141 -0
- ahuora_builder/tests/test_solver/configurations/propane_recycle_solved.json +104 -0
- ahuora_builder/tests/test_solver/configurations/pump.json +64 -0
- ahuora_builder/tests/test_solver/configurations/pump_solved.json +59 -0
- ahuora_builder/tests/test_solver/configurations/pump_unit_conversions.json +63 -0
- ahuora_builder/tests/test_solver/configurations/recycle.json +49 -0
- ahuora_builder/tests/test_solver/configurations/recycle_solved.json +50 -0
- ahuora_builder/tests/test_solver/configurations/sb_vapor_frac.json +29 -0
- ahuora_builder/tests/test_solver/configurations/sb_vapor_frac_solved.json +29 -0
- ahuora_builder/tests/test_solver/configurations/solar.json +67 -0
- ahuora_builder/tests/test_solver/configurations/solar_solved.json +50 -0
- ahuora_builder/tests/test_solver/configurations/vapor_frac_target.json +67 -0
- ahuora_builder/tests/test_solver/configurations/vapor_frac_target_solved.json +68 -0
- ahuora_builder/tests/test_solver/test_solve_models.py +250 -0
- ahuora_builder/timing.py +65 -0
- ahuora_builder/types/__init__.py +1 -0
- ahuora_builder/unit_model_manager.py +48 -0
- ahuora_builder-0.1.0.dist-info/METADATA +14 -0
- ahuora_builder-0.1.0.dist-info/RECORD +167 -0
- ahuora_builder-0.1.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,944 @@
|
|
|
1
|
+
# Pyomo core
|
|
2
|
+
from pyomo.environ import (
|
|
3
|
+
Constraint,
|
|
4
|
+
Expression,
|
|
5
|
+
NonNegativeReals,
|
|
6
|
+
Suffix,
|
|
7
|
+
Var,
|
|
8
|
+
value,
|
|
9
|
+
units as UNIT,
|
|
10
|
+
)
|
|
11
|
+
from pyomo.core.base.reference import Reference
|
|
12
|
+
from pyomo.common.config import ConfigBlock, ConfigValue, Bool
|
|
13
|
+
|
|
14
|
+
# IDAES core
|
|
15
|
+
from idaes.core import (
|
|
16
|
+
declare_process_block_class,
|
|
17
|
+
UnitModelBlockData,
|
|
18
|
+
useDefault,
|
|
19
|
+
StateBlock,
|
|
20
|
+
)
|
|
21
|
+
from idaes.core.util import scaling
|
|
22
|
+
from idaes.core.util.config import is_physical_parameter_block
|
|
23
|
+
from idaes.core.util.tables import create_stream_table_dataframe
|
|
24
|
+
from idaes.core.solvers import get_solver
|
|
25
|
+
from idaes.core.initialization import ModularInitializerBase
|
|
26
|
+
|
|
27
|
+
# Other
|
|
28
|
+
from property_packages.build_package import build_package
|
|
29
|
+
|
|
30
|
+
# Logger
|
|
31
|
+
import idaes.logger as idaeslog
|
|
32
|
+
|
|
33
|
+
# Typing
|
|
34
|
+
from typing import List
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
__author__ = "Ahuora Centre for Smart Energy Systems, University of Waikato, New Zealand"
|
|
38
|
+
|
|
39
|
+
# Set up logger
|
|
40
|
+
_log = idaeslog.getLogger(__name__)
|
|
41
|
+
|
|
42
|
+
class SteamUserInitializer(ModularInitializerBase):
|
|
43
|
+
"""Initializer for ``SteamUser``.
|
|
44
|
+
|
|
45
|
+
Parameters
|
|
46
|
+
----------
|
|
47
|
+
blk : SteamUser
|
|
48
|
+
The unit model block to initialize.
|
|
49
|
+
solver : optional
|
|
50
|
+
A Pyomo/IDAES solver instance. Defaults to :func:`idaes.core.solvers.get_solver`.
|
|
51
|
+
solver_options : dict, optional
|
|
52
|
+
Options to set on the solver, e.g. tolerances.
|
|
53
|
+
outlvl : int, optional
|
|
54
|
+
IDAES log level (e.g. :data:`idaes.logger.WARNING`).
|
|
55
|
+
|
|
56
|
+
Returns
|
|
57
|
+
-------
|
|
58
|
+
pyomo.opt.results.results_.SolverResults
|
|
59
|
+
Result from the final solve.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
def initialize(self, blk, **kwargs):
|
|
63
|
+
# --- Solver setup
|
|
64
|
+
solver = kwargs.get("solver", None) or get_solver()
|
|
65
|
+
solver_options = kwargs.get("solver_options", {})
|
|
66
|
+
for k, v in solver_options.items():
|
|
67
|
+
solver.options[k] = v
|
|
68
|
+
|
|
69
|
+
outlvl = kwargs.get("outlvl", idaeslog.WARNING)
|
|
70
|
+
log = idaeslog.getLogger(__name__)
|
|
71
|
+
|
|
72
|
+
# --- Time index
|
|
73
|
+
t0 = blk.flowsheet().time.first()
|
|
74
|
+
|
|
75
|
+
# --- 1) Initialize inlet state blocks
|
|
76
|
+
inlet_blocks = list(blk.inlet_blocks)
|
|
77
|
+
if blk.config.has_desuperheating:
|
|
78
|
+
inlet_steam, inlet_water = inlet_blocks
|
|
79
|
+
inlet_water[t0].pressure.set_value(
|
|
80
|
+
inlet_steam[t0].pressure
|
|
81
|
+
)
|
|
82
|
+
inlet_water[t0].enth_mol.set_value(
|
|
83
|
+
blk.water.htpx(
|
|
84
|
+
blk.bfw_temperature[t0],
|
|
85
|
+
inlet_water[t0].pressure,
|
|
86
|
+
)
|
|
87
|
+
)
|
|
88
|
+
else:
|
|
89
|
+
inlet_steam = inlet_blocks[0]
|
|
90
|
+
|
|
91
|
+
for sb in inlet_blocks:
|
|
92
|
+
if hasattr(sb, "initialize"):
|
|
93
|
+
sb.initialize(outlvl=outlvl)
|
|
94
|
+
|
|
95
|
+
# --- 2) Seed satuarate inlet-related state
|
|
96
|
+
if blk.config.has_desuperheating:
|
|
97
|
+
sb = blk._int_inlet_sat_vap_state
|
|
98
|
+
sb[t0].pressure.set_value(
|
|
99
|
+
inlet_steam[t0].pressure
|
|
100
|
+
)
|
|
101
|
+
sb[t0].enth_mol.set_value(
|
|
102
|
+
inlet_steam[t0].enth_mol_sat_phase["Vap"]
|
|
103
|
+
)
|
|
104
|
+
if hasattr(sb, "initialize"):
|
|
105
|
+
sb.initialize(outlvl=outlvl)
|
|
106
|
+
|
|
107
|
+
# --- 3) Aggregate inlet info for seeding mixed state block
|
|
108
|
+
ms = blk._int_mixed_inlet_state
|
|
109
|
+
ms[t0].pressure.set_value(
|
|
110
|
+
inlet_steam[t0].pressure
|
|
111
|
+
)
|
|
112
|
+
if blk.config.has_desuperheating:
|
|
113
|
+
if value(blk.deltaT_superheat[t0]) > 0:
|
|
114
|
+
ms[t0].enth_mol.set_value(
|
|
115
|
+
blk.water.htpx(
|
|
116
|
+
T=blk._int_inlet_sat_vap_state[t0].temperature + blk.deltaT_superheat[t0],
|
|
117
|
+
p=inlet_steam[t0].pressure,
|
|
118
|
+
)
|
|
119
|
+
)
|
|
120
|
+
else:
|
|
121
|
+
ms[t0].enth_mol.set_value(
|
|
122
|
+
blk._int_inlet_sat_vap_state[t0].enth_mol
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
if abs(value(ms[t0].enth_mol - inlet_water[t0].enth_mol)) > 0:
|
|
126
|
+
ms[t0].flow_mol.set_value(
|
|
127
|
+
inlet_steam[t0].flow_mol * (inlet_steam[t0].enth_mol - ms[t0].enth_mol) / (ms[t0].enth_mol - inlet_water[t0].enth_mol)
|
|
128
|
+
)
|
|
129
|
+
else:
|
|
130
|
+
ms[t0].flow_mol.set_value(
|
|
131
|
+
inlet_steam[t0].flow_mol
|
|
132
|
+
)
|
|
133
|
+
else:
|
|
134
|
+
ms[t0].enth_mol.set_value(
|
|
135
|
+
inlet_steam[t0].enth_mol
|
|
136
|
+
)
|
|
137
|
+
ms[t0].flow_mol.set_value(
|
|
138
|
+
inlet_steam[t0].flow_mol
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
if hasattr(ms, "initialize"):
|
|
142
|
+
ms.initialize(outlvl=outlvl)
|
|
143
|
+
|
|
144
|
+
# --- 4) Seed outlet-related states
|
|
145
|
+
# Condensate after heating (internal)
|
|
146
|
+
ios = blk._int_outlet_cond_state
|
|
147
|
+
ios[t0].flow_mol.set_value(
|
|
148
|
+
ms[t0].flow_mol
|
|
149
|
+
)
|
|
150
|
+
ios[t0].pressure.set_value(
|
|
151
|
+
ms[t0].pressure - blk.pressure_loss[t0]
|
|
152
|
+
)
|
|
153
|
+
ios[t0].enth_mol.set_value(
|
|
154
|
+
blk.water.htpx(
|
|
155
|
+
T=blk._int_outlet_cond_state[t0].temperature_sat - blk.deltaT_subcool[t0],
|
|
156
|
+
p=ios[t0].pressure,
|
|
157
|
+
)
|
|
158
|
+
)
|
|
159
|
+
if hasattr(ios, "initialize"):
|
|
160
|
+
ios.initialize(outlvl=outlvl)
|
|
161
|
+
|
|
162
|
+
# --- 5) Seed satuarate outlet-related state
|
|
163
|
+
sb = blk._int_outlet_sat_liq_state
|
|
164
|
+
sb[t0].pressure.set_value(
|
|
165
|
+
ios[t0].pressure
|
|
166
|
+
)
|
|
167
|
+
sb[t0].enth_mol.set_value(
|
|
168
|
+
ios[t0].enth_mol_sat_phase["Vap"]
|
|
169
|
+
)
|
|
170
|
+
if hasattr(sb, "initialize"):
|
|
171
|
+
sb.initialize(outlvl=outlvl)
|
|
172
|
+
|
|
173
|
+
# --- 5) Seed external outlets
|
|
174
|
+
# Return line properties at user-specified temperature
|
|
175
|
+
ors = blk.outlet_return_state
|
|
176
|
+
ors[t0].flow_mol.set_value(ms[t0].flow_mol * value(blk.cond_return_rate[t0]))
|
|
177
|
+
ors[t0].pressure.set_value(
|
|
178
|
+
ms[t0].pressure - blk.pressure_loss[t0]
|
|
179
|
+
)
|
|
180
|
+
ors[t0].enth_mol.set_value(
|
|
181
|
+
blk.water.htpx(
|
|
182
|
+
T=blk.cond_return_temperature[t0],
|
|
183
|
+
p=ors[t0].pressure,
|
|
184
|
+
)
|
|
185
|
+
)
|
|
186
|
+
if hasattr(ors, "initialize"):
|
|
187
|
+
ors.initialize(outlvl=outlvl)
|
|
188
|
+
|
|
189
|
+
# --- 6) Drain/blowdown outlet uses reference enthalpy (fixed later in build)
|
|
190
|
+
# Flow/pressure will be solved by constraints
|
|
191
|
+
if hasattr(blk.outlet_drain_state, "initialize"):
|
|
192
|
+
blk.outlet_drain_state.initialize(outlvl=outlvl)
|
|
193
|
+
|
|
194
|
+
# --- 7) Solve
|
|
195
|
+
res = solver.solve(blk, tee=False)
|
|
196
|
+
log.info(f"SteamUser init status: {res.solver.termination_condition}")
|
|
197
|
+
return res
|
|
198
|
+
|
|
199
|
+
def _make_config_block(config):
|
|
200
|
+
"""Declare configuration options for the SteamUser unit.
|
|
201
|
+
|
|
202
|
+
Declares property package references and integer counts for inlets and outlets.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
config (ConfigBlock): The mutable configuration block to populate.
|
|
206
|
+
"""
|
|
207
|
+
|
|
208
|
+
config.declare(
|
|
209
|
+
"property_package",
|
|
210
|
+
ConfigValue(
|
|
211
|
+
default=useDefault,
|
|
212
|
+
domain=is_physical_parameter_block,
|
|
213
|
+
description="Property package to use for control volume",
|
|
214
|
+
),
|
|
215
|
+
)
|
|
216
|
+
config.declare(
|
|
217
|
+
"property_package_args",
|
|
218
|
+
ConfigBlock(
|
|
219
|
+
implicit=True,
|
|
220
|
+
description="Arguments to use for constructing property packages",
|
|
221
|
+
),
|
|
222
|
+
)
|
|
223
|
+
config.declare(
|
|
224
|
+
"has_desuperheating",
|
|
225
|
+
ConfigValue(
|
|
226
|
+
default=False,
|
|
227
|
+
domain=Bool,
|
|
228
|
+
description="If true, include desuperheating prior to use as process heat. " \
|
|
229
|
+
"Adds the state variable of the degree of superheat after desuperheating. Default: 0.",
|
|
230
|
+
),
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
@declare_process_block_class("SteamUser")
|
|
234
|
+
class SteamUserData(UnitModelBlockData):
|
|
235
|
+
"""Steam user unit operation.
|
|
236
|
+
|
|
237
|
+
The SteamUser aggregates thermal loads from multiple sub-users (heaters) within a site.
|
|
238
|
+
Desuperheating the flow is optional. Heat loss and pressure loss may be defined.
|
|
239
|
+
A mixed (intermediate) states are used for balances.
|
|
240
|
+
|
|
241
|
+
Key features:
|
|
242
|
+
- Material, energy, and momentum balances around the user
|
|
243
|
+
- Optional desuperheating step prior to process use
|
|
244
|
+
- User-specified condensate return rate and temperature
|
|
245
|
+
- Optional heat and pressure losses.
|
|
246
|
+
|
|
247
|
+
Attributes:
|
|
248
|
+
inlet_list (list[str]): Names for inlet ports.
|
|
249
|
+
outlet_list (list[str]): Names for outlet ports (incl. condensate/ and vent).
|
|
250
|
+
inlet_blocks (list): StateBlocks for all inlets.
|
|
251
|
+
outlet_blocks (list): StateBlocks for all outlets.
|
|
252
|
+
_int_mixed_inlet_state: Intermediate mixture StateBlock.
|
|
253
|
+
|
|
254
|
+
heat_loss (Var): Heat loss from the header (W).
|
|
255
|
+
pressure_loss (Var): Pressure drop from inlet minimum to mixed state (Pa).
|
|
256
|
+
bfw_flow_mol (Var): Required inlet boiler feed water flow for desuperheating (mol/s).
|
|
257
|
+
"""
|
|
258
|
+
|
|
259
|
+
default_initializer=SteamUserInitializer
|
|
260
|
+
CONFIG = UnitModelBlockData.CONFIG()
|
|
261
|
+
_make_config_block(CONFIG)
|
|
262
|
+
|
|
263
|
+
def build(self) -> None:
|
|
264
|
+
"""Build the unit model structure (ports, states, constraints)."""
|
|
265
|
+
# 1. Inherit standard UnitModelBlockData properties and functions
|
|
266
|
+
super().build()
|
|
267
|
+
|
|
268
|
+
# 2. Validate input parameters are valid
|
|
269
|
+
self._validate_model_config()
|
|
270
|
+
|
|
271
|
+
# 3. Create lists of ports with state blocks to add
|
|
272
|
+
self.inlet_list = self._create_inlet_port_name_list()
|
|
273
|
+
self.outlet_list = self._create_outlet_port_name_list()
|
|
274
|
+
|
|
275
|
+
# 4. Declare ports, state blocks and state property bounds
|
|
276
|
+
self.inlet_blocks = self._add_ports_with_state_blocks(
|
|
277
|
+
stream_list=self.inlet_list,
|
|
278
|
+
is_inlet=True,
|
|
279
|
+
has_phase_equilibrium=False,
|
|
280
|
+
is_defined_state=True,
|
|
281
|
+
)
|
|
282
|
+
self.outlet_blocks = self._add_ports_with_state_blocks(
|
|
283
|
+
stream_list=self.outlet_list,
|
|
284
|
+
is_inlet=False,
|
|
285
|
+
has_phase_equilibrium=False,
|
|
286
|
+
is_defined_state=False
|
|
287
|
+
)
|
|
288
|
+
self._internal_blocks = self._add_internal_state_blocks()
|
|
289
|
+
self._ref_enth = self._add_environmental_reference_enth()
|
|
290
|
+
self._add_bounds_to_state_properties()
|
|
291
|
+
|
|
292
|
+
# 4. Declare references, variables and expressions for external and internal use
|
|
293
|
+
self._create_references()
|
|
294
|
+
self._create_variables()
|
|
295
|
+
self._create_expressions()
|
|
296
|
+
|
|
297
|
+
# 5. Set balance equations
|
|
298
|
+
self._add_material_balances()
|
|
299
|
+
self._add_energy_balances()
|
|
300
|
+
self._add_momentum_balances()
|
|
301
|
+
self._add_additional_constraints()
|
|
302
|
+
|
|
303
|
+
# 6. Other
|
|
304
|
+
self.scaling_factor = Suffix(direction=Suffix.EXPORT)
|
|
305
|
+
|
|
306
|
+
# ------------------------------------------------------------------
|
|
307
|
+
# Helpers & construction utilities
|
|
308
|
+
# ------------------------------------------------------------------
|
|
309
|
+
def _validate_model_config(self) -> bool:
|
|
310
|
+
"""Validate configuration for inlet and outlet counts.
|
|
311
|
+
|
|
312
|
+
Raises:
|
|
313
|
+
ValueError: If ``property_package is None``.
|
|
314
|
+
"""
|
|
315
|
+
if self.config.property_package is None:
|
|
316
|
+
raise ValueError("SteamUser: Property package not defined.")
|
|
317
|
+
return True
|
|
318
|
+
|
|
319
|
+
def _create_inlet_port_name_list(self) -> List[str]:
|
|
320
|
+
"""Build ordered inlet port names.
|
|
321
|
+
|
|
322
|
+
Returns:
|
|
323
|
+
list[str]: Names
|
|
324
|
+
"""
|
|
325
|
+
|
|
326
|
+
return (
|
|
327
|
+
[
|
|
328
|
+
"inlet_steam", "inlet_water"
|
|
329
|
+
]
|
|
330
|
+
if self.config.has_desuperheating else
|
|
331
|
+
[
|
|
332
|
+
"inlet_steam"
|
|
333
|
+
]
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
def _create_outlet_port_name_list(self) -> List[str]:
|
|
337
|
+
"""Build ordered outlet port names.
|
|
338
|
+
|
|
339
|
+
Returns:
|
|
340
|
+
list[str]: Names
|
|
341
|
+
"""
|
|
342
|
+
return [
|
|
343
|
+
"outlet_return",
|
|
344
|
+
"outlet_drain",
|
|
345
|
+
]
|
|
346
|
+
|
|
347
|
+
def _add_ports_with_state_blocks(self,
|
|
348
|
+
stream_list: List[str],
|
|
349
|
+
is_inlet: List[str],
|
|
350
|
+
has_phase_equilibrium: bool=False,
|
|
351
|
+
is_defined_state: bool=None,
|
|
352
|
+
) -> List[StateBlock]:
|
|
353
|
+
"""Construct StateBlocks and expose them as ports.
|
|
354
|
+
|
|
355
|
+
Creates a StateBlock per named stream and attaches a corresponding inlet or
|
|
356
|
+
outlet Port. Inlet blocks are defined states; outlet blocks are calculated states.
|
|
357
|
+
|
|
358
|
+
Args:
|
|
359
|
+
stream_list (list[str]): Port/StateBlock base names to create.
|
|
360
|
+
is_inlet (bool): If True, create inlet ports with ``defined_state=True``;
|
|
361
|
+
otherwise create outlet ports with ``defined_state=False``.
|
|
362
|
+
has_phase_equilibrium (bool)
|
|
363
|
+
|
|
364
|
+
Returns:
|
|
365
|
+
list: The created StateBlocks, in the same order as ``stream_list``.
|
|
366
|
+
"""
|
|
367
|
+
# Create empty list to hold StateBlocks for return
|
|
368
|
+
state_block_ls = []
|
|
369
|
+
|
|
370
|
+
# Setup StateBlock argument dict
|
|
371
|
+
tmp_dict = dict(**self.config.property_package_args)
|
|
372
|
+
tmp_dict["has_phase_equilibrium"] = has_phase_equilibrium
|
|
373
|
+
if is_defined_state == None:
|
|
374
|
+
tmp_dict["defined_state"] = True if is_inlet else False
|
|
375
|
+
else:
|
|
376
|
+
tmp_dict["defined_state"] = is_defined_state
|
|
377
|
+
|
|
378
|
+
# Create an instance of StateBlock for all streams
|
|
379
|
+
for s in stream_list:
|
|
380
|
+
sb = self.config.property_package.build_state_block(
|
|
381
|
+
self.flowsheet().time, doc=f"Thermophysical properties at {s}", **tmp_dict
|
|
382
|
+
)
|
|
383
|
+
setattr(
|
|
384
|
+
self, s + "_state",
|
|
385
|
+
sb
|
|
386
|
+
)
|
|
387
|
+
state_block_ls.append(sb)
|
|
388
|
+
add_fn = self.add_inlet_port if is_inlet else self.add_outlet_port
|
|
389
|
+
add_fn(
|
|
390
|
+
name=s,
|
|
391
|
+
block=sb,
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
return state_block_ls
|
|
395
|
+
|
|
396
|
+
def _add_internal_state_blocks(self) -> List[StateBlock]:
|
|
397
|
+
"""Create the intermediate StateBlock(s)."""
|
|
398
|
+
# The _int_outlet_cond_state:
|
|
399
|
+
# - Is not a defined state (solved from balances).
|
|
400
|
+
# - Represents the state of the condensate after delivering process heating.
|
|
401
|
+
tmp_dict = dict(**self.config.property_package_args)
|
|
402
|
+
tmp_dict["has_phase_equilibrium"] = False
|
|
403
|
+
tmp_dict["defined_state"] = False
|
|
404
|
+
|
|
405
|
+
self._int_outlet_cond_state = self.config.property_package.build_state_block(
|
|
406
|
+
self.flowsheet().time,
|
|
407
|
+
doc="Thermophysical properties of condensate after process heating.",
|
|
408
|
+
**tmp_dict
|
|
409
|
+
)
|
|
410
|
+
# The _int_mixed_inlet_state:
|
|
411
|
+
# - Has phase equilibrium enabled.
|
|
412
|
+
# - Is not a defined state (solved from balances).
|
|
413
|
+
# - Always exists even when not desuperheating
|
|
414
|
+
tmp_dict["has_phase_equilibrium"] = True
|
|
415
|
+
tmp_dict["defined_state"] = False
|
|
416
|
+
self._int_mixed_inlet_state = self.config.property_package.build_state_block(
|
|
417
|
+
self.flowsheet().time,
|
|
418
|
+
doc="Thermophysical properties internal mixed inlet state after desuperheating (if applicable).",
|
|
419
|
+
**tmp_dict
|
|
420
|
+
)
|
|
421
|
+
# The _int_outlet_sat_liq_state:
|
|
422
|
+
# - Has phase equilibrium enabled.
|
|
423
|
+
# - Is not a defined state (solved from balances).
|
|
424
|
+
# - Always exists even when not desuperheating
|
|
425
|
+
tmp_dict["has_phase_equilibrium"] = True
|
|
426
|
+
tmp_dict["defined_state"] = False
|
|
427
|
+
self._int_outlet_sat_liq_state = self.config.property_package.build_state_block(
|
|
428
|
+
self.flowsheet().time,
|
|
429
|
+
doc="Thermophysical properties internal mixed saturate state.",
|
|
430
|
+
**tmp_dict
|
|
431
|
+
)
|
|
432
|
+
self._int_outlet_sat_liq_state[:].flow_mol.fix(1)
|
|
433
|
+
|
|
434
|
+
if self.config.has_desuperheating:
|
|
435
|
+
# The _int_inlet_sat_vap_state:
|
|
436
|
+
# - Has phase equilibrium enabled.
|
|
437
|
+
# - Is not a defined state (solved from balances).
|
|
438
|
+
# - Only exists when desuperheating
|
|
439
|
+
tmp_dict["has_phase_equilibrium"] = True
|
|
440
|
+
tmp_dict["defined_state"] = False
|
|
441
|
+
self._int_inlet_sat_vap_state = self.config.property_package.build_state_block(
|
|
442
|
+
self.flowsheet().time,
|
|
443
|
+
doc="Thermophysical properties internal mixed saturate state.",
|
|
444
|
+
**tmp_dict
|
|
445
|
+
)
|
|
446
|
+
self._int_inlet_sat_vap_state[:].flow_mol.fix(1)
|
|
447
|
+
|
|
448
|
+
return [
|
|
449
|
+
self._int_mixed_inlet_state,
|
|
450
|
+
self._int_outlet_cond_state,
|
|
451
|
+
self._int_outlet_sat_liq_state,
|
|
452
|
+
self._int_inlet_sat_vap_state,
|
|
453
|
+
] if self.config.has_desuperheating else [
|
|
454
|
+
self._int_mixed_inlet_state,
|
|
455
|
+
self._int_outlet_cond_state,
|
|
456
|
+
self._int_outlet_sat_liq_state,
|
|
457
|
+
]
|
|
458
|
+
|
|
459
|
+
def _add_environmental_reference_enth(self) -> None:
|
|
460
|
+
"""Create a helper to compute reference enthalpy at 15°C, 1 atm (water)."""
|
|
461
|
+
self.water = build_package("helmholtz", ["water"], ["Liq"])
|
|
462
|
+
return self.water.htpx(
|
|
463
|
+
(15 + 273.15) * UNIT.K,
|
|
464
|
+
101325 * UNIT.Pa
|
|
465
|
+
)
|
|
466
|
+
|
|
467
|
+
def _add_bounds_to_state_properties(self) -> None:
|
|
468
|
+
"""Add lower and/or upper bounds to state properties.
|
|
469
|
+
|
|
470
|
+
- Set nonnegativity lower bounds on all inlet/intermediate/outlet flows.
|
|
471
|
+
"""
|
|
472
|
+
for sb in (self.inlet_blocks + self.outlet_blocks + self._internal_blocks):
|
|
473
|
+
for t in sb:
|
|
474
|
+
sb[t].flow_mol.setlb(0.0)
|
|
475
|
+
|
|
476
|
+
def _create_references(self) -> None:
|
|
477
|
+
"""Create convenient References.
|
|
478
|
+
|
|
479
|
+
Creates references to _int_mixed_inlet_state properties:
|
|
480
|
+
- ``bfw_temperature``
|
|
481
|
+
- ``bfw_flow_mass``
|
|
482
|
+
- ``bfw_flow_mol``
|
|
483
|
+
"""
|
|
484
|
+
# Read only variables, only applicable if desuperheating is active
|
|
485
|
+
if self.config.has_desuperheating:
|
|
486
|
+
self.bfw_flow_mass = Reference(
|
|
487
|
+
self.inlet_water_state[:].flow_mass
|
|
488
|
+
)
|
|
489
|
+
self.bfw_flow_mol = Reference(
|
|
490
|
+
self.inlet_water_state[:].flow_mol
|
|
491
|
+
)
|
|
492
|
+
self.inlet_water_state[:].flow_mol.unfix()
|
|
493
|
+
|
|
494
|
+
def _create_variables(self) -> None:
|
|
495
|
+
"""Declare decision/parameter variables for the unit.
|
|
496
|
+
|
|
497
|
+
Creates:
|
|
498
|
+
- ``heat_demand``
|
|
499
|
+
- ``cond_return_rate``
|
|
500
|
+
- ``cond_return_temperature``
|
|
501
|
+
- ``deltaT_subcool``
|
|
502
|
+
- ``heat_loss``
|
|
503
|
+
- ``pressure_loss``
|
|
504
|
+
If desuperheating:
|
|
505
|
+
- ``bfw_temperature``
|
|
506
|
+
- ``deltaT_superheat``
|
|
507
|
+
"""
|
|
508
|
+
# Get units consistent with the property package
|
|
509
|
+
units_meta = self.config.property_package.get_metadata()
|
|
510
|
+
|
|
511
|
+
# Calculated: Process heat demand (kW) — user typically fixes
|
|
512
|
+
self.heat_demand = Var(
|
|
513
|
+
self.flowsheet().time,
|
|
514
|
+
domain=NonNegativeReals,
|
|
515
|
+
doc="Process heat demand of the users. Default: 0 kW.",
|
|
516
|
+
units=units_meta.get_derived_units("power"),
|
|
517
|
+
)
|
|
518
|
+
self.heat_demand[:].set_value(
|
|
519
|
+
0 # Default value
|
|
520
|
+
)
|
|
521
|
+
# User defined: Fraction of total condensate that returns to boiler (dimensionless)
|
|
522
|
+
self.cond_return_rate = Var(
|
|
523
|
+
self.flowsheet().time,
|
|
524
|
+
domain=NonNegativeReals,
|
|
525
|
+
bounds=(0,1),
|
|
526
|
+
doc="Fraction of condensate returned to the boiler. Default: 0.7."
|
|
527
|
+
)
|
|
528
|
+
self.cond_return_rate.fix(
|
|
529
|
+
0.7 # Default value
|
|
530
|
+
)
|
|
531
|
+
# User defined: Condensate return temperature (degC or K)
|
|
532
|
+
self.cond_return_temperature = Var(
|
|
533
|
+
self.flowsheet().time,
|
|
534
|
+
domain=NonNegativeReals,
|
|
535
|
+
doc="Temperature at which the condensate returns to the boiler. Default: 80 degC.",
|
|
536
|
+
units=units_meta.get_derived_units("temperature"),
|
|
537
|
+
)
|
|
538
|
+
self.cond_return_temperature.fix(
|
|
539
|
+
(80 + 273.15) * UNIT.K # Default fixed value
|
|
540
|
+
)
|
|
541
|
+
# User defined: Subcooling target delta T for condensate after process heating (K)
|
|
542
|
+
self.deltaT_subcool = Var(
|
|
543
|
+
self.flowsheet().time,
|
|
544
|
+
domain=NonNegativeReals,
|
|
545
|
+
doc="The target amount of subcooling of the condensate after process heating. Default: 0 K.",
|
|
546
|
+
units=units_meta.get_derived_units("temperature"),
|
|
547
|
+
)
|
|
548
|
+
self.deltaT_subcool.fix(
|
|
549
|
+
0 # Default fixed value
|
|
550
|
+
)
|
|
551
|
+
# User defined: Heat and pressure losses
|
|
552
|
+
self.heat_loss = Var(
|
|
553
|
+
self.flowsheet().time,
|
|
554
|
+
domain=NonNegativeReals,
|
|
555
|
+
doc="Heat loss. Default: 0 kW.",
|
|
556
|
+
units=units_meta.get_derived_units("power")
|
|
557
|
+
)
|
|
558
|
+
self.heat_loss.fix(
|
|
559
|
+
0 # Default fixed value
|
|
560
|
+
)
|
|
561
|
+
self.pressure_loss = Var(
|
|
562
|
+
self.flowsheet().time,
|
|
563
|
+
domain=NonNegativeReals,
|
|
564
|
+
doc="Pressure loss. Default: 0 Pa.",
|
|
565
|
+
units=units_meta.get_derived_units("pressure")
|
|
566
|
+
)
|
|
567
|
+
self.pressure_loss.fix(
|
|
568
|
+
0 # Default fixed value
|
|
569
|
+
)
|
|
570
|
+
|
|
571
|
+
# User defined when desuperheating is active, otherwise do not show
|
|
572
|
+
if self.config.has_desuperheating:
|
|
573
|
+
# User defined: Boiler feed water temperature entering the desuperheater
|
|
574
|
+
self.bfw_temperature = Var(
|
|
575
|
+
self.flowsheet().time,
|
|
576
|
+
domain=NonNegativeReals,
|
|
577
|
+
doc="The target amount of subcooling of the condensate after process heating. Default: 0 K.",
|
|
578
|
+
units=units_meta.get_derived_units("temperature"),
|
|
579
|
+
)
|
|
580
|
+
self.bfw_temperature.fix(
|
|
581
|
+
(110 + 273.15) * UNIT.K # Default fixed value
|
|
582
|
+
)
|
|
583
|
+
# User defined: Target degree of superheat at the outlet of the desuperheater
|
|
584
|
+
self.deltaT_superheat = Var(
|
|
585
|
+
self.flowsheet().time,
|
|
586
|
+
domain=NonNegativeReals,
|
|
587
|
+
doc="The target amount of superheat present in the steam after desuperheating before use. Default: 0 K.",
|
|
588
|
+
units=units_meta.get_derived_units("temperature"),
|
|
589
|
+
)
|
|
590
|
+
self.deltaT_superheat.fix(
|
|
591
|
+
0 # Default fixed value
|
|
592
|
+
)
|
|
593
|
+
|
|
594
|
+
def _create_expressions(self) -> None:
|
|
595
|
+
"""Create helper Expressions.
|
|
596
|
+
|
|
597
|
+
Creates:
|
|
598
|
+
- ``energy_lost``
|
|
599
|
+
"""
|
|
600
|
+
# Calculated, always show
|
|
601
|
+
self.energy_lost = Expression(
|
|
602
|
+
self.flowsheet().time,
|
|
603
|
+
rule=lambda b, t: (
|
|
604
|
+
b._int_outlet_cond_state[t].flow_mol
|
|
605
|
+
* (b._int_outlet_cond_state[t].enth_mol - b._ref_enth)
|
|
606
|
+
-
|
|
607
|
+
b.outlet_return_state[t].flow_mol
|
|
608
|
+
* (b.outlet_return_state[t].enth_mol - b._ref_enth)
|
|
609
|
+
),
|
|
610
|
+
doc="Energy lost from condensate cooling and condensate to drain.",
|
|
611
|
+
)
|
|
612
|
+
|
|
613
|
+
# ------------------------------------------------------------------
|
|
614
|
+
# Balances
|
|
615
|
+
# ------------------------------------------------------------------
|
|
616
|
+
def _add_material_balances(self) -> None:
|
|
617
|
+
"""Material balance equations summary.
|
|
618
|
+
|
|
619
|
+
Balances / Constraints:
|
|
620
|
+
- ``overall_material_balance``
|
|
621
|
+
- ``condensate_return_material_eq``
|
|
622
|
+
- ``intermediate_material_balance_post_heating``
|
|
623
|
+
- ``intermediate_material_balance_pre_heating``
|
|
624
|
+
"""
|
|
625
|
+
@self.Constraint(
|
|
626
|
+
self.flowsheet().time,
|
|
627
|
+
doc="Overall material balance",
|
|
628
|
+
)
|
|
629
|
+
def overall_material_balance(b, t):
|
|
630
|
+
return (
|
|
631
|
+
sum(
|
|
632
|
+
o[t].flow_mol
|
|
633
|
+
for o in b.outlet_blocks
|
|
634
|
+
)
|
|
635
|
+
==
|
|
636
|
+
sum(
|
|
637
|
+
i[t].flow_mol
|
|
638
|
+
for i in b.inlet_blocks
|
|
639
|
+
)
|
|
640
|
+
)
|
|
641
|
+
@self.Constraint(
|
|
642
|
+
self.flowsheet().time,
|
|
643
|
+
doc="Intermediate material balance",
|
|
644
|
+
)
|
|
645
|
+
def intermediate_material_balance_pre_heating(b, t):
|
|
646
|
+
return (
|
|
647
|
+
b._int_mixed_inlet_state[t].flow_mol
|
|
648
|
+
==
|
|
649
|
+
sum(
|
|
650
|
+
i[t].flow_mol
|
|
651
|
+
for i in b.inlet_blocks
|
|
652
|
+
)
|
|
653
|
+
)
|
|
654
|
+
@self.Constraint(
|
|
655
|
+
self.flowsheet().time,
|
|
656
|
+
doc="Intermediate material balance",
|
|
657
|
+
)
|
|
658
|
+
def intermediate_material_balance_post_heating(b, t):
|
|
659
|
+
return (
|
|
660
|
+
b._int_outlet_cond_state[t].flow_mol
|
|
661
|
+
==
|
|
662
|
+
sum(
|
|
663
|
+
i[t].flow_mol
|
|
664
|
+
for i in b.inlet_blocks
|
|
665
|
+
)
|
|
666
|
+
)
|
|
667
|
+
@self.Constraint(
|
|
668
|
+
self.flowsheet().time,
|
|
669
|
+
doc="Condensate return material equation",
|
|
670
|
+
)
|
|
671
|
+
def condensate_return_material_eq(b, t):
|
|
672
|
+
return (
|
|
673
|
+
b.outlet_return_state[t].flow_mol
|
|
674
|
+
==
|
|
675
|
+
sum(
|
|
676
|
+
i[t].flow_mol
|
|
677
|
+
for i in b.inlet_blocks
|
|
678
|
+
)
|
|
679
|
+
*
|
|
680
|
+
b.cond_return_rate[t]
|
|
681
|
+
)
|
|
682
|
+
|
|
683
|
+
def _add_energy_balances(self) -> None:
|
|
684
|
+
"""Energy balance equations summary.
|
|
685
|
+
|
|
686
|
+
Balances / Constraints:
|
|
687
|
+
- ``mixing_energy_balance``
|
|
688
|
+
- ``heating_energy_balance``
|
|
689
|
+
"""
|
|
690
|
+
@self.Constraint(
|
|
691
|
+
self.flowsheet().time,
|
|
692
|
+
doc="Inlet mixing energy balance",
|
|
693
|
+
)
|
|
694
|
+
def mixing_energy_balance(b, t):
|
|
695
|
+
return (
|
|
696
|
+
b._int_mixed_inlet_state[t].flow_mol * b._int_mixed_inlet_state[t].enth_mol
|
|
697
|
+
==
|
|
698
|
+
sum(
|
|
699
|
+
i[t].flow_mol * i[t].enth_mol
|
|
700
|
+
for i in b.inlet_blocks
|
|
701
|
+
)
|
|
702
|
+
)
|
|
703
|
+
@self.Constraint(
|
|
704
|
+
self.flowsheet().time,
|
|
705
|
+
doc="Process heating energy balance",
|
|
706
|
+
)
|
|
707
|
+
def heating_energy_balance(b, t):
|
|
708
|
+
return (
|
|
709
|
+
b._int_mixed_inlet_state[t].flow_mol * b._int_mixed_inlet_state[t].enth_mol
|
|
710
|
+
==
|
|
711
|
+
b._int_outlet_cond_state[t].flow_mol * b._int_outlet_cond_state[t].enth_mol
|
|
712
|
+
+
|
|
713
|
+
b.heat_loss[t]
|
|
714
|
+
+
|
|
715
|
+
b.heat_demand[t]
|
|
716
|
+
)
|
|
717
|
+
self.outlet_drain_state[:].enth_mol.fix(
|
|
718
|
+
value(
|
|
719
|
+
self._ref_enth
|
|
720
|
+
)
|
|
721
|
+
)
|
|
722
|
+
@self.Constraint(
|
|
723
|
+
self.flowsheet().time,
|
|
724
|
+
doc="Saturated liquid enthalpy",
|
|
725
|
+
)
|
|
726
|
+
def saturated_liq_enthalpy_eq(b, t):
|
|
727
|
+
return (
|
|
728
|
+
b._int_outlet_sat_liq_state[t].enth_mol_sat_phase["Liq"]
|
|
729
|
+
==
|
|
730
|
+
b._int_outlet_sat_liq_state[t].enth_mol
|
|
731
|
+
)
|
|
732
|
+
if self.config.has_desuperheating:
|
|
733
|
+
@self.Constraint(
|
|
734
|
+
self.flowsheet().time,
|
|
735
|
+
doc="Saturated vapour enthalpy",
|
|
736
|
+
)
|
|
737
|
+
def saturated_vap_enthalpy_eq(b, t):
|
|
738
|
+
return (
|
|
739
|
+
b._int_inlet_sat_vap_state[t].enth_mol_sat_phase["Vap"]
|
|
740
|
+
==
|
|
741
|
+
b._int_inlet_sat_vap_state[t].enth_mol
|
|
742
|
+
)
|
|
743
|
+
|
|
744
|
+
def _add_momentum_balances(self) -> None:
|
|
745
|
+
"""Momentum balance equations summary.
|
|
746
|
+
|
|
747
|
+
Balances / Constraints:
|
|
748
|
+
- ``mixing_momentum_balance``
|
|
749
|
+
- ``heating_momentum_balance``
|
|
750
|
+
- ``outlet_momentum_balance``
|
|
751
|
+
If desuperheating:
|
|
752
|
+
- ``intlet_water_momentum_balance``
|
|
753
|
+
"""
|
|
754
|
+
@self.Constraint(
|
|
755
|
+
self.flowsheet().time,
|
|
756
|
+
doc="Momentum equalities",
|
|
757
|
+
)
|
|
758
|
+
def mixing_momentum_balance(b, t):
|
|
759
|
+
return (
|
|
760
|
+
b.inlet_steam_state[t].pressure
|
|
761
|
+
==
|
|
762
|
+
b._int_mixed_inlet_state[t].pressure
|
|
763
|
+
)
|
|
764
|
+
@self.Constraint(
|
|
765
|
+
self.flowsheet().time,
|
|
766
|
+
doc="Process heating momentum balance",
|
|
767
|
+
)
|
|
768
|
+
def heating_momentum_balance(b, t):
|
|
769
|
+
return (
|
|
770
|
+
b._int_mixed_inlet_state[t].pressure
|
|
771
|
+
==
|
|
772
|
+
b._int_outlet_cond_state[t].pressure
|
|
773
|
+
+
|
|
774
|
+
b.pressure_loss[t]
|
|
775
|
+
)
|
|
776
|
+
@self.Constraint(
|
|
777
|
+
self.flowsheet().time,
|
|
778
|
+
doc="Saturated liq pressure",
|
|
779
|
+
)
|
|
780
|
+
def saturated_liq_pressure_eq(b, t):
|
|
781
|
+
return (
|
|
782
|
+
b._int_outlet_sat_liq_state[t].pressure
|
|
783
|
+
==
|
|
784
|
+
b._int_outlet_cond_state[t].pressure
|
|
785
|
+
)
|
|
786
|
+
@self.Constraint(
|
|
787
|
+
self.flowsheet().time,
|
|
788
|
+
doc="Outlet momentum equality",
|
|
789
|
+
)
|
|
790
|
+
def outlet_momentum_balance(b, t):
|
|
791
|
+
return (
|
|
792
|
+
b.outlet_return_state[t].pressure
|
|
793
|
+
==
|
|
794
|
+
b.outlet_drain_state[t].pressure
|
|
795
|
+
)
|
|
796
|
+
self.outlet_return_state[:].pressure.fix(
|
|
797
|
+
101325 * UNIT.Pa # Fixed value, hidden from the user
|
|
798
|
+
)
|
|
799
|
+
if self.config.has_desuperheating:
|
|
800
|
+
@self.Constraint(
|
|
801
|
+
self.flowsheet().time,
|
|
802
|
+
doc="Inlet water momentum balance",
|
|
803
|
+
)
|
|
804
|
+
def intlet_water_momentum_balance(b, t):
|
|
805
|
+
return (
|
|
806
|
+
b.inlet_water_state[t].pressure
|
|
807
|
+
==
|
|
808
|
+
b.inlet_steam_state[t].pressure
|
|
809
|
+
)
|
|
810
|
+
@self.Constraint(
|
|
811
|
+
self.flowsheet().time,
|
|
812
|
+
doc="Saturated vapour pressure",
|
|
813
|
+
)
|
|
814
|
+
def saturated_vap_pressure_eq(b, t):
|
|
815
|
+
return (
|
|
816
|
+
b.inlet_steam_state[t].pressure
|
|
817
|
+
==
|
|
818
|
+
b._int_inlet_sat_vap_state[t].pressure
|
|
819
|
+
)
|
|
820
|
+
|
|
821
|
+
def _add_additional_constraints(self) -> None:
|
|
822
|
+
"""Add auxiliary constraints and bounds.
|
|
823
|
+
|
|
824
|
+
Constraints:
|
|
825
|
+
- ``condensate_temperature_eq``
|
|
826
|
+
- ``subcooling_temperature_eq``
|
|
827
|
+
If desuperheating:
|
|
828
|
+
- ``desuperheating_mixed_temperature_eq``
|
|
829
|
+
"""
|
|
830
|
+
@self.Constraint(
|
|
831
|
+
self.flowsheet().time,
|
|
832
|
+
doc="Condensate return temperature",
|
|
833
|
+
)
|
|
834
|
+
def condensate_temperature_eq(b, t):
|
|
835
|
+
return (
|
|
836
|
+
b.outlet_return_state[t].temperature
|
|
837
|
+
==
|
|
838
|
+
b.cond_return_temperature[t]
|
|
839
|
+
)
|
|
840
|
+
@self.Constraint(
|
|
841
|
+
self.flowsheet().time,
|
|
842
|
+
doc="Subcool temperature",
|
|
843
|
+
)
|
|
844
|
+
def subcooling_temperature_eq(b, t):
|
|
845
|
+
return (
|
|
846
|
+
b._int_outlet_cond_state[t].temperature
|
|
847
|
+
==
|
|
848
|
+
b._int_outlet_sat_liq_state[t].temperature - b.deltaT_subcool[t]
|
|
849
|
+
)
|
|
850
|
+
if self.config.has_desuperheating:
|
|
851
|
+
@self.Constraint(
|
|
852
|
+
self.flowsheet().time,
|
|
853
|
+
doc="Inlet water temperature",
|
|
854
|
+
)
|
|
855
|
+
def inlet_water_temperature_eq(b, t):
|
|
856
|
+
return (
|
|
857
|
+
b.inlet_water_state[t].temperature
|
|
858
|
+
==
|
|
859
|
+
b.bfw_temperature[t]
|
|
860
|
+
)
|
|
861
|
+
@self.Constraint(
|
|
862
|
+
self.flowsheet().time,
|
|
863
|
+
doc="Mixed temperature after desuperheating",
|
|
864
|
+
)
|
|
865
|
+
def desuperheating_mixed_temperature_eq(b, t):
|
|
866
|
+
return (
|
|
867
|
+
b._int_mixed_inlet_state[t].temperature
|
|
868
|
+
==
|
|
869
|
+
b._int_inlet_sat_vap_state[t].temperature + b.deltaT_superheat[t]
|
|
870
|
+
)
|
|
871
|
+
|
|
872
|
+
def calculate_scaling_factors(self):
|
|
873
|
+
"""Assign scaling factors to improve numerical conditioning.
|
|
874
|
+
|
|
875
|
+
Sets scaling factors for performance and auxiliary variables.
|
|
876
|
+
"""
|
|
877
|
+
super().calculate_scaling_factors()
|
|
878
|
+
scaling.set_scaling_factor(self.heat_loss, 1e-6) # kW scale
|
|
879
|
+
scaling.set_scaling_factor(self.pressure_loss, 1e-6) # Pa scale
|
|
880
|
+
|
|
881
|
+
def _get_stream_table_contents(self, time_point=0):
|
|
882
|
+
"""Create a stream table for all inlets and outlets.
|
|
883
|
+
|
|
884
|
+
Args:
|
|
885
|
+
time_point (int | float): Time index at which to extract stream data.
|
|
886
|
+
|
|
887
|
+
Returns:
|
|
888
|
+
pandas.DataFrame: A tabular view suitable for reporting via
|
|
889
|
+
``create_stream_table_dataframe``.
|
|
890
|
+
"""
|
|
891
|
+
io_dict = {}
|
|
892
|
+
|
|
893
|
+
for inlet_name in self.inlet_list:
|
|
894
|
+
io_dict[inlet_name] = getattr(self, inlet_name)
|
|
895
|
+
|
|
896
|
+
for outlet_name in self.outlet_list:
|
|
897
|
+
io_dict[outlet_name] = getattr(self, outlet_name)
|
|
898
|
+
|
|
899
|
+
return create_stream_table_dataframe(io_dict, time_point=time_point)
|
|
900
|
+
|
|
901
|
+
def _get_performance_contents(self, time_point=0):
|
|
902
|
+
"""Collect performance variables for reporting.
|
|
903
|
+
|
|
904
|
+
Args:
|
|
905
|
+
time_point (int | float): Time index at which to report values.
|
|
906
|
+
|
|
907
|
+
Returns:
|
|
908
|
+
dict: Mapping used by IDAES reporters, containing human-friendly labels
|
|
909
|
+
to Vars/References (e.g., heat/pressure loss, mixed-state properties).
|
|
910
|
+
"""
|
|
911
|
+
perf = {
|
|
912
|
+
"vars": {
|
|
913
|
+
"Heat demand [W]": self.heat_demand[time_point],
|
|
914
|
+
"Degree of subcooling target [K]": self.deltaT_subcool[time_point],
|
|
915
|
+
"Heat loss [W]": self.heat_loss[time_point],
|
|
916
|
+
"Pressure loss [Pa]": self.pressure_loss[time_point],
|
|
917
|
+
"Condensate return rate [-]:": self.cond_return_rate[time_point],
|
|
918
|
+
"Condensate return temperature [degC]": UNIT.convert_temp_K_to_C(self.cond_return_temperature[time_point]),
|
|
919
|
+
},
|
|
920
|
+
"exprs": {
|
|
921
|
+
"Energy lost to return network [W]": self.energy_lost[time_point],
|
|
922
|
+
},
|
|
923
|
+
}
|
|
924
|
+
if self.config.has_desuperheating:
|
|
925
|
+
perf["vars"].update(
|
|
926
|
+
{
|
|
927
|
+
"BFW temperature [K]": self.bfw_temperature[time_point],
|
|
928
|
+
"Degree of superheat target [K]": self.deltaT_superheat[time_point],
|
|
929
|
+
}
|
|
930
|
+
)
|
|
931
|
+
return perf
|
|
932
|
+
|
|
933
|
+
def initialize(self, *args, **kwargs):
|
|
934
|
+
"""Initialize the SteamUser unit using :class:`SteamUserInitializer`.
|
|
935
|
+
|
|
936
|
+
Args:
|
|
937
|
+
*args: Forwarded to ``SteamUserInitializer.initialize``.
|
|
938
|
+
**kwargs: Forwarded to ``SteamUserInitializer.initialize`` (e.g., solver, options).
|
|
939
|
+
|
|
940
|
+
Returns:
|
|
941
|
+
pyomo.opt.results.results_.SolverResults: Results from the initializer's solve.
|
|
942
|
+
"""
|
|
943
|
+
init = SteamUserInitializer()
|
|
944
|
+
return init.initialize(self, *args, **kwargs)
|