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,1404 @@
|
|
|
1
|
+
# THis is unncecessary when https://github.com/IDAES/idaes-pse/pull/1556/ is merged
|
|
2
|
+
# and in the production of the idaes release
|
|
3
|
+
#################################################################################
|
|
4
|
+
# The Institute for the Design of Advanced Energy Systems Integrated Platform
|
|
5
|
+
# Framework (IDAES IP) was produced under the DOE Institute for the
|
|
6
|
+
# Design of Advanced Energy Systems (IDAES).
|
|
7
|
+
#
|
|
8
|
+
# Copyright (c) 2018-2024 by the software owners: The Regents of the
|
|
9
|
+
# University of California, through Lawrence Berkeley National Laboratory,
|
|
10
|
+
# National Technology & Engineering Solutions of Sandia, LLC, Carnegie Mellon
|
|
11
|
+
# University, West Virginia University Research Corporation, et al.
|
|
12
|
+
# All rights reserved. Please see the files COPYRIGHT.md and LICENSE.md
|
|
13
|
+
# for full copyright and license information.
|
|
14
|
+
#################################################################################
|
|
15
|
+
"""
|
|
16
|
+
Standard IDAES pressure changer model.
|
|
17
|
+
"""
|
|
18
|
+
# TODO: Missing docstrings
|
|
19
|
+
# pylint: disable=missing-function-docstring
|
|
20
|
+
|
|
21
|
+
# Changing existing config block attributes
|
|
22
|
+
# pylint: disable=protected-access
|
|
23
|
+
|
|
24
|
+
# Import Python libraries
|
|
25
|
+
from enum import Enum
|
|
26
|
+
|
|
27
|
+
# Import Pyomo libraries
|
|
28
|
+
from pyomo.environ import (
|
|
29
|
+
Block,
|
|
30
|
+
value,
|
|
31
|
+
Var,
|
|
32
|
+
Expression,
|
|
33
|
+
Constraint,
|
|
34
|
+
Reference,
|
|
35
|
+
check_optimal_termination,
|
|
36
|
+
Reals,
|
|
37
|
+
)
|
|
38
|
+
from pyomo.common.config import ConfigBlock, ConfigValue, In, Bool
|
|
39
|
+
|
|
40
|
+
# Import IDAES cores
|
|
41
|
+
from idaes.core import (
|
|
42
|
+
ControlVolume0DBlock,
|
|
43
|
+
declare_process_block_class,
|
|
44
|
+
EnergyBalanceType,
|
|
45
|
+
MomentumBalanceType,
|
|
46
|
+
MaterialBalanceType,
|
|
47
|
+
ProcessBlockData,
|
|
48
|
+
UnitModelBlockData,
|
|
49
|
+
useDefault,
|
|
50
|
+
)
|
|
51
|
+
from idaes.core.util.exceptions import PropertyNotSupportedError, InitializationError
|
|
52
|
+
from idaes.core.util.config import is_physical_parameter_block
|
|
53
|
+
import idaes.logger as idaeslog
|
|
54
|
+
from idaes.core.util import scaling as iscale
|
|
55
|
+
from idaes.core.solvers import get_solver
|
|
56
|
+
from idaes.core.initialization import SingleControlVolumeUnitInitializer
|
|
57
|
+
from idaes.core.util import to_json, from_json, StoreSpec
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
__author__ = "Emmanuel Ogbe, Andrew Lee"
|
|
61
|
+
_log = idaeslog.getLogger(__name__)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class ThermodynamicAssumption(Enum):
|
|
65
|
+
"""
|
|
66
|
+
Enum of supported thermodynamic assumptions.
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
isothermal = 1
|
|
70
|
+
isentropic = 2
|
|
71
|
+
pump = 3
|
|
72
|
+
adiabatic = 4
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class IsentropicPressureChangerInitializer(SingleControlVolumeUnitInitializer):
|
|
76
|
+
"""
|
|
77
|
+
Initializer for isentropic pressure changer models (and derived types).
|
|
78
|
+
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
def initialization_routine(
|
|
82
|
+
self,
|
|
83
|
+
model: Block,
|
|
84
|
+
):
|
|
85
|
+
"""
|
|
86
|
+
Initialization routine for isentropic pressure changers.
|
|
87
|
+
|
|
88
|
+
This routine starts by initializing the inlet and outlet states as usual,
|
|
89
|
+
using the user provided operating conditions to estimate the outlet state.
|
|
90
|
+
The isentropic state is then initialized at the same conditions as the outlet.
|
|
91
|
+
Next, the pressure changer is solved with an isothermal assumption and fixed efficiency,
|
|
92
|
+
followed by a second solve with the isentropic constraints. Finally, if user-provided
|
|
93
|
+
performance constraints are present, these are activated and the model solved again.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
model: model to be initialized
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
Pyomo solver status object
|
|
100
|
+
|
|
101
|
+
"""
|
|
102
|
+
init_log = idaeslog.getInitLogger(
|
|
103
|
+
model.name, self.get_output_level(), tag="unit"
|
|
104
|
+
)
|
|
105
|
+
solve_log = idaeslog.getSolveLogger(
|
|
106
|
+
model.name, self.get_output_level(), tag="unit"
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# Create solver
|
|
110
|
+
solver = self._get_solver()
|
|
111
|
+
|
|
112
|
+
cv = model.control_volume
|
|
113
|
+
|
|
114
|
+
# check if performance curves exist and are active
|
|
115
|
+
activate_performance_curves = (
|
|
116
|
+
hasattr(model, "performance_curve")
|
|
117
|
+
and model.performance_curve.has_constraints()
|
|
118
|
+
and model.performance_curve.active
|
|
119
|
+
)
|
|
120
|
+
if activate_performance_curves:
|
|
121
|
+
model.performance_curve.deactivate()
|
|
122
|
+
# The performance curves will provide (maybe indirectly) efficiency
|
|
123
|
+
# and/or pressure ratio. To get through the standard isentropic
|
|
124
|
+
# pressure changer init, we'll see if the user provided a guess for
|
|
125
|
+
# pressure ratio or isentropic efficiency and fix them if needed. If
|
|
126
|
+
# not fixed and no guess provided, fill in something reasonable
|
|
127
|
+
# until the performance curves are turned on.
|
|
128
|
+
unfix_eff = {}
|
|
129
|
+
unfix_ratioP = {}
|
|
130
|
+
for t in model.flowsheet().time:
|
|
131
|
+
if not (
|
|
132
|
+
model.ratioP[t].fixed
|
|
133
|
+
or model.deltaP[t].fixed
|
|
134
|
+
or cv.properties_out[t].pressure.fixed
|
|
135
|
+
):
|
|
136
|
+
if model.config.compressor:
|
|
137
|
+
if not (
|
|
138
|
+
value(model.ratioP[t]) >= 1.01
|
|
139
|
+
and value(model.ratioP[t]) <= 50
|
|
140
|
+
):
|
|
141
|
+
model.ratioP[t] = 1.8
|
|
142
|
+
else:
|
|
143
|
+
if not (
|
|
144
|
+
value(model.ratioP[t]) >= 0.01
|
|
145
|
+
and value(model.ratioP[t]) <= 0.999
|
|
146
|
+
):
|
|
147
|
+
model.ratioP[t] = 0.7
|
|
148
|
+
model.ratioP[t].fix()
|
|
149
|
+
unfix_ratioP[t] = True
|
|
150
|
+
if not model.efficiency_isentropic[t].fixed:
|
|
151
|
+
if not (
|
|
152
|
+
value(model.efficiency_isentropic[t]) >= 0.05
|
|
153
|
+
and value(model.efficiency_isentropic[t]) <= 1.0
|
|
154
|
+
):
|
|
155
|
+
model.efficiency_isentropic[t] = 0.8
|
|
156
|
+
model.efficiency_isentropic[t].fix()
|
|
157
|
+
unfix_eff[t] = True
|
|
158
|
+
|
|
159
|
+
# Initialize state blocks
|
|
160
|
+
self.initialize_control_volume(cv, copy_inlet_state=False)
|
|
161
|
+
|
|
162
|
+
init_log.info_high("Initialization Step 1 Complete.")
|
|
163
|
+
# ---------------------------------------------------------------------
|
|
164
|
+
# Copy outlet state to isentropic state
|
|
165
|
+
|
|
166
|
+
# Map solution from inlet properties to outlet properties
|
|
167
|
+
state = to_json(
|
|
168
|
+
cv.properties_out,
|
|
169
|
+
wts=StoreSpec().value(),
|
|
170
|
+
return_dict=True,
|
|
171
|
+
)
|
|
172
|
+
from_json(
|
|
173
|
+
model.properties_isentropic,
|
|
174
|
+
sd=state,
|
|
175
|
+
wts=StoreSpec().value(only_not_fixed=True),
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
init_log.info_high("Initialization Step 2 Complete.")
|
|
179
|
+
|
|
180
|
+
# ---------------------------------------------------------------------
|
|
181
|
+
# Solve for isothermal conditions
|
|
182
|
+
if isinstance(
|
|
183
|
+
model.properties_isentropic[model.flowsheet().time.first()].temperature,
|
|
184
|
+
Var,
|
|
185
|
+
):
|
|
186
|
+
model.properties_isentropic[:].temperature.fix()
|
|
187
|
+
elif isinstance(
|
|
188
|
+
model.properties_isentropic[model.flowsheet().time.first()].enth_mol,
|
|
189
|
+
Var,
|
|
190
|
+
):
|
|
191
|
+
model.properties_isentropic[:].enth_mol.fix()
|
|
192
|
+
elif isinstance(
|
|
193
|
+
model.properties_isentropic[model.flowsheet().time.first()].temperature,
|
|
194
|
+
Expression,
|
|
195
|
+
):
|
|
196
|
+
|
|
197
|
+
def tmp_rule(blk, t):
|
|
198
|
+
return (
|
|
199
|
+
blk.properties_isentropic[t].temperature
|
|
200
|
+
== blk.control_volume.properties_in[t].temperature
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
model.tmp_init_constraint = Constraint(
|
|
204
|
+
model.flowsheet().time, rule=tmp_rule
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
model.isentropic.deactivate()
|
|
208
|
+
|
|
209
|
+
with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc:
|
|
210
|
+
res = solver.solve(model, tee=slc.tee)
|
|
211
|
+
init_log.info_high("Initialization Step 3 {}.".format(idaeslog.condition(res)))
|
|
212
|
+
|
|
213
|
+
# Revert changes for isothermal assumption
|
|
214
|
+
if isinstance(
|
|
215
|
+
model.properties_isentropic[model.flowsheet().time.first()].temperature,
|
|
216
|
+
Var,
|
|
217
|
+
):
|
|
218
|
+
model.properties_isentropic[:].temperature.unfix()
|
|
219
|
+
elif isinstance(
|
|
220
|
+
model.properties_isentropic[model.flowsheet().time.first()].enth_mol,
|
|
221
|
+
Var,
|
|
222
|
+
):
|
|
223
|
+
model.properties_isentropic[:].enth_mol.unfix()
|
|
224
|
+
elif isinstance(
|
|
225
|
+
model.properties_isentropic[model.flowsheet().time.first()].temperature,
|
|
226
|
+
Expression,
|
|
227
|
+
):
|
|
228
|
+
model.del_component(model.tmp_init_constraint)
|
|
229
|
+
|
|
230
|
+
model.isentropic.activate()
|
|
231
|
+
|
|
232
|
+
# ---------------------------------------------------------------------
|
|
233
|
+
# Solve unit
|
|
234
|
+
with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc:
|
|
235
|
+
res = solver.solve(model, tee=slc.tee)
|
|
236
|
+
init_log.info_high("Initialization Step 4 {}.".format(idaeslog.condition(res)))
|
|
237
|
+
|
|
238
|
+
if activate_performance_curves:
|
|
239
|
+
model.performance_curve.activate()
|
|
240
|
+
for t, v in unfix_eff.items():
|
|
241
|
+
if v:
|
|
242
|
+
model.efficiency_isentropic[t].unfix()
|
|
243
|
+
for t, v in unfix_ratioP.items():
|
|
244
|
+
if v:
|
|
245
|
+
model.ratioP[t].unfix()
|
|
246
|
+
with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc:
|
|
247
|
+
res = solver.solve(model, tee=slc.tee)
|
|
248
|
+
init_log.info_high(f"Initialization Step 5 {idaeslog.condition(res)}.")
|
|
249
|
+
|
|
250
|
+
return res
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
@declare_process_block_class("IsentropicPerformanceCurve")
|
|
254
|
+
class IsentropicPerformanceCurveData(ProcessBlockData):
|
|
255
|
+
"""Block that holds performance curves. Typically, these are in the form of
|
|
256
|
+
constraints that relate head, efficiency, or pressure ratio to volumetric
|
|
257
|
+
or mass flow. Additional variables can be included if needed, such as
|
|
258
|
+
speed. For convenience an option is provided to add head expressions to the
|
|
259
|
+
block. performance curves, and any additional variables, constraints, or
|
|
260
|
+
expressions can be added to this block either via callback provided to the
|
|
261
|
+
configuration, or after the model is constructed."""
|
|
262
|
+
|
|
263
|
+
CONFIG = ProcessBlockData.CONFIG(
|
|
264
|
+
doc="Configuration dictionary for the performance curve block."
|
|
265
|
+
)
|
|
266
|
+
CONFIG.declare(
|
|
267
|
+
"build_callback",
|
|
268
|
+
ConfigValue(
|
|
269
|
+
default=None, doc="Optional callback to add performance curve constraints"
|
|
270
|
+
),
|
|
271
|
+
)
|
|
272
|
+
CONFIG.declare(
|
|
273
|
+
"build_head_expressions",
|
|
274
|
+
ConfigValue(
|
|
275
|
+
default=True,
|
|
276
|
+
domain=bool,
|
|
277
|
+
doc="If true add expressions for 'head' and 'head_isentropic'. "
|
|
278
|
+
"These expressions can be used in performance curve constraints.",
|
|
279
|
+
),
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
def has_constraints(self):
|
|
283
|
+
for _ in self.component_data_objects(Constraint):
|
|
284
|
+
return True
|
|
285
|
+
return False
|
|
286
|
+
|
|
287
|
+
def build(self):
|
|
288
|
+
super().build()
|
|
289
|
+
if self.config.build_head_expressions:
|
|
290
|
+
try:
|
|
291
|
+
|
|
292
|
+
@self.Expression(self.flowsheet().time)
|
|
293
|
+
def head_isentropic(self, t): # units are energy/mass
|
|
294
|
+
b = self.parent_block()
|
|
295
|
+
if hasattr(b.control_volume.properties_in[t], "flow_mass"):
|
|
296
|
+
return (
|
|
297
|
+
b.work_isentropic[t]
|
|
298
|
+
/ b.control_volume.properties_in[t].flow_mass
|
|
299
|
+
)
|
|
300
|
+
else:
|
|
301
|
+
return (
|
|
302
|
+
b.work_isentropic[t]
|
|
303
|
+
/ b.control_volume.properties_in[t].flow_mol
|
|
304
|
+
/ b.control_volume.properties_in[t].mw
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
@self.Expression(self.flowsheet().time)
|
|
308
|
+
def head(self, t): # units are energy/mass
|
|
309
|
+
b = self.parent_block()
|
|
310
|
+
if hasattr(b.control_volume.properties_in[t], "flow_mass"):
|
|
311
|
+
return (
|
|
312
|
+
b.work_mechanical[t]
|
|
313
|
+
/ b.control_volume.properties_in[t].flow_mass
|
|
314
|
+
)
|
|
315
|
+
else:
|
|
316
|
+
return (
|
|
317
|
+
b.work_mechanical[t]
|
|
318
|
+
/ b.control_volume.properties_in[t].flow_mol
|
|
319
|
+
/ b.control_volume.properties_in[t].mw
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
except PropertyNotSupportedError:
|
|
323
|
+
_log.exception(
|
|
324
|
+
"flow_mass or flow_mol and mw are not supported by the "
|
|
325
|
+
"property package but are required for isentropic pressure"
|
|
326
|
+
" changer head calculation"
|
|
327
|
+
)
|
|
328
|
+
raise
|
|
329
|
+
|
|
330
|
+
if self.config.build_callback is not None:
|
|
331
|
+
self.config.build_callback(self)
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
@declare_process_block_class("PressureChanger")
|
|
335
|
+
class PressureChangerData(UnitModelBlockData):
|
|
336
|
+
"""
|
|
337
|
+
Standard Compressor/Expander Unit Model Class
|
|
338
|
+
"""
|
|
339
|
+
|
|
340
|
+
CONFIG = UnitModelBlockData.CONFIG()
|
|
341
|
+
|
|
342
|
+
CONFIG.declare(
|
|
343
|
+
"material_balance_type",
|
|
344
|
+
ConfigValue(
|
|
345
|
+
default=MaterialBalanceType.useDefault,
|
|
346
|
+
domain=In(MaterialBalanceType),
|
|
347
|
+
description="Material balance construction flag",
|
|
348
|
+
doc="""Indicates what type of mass balance should be constructed,
|
|
349
|
+
**default** - MaterialBalanceType.useDefault.
|
|
350
|
+
**Valid values:** {
|
|
351
|
+
**MaterialBalanceType.useDefault - refer to property package for default
|
|
352
|
+
balance type
|
|
353
|
+
**MaterialBalanceType.none** - exclude material balances,
|
|
354
|
+
**MaterialBalanceType.componentPhase** - use phase component balances,
|
|
355
|
+
**MaterialBalanceType.componentTotal** - use total component balances,
|
|
356
|
+
**MaterialBalanceType.elementTotal** - use total element balances,
|
|
357
|
+
**MaterialBalanceType.total** - use total material balance.}""",
|
|
358
|
+
),
|
|
359
|
+
)
|
|
360
|
+
CONFIG.declare(
|
|
361
|
+
"energy_balance_type",
|
|
362
|
+
ConfigValue(
|
|
363
|
+
default=EnergyBalanceType.useDefault,
|
|
364
|
+
domain=In(EnergyBalanceType),
|
|
365
|
+
description="Energy balance construction flag",
|
|
366
|
+
doc="""Indicates what type of energy balance should be constructed,
|
|
367
|
+
**default** - EnergyBalanceType.useDefault.
|
|
368
|
+
**Valid values:** {
|
|
369
|
+
**EnergyBalanceType.useDefault - refer to property package for default
|
|
370
|
+
balance type
|
|
371
|
+
**EnergyBalanceType.none** - exclude energy balances,
|
|
372
|
+
**EnergyBalanceType.enthalpyTotal** - single enthalpy balance for material,
|
|
373
|
+
**EnergyBalanceType.enthalpyPhase** - enthalpy balances for each phase,
|
|
374
|
+
**EnergyBalanceType.energyTotal** - single energy balance for material,
|
|
375
|
+
**EnergyBalanceType.energyPhase** - energy balances for each phase.}""",
|
|
376
|
+
),
|
|
377
|
+
)
|
|
378
|
+
CONFIG.declare(
|
|
379
|
+
"momentum_balance_type",
|
|
380
|
+
ConfigValue(
|
|
381
|
+
default=MomentumBalanceType.pressureTotal,
|
|
382
|
+
domain=In(MomentumBalanceType),
|
|
383
|
+
description="Momentum balance construction flag",
|
|
384
|
+
doc="""Indicates what type of momentum balance should be
|
|
385
|
+
constructed, **default** - MomentumBalanceType.pressureTotal.
|
|
386
|
+
**Valid values:** {
|
|
387
|
+
**MomentumBalanceType.none** - exclude momentum balances,
|
|
388
|
+
**MomentumBalanceType.pressureTotal** - single pressure balance for material,
|
|
389
|
+
**MomentumBalanceType.pressurePhase** - pressure balances for each phase,
|
|
390
|
+
**MomentumBalanceType.momentumTotal** - single momentum balance for material,
|
|
391
|
+
**MomentumBalanceType.momentumPhase** - momentum balances for each phase.}""",
|
|
392
|
+
),
|
|
393
|
+
)
|
|
394
|
+
CONFIG.declare(
|
|
395
|
+
"has_phase_equilibrium",
|
|
396
|
+
ConfigValue(
|
|
397
|
+
default=False,
|
|
398
|
+
domain=Bool,
|
|
399
|
+
description="Phase equilibrium construction flag",
|
|
400
|
+
doc="""Indicates whether terms for phase equilibrium should be
|
|
401
|
+
constructed, **default** = False.
|
|
402
|
+
**Valid values:** {
|
|
403
|
+
**True** - include phase equilibrium terms
|
|
404
|
+
**False** - exclude phase equilibrium terms.}""",
|
|
405
|
+
),
|
|
406
|
+
)
|
|
407
|
+
CONFIG.declare(
|
|
408
|
+
"compressor",
|
|
409
|
+
ConfigValue(
|
|
410
|
+
default=True,
|
|
411
|
+
domain=Bool,
|
|
412
|
+
description="Compressor flag",
|
|
413
|
+
doc="""Indicates whether this unit should be considered a
|
|
414
|
+
compressor (True (default), pressure increase) or an expander
|
|
415
|
+
(False, pressure decrease).""",
|
|
416
|
+
),
|
|
417
|
+
)
|
|
418
|
+
CONFIG.declare(
|
|
419
|
+
"thermodynamic_assumption",
|
|
420
|
+
ConfigValue(
|
|
421
|
+
default=ThermodynamicAssumption.isothermal,
|
|
422
|
+
domain=In(ThermodynamicAssumption),
|
|
423
|
+
description="Thermodynamic assumption to use",
|
|
424
|
+
doc="""Flag to set the thermodynamic assumption to use for the unit.
|
|
425
|
+
- ThermodynamicAssumption.isothermal (default)
|
|
426
|
+
- ThermodynamicAssumption.isentropic
|
|
427
|
+
- ThermodynamicAssumption.pump
|
|
428
|
+
- ThermodynamicAssumption.adiabatic""",
|
|
429
|
+
),
|
|
430
|
+
)
|
|
431
|
+
CONFIG.declare(
|
|
432
|
+
"property_package",
|
|
433
|
+
ConfigValue(
|
|
434
|
+
default=useDefault,
|
|
435
|
+
domain=is_physical_parameter_block,
|
|
436
|
+
description="Property package to use for control volume",
|
|
437
|
+
doc="""Property parameter object used to define property
|
|
438
|
+
calculations, **default** - useDefault.
|
|
439
|
+
**Valid values:** {
|
|
440
|
+
**useDefault** - use default package from parent model or flowsheet,
|
|
441
|
+
**PropertyParameterObject** - a PropertyParameterBlock object.}""",
|
|
442
|
+
),
|
|
443
|
+
)
|
|
444
|
+
CONFIG.declare(
|
|
445
|
+
"property_package_args",
|
|
446
|
+
ConfigBlock(
|
|
447
|
+
implicit=True,
|
|
448
|
+
description="Arguments to use for constructing property packages",
|
|
449
|
+
doc="""A ConfigBlock with arguments to be passed to a property
|
|
450
|
+
block(s) and used when constructing these,
|
|
451
|
+
**default** - None.
|
|
452
|
+
**Valid values:** {
|
|
453
|
+
see property package for documentation.}""",
|
|
454
|
+
),
|
|
455
|
+
)
|
|
456
|
+
CONFIG.declare(
|
|
457
|
+
"support_isentropic_performance_curves",
|
|
458
|
+
ConfigValue(
|
|
459
|
+
default=False,
|
|
460
|
+
domain=Bool,
|
|
461
|
+
doc="Include a block for performance curves, configure via"
|
|
462
|
+
" isentropic_performance_curves.",
|
|
463
|
+
),
|
|
464
|
+
)
|
|
465
|
+
CONFIG.declare(
|
|
466
|
+
"isentropic_performance_curves",
|
|
467
|
+
IsentropicPerformanceCurveData.CONFIG(),
|
|
468
|
+
# doc included in IsentropicPerformanceCurveData
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
def build(self):
|
|
472
|
+
"""
|
|
473
|
+
|
|
474
|
+
Args:
|
|
475
|
+
None
|
|
476
|
+
|
|
477
|
+
Returns:
|
|
478
|
+
None
|
|
479
|
+
"""
|
|
480
|
+
# Call UnitModel.build
|
|
481
|
+
super().build()
|
|
482
|
+
|
|
483
|
+
# Add a control volume to the unit including setting up dynamics.
|
|
484
|
+
self.control_volume = ControlVolume0DBlock(
|
|
485
|
+
dynamic=self.config.dynamic,
|
|
486
|
+
has_holdup=self.config.has_holdup,
|
|
487
|
+
property_package=self.config.property_package,
|
|
488
|
+
property_package_args=self.config.property_package_args,
|
|
489
|
+
)
|
|
490
|
+
|
|
491
|
+
# Add geometry variables to control volume
|
|
492
|
+
if self.config.has_holdup:
|
|
493
|
+
self.control_volume.add_geometry()
|
|
494
|
+
|
|
495
|
+
# Add inlet and outlet state blocks to control volume
|
|
496
|
+
self.control_volume.add_state_blocks(
|
|
497
|
+
has_phase_equilibrium=self.config.has_phase_equilibrium
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
# Add mass balance
|
|
501
|
+
# Set has_equilibrium is False for now
|
|
502
|
+
# TO DO; set has_equilibrium to True
|
|
503
|
+
self.control_volume.add_material_balances(
|
|
504
|
+
balance_type=self.config.material_balance_type,
|
|
505
|
+
has_phase_equilibrium=self.config.has_phase_equilibrium,
|
|
506
|
+
)
|
|
507
|
+
|
|
508
|
+
# Add energy balance
|
|
509
|
+
eb = self.control_volume.add_energy_balances(
|
|
510
|
+
balance_type=self.config.energy_balance_type, has_work_transfer=True
|
|
511
|
+
)
|
|
512
|
+
|
|
513
|
+
# add momentum balance
|
|
514
|
+
self.control_volume.add_momentum_balances(
|
|
515
|
+
balance_type=self.config.momentum_balance_type, has_pressure_change=True
|
|
516
|
+
)
|
|
517
|
+
|
|
518
|
+
# Add Ports
|
|
519
|
+
self.add_inlet_port()
|
|
520
|
+
self.add_outlet_port()
|
|
521
|
+
|
|
522
|
+
# Set Unit Geometry and holdup Volume
|
|
523
|
+
if self.config.has_holdup is True:
|
|
524
|
+
self.volume = Reference(self.control_volume.volume[:])
|
|
525
|
+
|
|
526
|
+
# Construct performance equations
|
|
527
|
+
# Set references to balance terms at unit level
|
|
528
|
+
# Add Work transfer variable 'work'
|
|
529
|
+
# If the 'work' variable wasn't already built on the control volume but is needed, create it now.
|
|
530
|
+
if (
|
|
531
|
+
not hasattr(self.control_volume, "work")
|
|
532
|
+
and self.config.thermodynamic_assumption == ThermodynamicAssumption.pump
|
|
533
|
+
and eb is None
|
|
534
|
+
):
|
|
535
|
+
units = (
|
|
536
|
+
self.control_volume.config.property_package.get_metadata().get_derived_units
|
|
537
|
+
)
|
|
538
|
+
self.control_volume.work = Var(
|
|
539
|
+
self.flowsheet().time,
|
|
540
|
+
domain=Reals,
|
|
541
|
+
initialize=0.0,
|
|
542
|
+
doc="Work transferred into control volume",
|
|
543
|
+
units=units("power"),
|
|
544
|
+
)
|
|
545
|
+
self.work_mechanical = Reference(self.control_volume.work[:])
|
|
546
|
+
|
|
547
|
+
# Add Momentum balance variable 'deltaP'
|
|
548
|
+
self.deltaP = Reference(self.control_volume.deltaP[:])
|
|
549
|
+
|
|
550
|
+
# Performance Variables
|
|
551
|
+
self.ratioP = Var(self.flowsheet().time, initialize=1.0, doc="Pressure Ratio")
|
|
552
|
+
|
|
553
|
+
# Pressure Ratio
|
|
554
|
+
@self.Constraint(self.flowsheet().time, doc="Pressure ratio constraint")
|
|
555
|
+
def ratioP_calculation(self, t):
|
|
556
|
+
return (
|
|
557
|
+
self.ratioP[t] * self.control_volume.properties_in[t].pressure
|
|
558
|
+
== self.control_volume.properties_out[t].pressure
|
|
559
|
+
)
|
|
560
|
+
|
|
561
|
+
# Construct equations for thermodynamic assumption
|
|
562
|
+
if self.config.thermodynamic_assumption == ThermodynamicAssumption.isothermal:
|
|
563
|
+
self.add_isothermal()
|
|
564
|
+
elif self.config.thermodynamic_assumption == ThermodynamicAssumption.isentropic:
|
|
565
|
+
self.add_isentropic()
|
|
566
|
+
elif self.config.thermodynamic_assumption == ThermodynamicAssumption.pump:
|
|
567
|
+
self.add_pump()
|
|
568
|
+
elif self.config.thermodynamic_assumption == ThermodynamicAssumption.adiabatic:
|
|
569
|
+
self.add_adiabatic()
|
|
570
|
+
|
|
571
|
+
def add_pump(self):
|
|
572
|
+
"""
|
|
573
|
+
Add constraints for the incompressible fluid assumption
|
|
574
|
+
|
|
575
|
+
Args:
|
|
576
|
+
None
|
|
577
|
+
|
|
578
|
+
Returns:
|
|
579
|
+
None
|
|
580
|
+
"""
|
|
581
|
+
units_meta = self.control_volume.config.property_package.get_metadata()
|
|
582
|
+
|
|
583
|
+
self.work_fluid = Var(
|
|
584
|
+
self.flowsheet().time,
|
|
585
|
+
initialize=1.0,
|
|
586
|
+
doc="Work required to increase the pressure of the liquid",
|
|
587
|
+
units=units_meta.get_derived_units("power"),
|
|
588
|
+
)
|
|
589
|
+
self.efficiency_pump = Var(
|
|
590
|
+
self.flowsheet().time, initialize=1.0, doc="Pump efficiency"
|
|
591
|
+
)
|
|
592
|
+
|
|
593
|
+
@self.Constraint(self.flowsheet().time, doc="Pump fluid work constraint")
|
|
594
|
+
def fluid_work_calculation(self, t):
|
|
595
|
+
return self.work_fluid[t] == (
|
|
596
|
+
(
|
|
597
|
+
self.control_volume.properties_out[t].pressure
|
|
598
|
+
- self.control_volume.properties_in[t].pressure
|
|
599
|
+
)
|
|
600
|
+
* self.control_volume.properties_out[t].flow_vol
|
|
601
|
+
)
|
|
602
|
+
|
|
603
|
+
# Actual work
|
|
604
|
+
@self.Constraint(
|
|
605
|
+
self.flowsheet().time, doc="Actual mechanical work calculation"
|
|
606
|
+
)
|
|
607
|
+
def actual_work(self, t):
|
|
608
|
+
if self.config.compressor:
|
|
609
|
+
return self.work_fluid[t] == (
|
|
610
|
+
self.work_mechanical[t] * self.efficiency_pump[t]
|
|
611
|
+
)
|
|
612
|
+
else:
|
|
613
|
+
return self.work_mechanical[t] == (
|
|
614
|
+
self.work_fluid[t] * self.efficiency_pump[t]
|
|
615
|
+
)
|
|
616
|
+
|
|
617
|
+
def add_isothermal(self):
|
|
618
|
+
"""
|
|
619
|
+
Add constraints for isothermal assumption.
|
|
620
|
+
|
|
621
|
+
Args:
|
|
622
|
+
None
|
|
623
|
+
|
|
624
|
+
Returns:
|
|
625
|
+
None
|
|
626
|
+
"""
|
|
627
|
+
|
|
628
|
+
# Isothermal constraint
|
|
629
|
+
@self.Constraint(
|
|
630
|
+
self.flowsheet().time,
|
|
631
|
+
doc="For isothermal condition: Equate inlet and " "outlet temperature",
|
|
632
|
+
)
|
|
633
|
+
def isothermal(self, t):
|
|
634
|
+
return (
|
|
635
|
+
self.control_volume.properties_in[t].temperature
|
|
636
|
+
== self.control_volume.properties_out[t].temperature
|
|
637
|
+
)
|
|
638
|
+
|
|
639
|
+
def add_adiabatic(self):
|
|
640
|
+
"""
|
|
641
|
+
Add constraints for adiabatic assumption.
|
|
642
|
+
|
|
643
|
+
Args:
|
|
644
|
+
None
|
|
645
|
+
|
|
646
|
+
Returns:
|
|
647
|
+
None
|
|
648
|
+
"""
|
|
649
|
+
|
|
650
|
+
@self.Constraint(self.flowsheet().time)
|
|
651
|
+
def zero_work_equation(self, t):
|
|
652
|
+
return self.control_volume.work[t] == 0
|
|
653
|
+
|
|
654
|
+
def add_isentropic(self):
|
|
655
|
+
"""
|
|
656
|
+
Add constraints for isentropic assumption.
|
|
657
|
+
|
|
658
|
+
Args:
|
|
659
|
+
None
|
|
660
|
+
|
|
661
|
+
Returns:
|
|
662
|
+
None
|
|
663
|
+
"""
|
|
664
|
+
units_meta = self.control_volume.config.property_package.get_metadata()
|
|
665
|
+
|
|
666
|
+
# Get indexing sets from control volume
|
|
667
|
+
# Add isentropic variables
|
|
668
|
+
self.efficiency_isentropic = Var(
|
|
669
|
+
self.flowsheet().time,
|
|
670
|
+
initialize=0.8,
|
|
671
|
+
doc="Efficiency with respect to an isentropic process [-]",
|
|
672
|
+
)
|
|
673
|
+
self.work_isentropic = Var(
|
|
674
|
+
self.flowsheet().time,
|
|
675
|
+
initialize=0.0,
|
|
676
|
+
doc="Work input to unit if isentropic process",
|
|
677
|
+
units=units_meta.get_derived_units("power"),
|
|
678
|
+
)
|
|
679
|
+
|
|
680
|
+
# Build isentropic state block
|
|
681
|
+
tmp_dict = dict(**self.config.property_package_args)
|
|
682
|
+
tmp_dict["has_phase_equilibrium"] = self.config.has_phase_equilibrium
|
|
683
|
+
tmp_dict["defined_state"] = False
|
|
684
|
+
|
|
685
|
+
self.properties_isentropic = self.config.property_package.build_state_block(
|
|
686
|
+
self.flowsheet().time, doc="isentropic properties at outlet", **tmp_dict
|
|
687
|
+
)
|
|
688
|
+
|
|
689
|
+
# Connect isentropic state block properties
|
|
690
|
+
@self.Constraint(
|
|
691
|
+
self.flowsheet().time, doc="Pressure for isentropic calculations"
|
|
692
|
+
)
|
|
693
|
+
def isentropic_pressure(self, t):
|
|
694
|
+
return (
|
|
695
|
+
self.properties_isentropic[t].pressure
|
|
696
|
+
== self.control_volume.properties_out[t].pressure
|
|
697
|
+
)
|
|
698
|
+
|
|
699
|
+
# This assumes isentropic composition is the same as outlet
|
|
700
|
+
self.add_state_material_balances(
|
|
701
|
+
self.config.material_balance_type,
|
|
702
|
+
self.properties_isentropic,
|
|
703
|
+
self.control_volume.properties_out,
|
|
704
|
+
)
|
|
705
|
+
|
|
706
|
+
# This assumes isentropic entropy is the same as inlet
|
|
707
|
+
@self.Constraint(self.flowsheet().time, doc="Isentropic assumption")
|
|
708
|
+
def isentropic(self, t):
|
|
709
|
+
return (
|
|
710
|
+
self.properties_isentropic[t].entr_mol
|
|
711
|
+
== self.control_volume.properties_in[t].entr_mol
|
|
712
|
+
)
|
|
713
|
+
|
|
714
|
+
# Isentropic work
|
|
715
|
+
@self.Constraint(
|
|
716
|
+
self.flowsheet().time, doc="Calculate work of isentropic process"
|
|
717
|
+
)
|
|
718
|
+
def isentropic_energy_balance(self, t):
|
|
719
|
+
return self.work_isentropic[t] == (
|
|
720
|
+
sum(
|
|
721
|
+
self.properties_isentropic[t].get_enthalpy_flow_terms(p)
|
|
722
|
+
for p in self.properties_isentropic.phase_list
|
|
723
|
+
)
|
|
724
|
+
- sum(
|
|
725
|
+
self.control_volume.properties_in[t].get_enthalpy_flow_terms(p)
|
|
726
|
+
for p in self.control_volume.properties_in.phase_list
|
|
727
|
+
)
|
|
728
|
+
)
|
|
729
|
+
|
|
730
|
+
# Actual work
|
|
731
|
+
@self.Constraint(
|
|
732
|
+
self.flowsheet().time, doc="Actual mechanical work calculation"
|
|
733
|
+
)
|
|
734
|
+
def actual_work(self, t):
|
|
735
|
+
if self.config.compressor:
|
|
736
|
+
return self.work_isentropic[t] == (
|
|
737
|
+
self.work_mechanical[t] * self.efficiency_isentropic[t]
|
|
738
|
+
)
|
|
739
|
+
else:
|
|
740
|
+
return self.work_mechanical[t] == (
|
|
741
|
+
self.work_isentropic[t] * self.efficiency_isentropic[t]
|
|
742
|
+
)
|
|
743
|
+
|
|
744
|
+
if self.config.support_isentropic_performance_curves:
|
|
745
|
+
self.performance_curve = IsentropicPerformanceCurve(
|
|
746
|
+
**self.config.isentropic_performance_curves
|
|
747
|
+
)
|
|
748
|
+
|
|
749
|
+
def model_check(blk):
|
|
750
|
+
"""
|
|
751
|
+
Check that pressure change matches with compressor argument (i.e. if
|
|
752
|
+
compressor = True, pressure should increase or work should be positive)
|
|
753
|
+
|
|
754
|
+
Args:
|
|
755
|
+
None
|
|
756
|
+
|
|
757
|
+
Returns:
|
|
758
|
+
None
|
|
759
|
+
"""
|
|
760
|
+
if blk.config.compressor:
|
|
761
|
+
# Compressor
|
|
762
|
+
# Check that pressure does not decrease
|
|
763
|
+
if any(
|
|
764
|
+
blk.deltaP[t].fixed and (value(blk.deltaP[t]) < 0.0)
|
|
765
|
+
for t in blk.flowsheet().time
|
|
766
|
+
):
|
|
767
|
+
_log.warning("{} Compressor set with negative deltaP.".format(blk.name))
|
|
768
|
+
if any(
|
|
769
|
+
blk.ratioP[t].fixed and (value(blk.ratioP[t]) < 1.0)
|
|
770
|
+
for t in blk.flowsheet().time
|
|
771
|
+
):
|
|
772
|
+
_log.warning(
|
|
773
|
+
"{} Compressor set with ratioP less than 1.".format(blk.name)
|
|
774
|
+
)
|
|
775
|
+
if any(
|
|
776
|
+
blk.control_volume.properties_out[t].pressure.fixed
|
|
777
|
+
and (
|
|
778
|
+
value(blk.control_volume.properties_in[t].pressure)
|
|
779
|
+
> value(blk.control_volume.properties_out[t].pressure)
|
|
780
|
+
)
|
|
781
|
+
for t in blk.flowsheet().time
|
|
782
|
+
):
|
|
783
|
+
_log.warning(
|
|
784
|
+
"{} Compressor set with pressure decrease.".format(blk.name)
|
|
785
|
+
)
|
|
786
|
+
# Check that work is not negative
|
|
787
|
+
if any(
|
|
788
|
+
blk.work_mechanical[t].fixed and (value(blk.work_mechanical[t]) < 0.0)
|
|
789
|
+
for t in blk.flowsheet().time
|
|
790
|
+
):
|
|
791
|
+
_log.warning(
|
|
792
|
+
"{} Compressor maybe set with negative work.".format(blk.name)
|
|
793
|
+
)
|
|
794
|
+
else:
|
|
795
|
+
# Expander
|
|
796
|
+
# Check that pressure does not increase
|
|
797
|
+
if any(
|
|
798
|
+
blk.deltaP[t].fixed and (value(blk.deltaP[t]) > 0.0)
|
|
799
|
+
for t in blk.flowsheet().time
|
|
800
|
+
):
|
|
801
|
+
_log.warning(
|
|
802
|
+
"{} Expander/turbine set with positive deltaP.".format(blk.name)
|
|
803
|
+
)
|
|
804
|
+
if any(
|
|
805
|
+
blk.ratioP[t].fixed and (value(blk.ratioP[t]) > 1.0)
|
|
806
|
+
for t in blk.flowsheet().time
|
|
807
|
+
):
|
|
808
|
+
_log.warning(
|
|
809
|
+
"{} Expander/turbine set with ratioP greater "
|
|
810
|
+
"than 1.".format(blk.name)
|
|
811
|
+
)
|
|
812
|
+
if any(
|
|
813
|
+
blk.control_volume.properties_out[t].pressure.fixed
|
|
814
|
+
and (
|
|
815
|
+
value(blk.control_volume.properties_in[t].pressure)
|
|
816
|
+
< value(blk.control_volume.properties_out[t].pressure)
|
|
817
|
+
)
|
|
818
|
+
for t in blk.flowsheet().time
|
|
819
|
+
):
|
|
820
|
+
_log.warning(
|
|
821
|
+
"{} Expander/turbine maybe set with pressure "
|
|
822
|
+
"increase.".format(blk.name),
|
|
823
|
+
)
|
|
824
|
+
# Check that work is not positive
|
|
825
|
+
if any(
|
|
826
|
+
blk.work_mechanical[t].fixed and (value(blk.work_mechanical[t]) > 0.0)
|
|
827
|
+
for t in blk.flowsheet().time
|
|
828
|
+
):
|
|
829
|
+
_log.warning(
|
|
830
|
+
"{} Expander/turbine set with positive work.".format(blk.name)
|
|
831
|
+
)
|
|
832
|
+
|
|
833
|
+
# Run holdup block model checks
|
|
834
|
+
blk.control_volume.model_check()
|
|
835
|
+
|
|
836
|
+
# Run model checks on isentropic property block
|
|
837
|
+
try:
|
|
838
|
+
for t in blk.flowsheet().time:
|
|
839
|
+
blk.properties_in[t].model_check()
|
|
840
|
+
except AttributeError:
|
|
841
|
+
pass
|
|
842
|
+
|
|
843
|
+
def initialize_build(
|
|
844
|
+
blk,
|
|
845
|
+
state_args=None,
|
|
846
|
+
routine=None,
|
|
847
|
+
outlvl=idaeslog.NOTSET,
|
|
848
|
+
solver=None,
|
|
849
|
+
optarg=None,
|
|
850
|
+
):
|
|
851
|
+
"""
|
|
852
|
+
General wrapper for pressure changer initialization routines
|
|
853
|
+
|
|
854
|
+
Keyword Arguments:
|
|
855
|
+
routine : str stating which initialization routine to execute
|
|
856
|
+
* None - use routine matching thermodynamic_assumption
|
|
857
|
+
* 'isentropic' - use isentropic initialization routine
|
|
858
|
+
* 'isothermal' - use isothermal initialization routine
|
|
859
|
+
state_args : a dict of arguments to be passed to the property
|
|
860
|
+
package(s) to provide an initial state for
|
|
861
|
+
initialization (see documentation of the specific
|
|
862
|
+
property package) (default = {}).
|
|
863
|
+
outlvl : sets output level of initialization routine
|
|
864
|
+
optarg : solver options dictionary object (default=None, use
|
|
865
|
+
default solver options)
|
|
866
|
+
solver : str indicating which solver to use during
|
|
867
|
+
initialization (default = None, use default solver)
|
|
868
|
+
|
|
869
|
+
Returns:
|
|
870
|
+
None
|
|
871
|
+
"""
|
|
872
|
+
if routine is None:
|
|
873
|
+
# Use routine for specific type of unit
|
|
874
|
+
routine = blk.config.thermodynamic_assumption
|
|
875
|
+
|
|
876
|
+
# Call initialization routine
|
|
877
|
+
if routine is ThermodynamicAssumption.isentropic:
|
|
878
|
+
blk.init_isentropic(
|
|
879
|
+
state_args=state_args, outlvl=outlvl, solver=solver, optarg=optarg
|
|
880
|
+
)
|
|
881
|
+
elif routine is ThermodynamicAssumption.adiabatic:
|
|
882
|
+
blk.init_adiabatic(
|
|
883
|
+
state_args=state_args, outlvl=outlvl, solver=solver, optarg=optarg
|
|
884
|
+
)
|
|
885
|
+
else:
|
|
886
|
+
# Call the general initialization routine in UnitModelBlockData
|
|
887
|
+
super().initialize_build(
|
|
888
|
+
state_args=state_args, outlvl=outlvl, solver=solver, optarg=optarg
|
|
889
|
+
)
|
|
890
|
+
|
|
891
|
+
def init_adiabatic(blk, state_args, outlvl, solver, optarg):
|
|
892
|
+
"""
|
|
893
|
+
Initialization routine for adiabatic pressure changers.
|
|
894
|
+
|
|
895
|
+
Keyword Arguments:
|
|
896
|
+
state_args : a dict of arguments to be passed to the property
|
|
897
|
+
package(s) to provide an initial state for
|
|
898
|
+
initialization (see documentation of the specific
|
|
899
|
+
property package) (default = {}).
|
|
900
|
+
outlvl : sets output level of initialization routine
|
|
901
|
+
optarg : solver options dictionary object (default={})
|
|
902
|
+
solver : str indicating which solver to use during
|
|
903
|
+
initialization (default = None)
|
|
904
|
+
|
|
905
|
+
Returns:
|
|
906
|
+
None
|
|
907
|
+
"""
|
|
908
|
+
init_log = idaeslog.getInitLogger(blk.name, outlvl, tag="unit")
|
|
909
|
+
solve_log = idaeslog.getSolveLogger(blk.name, outlvl, tag="unit")
|
|
910
|
+
|
|
911
|
+
# Create solver
|
|
912
|
+
opt = get_solver(solver, optarg)
|
|
913
|
+
|
|
914
|
+
cv = blk.control_volume
|
|
915
|
+
t0 = blk.flowsheet().time.first()
|
|
916
|
+
|
|
917
|
+
if state_args is None:
|
|
918
|
+
state_args = {}
|
|
919
|
+
state_dict = cv.properties_in[t0].define_port_members()
|
|
920
|
+
|
|
921
|
+
for k in state_dict.keys():
|
|
922
|
+
if state_dict[k].is_indexed():
|
|
923
|
+
state_args[k] = {}
|
|
924
|
+
for m in state_dict[k].keys():
|
|
925
|
+
state_args[k][m] = state_dict[k][m].value
|
|
926
|
+
else:
|
|
927
|
+
state_args[k] = state_dict[k].value
|
|
928
|
+
|
|
929
|
+
# Initialize state blocks
|
|
930
|
+
flags = cv.properties_in.initialize(
|
|
931
|
+
outlvl=outlvl,
|
|
932
|
+
optarg=optarg,
|
|
933
|
+
solver=solver,
|
|
934
|
+
hold_state=True,
|
|
935
|
+
state_args=state_args,
|
|
936
|
+
)
|
|
937
|
+
|
|
938
|
+
# Get initialisation guesses for outlet state
|
|
939
|
+
state_args_out = {}
|
|
940
|
+
# refresh state_dict (may have changed during initialization)
|
|
941
|
+
state_dict = cv.properties_in[t0].define_port_members()
|
|
942
|
+
for k in state_args:
|
|
943
|
+
if k == "pressure" and k not in state_args_out:
|
|
944
|
+
# Work out how to estimate outlet pressure
|
|
945
|
+
if cv.properties_out[t0].pressure.fixed:
|
|
946
|
+
# Fixed outlet pressure, use this value
|
|
947
|
+
state_args_out[k] = value(cv.properties_out[t0].pressure)
|
|
948
|
+
elif blk.deltaP[t0].fixed:
|
|
949
|
+
state_args_out[k] = value(
|
|
950
|
+
cv.properties_in[t0].pressure + blk.deltaP[t0]
|
|
951
|
+
)
|
|
952
|
+
elif blk.ratioP[t0].fixed:
|
|
953
|
+
state_args_out[k] = value(
|
|
954
|
+
cv.properties_in[t0].pressure * blk.ratioP[t0]
|
|
955
|
+
)
|
|
956
|
+
else:
|
|
957
|
+
# Not obvious what to do, use inlet state
|
|
958
|
+
state_args_out[k] = value(cv.properties_in[t0].pressure)
|
|
959
|
+
elif k not in state_args_out:
|
|
960
|
+
# use the inlet state as a guess for the outlet state
|
|
961
|
+
if state_dict[k].is_indexed():
|
|
962
|
+
state_args_out[k] = {}
|
|
963
|
+
for m in state_dict[k].keys():
|
|
964
|
+
state_args_out[k][m] = state_dict[k][m].value
|
|
965
|
+
else:
|
|
966
|
+
state_args_out[k] = state_dict[k].value
|
|
967
|
+
|
|
968
|
+
cv.properties_out.initialize(
|
|
969
|
+
outlvl=outlvl,
|
|
970
|
+
optarg=optarg,
|
|
971
|
+
solver=solver,
|
|
972
|
+
hold_state=False,
|
|
973
|
+
state_args=state_args_out,
|
|
974
|
+
)
|
|
975
|
+
init_log.info_high("Initialization Step 1 Complete.")
|
|
976
|
+
|
|
977
|
+
# ---------------------------------------------------------------------
|
|
978
|
+
# Solve unit
|
|
979
|
+
with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc:
|
|
980
|
+
res = opt.solve(blk, tee=slc.tee)
|
|
981
|
+
init_log.info_high("Initialization Step 2 {}.".format(idaeslog.condition(res)))
|
|
982
|
+
|
|
983
|
+
# ---------------------------------------------------------------------
|
|
984
|
+
# Release Inlet state
|
|
985
|
+
blk.control_volume.release_state(flags, outlvl)
|
|
986
|
+
|
|
987
|
+
if not check_optimal_termination(res):
|
|
988
|
+
raise InitializationError(
|
|
989
|
+
f"{blk.name} failed to initialize successfully. Please check "
|
|
990
|
+
f"the output logs for more information."
|
|
991
|
+
)
|
|
992
|
+
|
|
993
|
+
init_log.info(f"Initialization Complete: {idaeslog.condition(res)}")
|
|
994
|
+
|
|
995
|
+
def init_isentropic(blk, state_args, outlvl, solver, optarg):
|
|
996
|
+
"""
|
|
997
|
+
Initialization routine for isentropic pressure changers.
|
|
998
|
+
|
|
999
|
+
Keyword Arguments:
|
|
1000
|
+
state_args : a dict of arguments to be passed to the property
|
|
1001
|
+
package(s) to provide an initial state for
|
|
1002
|
+
initialization (see documentation of the specific
|
|
1003
|
+
property package) (default = {}).
|
|
1004
|
+
outlvl : sets output level of initialization routine
|
|
1005
|
+
optarg : solver options dictionary object (default={})
|
|
1006
|
+
solver : str indicating which solver to use during
|
|
1007
|
+
initialization (default = None)
|
|
1008
|
+
|
|
1009
|
+
Returns:
|
|
1010
|
+
None
|
|
1011
|
+
"""
|
|
1012
|
+
init_log = idaeslog.getInitLogger(blk.name, outlvl, tag="unit")
|
|
1013
|
+
solve_log = idaeslog.getSolveLogger(blk.name, outlvl, tag="unit")
|
|
1014
|
+
|
|
1015
|
+
# Create solver
|
|
1016
|
+
opt = get_solver(solver, optarg)
|
|
1017
|
+
|
|
1018
|
+
cv = blk.control_volume
|
|
1019
|
+
t0 = blk.flowsheet().time.first()
|
|
1020
|
+
|
|
1021
|
+
# performance curves exist and are active so initialize with them
|
|
1022
|
+
activate_performance_curves = (
|
|
1023
|
+
hasattr(blk, "performance_curve")
|
|
1024
|
+
and blk.performance_curve.has_constraints()
|
|
1025
|
+
and blk.performance_curve.active
|
|
1026
|
+
)
|
|
1027
|
+
if activate_performance_curves:
|
|
1028
|
+
blk.performance_curve.deactivate()
|
|
1029
|
+
# The performance curves will provide (maybe indirectly) efficiency
|
|
1030
|
+
# and/or pressure ratio. To get through the standard isentropic
|
|
1031
|
+
# pressure changer init, we'll see if the user provided a guess for
|
|
1032
|
+
# pressure ratio or isentropic efficiency and fix them if needed. If
|
|
1033
|
+
# not fixed and no guess provided, fill in something reasonable
|
|
1034
|
+
# until the performance curves are turned on.
|
|
1035
|
+
unfix_eff = {}
|
|
1036
|
+
unfix_ratioP = {}
|
|
1037
|
+
for t in blk.flowsheet().time:
|
|
1038
|
+
if not (
|
|
1039
|
+
blk.ratioP[t].fixed
|
|
1040
|
+
or blk.deltaP[t].fixed
|
|
1041
|
+
or cv.properties_out[t].pressure.fixed
|
|
1042
|
+
):
|
|
1043
|
+
if blk.config.compressor:
|
|
1044
|
+
if not (
|
|
1045
|
+
value(blk.ratioP[t]) >= 1.01 and value(blk.ratioP[t]) <= 50
|
|
1046
|
+
):
|
|
1047
|
+
blk.ratioP[t] = 1.8
|
|
1048
|
+
else:
|
|
1049
|
+
if not (
|
|
1050
|
+
value(blk.ratioP[t]) >= 0.01
|
|
1051
|
+
and value(blk.ratioP[t]) <= 0.999
|
|
1052
|
+
):
|
|
1053
|
+
blk.ratioP[t] = 0.7
|
|
1054
|
+
blk.ratioP[t].fix()
|
|
1055
|
+
unfix_ratioP[t] = True
|
|
1056
|
+
if not blk.efficiency_isentropic[t].fixed:
|
|
1057
|
+
if not (
|
|
1058
|
+
value(blk.efficiency_isentropic[t]) >= 0.05
|
|
1059
|
+
and value(blk.efficiency_isentropic[t]) <= 1.0
|
|
1060
|
+
):
|
|
1061
|
+
blk.efficiency_isentropic[t] = 0.8
|
|
1062
|
+
blk.efficiency_isentropic[t].fix()
|
|
1063
|
+
unfix_eff[t] = True
|
|
1064
|
+
|
|
1065
|
+
if state_args is None:
|
|
1066
|
+
state_args = {}
|
|
1067
|
+
state_dict = cv.properties_in[t0].define_port_members()
|
|
1068
|
+
|
|
1069
|
+
for k in state_dict.keys():
|
|
1070
|
+
if state_dict[k].is_indexed():
|
|
1071
|
+
state_args[k] = {}
|
|
1072
|
+
for m in state_dict[k].keys():
|
|
1073
|
+
state_args[k][m] = state_dict[k][m].value
|
|
1074
|
+
else:
|
|
1075
|
+
state_args[k] = state_dict[k].value
|
|
1076
|
+
|
|
1077
|
+
# Initialize state blocks
|
|
1078
|
+
flags = cv.properties_in.initialize(
|
|
1079
|
+
outlvl=outlvl,
|
|
1080
|
+
optarg=optarg,
|
|
1081
|
+
solver=solver,
|
|
1082
|
+
hold_state=True,
|
|
1083
|
+
state_args=state_args,
|
|
1084
|
+
)
|
|
1085
|
+
|
|
1086
|
+
# Get initialisation guesses for outlet and isentropic states
|
|
1087
|
+
state_args_out = {}
|
|
1088
|
+
# refresh state_dict (may have changed during initialization)
|
|
1089
|
+
state_dict = cv.properties_in[t0].define_port_members()
|
|
1090
|
+
for k in state_args:
|
|
1091
|
+
if k == "pressure" and k not in state_args_out:
|
|
1092
|
+
# Work out how to estimate outlet pressure
|
|
1093
|
+
if cv.properties_out[t0].pressure.fixed:
|
|
1094
|
+
# Fixed outlet pressure, use this value
|
|
1095
|
+
state_args_out[k] = value(cv.properties_out[t0].pressure)
|
|
1096
|
+
elif blk.deltaP[t0].fixed:
|
|
1097
|
+
state_args_out[k] = value(
|
|
1098
|
+
cv.properties_in[t0].pressure + blk.deltaP[t0]
|
|
1099
|
+
)
|
|
1100
|
+
elif blk.ratioP[t0].fixed:
|
|
1101
|
+
state_args_out[k] = value(
|
|
1102
|
+
cv.properties_in[t0].pressure * blk.ratioP[t0]
|
|
1103
|
+
)
|
|
1104
|
+
else:
|
|
1105
|
+
# Not obvious what to do, use inlet state
|
|
1106
|
+
state_args_out[k] = value(cv.properties_in[t0].pressure)
|
|
1107
|
+
elif k not in state_args_out:
|
|
1108
|
+
# use the inlet state as a guess for the outlet state
|
|
1109
|
+
if state_dict[k].is_indexed():
|
|
1110
|
+
state_args_out[k] = {}
|
|
1111
|
+
for m in state_dict[k].keys():
|
|
1112
|
+
state_args_out[k][m] = state_dict[k][m].value
|
|
1113
|
+
else:
|
|
1114
|
+
state_args_out[k] = state_dict[k].value
|
|
1115
|
+
|
|
1116
|
+
cv.properties_out.initialize(
|
|
1117
|
+
outlvl=outlvl,
|
|
1118
|
+
optarg=optarg,
|
|
1119
|
+
solver=solver,
|
|
1120
|
+
hold_state=False,
|
|
1121
|
+
state_args=state_args_out,
|
|
1122
|
+
)
|
|
1123
|
+
|
|
1124
|
+
init_log.info_high("Initialization Step 1 Complete.")
|
|
1125
|
+
# ---------------------------------------------------------------------
|
|
1126
|
+
# Initialize Isentropic block
|
|
1127
|
+
|
|
1128
|
+
blk.properties_isentropic.initialize(
|
|
1129
|
+
outlvl=outlvl,
|
|
1130
|
+
optarg=optarg,
|
|
1131
|
+
solver=solver,
|
|
1132
|
+
state_args=state_args_out,
|
|
1133
|
+
)
|
|
1134
|
+
|
|
1135
|
+
init_log.info_high("Initialization Step 2 Complete.")
|
|
1136
|
+
|
|
1137
|
+
# ---------------------------------------------------------------------
|
|
1138
|
+
# Solve for isothermal conditions
|
|
1139
|
+
if isinstance(
|
|
1140
|
+
blk.properties_isentropic[blk.flowsheet().time.first()].temperature,
|
|
1141
|
+
Var,
|
|
1142
|
+
):
|
|
1143
|
+
blk.properties_isentropic[:].temperature.fix()
|
|
1144
|
+
elif isinstance(
|
|
1145
|
+
blk.properties_isentropic[blk.flowsheet().time.first()].enth_mol,
|
|
1146
|
+
Var,
|
|
1147
|
+
):
|
|
1148
|
+
blk.properties_isentropic[:].enth_mol.fix()
|
|
1149
|
+
elif isinstance(
|
|
1150
|
+
blk.properties_isentropic[blk.flowsheet().time.first()].temperature,
|
|
1151
|
+
Expression,
|
|
1152
|
+
):
|
|
1153
|
+
|
|
1154
|
+
def tmp_rule(self, t):
|
|
1155
|
+
return (
|
|
1156
|
+
blk.properties_isentropic[t].temperature
|
|
1157
|
+
== blk.control_volume.properties_in[t].temperature
|
|
1158
|
+
)
|
|
1159
|
+
|
|
1160
|
+
blk.tmp_init_constraint = Constraint(blk.flowsheet().time, rule=tmp_rule)
|
|
1161
|
+
|
|
1162
|
+
blk.isentropic.deactivate()
|
|
1163
|
+
|
|
1164
|
+
with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc:
|
|
1165
|
+
res = opt.solve(blk, tee=slc.tee)
|
|
1166
|
+
init_log.info_high("Initialization Step 3 {}.".format(idaeslog.condition(res)))
|
|
1167
|
+
|
|
1168
|
+
if isinstance(
|
|
1169
|
+
blk.properties_isentropic[blk.flowsheet().time.first()].temperature,
|
|
1170
|
+
Var,
|
|
1171
|
+
):
|
|
1172
|
+
blk.properties_isentropic[:].temperature.unfix()
|
|
1173
|
+
elif isinstance(
|
|
1174
|
+
blk.properties_isentropic[blk.flowsheet().time.first()].enth_mol,
|
|
1175
|
+
Var,
|
|
1176
|
+
):
|
|
1177
|
+
blk.properties_isentropic[:].enth_mol.unfix()
|
|
1178
|
+
elif isinstance(
|
|
1179
|
+
blk.properties_isentropic[blk.flowsheet().time.first()].temperature,
|
|
1180
|
+
Expression,
|
|
1181
|
+
):
|
|
1182
|
+
blk.del_component(blk.tmp_init_constraint)
|
|
1183
|
+
|
|
1184
|
+
blk.isentropic.activate()
|
|
1185
|
+
|
|
1186
|
+
# ---------------------------------------------------------------------
|
|
1187
|
+
# Solve unit
|
|
1188
|
+
with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc:
|
|
1189
|
+
res = opt.solve(blk, tee=slc.tee)
|
|
1190
|
+
init_log.info_high("Initialization Step 4 {}.".format(idaeslog.condition(res)))
|
|
1191
|
+
|
|
1192
|
+
if activate_performance_curves:
|
|
1193
|
+
blk.performance_curve.activate()
|
|
1194
|
+
for t, v in unfix_eff.items():
|
|
1195
|
+
if v:
|
|
1196
|
+
blk.efficiency_isentropic[t].unfix()
|
|
1197
|
+
for t, v in unfix_ratioP.items():
|
|
1198
|
+
if v:
|
|
1199
|
+
blk.ratioP[t].unfix()
|
|
1200
|
+
with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc:
|
|
1201
|
+
res = opt.solve(blk, tee=slc.tee)
|
|
1202
|
+
init_log.info_high(f"Initialization Step 5 {idaeslog.condition(res)}.")
|
|
1203
|
+
|
|
1204
|
+
# ---------------------------------------------------------------------
|
|
1205
|
+
# Release Inlet state
|
|
1206
|
+
blk.control_volume.release_state(flags, outlvl)
|
|
1207
|
+
|
|
1208
|
+
if not check_optimal_termination(res):
|
|
1209
|
+
raise InitializationError(
|
|
1210
|
+
f"{blk.name} failed to initialize successfully. Please check "
|
|
1211
|
+
f"the output logs for more information."
|
|
1212
|
+
)
|
|
1213
|
+
|
|
1214
|
+
init_log.info(f"Initialization Complete: {idaeslog.condition(res)}")
|
|
1215
|
+
|
|
1216
|
+
def _get_performance_contents(self, time_point=0):
|
|
1217
|
+
var_dict = {}
|
|
1218
|
+
if hasattr(self, "deltaP"):
|
|
1219
|
+
var_dict["Mechanical Work"] = self.work_mechanical[time_point]
|
|
1220
|
+
if hasattr(self, "deltaP"):
|
|
1221
|
+
var_dict["Pressure Change"] = self.deltaP[time_point]
|
|
1222
|
+
if hasattr(self, "ratioP"):
|
|
1223
|
+
var_dict["Pressure Ratio"] = self.ratioP[time_point]
|
|
1224
|
+
if hasattr(self, "efficiency_pump"):
|
|
1225
|
+
var_dict["Efficiency"] = self.efficiency_pump[time_point]
|
|
1226
|
+
if hasattr(self, "efficiency_isentropic"):
|
|
1227
|
+
var_dict["Isentropic Efficiency"] = self.efficiency_isentropic[time_point]
|
|
1228
|
+
|
|
1229
|
+
return {"vars": var_dict}
|
|
1230
|
+
|
|
1231
|
+
def calculate_scaling_factors(self):
|
|
1232
|
+
super().calculate_scaling_factors()
|
|
1233
|
+
|
|
1234
|
+
if hasattr(self, "work_fluid"):
|
|
1235
|
+
for t, v in self.work_fluid.items():
|
|
1236
|
+
iscale.set_scaling_factor(
|
|
1237
|
+
v,
|
|
1238
|
+
iscale.get_scaling_factor(
|
|
1239
|
+
self.control_volume.work[t], default=1, warning=True
|
|
1240
|
+
),
|
|
1241
|
+
)
|
|
1242
|
+
|
|
1243
|
+
if hasattr(self, "work_mechanical"):
|
|
1244
|
+
for t, v in self.work_mechanical.items():
|
|
1245
|
+
iscale.set_scaling_factor(
|
|
1246
|
+
v,
|
|
1247
|
+
iscale.get_scaling_factor(
|
|
1248
|
+
self.control_volume.work[t], default=1, warning=True
|
|
1249
|
+
),
|
|
1250
|
+
)
|
|
1251
|
+
|
|
1252
|
+
if hasattr(self, "work_isentropic"):
|
|
1253
|
+
for t, v in self.work_isentropic.items():
|
|
1254
|
+
iscale.set_scaling_factor(
|
|
1255
|
+
v,
|
|
1256
|
+
iscale.get_scaling_factor(
|
|
1257
|
+
self.control_volume.work[t], default=1, warning=True
|
|
1258
|
+
),
|
|
1259
|
+
)
|
|
1260
|
+
|
|
1261
|
+
if hasattr(self, "ratioP_calculation"):
|
|
1262
|
+
for t, c in self.ratioP_calculation.items():
|
|
1263
|
+
iscale.constraint_scaling_transform(
|
|
1264
|
+
c,
|
|
1265
|
+
iscale.get_scaling_factor(
|
|
1266
|
+
self.control_volume.properties_in[t].pressure,
|
|
1267
|
+
default=1,
|
|
1268
|
+
warning=True,
|
|
1269
|
+
),
|
|
1270
|
+
overwrite=False,
|
|
1271
|
+
)
|
|
1272
|
+
|
|
1273
|
+
if hasattr(self, "fluid_work_calculation"):
|
|
1274
|
+
for t, c in self.fluid_work_calculation.items():
|
|
1275
|
+
iscale.constraint_scaling_transform(
|
|
1276
|
+
c,
|
|
1277
|
+
iscale.get_scaling_factor(
|
|
1278
|
+
self.control_volume.deltaP[t], default=1, warning=True
|
|
1279
|
+
),
|
|
1280
|
+
overwrite=False,
|
|
1281
|
+
)
|
|
1282
|
+
|
|
1283
|
+
if hasattr(self, "actual_work"):
|
|
1284
|
+
for t, c in self.actual_work.items():
|
|
1285
|
+
iscale.constraint_scaling_transform(
|
|
1286
|
+
c,
|
|
1287
|
+
iscale.get_scaling_factor(
|
|
1288
|
+
self.control_volume.work[t], default=1, warning=True
|
|
1289
|
+
),
|
|
1290
|
+
overwrite=False,
|
|
1291
|
+
)
|
|
1292
|
+
|
|
1293
|
+
if hasattr(self, "isentropic_pressure"):
|
|
1294
|
+
for t, c in self.isentropic_pressure.items():
|
|
1295
|
+
iscale.constraint_scaling_transform(
|
|
1296
|
+
c,
|
|
1297
|
+
iscale.get_scaling_factor(
|
|
1298
|
+
self.control_volume.properties_in[t].pressure,
|
|
1299
|
+
default=1,
|
|
1300
|
+
warning=True,
|
|
1301
|
+
),
|
|
1302
|
+
overwrite=False,
|
|
1303
|
+
)
|
|
1304
|
+
|
|
1305
|
+
if hasattr(self, "isentropic"):
|
|
1306
|
+
for t, c in self.isentropic.items():
|
|
1307
|
+
iscale.constraint_scaling_transform(
|
|
1308
|
+
c,
|
|
1309
|
+
iscale.get_scaling_factor(
|
|
1310
|
+
self.control_volume.properties_in[t].entr_mol,
|
|
1311
|
+
default=1,
|
|
1312
|
+
warning=True,
|
|
1313
|
+
),
|
|
1314
|
+
overwrite=False,
|
|
1315
|
+
)
|
|
1316
|
+
|
|
1317
|
+
if hasattr(self, "isentropic_energy_balance"):
|
|
1318
|
+
for t, c in self.isentropic_energy_balance.items():
|
|
1319
|
+
iscale.constraint_scaling_transform(
|
|
1320
|
+
c,
|
|
1321
|
+
iscale.get_scaling_factor(
|
|
1322
|
+
self.control_volume.work[t], default=1, warning=True
|
|
1323
|
+
),
|
|
1324
|
+
overwrite=False,
|
|
1325
|
+
)
|
|
1326
|
+
|
|
1327
|
+
if hasattr(self, "zero_work_equation"):
|
|
1328
|
+
for t, c in self.zero_work_equation.items():
|
|
1329
|
+
iscale.constraint_scaling_transform(
|
|
1330
|
+
c,
|
|
1331
|
+
iscale.get_scaling_factor(
|
|
1332
|
+
self.control_volume.work[t], default=1, warning=True
|
|
1333
|
+
),
|
|
1334
|
+
)
|
|
1335
|
+
|
|
1336
|
+
if hasattr(self, "state_material_balances"):
|
|
1337
|
+
cvol = self.control_volume
|
|
1338
|
+
phase_list = cvol.properties_in.phase_list
|
|
1339
|
+
phase_component_set = cvol.properties_in.phase_component_set
|
|
1340
|
+
mb_type = cvol._constructed_material_balance_type
|
|
1341
|
+
if mb_type == MaterialBalanceType.componentPhase:
|
|
1342
|
+
for (t, p, j), c in self.state_material_balances.items():
|
|
1343
|
+
sf = iscale.get_scaling_factor(
|
|
1344
|
+
cvol.properties_in[t].get_material_flow_terms(p, j),
|
|
1345
|
+
default=1,
|
|
1346
|
+
warning=True,
|
|
1347
|
+
)
|
|
1348
|
+
iscale.constraint_scaling_transform(c, sf)
|
|
1349
|
+
elif mb_type == MaterialBalanceType.componentTotal:
|
|
1350
|
+
for (t, j), c in self.state_material_balances.items():
|
|
1351
|
+
sf = iscale.min_scaling_factor(
|
|
1352
|
+
[
|
|
1353
|
+
cvol.properties_in[t].get_material_flow_terms(p, j)
|
|
1354
|
+
for p in phase_list
|
|
1355
|
+
if (p, j) in phase_component_set
|
|
1356
|
+
]
|
|
1357
|
+
)
|
|
1358
|
+
iscale.constraint_scaling_transform(c, sf)
|
|
1359
|
+
else:
|
|
1360
|
+
# There are some other material balance types but they create
|
|
1361
|
+
# constraints with different names.
|
|
1362
|
+
_log.warning(f"Unknown material balance type {mb_type}")
|
|
1363
|
+
|
|
1364
|
+
|
|
1365
|
+
@declare_process_block_class("Turbine", doc="Isentropic turbine model")
|
|
1366
|
+
class TurbineData(PressureChangerData):
|
|
1367
|
+
"""
|
|
1368
|
+
Pressure changer with isentropic turbine options
|
|
1369
|
+
"""
|
|
1370
|
+
|
|
1371
|
+
default_initializer = IsentropicPressureChangerInitializer
|
|
1372
|
+
|
|
1373
|
+
CONFIG = PressureChangerData.CONFIG()
|
|
1374
|
+
CONFIG.compressor = False
|
|
1375
|
+
CONFIG.get("compressor")._default = False
|
|
1376
|
+
CONFIG.get("compressor")._domain = In([False])
|
|
1377
|
+
CONFIG.thermodynamic_assumption = ThermodynamicAssumption.isentropic
|
|
1378
|
+
CONFIG.get("thermodynamic_assumption")._default = ThermodynamicAssumption.isentropic
|
|
1379
|
+
|
|
1380
|
+
|
|
1381
|
+
@declare_process_block_class("Compressor", doc="Isentropic compressor model")
|
|
1382
|
+
class CompressorData(PressureChangerData):
|
|
1383
|
+
"""Pressure changer with isentropic compressor options"""
|
|
1384
|
+
|
|
1385
|
+
default_initializer = IsentropicPressureChangerInitializer
|
|
1386
|
+
|
|
1387
|
+
CONFIG = PressureChangerData.CONFIG()
|
|
1388
|
+
CONFIG.compressor = True
|
|
1389
|
+
CONFIG.get("compressor")._default = True
|
|
1390
|
+
CONFIG.get("compressor")._domain = In([True])
|
|
1391
|
+
CONFIG.thermodynamic_assumption = ThermodynamicAssumption.isentropic
|
|
1392
|
+
CONFIG.get("thermodynamic_assumption")._default = ThermodynamicAssumption.isentropic
|
|
1393
|
+
|
|
1394
|
+
|
|
1395
|
+
@declare_process_block_class("Pump", doc="Pump model")
|
|
1396
|
+
class PumpData(PressureChangerData):
|
|
1397
|
+
"""Pressure changer with pump options"""
|
|
1398
|
+
|
|
1399
|
+
CONFIG = PressureChangerData.CONFIG()
|
|
1400
|
+
CONFIG.compressor = True
|
|
1401
|
+
CONFIG.get("compressor")._default = True
|
|
1402
|
+
CONFIG.get("compressor")._domain = In([True])
|
|
1403
|
+
CONFIG.thermodynamic_assumption = ThermodynamicAssumption.pump
|
|
1404
|
+
CONFIG.get("thermodynamic_assumption")._default = ThermodynamicAssumption.pump
|