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,567 @@
|
|
|
1
|
+
#################################################################################
|
|
2
|
+
# The Institute for the Design of Advanced Energy Systems Integrated Platform
|
|
3
|
+
# Framework (IDAES IP) was produced under the DOE Institute for the
|
|
4
|
+
# Design of Advanced Energy Systems (IDAES).
|
|
5
|
+
#
|
|
6
|
+
# Copyright (c) 2018-2024 by the software owners: The Regents of the
|
|
7
|
+
# University of California, through Lawrence Berkeley National Laboratory,
|
|
8
|
+
# National Technology & Engineering Solutions of Sandia, LLC, Carnegie Mellon
|
|
9
|
+
# University, West Virginia University Research Corporation, et al.
|
|
10
|
+
# All rights reserved. Please see the files COPYRIGHT.md and LICENSE.md
|
|
11
|
+
# for full copyright and license information.
|
|
12
|
+
#################################################################################
|
|
13
|
+
"""
|
|
14
|
+
Heat Exchanger Models.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
__author__ = "Team Ahuora"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# Import Pyomo libraries
|
|
22
|
+
from pyomo.environ import (
|
|
23
|
+
Block,
|
|
24
|
+
Var,
|
|
25
|
+
Param,
|
|
26
|
+
log,
|
|
27
|
+
Reference,
|
|
28
|
+
PositiveReals,
|
|
29
|
+
ExternalFunction,
|
|
30
|
+
units as pyunits,
|
|
31
|
+
check_optimal_termination,
|
|
32
|
+
)
|
|
33
|
+
from pyomo.common.config import ConfigBlock, ConfigValue, In
|
|
34
|
+
|
|
35
|
+
# Import IDAES cores
|
|
36
|
+
from idaes.core import (
|
|
37
|
+
declare_process_block_class,
|
|
38
|
+
UnitModelBlockData,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
import idaes.logger as idaeslog
|
|
42
|
+
from idaes.core.util.functions import functions_lib
|
|
43
|
+
from idaes.core.util.tables import create_stream_table_dataframe
|
|
44
|
+
from idaes.models.unit_models.heater import (
|
|
45
|
+
_make_heater_config_block,
|
|
46
|
+
_make_heater_control_volume,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
from idaes.core.util.misc import add_object_reference
|
|
50
|
+
from idaes.core.util import scaling as iscale
|
|
51
|
+
from idaes.core.solvers import get_solver
|
|
52
|
+
from idaes.core.util.exceptions import ConfigurationError, InitializationError
|
|
53
|
+
from idaes.core.initialization import SingleControlVolumeUnitInitializer
|
|
54
|
+
|
|
55
|
+
_log = idaeslog.getLogger(__name__)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class SimpleHeatPumpInitializer(SingleControlVolumeUnitInitializer):
|
|
59
|
+
"""
|
|
60
|
+
Initializer for 0D Heat Exchanger units.
|
|
61
|
+
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
def initialization_routine(
|
|
65
|
+
self,
|
|
66
|
+
model: Block,
|
|
67
|
+
plugin_initializer_args: dict = None,
|
|
68
|
+
copy_inlet_state: bool = False,
|
|
69
|
+
duty=1000 * pyunits.W,
|
|
70
|
+
):
|
|
71
|
+
"""
|
|
72
|
+
Common initialization routine for 0D Heat Exchangers.
|
|
73
|
+
|
|
74
|
+
This routine starts by initializing the hot and cold side properties. Next, the heat
|
|
75
|
+
transfer between the two sides is fixed to an initial guess for the heat duty (provided by the duty
|
|
76
|
+
argument), the associated constraint is deactivated, and the model is then solved. Finally, the heat
|
|
77
|
+
duty is unfixed and the heat transfer constraint reactivated followed by a final solve of the model.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
model: Pyomo Block to be initialized
|
|
81
|
+
plugin_initializer_args: dict-of-dicts containing arguments to be passed to plug-in Initializers.
|
|
82
|
+
Keys should be submodel components.
|
|
83
|
+
copy_inlet_state: bool (default=False). Whether to copy inlet state to other states or not
|
|
84
|
+
(0-D control volumes only). Copying will generally be faster, but inlet states may not contain
|
|
85
|
+
all properties required elsewhere.
|
|
86
|
+
duty: initial guess for heat duty to assist with initialization. Can be a Pyomo expression with units.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
Pyomo solver results object
|
|
90
|
+
"""
|
|
91
|
+
return super(SingleControlVolumeUnitInitializer, self).initialization_routine(
|
|
92
|
+
model=model,
|
|
93
|
+
plugin_initializer_args=plugin_initializer_args,
|
|
94
|
+
copy_inlet_state=copy_inlet_state,
|
|
95
|
+
duty=duty,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
def initialize_main_model(
|
|
99
|
+
self,
|
|
100
|
+
model: Block,
|
|
101
|
+
copy_inlet_state: bool = False,
|
|
102
|
+
duty=1000 * pyunits.W,
|
|
103
|
+
):
|
|
104
|
+
"""
|
|
105
|
+
Initialization routine for main 0D HX models.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
model: Pyomo Block to be initialized.
|
|
109
|
+
copy_inlet_state: bool (default=False). Whether to copy inlet state to other states or not
|
|
110
|
+
(0-D control volumes only). Copying will generally be faster, but inlet states may not contain
|
|
111
|
+
all properties required elsewhere.
|
|
112
|
+
duty: initial guess for heat duty to assist with initialization, default = 1000 W. Can
|
|
113
|
+
be a Pyomo expression with units.
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
Pyomo solver results object.
|
|
117
|
+
|
|
118
|
+
"""
|
|
119
|
+
# Get loggers
|
|
120
|
+
init_log = idaeslog.getInitLogger(
|
|
121
|
+
model.name, self.get_output_level(), tag="unit"
|
|
122
|
+
)
|
|
123
|
+
solve_log = idaeslog.getSolveLogger(
|
|
124
|
+
model.name, self.get_output_level(), tag="unit"
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
# Create solver
|
|
128
|
+
solver = self._get_solver()
|
|
129
|
+
|
|
130
|
+
self.initialize_control_volume(model.source, copy_inlet_state)
|
|
131
|
+
init_log.info_high("Initialization Step 1a (heat output (sink)) Complete.")
|
|
132
|
+
|
|
133
|
+
self.initialize_control_volume(model.sink, copy_inlet_state)
|
|
134
|
+
init_log.info_high("Initialization Step 1b (cold side) Complete.")
|
|
135
|
+
# ---------------------------------------------------------------------
|
|
136
|
+
# Solve unit without heat transfer equation
|
|
137
|
+
model.heat_transfer_equation.deactivate()
|
|
138
|
+
|
|
139
|
+
# Check to see if heat duty is fixed
|
|
140
|
+
# We will assume that if the first point is fixed, it is fixed at all points
|
|
141
|
+
if not model.sink.heat[model.flowsheet().time.first()].fixed:
|
|
142
|
+
cs_fixed = False
|
|
143
|
+
|
|
144
|
+
model.sink.heat.fix(duty)
|
|
145
|
+
for i in model.source.heat:
|
|
146
|
+
model.source.heat[i].set_value(-duty)
|
|
147
|
+
else:
|
|
148
|
+
cs_fixed = True
|
|
149
|
+
for i in model.source.heat:
|
|
150
|
+
model.source.heat[i].set_value(model.sink.heat[i])
|
|
151
|
+
|
|
152
|
+
with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc:
|
|
153
|
+
res = solver.solve(model, tee=slc.tee)
|
|
154
|
+
init_log.info_high("Initialization Step 2 {}.".format(idaeslog.condition(res)))
|
|
155
|
+
if not cs_fixed:
|
|
156
|
+
model.sink.heat.unfix()
|
|
157
|
+
model.heat_transfer_equation.activate()
|
|
158
|
+
# ---------------------------------------------------------------------
|
|
159
|
+
# Solve unit
|
|
160
|
+
with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc:
|
|
161
|
+
res = solver.solve(model, tee=slc.tee)
|
|
162
|
+
init_log.info("Initialization Completed, {}".format(idaeslog.condition(res)))
|
|
163
|
+
|
|
164
|
+
return res
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _make_heat_pump_config(config):
|
|
168
|
+
"""
|
|
169
|
+
Declare configuration options for HeatExchangerData block.
|
|
170
|
+
"""
|
|
171
|
+
config.declare(
|
|
172
|
+
"source",
|
|
173
|
+
ConfigBlock(
|
|
174
|
+
description="Config block for heat output (sink)",
|
|
175
|
+
doc="""A config block used to construct the heat output (sink) control volume.
|
|
176
|
+
This config can be given by the heat output (sink) name instead of source.""",
|
|
177
|
+
),
|
|
178
|
+
)
|
|
179
|
+
config.declare(
|
|
180
|
+
"sink",
|
|
181
|
+
ConfigBlock(
|
|
182
|
+
description="Config block for cold side",
|
|
183
|
+
doc="""A config block used to construct the cold side control volume.
|
|
184
|
+
This config can be given by the cold side name instead of sink.""",
|
|
185
|
+
),
|
|
186
|
+
)
|
|
187
|
+
_make_heater_config_block(config.source)
|
|
188
|
+
_make_heater_config_block(config.sink)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
@declare_process_block_class("SimpleHeatPump", doc="Simple 0D heat pump model.")
|
|
192
|
+
class SimpleHeatPumpData(UnitModelBlockData):
|
|
193
|
+
"""
|
|
194
|
+
Simple 0D heat pump unit.
|
|
195
|
+
Unit model to transfer heat from one material to another.
|
|
196
|
+
"""
|
|
197
|
+
|
|
198
|
+
default_initializer = SimpleHeatPumpInitializer
|
|
199
|
+
|
|
200
|
+
CONFIG = UnitModelBlockData.CONFIG(implicit=True)
|
|
201
|
+
_make_heat_pump_config(CONFIG)
|
|
202
|
+
|
|
203
|
+
def build(self):
|
|
204
|
+
"""
|
|
205
|
+
Building model
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
None
|
|
209
|
+
Returns:
|
|
210
|
+
None
|
|
211
|
+
"""
|
|
212
|
+
########################################################################
|
|
213
|
+
# Call UnitModel.build to setup dynamics and configure #
|
|
214
|
+
########################################################################
|
|
215
|
+
super().build()
|
|
216
|
+
config = self.config
|
|
217
|
+
|
|
218
|
+
########################################################################
|
|
219
|
+
# Add control volumes #
|
|
220
|
+
########################################################################
|
|
221
|
+
source = _make_heater_control_volume(
|
|
222
|
+
self,
|
|
223
|
+
"source",
|
|
224
|
+
config.source,
|
|
225
|
+
dynamic=config.dynamic,
|
|
226
|
+
has_holdup=config.has_holdup,
|
|
227
|
+
)
|
|
228
|
+
sink = _make_heater_control_volume(
|
|
229
|
+
self,
|
|
230
|
+
"sink",
|
|
231
|
+
config.sink,
|
|
232
|
+
dynamic=config.dynamic,
|
|
233
|
+
has_holdup=config.has_holdup,
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
########################################################################
|
|
237
|
+
# Add variables #
|
|
238
|
+
########################################################################
|
|
239
|
+
# Use heat output (sink) units as basis
|
|
240
|
+
s1_metadata = self.source.config.property_package.get_metadata()
|
|
241
|
+
|
|
242
|
+
q_units = s1_metadata.get_derived_units("power")
|
|
243
|
+
temp_units = s1_metadata.get_derived_units("temperature")
|
|
244
|
+
|
|
245
|
+
self.work_mechanical = Var(
|
|
246
|
+
self.flowsheet().time,
|
|
247
|
+
domain=PositiveReals,
|
|
248
|
+
initialize=100.0,
|
|
249
|
+
doc="Mechanical work input",
|
|
250
|
+
units=q_units,
|
|
251
|
+
)
|
|
252
|
+
self.coefficient_of_performance = Var(
|
|
253
|
+
domain=PositiveReals,
|
|
254
|
+
initialize=2.0,
|
|
255
|
+
doc="Coefficient of performance",
|
|
256
|
+
units=pyunits.dimensionless,
|
|
257
|
+
)
|
|
258
|
+
self.efficiency = Var(
|
|
259
|
+
domain=PositiveReals,
|
|
260
|
+
initialize=0.5,
|
|
261
|
+
doc="Efficiency (Carnot = 1)",
|
|
262
|
+
units=pyunits.dimensionless,
|
|
263
|
+
)
|
|
264
|
+
self.delta_temperature_lift = Var(
|
|
265
|
+
self.flowsheet().time,
|
|
266
|
+
domain=PositiveReals,
|
|
267
|
+
initialize=10.0,
|
|
268
|
+
doc="Temperature lift between source inlet and sink outlet",
|
|
269
|
+
units=temp_units,
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
self.approach_temperature = Var(
|
|
273
|
+
domain=PositiveReals,
|
|
274
|
+
initialize=10.0,
|
|
275
|
+
doc="Approach temperature of refrigerant",
|
|
276
|
+
units=temp_units,
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
self.heat_duty = Reference(sink.heat)
|
|
280
|
+
########################################################################
|
|
281
|
+
# Add ports #
|
|
282
|
+
########################################################################
|
|
283
|
+
self.add_inlet_port(name="source_inlet", block=source, doc="heat output (sink) inlet")
|
|
284
|
+
self.add_inlet_port(
|
|
285
|
+
name="sink_inlet",
|
|
286
|
+
block=sink,
|
|
287
|
+
doc="Cold side inlet",
|
|
288
|
+
)
|
|
289
|
+
self.add_outlet_port(
|
|
290
|
+
name="source_outlet", block=source, doc="heat output (sink) outlet"
|
|
291
|
+
)
|
|
292
|
+
self.add_outlet_port(
|
|
293
|
+
name="sink_outlet",
|
|
294
|
+
block=sink,
|
|
295
|
+
doc="Cold side outlet",
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
########################################################################
|
|
299
|
+
# Add temperature lift constraints #
|
|
300
|
+
########################################################################
|
|
301
|
+
|
|
302
|
+
@self.Constraint(self.flowsheet().time)
|
|
303
|
+
def delta_temperature_lift_equation(b, t):
|
|
304
|
+
# Refrigerant saturation levels (K)
|
|
305
|
+
T_cond = b.sink.properties_out[0].temperature + b.approach_temperature
|
|
306
|
+
T_evap = b.source.properties_out[0].temperature - b.approach_temperature
|
|
307
|
+
# Positive temperature lift
|
|
308
|
+
return b.delta_temperature_lift[t] == T_cond - T_evap
|
|
309
|
+
|
|
310
|
+
########################################################################
|
|
311
|
+
# Add a unit level energy balance #
|
|
312
|
+
########################################################################
|
|
313
|
+
@self.Constraint(self.flowsheet().time)
|
|
314
|
+
def unit_heat_balance(b, t):
|
|
315
|
+
# The duty of the sink = the source + work input
|
|
316
|
+
return 0 == (
|
|
317
|
+
source.heat[t] + pyunits.convert(sink.heat[t], to_units=q_units) - b.work_mechanical[t]
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
########################################################################
|
|
322
|
+
# Add Heat transfer equation #
|
|
323
|
+
########################################################################
|
|
324
|
+
|
|
325
|
+
@self.Constraint(self.flowsheet().time)
|
|
326
|
+
def heat_transfer_equation(b, t):
|
|
327
|
+
# This equation defines the heat duty using the cop of heating.
|
|
328
|
+
return sink.heat[t] == b.coefficient_of_performance * b.work_mechanical[t]
|
|
329
|
+
|
|
330
|
+
########################################################################
|
|
331
|
+
# Add COP Equation #
|
|
332
|
+
########################################################################
|
|
333
|
+
@self.Constraint()
|
|
334
|
+
def cop_equation(b):
|
|
335
|
+
# Refrigerant saturation levels (K)
|
|
336
|
+
T_cond = b.sink.properties_out[0].temperature + b.approach_temperature
|
|
337
|
+
T_evap = b.source.properties_out[0].temperature - b.approach_temperature
|
|
338
|
+
# Carnot CoP with efficiency
|
|
339
|
+
return b.coefficient_of_performance == (T_cond / (T_cond - T_evap)) * b.efficiency
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
########################################################################
|
|
343
|
+
# Add symbols for LaTeX equation rendering #
|
|
344
|
+
########################################################################
|
|
345
|
+
self.work_mechanical.latex_symbol = "W_{mech}"
|
|
346
|
+
self.coefficient_of_performance.latex_symbol = "COP"
|
|
347
|
+
self.efficiency.latex_symbol = "\\eta"
|
|
348
|
+
self.heat_duty.latex_symbol = "Q_{HP}"
|
|
349
|
+
self.delta_temperature_lift.latex_symbol = "\\Delta T_{lift}"
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def initialize_build(
|
|
353
|
+
self,
|
|
354
|
+
state_args_1=None,
|
|
355
|
+
state_args_2=None,
|
|
356
|
+
outlvl=idaeslog.NOTSET,
|
|
357
|
+
solver=None,
|
|
358
|
+
optarg=None,
|
|
359
|
+
duty=None,
|
|
360
|
+
):
|
|
361
|
+
"""
|
|
362
|
+
Heat exchanger initialization method.
|
|
363
|
+
|
|
364
|
+
Args:
|
|
365
|
+
state_args_1 : a dict of arguments to be passed to the property
|
|
366
|
+
initialization for the heat output (sink) (see documentation of the specific
|
|
367
|
+
property package) (default = {}).
|
|
368
|
+
state_args_2 : a dict of arguments to be passed to the property
|
|
369
|
+
initialization for the cold side (see documentation of the specific
|
|
370
|
+
property package) (default = {}).
|
|
371
|
+
outlvl : sets output level of initialization routine
|
|
372
|
+
optarg : solver options dictionary object (default=None, use
|
|
373
|
+
default solver options)
|
|
374
|
+
solver : str indicating which solver to use during
|
|
375
|
+
initialization (default = None, use default solver)
|
|
376
|
+
duty : an initial guess for the amount of heat transferred. This
|
|
377
|
+
should be a tuple in the form (value, units), (default
|
|
378
|
+
= (1000 J/s))
|
|
379
|
+
|
|
380
|
+
Returns:
|
|
381
|
+
None
|
|
382
|
+
|
|
383
|
+
"""
|
|
384
|
+
# Set solver options
|
|
385
|
+
init_log = idaeslog.getInitLogger(self.name, outlvl, tag="unit")
|
|
386
|
+
solve_log = idaeslog.getSolveLogger(self.name, outlvl, tag="unit")
|
|
387
|
+
|
|
388
|
+
# Create solver
|
|
389
|
+
opt = get_solver(solver, optarg)
|
|
390
|
+
|
|
391
|
+
flags1 = self.source.initialize(
|
|
392
|
+
outlvl=outlvl, optarg=optarg, solver=solver, state_args=state_args_1
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
init_log.info_high("Initialization Step 1a (heat output (sink)) Complete.")
|
|
396
|
+
|
|
397
|
+
flags2 = self.sink.initialize(
|
|
398
|
+
outlvl=outlvl, optarg=optarg, solver=solver, state_args=state_args_2
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
init_log.info_high("Initialization Step 1b (cold side) Complete.")
|
|
402
|
+
# ---------------------------------------------------------------------
|
|
403
|
+
# Solve unit without heat transfer equation
|
|
404
|
+
self.heat_transfer_equation.deactivate()
|
|
405
|
+
|
|
406
|
+
# Get side 1 and side 2 heat units, and convert duty as needed
|
|
407
|
+
s1_units = self.source.heat.get_units()
|
|
408
|
+
s2_units = self.sink.heat.get_units()
|
|
409
|
+
|
|
410
|
+
# Check to see if heat duty is fixed
|
|
411
|
+
# WE will assume that if the first point is fixed, it is fixed at all points
|
|
412
|
+
if not self.sink.heat[self.flowsheet().time.first()].fixed:
|
|
413
|
+
cs_fixed = False
|
|
414
|
+
if duty is None:
|
|
415
|
+
# Assume 1000 J/s and check for unitless properties
|
|
416
|
+
if s1_units is None and s2_units is None:
|
|
417
|
+
# Backwards compatibility for unitless properties
|
|
418
|
+
s1_duty = -1000
|
|
419
|
+
s2_duty = 1000
|
|
420
|
+
else:
|
|
421
|
+
s1_duty = pyunits.convert_value(
|
|
422
|
+
-1000, from_units=pyunits.W, to_units=s1_units
|
|
423
|
+
)
|
|
424
|
+
s2_duty = pyunits.convert_value(
|
|
425
|
+
1000, from_units=pyunits.W, to_units=s2_units
|
|
426
|
+
)
|
|
427
|
+
else:
|
|
428
|
+
# Duty provided with explicit units
|
|
429
|
+
s1_duty = -pyunits.convert_value(
|
|
430
|
+
duty[0], from_units=duty[1], to_units=s1_units
|
|
431
|
+
)
|
|
432
|
+
s2_duty = pyunits.convert_value(
|
|
433
|
+
duty[0], from_units=duty[1], to_units=s2_units
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
self.sink.heat.fix(s2_duty)
|
|
437
|
+
for i in self.source.heat:
|
|
438
|
+
self.source.heat[i].value = s1_duty
|
|
439
|
+
else:
|
|
440
|
+
cs_fixed = True
|
|
441
|
+
for i in self.source.heat:
|
|
442
|
+
self.source.heat[i].set_value(self.sink.heat[i])
|
|
443
|
+
|
|
444
|
+
with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc:
|
|
445
|
+
res = opt.solve(self, tee=slc.tee)
|
|
446
|
+
init_log.info_high("Initialization Step 2 {}.".format(idaeslog.condition(res)))
|
|
447
|
+
if not cs_fixed:
|
|
448
|
+
self.sink.heat.unfix()
|
|
449
|
+
self.heat_transfer_equation.activate()
|
|
450
|
+
# ---------------------------------------------------------------------
|
|
451
|
+
# Solve unit
|
|
452
|
+
with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc:
|
|
453
|
+
res = opt.solve(self, tee=slc.tee)
|
|
454
|
+
init_log.info_high("Initialization Step 3 {}.".format(idaeslog.condition(res)))
|
|
455
|
+
# ---------------------------------------------------------------------
|
|
456
|
+
# Release Inlet state
|
|
457
|
+
self.source.release_state(flags1, outlvl=outlvl)
|
|
458
|
+
self.sink.release_state(flags2, outlvl=outlvl)
|
|
459
|
+
|
|
460
|
+
init_log.info("Initialization Completed, {}".format(idaeslog.condition(res)))
|
|
461
|
+
|
|
462
|
+
if not check_optimal_termination(res):
|
|
463
|
+
raise InitializationError(
|
|
464
|
+
f"{self.name} failed to initialize successfully. Please check "
|
|
465
|
+
f"the output logs for more information."
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
def _get_performance_contents(self, time_point=0):
|
|
469
|
+
# this shows as the performance table when calling the report method
|
|
470
|
+
var_dict = {
|
|
471
|
+
|
|
472
|
+
"Coefficient of Performance": self.coefficient_of_performance,
|
|
473
|
+
"Efficiency": self.efficiency,
|
|
474
|
+
"Source Heat Duty": self.source.heat[time_point],
|
|
475
|
+
"Mechanical Work Input": self.work_mechanical[time_point],
|
|
476
|
+
"Sink Heat Duty": self.sink.heat[time_point],
|
|
477
|
+
"Temperature Lift": self.delta_temperature_lift[time_point],
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
expr_dict = {}
|
|
481
|
+
|
|
482
|
+
return {"vars": var_dict, "exprs": expr_dict}
|
|
483
|
+
|
|
484
|
+
def _get_stream_table_contents(self, time_point=0):
|
|
485
|
+
# this shows as t he stream table when calling the report method
|
|
486
|
+
# Get names for hot and cold sides
|
|
487
|
+
return create_stream_table_dataframe(
|
|
488
|
+
{
|
|
489
|
+
f"Source Inlet": self.source_inlet,
|
|
490
|
+
f"Source Outlet": self.source_outlet,
|
|
491
|
+
f"Sink Inlet": self.sink_inlet,
|
|
492
|
+
f"Sink Outlet": self.sink_outlet,
|
|
493
|
+
},
|
|
494
|
+
time_point=time_point,
|
|
495
|
+
)
|
|
496
|
+
|
|
497
|
+
def calculate_scaling_factors(self):
|
|
498
|
+
super().calculate_scaling_factors()
|
|
499
|
+
# TODO: Review this code to check it makes sense.
|
|
500
|
+
|
|
501
|
+
# Scaling for heat pump variables
|
|
502
|
+
# Mechanical work input: typical values vary, set default scaling to 0.01
|
|
503
|
+
sf_work = dict(
|
|
504
|
+
zip(
|
|
505
|
+
self.work_mechanical.keys(),
|
|
506
|
+
[
|
|
507
|
+
iscale.get_scaling_factor(v, default=0.01)
|
|
508
|
+
for v in self.work_mechanical.values()
|
|
509
|
+
],
|
|
510
|
+
)
|
|
511
|
+
)
|
|
512
|
+
# COP and efficiency are dimensionless, usually between 1 and 10, default scaling 0.1
|
|
513
|
+
sf_cop = iscale.get_scaling_factor(self.coefficient_of_performance, default=0.1)
|
|
514
|
+
sf_eff = iscale.get_scaling_factor(self.efficiency, default=0.1)
|
|
515
|
+
|
|
516
|
+
# Delta Ts: typical range 1-100, default scaling 0.1
|
|
517
|
+
sf_dT_in = dict(
|
|
518
|
+
zip(
|
|
519
|
+
self.delta_temperature_in.keys(),
|
|
520
|
+
[
|
|
521
|
+
iscale.get_scaling_factor(v, default=0.1)
|
|
522
|
+
for v in self.delta_temperature_in.values()
|
|
523
|
+
],
|
|
524
|
+
)
|
|
525
|
+
)
|
|
526
|
+
sf_dT_out = dict(
|
|
527
|
+
zip(
|
|
528
|
+
self.delta_temperature_out.keys(),
|
|
529
|
+
[
|
|
530
|
+
iscale.get_scaling_factor(v, default=0.1)
|
|
531
|
+
for v in self.delta_temperature_out.values()
|
|
532
|
+
],
|
|
533
|
+
)
|
|
534
|
+
)
|
|
535
|
+
|
|
536
|
+
# Heat duty: depends on process, default scaling 0.01
|
|
537
|
+
sf_q = dict(
|
|
538
|
+
zip(
|
|
539
|
+
self.heat_duty.keys(),
|
|
540
|
+
[
|
|
541
|
+
iscale.get_scaling_factor(v, default=0.01)
|
|
542
|
+
for v in self.heat_duty.values()
|
|
543
|
+
],
|
|
544
|
+
)
|
|
545
|
+
)
|
|
546
|
+
|
|
547
|
+
# Apply scaling to constraints
|
|
548
|
+
for t, c in self.heat_transfer_equation.items():
|
|
549
|
+
iscale.constraint_scaling_transform(
|
|
550
|
+
c, sf_cop * sf_work[t], overwrite=False
|
|
551
|
+
)
|
|
552
|
+
|
|
553
|
+
for t, c in self.unit_heat_balance.items():
|
|
554
|
+
iscale.constraint_scaling_transform(
|
|
555
|
+
c, sf_q[t], overwrite=False
|
|
556
|
+
)
|
|
557
|
+
|
|
558
|
+
for t, c in self.delta_temperature_in_equation.items():
|
|
559
|
+
iscale.constraint_scaling_transform(c, sf_dT_in[t], overwrite=False)
|
|
560
|
+
|
|
561
|
+
for t, c in self.delta_temperature_out_equation.items():
|
|
562
|
+
iscale.constraint_scaling_transform(c, sf_dT_out[t], overwrite=False)
|
|
563
|
+
|
|
564
|
+
# COP equation scaling
|
|
565
|
+
iscale.constraint_scaling_transform(
|
|
566
|
+
self.cop_equation, sf_cop, overwrite=False
|
|
567
|
+
)
|