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,38 @@
|
|
|
1
|
+
from .custom_valve import Valve as CustomValve, ValveFunctionType
|
|
2
|
+
from idaes.models.unit_models.pressure_changer import ThermodynamicAssumption
|
|
3
|
+
from .custom_pressure_changer import CustomPressureChanger
|
|
4
|
+
|
|
5
|
+
VALVE_FUNCTION_MAP = {
|
|
6
|
+
"linear": ValveFunctionType.linear,
|
|
7
|
+
"quick_opening": ValveFunctionType.quick_opening,
|
|
8
|
+
"equal_percentage": ValveFunctionType.equal_percentage,
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
def ValveWrapper(**kwargs):
|
|
12
|
+
enable_coefficients = kwargs.pop('enable_coefficients', False)
|
|
13
|
+
valve_function = kwargs.pop('valve_function', None)
|
|
14
|
+
|
|
15
|
+
# Set default valve function if none provided
|
|
16
|
+
if valve_function is None:
|
|
17
|
+
valve_function = "linear"
|
|
18
|
+
|
|
19
|
+
# Check if valve_function is a string and map it to the corresponding callback
|
|
20
|
+
if isinstance(valve_function, str):
|
|
21
|
+
if valve_function in VALVE_FUNCTION_MAP:
|
|
22
|
+
valve_function_callback = VALVE_FUNCTION_MAP[valve_function]
|
|
23
|
+
else:
|
|
24
|
+
raise ValueError(f"Unknown valve_function: {valve_function}")
|
|
25
|
+
else:
|
|
26
|
+
# just in case it is already a enum or a callback
|
|
27
|
+
valve_function_callback = valve_function
|
|
28
|
+
|
|
29
|
+
if enable_coefficients:
|
|
30
|
+
# use the custom valve model in full
|
|
31
|
+
return CustomValve(valve_function_callback=valve_function_callback, **kwargs)
|
|
32
|
+
else:
|
|
33
|
+
# add thermodynamic_assumption kwarg to
|
|
34
|
+
# the PressureChanger model
|
|
35
|
+
kwargs['thermodynamic_assumption'] = ThermodynamicAssumption.adiabatic
|
|
36
|
+
kwargs['compressor'] = False
|
|
37
|
+
# just use a pressure changer
|
|
38
|
+
return CustomPressureChanger(**kwargs)
|
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
# Water tank model from IDAES, with the units added. Ideally, we can remove this
|
|
2
|
+
# once our tank units pr to IDAES is merged.
|
|
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
|
+
Watertank model
|
|
17
|
+
The water tank has only one inlet and one outlet
|
|
18
|
+
|
|
19
|
+
main assumptions:
|
|
20
|
+
|
|
21
|
+
1.- Heat loss is a variable given by the user (zero heat loss can be
|
|
22
|
+
specified if adiabatic)
|
|
23
|
+
2.- Calculate pressure change due to gravity based on water level
|
|
24
|
+
and contraction to downcomer
|
|
25
|
+
3.- Water level is either fixed for steady-state model or calculated for
|
|
26
|
+
dynamic model
|
|
27
|
+
4.- Assume enthalpy_in = enthalpy_out + heat loss
|
|
28
|
+
|
|
29
|
+
5.- Subcooled water from economizer and saturated water from waterwall
|
|
30
|
+
are mixed before entering the tank
|
|
31
|
+
|
|
32
|
+
Created: November 04 2020
|
|
33
|
+
"""
|
|
34
|
+
# TODO: Missing docstrings
|
|
35
|
+
# pylint: disable=missing-function-docstring
|
|
36
|
+
|
|
37
|
+
# Import Pyomo libraries
|
|
38
|
+
from pyomo.environ import value, Var, Reference, acos
|
|
39
|
+
from pyomo.common.config import ConfigBlock, ConfigValue, In, Bool
|
|
40
|
+
|
|
41
|
+
# Import IDAES cores
|
|
42
|
+
from idaes.core import (
|
|
43
|
+
ControlVolume0DBlock,
|
|
44
|
+
declare_process_block_class,
|
|
45
|
+
MaterialBalanceType,
|
|
46
|
+
EnergyBalanceType,
|
|
47
|
+
MomentumBalanceType,
|
|
48
|
+
UnitModelBlockData,
|
|
49
|
+
useDefault,
|
|
50
|
+
)
|
|
51
|
+
from idaes.core.util.config import is_physical_parameter_block
|
|
52
|
+
from idaes.core.solvers import get_solver
|
|
53
|
+
from idaes.core.util.constants import Constants as const
|
|
54
|
+
|
|
55
|
+
import idaes.logger as idaeslog
|
|
56
|
+
|
|
57
|
+
__author__ = "Boiler Subsystem Team (J. Ma, D. Caballero, M. Zamarripa)"
|
|
58
|
+
__version__ = "2.0.0"
|
|
59
|
+
|
|
60
|
+
# Set up logger
|
|
61
|
+
_log = idaeslog.getLogger(__name__)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@declare_process_block_class("WaterTank")
|
|
65
|
+
class WaterTankData(UnitModelBlockData):
|
|
66
|
+
"""
|
|
67
|
+
Water Tank Unit Operation Class
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
CONFIG = UnitModelBlockData.CONFIG()
|
|
71
|
+
|
|
72
|
+
CONFIG.declare(
|
|
73
|
+
"tank_type",
|
|
74
|
+
ConfigValue(
|
|
75
|
+
default="simple_tank",
|
|
76
|
+
domain=In(
|
|
77
|
+
[
|
|
78
|
+
"simple_tank",
|
|
79
|
+
"rectangular_tank",
|
|
80
|
+
"vertical_cylindrical_tank",
|
|
81
|
+
"horizontal_cylindrical_tank",
|
|
82
|
+
]
|
|
83
|
+
),
|
|
84
|
+
description="Flag indicating the tank type",
|
|
85
|
+
doc="""Flag indicating the type of tank to be modeled, and
|
|
86
|
+
then calculate the volume of the filled level consequently,
|
|
87
|
+
**default** - simple_tank.
|
|
88
|
+
**Valid values:** {
|
|
89
|
+
**simple_tank** - use a general tank and provide the area,
|
|
90
|
+
**rectangular_tank** - use a rectangular tank and provide the width and length,
|
|
91
|
+
**vertical_cylindrical_tank** - use a vertical cylindrical tank
|
|
92
|
+
and provide the diameter,
|
|
93
|
+
**horizontal_cylindrical_tank** - use a horizontal cylindrical tank and
|
|
94
|
+
provide the length and diameter.}""",
|
|
95
|
+
),
|
|
96
|
+
)
|
|
97
|
+
CONFIG.declare(
|
|
98
|
+
"material_balance_type",
|
|
99
|
+
ConfigValue(
|
|
100
|
+
default=MaterialBalanceType.componentPhase,
|
|
101
|
+
domain=In(MaterialBalanceType),
|
|
102
|
+
description="Material balance construction flag",
|
|
103
|
+
doc="""Indicates what type of material balance should be constructed,
|
|
104
|
+
**default** - MaterialBalanceType.componentPhase.
|
|
105
|
+
**Valid values:** {
|
|
106
|
+
**MaterialBalanceType.none** - exclude material balances,
|
|
107
|
+
**MaterialBalanceType.componentPhase** - use phase component balances,
|
|
108
|
+
**MaterialBalanceType.componentTotal** - use total component balances,
|
|
109
|
+
**MaterialBalanceType.elementTotal** - use total element balances,
|
|
110
|
+
**MaterialBalanceType.total** - use total material balance.}""",
|
|
111
|
+
),
|
|
112
|
+
)
|
|
113
|
+
CONFIG.declare(
|
|
114
|
+
"energy_balance_type",
|
|
115
|
+
ConfigValue(
|
|
116
|
+
default=EnergyBalanceType.enthalpyTotal,
|
|
117
|
+
domain=In(EnergyBalanceType),
|
|
118
|
+
description="Energy balance construction flag",
|
|
119
|
+
doc="""Indicates what type of energy balance should be constructed,
|
|
120
|
+
**default** - EnergyBalanceType.enthalpyTotal.
|
|
121
|
+
**Valid values:** {
|
|
122
|
+
**EnergyBalanceType.none** - exclude energy balances,
|
|
123
|
+
**EnergyBalanceType.enthalpyTotal** - single ethalpy balance for material,
|
|
124
|
+
**EnergyBalanceType.enthalpyPhase** - ethalpy balances for each phase,
|
|
125
|
+
**EnergyBalanceType.energyTotal** - single energy balance for material,
|
|
126
|
+
**EnergyBalanceType.energyPhase** - energy balances for each phase.}""",
|
|
127
|
+
),
|
|
128
|
+
)
|
|
129
|
+
CONFIG.declare(
|
|
130
|
+
"momentum_balance_type",
|
|
131
|
+
ConfigValue(
|
|
132
|
+
default=MomentumBalanceType.pressureTotal,
|
|
133
|
+
domain=In(MomentumBalanceType),
|
|
134
|
+
description="Momentum balance construction flag",
|
|
135
|
+
doc="""Indicates what type of momentum balance should be constructed,
|
|
136
|
+
**default** - MomentumBalanceType.pressureTotal.
|
|
137
|
+
**Valid values:** {
|
|
138
|
+
**MomentumBalanceType.none** - exclude momentum balances,
|
|
139
|
+
**MomentumBalanceType.pressureTotal** - single pressure balance for material,
|
|
140
|
+
**MomentumBalanceType.pressurePhase** - pressure balances for each phase,
|
|
141
|
+
**MomentumBalanceType.momentumTotal** - single momentum balance for material,
|
|
142
|
+
**MomentumBalanceType.momentumPhase** - momentum balances for each phase.}""",
|
|
143
|
+
),
|
|
144
|
+
)
|
|
145
|
+
CONFIG.declare(
|
|
146
|
+
"has_heat_transfer",
|
|
147
|
+
ConfigValue(
|
|
148
|
+
default=False,
|
|
149
|
+
domain=Bool,
|
|
150
|
+
description="Heat transfer term construction flag",
|
|
151
|
+
doc="""Indicates whether terms for heat transfer should be constructed,
|
|
152
|
+
**default** - False.
|
|
153
|
+
**Valid values:** {
|
|
154
|
+
**True** - include heat transfer terms,
|
|
155
|
+
**False** - exclude heat transfer terms.}""",
|
|
156
|
+
),
|
|
157
|
+
)
|
|
158
|
+
CONFIG.declare(
|
|
159
|
+
"has_pressure_change",
|
|
160
|
+
ConfigValue(
|
|
161
|
+
default=True,
|
|
162
|
+
domain=Bool,
|
|
163
|
+
description="Pressure change term construction flag",
|
|
164
|
+
doc="""Indicates whether terms for pressure change should be
|
|
165
|
+
constructed,
|
|
166
|
+
**default** - False.
|
|
167
|
+
**Valid values:** {
|
|
168
|
+
**True** - include pressure change terms,
|
|
169
|
+
**False** - exclude pressure change terms.}""",
|
|
170
|
+
),
|
|
171
|
+
)
|
|
172
|
+
CONFIG.declare(
|
|
173
|
+
"property_package",
|
|
174
|
+
ConfigValue(
|
|
175
|
+
default=useDefault,
|
|
176
|
+
domain=is_physical_parameter_block,
|
|
177
|
+
description="Property package to use for control volume",
|
|
178
|
+
doc="""Property parameter object used to define property calculations,
|
|
179
|
+
**default** - useDefault.
|
|
180
|
+
**Valid values:** {
|
|
181
|
+
**useDefault** - use default package from parent model or flowsheet,
|
|
182
|
+
**PhysicalParameterObject** - a PhysicalParameterBlock object.}""",
|
|
183
|
+
),
|
|
184
|
+
)
|
|
185
|
+
CONFIG.declare(
|
|
186
|
+
"property_package_args",
|
|
187
|
+
ConfigBlock(
|
|
188
|
+
implicit=True,
|
|
189
|
+
description="Arguments to use for constructing property packages",
|
|
190
|
+
doc="""A ConfigBlock with arguments to be passed to a property block(s)
|
|
191
|
+
and used when constructing these,
|
|
192
|
+
**default** - None.
|
|
193
|
+
**Valid values:** {
|
|
194
|
+
see property package for documentation.}""",
|
|
195
|
+
),
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
def build(self):
|
|
199
|
+
"""
|
|
200
|
+
Begin building model (pre-DAE transformation).
|
|
201
|
+
Args:
|
|
202
|
+
None
|
|
203
|
+
Returns:
|
|
204
|
+
None
|
|
205
|
+
"""
|
|
206
|
+
|
|
207
|
+
# Call UnitModel.build to setup dynamics
|
|
208
|
+
super().build()
|
|
209
|
+
|
|
210
|
+
# Build Control Volume
|
|
211
|
+
self.control_volume = ControlVolume0DBlock(
|
|
212
|
+
dynamic=self.config.dynamic,
|
|
213
|
+
has_holdup=self.config.has_holdup,
|
|
214
|
+
property_package=self.config.property_package,
|
|
215
|
+
property_package_args=self.config.property_package_args,
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
self.control_volume.add_geometry()
|
|
219
|
+
|
|
220
|
+
self.control_volume.add_state_blocks(has_phase_equilibrium=False)
|
|
221
|
+
|
|
222
|
+
self.control_volume.add_material_balances(
|
|
223
|
+
balance_type=self.config.material_balance_type
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
self.control_volume.add_energy_balances(
|
|
227
|
+
balance_type=self.config.energy_balance_type,
|
|
228
|
+
has_heat_transfer=self.config.has_heat_transfer,
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
self.control_volume.add_momentum_balances(
|
|
232
|
+
balance_type=self.config.momentum_balance_type, has_pressure_change=True
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
if self.config.has_holdup is True:
|
|
236
|
+
# enforce that holdup must start above zero. This fixes some problems with initialisation from steady state assumptions.
|
|
237
|
+
self.control_volume.material_holdup[0, :, :].setlb(0)
|
|
238
|
+
|
|
239
|
+
# Add Inlet and Outlet Ports
|
|
240
|
+
self.add_inlet_port()
|
|
241
|
+
self.add_outlet_port()
|
|
242
|
+
|
|
243
|
+
# Add object references
|
|
244
|
+
self.volume = Reference(self.control_volume.volume)
|
|
245
|
+
|
|
246
|
+
# Set references to balance terms at unit level
|
|
247
|
+
if (
|
|
248
|
+
self.config.has_heat_transfer is True
|
|
249
|
+
and self.config.energy_balance_type != EnergyBalanceType.none
|
|
250
|
+
):
|
|
251
|
+
self.heat_duty = Reference(self.control_volume.heat)
|
|
252
|
+
|
|
253
|
+
if (
|
|
254
|
+
self.config.has_pressure_change is True
|
|
255
|
+
and self.config.momentum_balance_type != "none"
|
|
256
|
+
):
|
|
257
|
+
self.deltaP = Reference(self.control_volume.deltaP)
|
|
258
|
+
|
|
259
|
+
# Set Unit Geometry and Holdup Volume
|
|
260
|
+
self._set_geometry()
|
|
261
|
+
|
|
262
|
+
# Construct performance equations
|
|
263
|
+
self._make_performance()
|
|
264
|
+
|
|
265
|
+
def _set_geometry(self):
|
|
266
|
+
"""
|
|
267
|
+
Define the geometry of the unit as necessary
|
|
268
|
+
"""
|
|
269
|
+
|
|
270
|
+
units = self.config.property_package.get_metadata().get_derived_units
|
|
271
|
+
|
|
272
|
+
if self.config.tank_type == "simple_tank":
|
|
273
|
+
# Declare a variable for cross sectional area
|
|
274
|
+
self.tank_cross_sect_area = Var(
|
|
275
|
+
initialize=1.0,
|
|
276
|
+
doc="Cross-sectional area of the tank",
|
|
277
|
+
units=units("length") ** 2,
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
elif self.config.tank_type == "rectangular_tank":
|
|
281
|
+
# Declare variables for width and length
|
|
282
|
+
self.tank_width = Var(
|
|
283
|
+
initialize=1.0, units=units("length"), doc="Width of the tank"
|
|
284
|
+
)
|
|
285
|
+
self.tank_length = Var(
|
|
286
|
+
initialize=1.0, units=units("length"), doc="Length of the tank"
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
elif (
|
|
290
|
+
self.config.tank_type == "horizontal_cylindrical_tank"
|
|
291
|
+
or "vertical_cylindrical_tank"
|
|
292
|
+
):
|
|
293
|
+
# Declare a variable for diameter of the tank
|
|
294
|
+
self.tank_diameter = Var(
|
|
295
|
+
initialize=0.5, units=units("length"), doc="Inside diameter of the tank"
|
|
296
|
+
)
|
|
297
|
+
if self.config.tank_type == "horizontal_cylindrical_tank":
|
|
298
|
+
# Declare a variable for length of the tank
|
|
299
|
+
self.tank_length = Var(
|
|
300
|
+
initialize=1, units=units("length"), doc="Length of the tank"
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
def _make_performance(self):
|
|
304
|
+
"""
|
|
305
|
+
Define constraints which describe the behaviour of the unit model
|
|
306
|
+
"""
|
|
307
|
+
units = self.config.property_package.get_metadata().get_derived_units
|
|
308
|
+
|
|
309
|
+
# Add performance variables
|
|
310
|
+
self.tank_level = Var(
|
|
311
|
+
self.flowsheet().time,
|
|
312
|
+
initialize=1.0,
|
|
313
|
+
doc="Water level from in the tank",
|
|
314
|
+
units=units("length"),
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
# Auxiliary expressions for volume
|
|
318
|
+
# Rectangular tank
|
|
319
|
+
if self.config.tank_type == "rectangular_tank":
|
|
320
|
+
# Calculation of cross-sectional area of the rectangle
|
|
321
|
+
@self.Expression(doc="Cross-sectional area of the tank")
|
|
322
|
+
def tank_cross_sect_area(b):
|
|
323
|
+
return b.tank_width * b.tank_length
|
|
324
|
+
|
|
325
|
+
# Vertical cylindrical tank
|
|
326
|
+
elif self.config.tank_type == "vertical_cylindrical_tank":
|
|
327
|
+
|
|
328
|
+
@self.Expression(doc="Radius of the tank")
|
|
329
|
+
def tank_radius(b):
|
|
330
|
+
return b.tank_diameter / 2
|
|
331
|
+
|
|
332
|
+
# Calculation of cross-sectional area of the vertical cylinder
|
|
333
|
+
@self.Expression(doc="Cross-sectional area of the tank")
|
|
334
|
+
def tank_cross_sect_area(b):
|
|
335
|
+
return const.pi * b.tank_radius**2
|
|
336
|
+
|
|
337
|
+
# Horizontal cylindrical tank
|
|
338
|
+
elif self.config.tank_type == "horizontal_cylindrical_tank":
|
|
339
|
+
# Calculation of area covered by the liquid level
|
|
340
|
+
# at one end of the tank
|
|
341
|
+
@self.Expression(doc="Radius of the tank")
|
|
342
|
+
def tank_radius(b):
|
|
343
|
+
return b.tank_diameter / 2
|
|
344
|
+
|
|
345
|
+
# Angle of the circular sector used to calculate the area
|
|
346
|
+
|
|
347
|
+
@self.Expression(
|
|
348
|
+
self.flowsheet().time,
|
|
349
|
+
doc="Angle of the circular" " sector of liquid level",
|
|
350
|
+
)
|
|
351
|
+
def alpha_tank(b, t):
|
|
352
|
+
return 2 * acos((b.tank_radius - b.tank_level[t]) / b.tank_radius)
|
|
353
|
+
|
|
354
|
+
@self.Expression(
|
|
355
|
+
self.flowsheet().time,
|
|
356
|
+
doc="Area covered by the liquid level" " at one end of the tank",
|
|
357
|
+
)
|
|
358
|
+
def tank_area(b, t):
|
|
359
|
+
return (
|
|
360
|
+
0.5 * b.alpha_tank[t] * b.tank_radius**2
|
|
361
|
+
- (b.tank_radius - b.tank_level[t])
|
|
362
|
+
* (2 * b.tank_radius * b.tank_level[t] - b.tank_level[t] ** 2)
|
|
363
|
+
** 0.5
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
# Constraint for volume of the liquid in tank
|
|
367
|
+
@self.Constraint(self.flowsheet().time, doc="volume of liquid in the tank")
|
|
368
|
+
def volume_eqn(b, t):
|
|
369
|
+
if self.config.tank_type == "horizontal_cylindrical_tank":
|
|
370
|
+
return b.volume[t] == b.tank_length * b.tank_area[t]
|
|
371
|
+
else:
|
|
372
|
+
return b.volume[t] == b.tank_level[t] * b.tank_cross_sect_area
|
|
373
|
+
|
|
374
|
+
# Pressure change equation due gravity
|
|
375
|
+
@self.Constraint(self.flowsheet().time, doc="pressure drop")
|
|
376
|
+
def pressure_change_eqn(b, t):
|
|
377
|
+
return (
|
|
378
|
+
b.deltaP[t]
|
|
379
|
+
== b.control_volume.properties_in[t].dens_mass_phase["Liq"]
|
|
380
|
+
* const.acceleration_gravity
|
|
381
|
+
* b.tank_level[t]
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
def set_initial_condition(self):
|
|
385
|
+
if self.config.dynamic is True:
|
|
386
|
+
self.control_volume.material_accumulation[:, :, :].value = 0
|
|
387
|
+
self.control_volume.energy_accumulation[:, :].value = 0
|
|
388
|
+
self.control_volume.material_accumulation[0, :, :].fix(0)
|
|
389
|
+
self.control_volume.energy_accumulation[0, :].fix(0)
|
|
390
|
+
|
|
391
|
+
def initialize_build(
|
|
392
|
+
blk, state_args=None, outlvl=idaeslog.NOTSET, solver=None, optarg=None
|
|
393
|
+
):
|
|
394
|
+
"""
|
|
395
|
+
Water tank initialization routine.
|
|
396
|
+
|
|
397
|
+
Keyword Arguments:
|
|
398
|
+
state_args : a dict of arguments to be passed to the property
|
|
399
|
+
package(s) for the control_volume of the model to
|
|
400
|
+
provide an initial state for initialization
|
|
401
|
+
(see documentation of the specific property package)
|
|
402
|
+
(default = None).
|
|
403
|
+
outlvl : sets output level of initialisation routine
|
|
404
|
+
|
|
405
|
+
* 0 = no output (default)
|
|
406
|
+
* 1 = return solver state for each step in routine
|
|
407
|
+
* 2 = return solver state for each step in subroutines
|
|
408
|
+
* 3 = include solver output information (tee=True)
|
|
409
|
+
|
|
410
|
+
optarg : solver options dictionary object (default=None, use
|
|
411
|
+
default solver options)
|
|
412
|
+
solver : str indicating which solver to use during
|
|
413
|
+
initialization (default = None, use default solver)
|
|
414
|
+
|
|
415
|
+
Returns:
|
|
416
|
+
None
|
|
417
|
+
"""
|
|
418
|
+
init_log = idaeslog.getInitLogger(blk.name, outlvl, tag="unit")
|
|
419
|
+
solve_log = idaeslog.getSolveLogger(blk.name, outlvl, tag="unit")
|
|
420
|
+
|
|
421
|
+
# Create solver
|
|
422
|
+
opt = get_solver(solver, optarg)
|
|
423
|
+
|
|
424
|
+
init_log.info_low("Starting initialization...")
|
|
425
|
+
|
|
426
|
+
flags = blk.control_volume.initialize(
|
|
427
|
+
state_args=state_args, outlvl=outlvl, optarg=optarg, solver=solver
|
|
428
|
+
)
|
|
429
|
+
init_log.info_high("Initialization Step 1 Complete.")
|
|
430
|
+
|
|
431
|
+
# Fix outlet pressure
|
|
432
|
+
for t in blk.flowsheet().time:
|
|
433
|
+
blk.control_volume.properties_out[t].pressure.fix(
|
|
434
|
+
value(blk.control_volume.properties_in[t].pressure)
|
|
435
|
+
)
|
|
436
|
+
blk.pressure_change_eqn.deactivate()
|
|
437
|
+
|
|
438
|
+
# solve model
|
|
439
|
+
with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc:
|
|
440
|
+
res = opt.solve(blk, tee=slc.tee)
|
|
441
|
+
init_log.info_high("Initialization Step 2 {}.".format(idaeslog.condition(res)))
|
|
442
|
+
|
|
443
|
+
# Unfix outlet pressure
|
|
444
|
+
for t in blk.flowsheet().time:
|
|
445
|
+
blk.control_volume.properties_out[t].pressure.unfix()
|
|
446
|
+
blk.pressure_change_eqn.activate()
|
|
447
|
+
|
|
448
|
+
with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc:
|
|
449
|
+
res = opt.solve(blk, tee=slc.tee)
|
|
450
|
+
init_log.info_high("Initialization Step 3 {}.".format(idaeslog.condition(res)))
|
|
451
|
+
|
|
452
|
+
blk.control_volume.release_state(flags, outlvl)
|
|
453
|
+
init_log.info("Initialization Complete.")
|
|
454
|
+
|
|
455
|
+
def calculate_scaling_factors(self):
|
|
456
|
+
pass
|
|
File without changes
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from ..properties_manager import PropertiesManager
|
|
2
|
+
from typing import Iterator, TextIO
|
|
3
|
+
import sys
|
|
4
|
+
import pyomo.environ as pyo
|
|
5
|
+
from pyomo.core.base.constraint import ScalarConstraint
|
|
6
|
+
|
|
7
|
+
def compute_infeasibilities(properties_map: PropertiesManager) -> Iterator[tuple[any,float]]:
|
|
8
|
+
"""
|
|
9
|
+
Compute how far each constrained property is from its target value.
|
|
10
|
+
|
|
11
|
+
:param properties_map: PropertiesManager instance containing property components
|
|
12
|
+
:type properties_map: PropertiesManager
|
|
13
|
+
:return: A tuple list of property IDs and their infeasibility magnitudes
|
|
14
|
+
:rtype: list[tuple[Any, float]]
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
for id, property_component in properties_map.items():
|
|
18
|
+
if property_component.corresponding_constraint is not None and type(next(iter(property_component.corresponding_constraint))) is ScalarConstraint:
|
|
19
|
+
# Assuming the component is a Pyomo Var or Expression
|
|
20
|
+
current_value :float = pyo.value(next(iter(property_component.corresponding_constraint)))
|
|
21
|
+
target_value :float = pyo.value(next(iter(property_component.corresponding_constraint)).upper)
|
|
22
|
+
infeasibility = abs(current_value - target_value)
|
|
23
|
+
yield (id, infeasibility)
|
|
24
|
+
|
|
25
|
+
def print_infeasibilities(properties_map: PropertiesManager):
|
|
26
|
+
"""
|
|
27
|
+
Print the infeasibilities of constrained properties, sorted by magnitude.
|
|
28
|
+
|
|
29
|
+
:param properties_map: PropertiesManager instance containing property components
|
|
30
|
+
:type properties_map: PropertiesManager
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# Sort from most to least infeasible
|
|
35
|
+
infeasibilities = list(compute_infeasibilities(properties_map))
|
|
36
|
+
infeasibilities.sort(key=lambda x: x[1], reverse=True)
|
|
37
|
+
|
|
38
|
+
print("Property Infeasibilities:")
|
|
39
|
+
for id, infeasibility in infeasibilities:
|
|
40
|
+
print(f"Property: {properties_map.get(id).name}, Infeasibility: {infeasibility}")
|
|
File without changes
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import pyomo.environ as pyo
|
|
2
|
+
from ..infeasibilities import compute_infeasibilities, print_infeasibilities
|
|
3
|
+
from ...properties_manager import PropertiesManager
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def test_infeasibilities(capsys):
|
|
8
|
+
model = pyo.ConcreteModel()
|
|
9
|
+
model.x = pyo.Var(initialize=5.0)
|
|
10
|
+
model.c = pyo.Constraint(expr=model.x == 10.0)
|
|
11
|
+
|
|
12
|
+
properties_map = PropertiesManager()
|
|
13
|
+
properties_map.add(
|
|
14
|
+
12345,
|
|
15
|
+
pyo.Reference(model.x),
|
|
16
|
+
"property name"
|
|
17
|
+
)
|
|
18
|
+
properties_map.add_constraint(
|
|
19
|
+
12345,
|
|
20
|
+
[model.c])
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
print_infeasibilities(properties_map)
|
|
24
|
+
captured = capsys.readouterr()
|
|
25
|
+
assert "property name" in captured.out
|
|
26
|
+
assert "5.0" in captured.out
|
|
27
|
+
|
|
28
|
+
|