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