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.
Files changed (167) hide show
  1. ahuora_builder/__init__.py +0 -0
  2. ahuora_builder/arc_manager.py +33 -0
  3. ahuora_builder/build_state.py +57 -0
  4. ahuora_builder/custom/PIDController.py +494 -0
  5. ahuora_builder/custom/PySMOModel.py +178 -0
  6. ahuora_builder/custom/SimpleEffectivenessHX_DH.py +727 -0
  7. ahuora_builder/custom/__init__.py +0 -0
  8. ahuora_builder/custom/add_initial_dynamics.py +35 -0
  9. ahuora_builder/custom/custom_compressor.py +107 -0
  10. ahuora_builder/custom/custom_cooler.py +33 -0
  11. ahuora_builder/custom/custom_heat_exchanger.py +183 -0
  12. ahuora_builder/custom/custom_heat_exchanger_1d.py +258 -0
  13. ahuora_builder/custom/custom_heater.py +41 -0
  14. ahuora_builder/custom/custom_pressure_changer.py +34 -0
  15. ahuora_builder/custom/custom_pump.py +107 -0
  16. ahuora_builder/custom/custom_separator.py +371 -0
  17. ahuora_builder/custom/custom_tank.py +133 -0
  18. ahuora_builder/custom/custom_turbine.py +132 -0
  19. ahuora_builder/custom/custom_valve.py +300 -0
  20. ahuora_builder/custom/custom_variable.py +29 -0
  21. ahuora_builder/custom/direct_steam_injection.py +371 -0
  22. ahuora_builder/custom/energy/__init__.py +0 -0
  23. ahuora_builder/custom/energy/acBus.py +280 -0
  24. ahuora_builder/custom/energy/ac_property_package.py +279 -0
  25. ahuora_builder/custom/energy/battery.py +170 -0
  26. ahuora_builder/custom/energy/bus.py +182 -0
  27. ahuora_builder/custom/energy/energy_mixer.py +195 -0
  28. ahuora_builder/custom/energy/energy_splitter.py +228 -0
  29. ahuora_builder/custom/energy/grid.py +173 -0
  30. ahuora_builder/custom/energy/hydro.py +169 -0
  31. ahuora_builder/custom/energy/link.py +137 -0
  32. ahuora_builder/custom/energy/load.py +155 -0
  33. ahuora_builder/custom/energy/mainDistributionBoard.py +257 -0
  34. ahuora_builder/custom/energy/power_property_package.py +253 -0
  35. ahuora_builder/custom/energy/solar.py +176 -0
  36. ahuora_builder/custom/energy/storage.py +230 -0
  37. ahuora_builder/custom/energy/storage_wrapper +0 -0
  38. ahuora_builder/custom/energy/tests/__init__.py +0 -0
  39. ahuora_builder/custom/energy/tests/test_bus.py +44 -0
  40. ahuora_builder/custom/energy/tests/test_energy_mixer.py +46 -0
  41. ahuora_builder/custom/energy/tests/test_mdb.py +49 -0
  42. ahuora_builder/custom/energy/transformer.py +187 -0
  43. ahuora_builder/custom/energy/transformer_property_package.py +267 -0
  44. ahuora_builder/custom/energy/transmissionLine.py +228 -0
  45. ahuora_builder/custom/energy/wind.py +206 -0
  46. ahuora_builder/custom/hda_ideal_VLE.py +1341 -0
  47. ahuora_builder/custom/hda_reaction.py +182 -0
  48. ahuora_builder/custom/heat_exchanger_1d_wrapper.py +31 -0
  49. ahuora_builder/custom/integration_block.py +106 -0
  50. ahuora_builder/custom/inverted.py +81 -0
  51. ahuora_builder/custom/performance_curves.py +1 -0
  52. ahuora_builder/custom/reactions/__init__.py +0 -0
  53. ahuora_builder/custom/reactions/hda_stoich.py +10 -0
  54. ahuora_builder/custom/simple_separator.py +680 -0
  55. ahuora_builder/custom/tests/__init__.py +0 -0
  56. ahuora_builder/custom/tests/test_SimpleEffectivenessHX_DH.py +91 -0
  57. ahuora_builder/custom/tests/test_custom_tank.py +70 -0
  58. ahuora_builder/custom/tests/test_direct_steam_injection.py +41 -0
  59. ahuora_builder/custom/tests/test_simple_separator.py +46 -0
  60. ahuora_builder/custom/tests/test_waterpipe.py +46 -0
  61. ahuora_builder/custom/thermal_utility_systems/desuperheater.py +624 -0
  62. ahuora_builder/custom/thermal_utility_systems/header.py +889 -0
  63. ahuora_builder/custom/thermal_utility_systems/simple_heat_pump.py +567 -0
  64. ahuora_builder/custom/thermal_utility_systems/steam_header.py +353 -0
  65. ahuora_builder/custom/thermal_utility_systems/steam_user.py +944 -0
  66. ahuora_builder/custom/thermal_utility_systems/temp.py +349 -0
  67. ahuora_builder/custom/thermal_utility_systems/tests/test_desuperheater.py +142 -0
  68. ahuora_builder/custom/thermal_utility_systems/tests/test_header.py +998 -0
  69. ahuora_builder/custom/thermal_utility_systems/tests/test_ntu_hx.py +129 -0
  70. ahuora_builder/custom/thermal_utility_systems/tests/test_simple_heat_pump.py +120 -0
  71. ahuora_builder/custom/thermal_utility_systems/tests/test_steam_header.py +703 -0
  72. ahuora_builder/custom/thermal_utility_systems/tests/test_steam_user.py +277 -0
  73. ahuora_builder/custom/thermal_utility_systems/tests/test_waterpipe.py +36 -0
  74. ahuora_builder/custom/thermal_utility_systems/tests/test_willans_turbine.py +253 -0
  75. ahuora_builder/custom/thermal_utility_systems/willans_turbine.py +804 -0
  76. ahuora_builder/custom/translator.py +129 -0
  77. ahuora_builder/custom/updated_pressure_changer.py +1404 -0
  78. ahuora_builder/custom/valve_wrapper.py +38 -0
  79. ahuora_builder/custom/water_tank_with_units.py +456 -0
  80. ahuora_builder/diagnostics/__init__.py +0 -0
  81. ahuora_builder/diagnostics/infeasibilities.py +40 -0
  82. ahuora_builder/diagnostics/tests/__init__.py +0 -0
  83. ahuora_builder/diagnostics/tests/test_infeasibilities.py +28 -0
  84. ahuora_builder/flowsheet_manager.py +542 -0
  85. ahuora_builder/flowsheet_manager_type.py +20 -0
  86. ahuora_builder/generate_python_file.py +440 -0
  87. ahuora_builder/methods/BlockContext.py +84 -0
  88. ahuora_builder/methods/__init__.py +0 -0
  89. ahuora_builder/methods/adapter.py +355 -0
  90. ahuora_builder/methods/adapter_library.py +549 -0
  91. ahuora_builder/methods/adapter_methods.py +80 -0
  92. ahuora_builder/methods/expression_parsing.py +105 -0
  93. ahuora_builder/methods/load_unit_model.py +147 -0
  94. ahuora_builder/methods/slice_manipulation.py +7 -0
  95. ahuora_builder/methods/tests/__init__.py +0 -0
  96. ahuora_builder/methods/tests/test_expression_parsing.py +15 -0
  97. ahuora_builder/methods/units_handler.py +129 -0
  98. ahuora_builder/ml_wizard.py +101 -0
  99. ahuora_builder/port_manager.py +20 -0
  100. ahuora_builder/properties_manager.py +44 -0
  101. ahuora_builder/property_package_manager.py +78 -0
  102. ahuora_builder/solver.py +38 -0
  103. ahuora_builder/tear_manager.py +98 -0
  104. ahuora_builder/tests/__init__.py +0 -0
  105. ahuora_builder/tests/test_generate_python_file/__init__.py +0 -0
  106. ahuora_builder/tests/test_generate_python_file/configurations/compressor_generated.py +63 -0
  107. ahuora_builder/tests/test_generate_python_file/configurations/heat_exchanger_generated.py +70 -0
  108. ahuora_builder/tests/test_generate_python_file/configurations/pump_generated.py +84 -0
  109. ahuora_builder/tests/test_generate_python_file/configurations/recycle_generated.py +73 -0
  110. ahuora_builder/tests/test_generate_python_file/test_generate_python_file.py +108 -0
  111. ahuora_builder/tests/test_solver/__init__.py +0 -0
  112. ahuora_builder/tests/test_solver/configurations/BT_PR.json +59 -0
  113. ahuora_builder/tests/test_solver/configurations/BT_PR_solved.json +59 -0
  114. ahuora_builder/tests/test_solver/configurations/bus.json +99 -0
  115. ahuora_builder/tests/test_solver/configurations/bus_solved.json +50 -0
  116. ahuora_builder/tests/test_solver/configurations/compound_separator.json +377 -0
  117. ahuora_builder/tests/test_solver/configurations/compound_separator_solved.json +374 -0
  118. ahuora_builder/tests/test_solver/configurations/compressor.json +38 -0
  119. ahuora_builder/tests/test_solver/configurations/compressor_solved.json +68 -0
  120. ahuora_builder/tests/test_solver/configurations/constraints.json +44 -0
  121. ahuora_builder/tests/test_solver/configurations/constraints_solved.json +59 -0
  122. ahuora_builder/tests/test_solver/configurations/control.json +39 -0
  123. ahuora_builder/tests/test_solver/configurations/control_solved.json +68 -0
  124. ahuora_builder/tests/test_solver/configurations/dynamic_tank.json +733 -0
  125. ahuora_builder/tests/test_solver/configurations/dynamic_tank_solved.json +846 -0
  126. ahuora_builder/tests/test_solver/configurations/elimination.json +39 -0
  127. ahuora_builder/tests/test_solver/configurations/elimination_solved.json +68 -0
  128. ahuora_builder/tests/test_solver/configurations/expressions.json +68 -0
  129. ahuora_builder/tests/test_solver/configurations/expressions_solved.json +104 -0
  130. ahuora_builder/tests/test_solver/configurations/header.json +1192 -0
  131. ahuora_builder/tests/test_solver/configurations/header_solved.json +761 -0
  132. ahuora_builder/tests/test_solver/configurations/heat_exchanger.json +63 -0
  133. ahuora_builder/tests/test_solver/configurations/heat_exchanger_solved.json +104 -0
  134. ahuora_builder/tests/test_solver/configurations/heat_pump.json +137 -0
  135. ahuora_builder/tests/test_solver/configurations/heat_pump_solved.json +104 -0
  136. ahuora_builder/tests/test_solver/configurations/machine_learning.json +2156 -0
  137. ahuora_builder/tests/test_solver/configurations/machine_learning_solved.json +266 -0
  138. ahuora_builder/tests/test_solver/configurations/mass_flow_tear.json +77 -0
  139. ahuora_builder/tests/test_solver/configurations/mass_flow_tear_solved.json +68 -0
  140. ahuora_builder/tests/test_solver/configurations/milk_heater.json +521 -0
  141. ahuora_builder/tests/test_solver/configurations/milk_heater_solved.json +311 -0
  142. ahuora_builder/tests/test_solver/configurations/mixer.json +44 -0
  143. ahuora_builder/tests/test_solver/configurations/mixer_solved.json +86 -0
  144. ahuora_builder/tests/test_solver/configurations/optimization.json +62 -0
  145. ahuora_builder/tests/test_solver/configurations/optimization_solved.json +59 -0
  146. ahuora_builder/tests/test_solver/configurations/propane_heat_pump.json +167 -0
  147. ahuora_builder/tests/test_solver/configurations/propane_heat_pump_solved.json +158 -0
  148. ahuora_builder/tests/test_solver/configurations/propane_recycle.json +141 -0
  149. ahuora_builder/tests/test_solver/configurations/propane_recycle_solved.json +104 -0
  150. ahuora_builder/tests/test_solver/configurations/pump.json +64 -0
  151. ahuora_builder/tests/test_solver/configurations/pump_solved.json +59 -0
  152. ahuora_builder/tests/test_solver/configurations/pump_unit_conversions.json +63 -0
  153. ahuora_builder/tests/test_solver/configurations/recycle.json +49 -0
  154. ahuora_builder/tests/test_solver/configurations/recycle_solved.json +50 -0
  155. ahuora_builder/tests/test_solver/configurations/sb_vapor_frac.json +29 -0
  156. ahuora_builder/tests/test_solver/configurations/sb_vapor_frac_solved.json +29 -0
  157. ahuora_builder/tests/test_solver/configurations/solar.json +67 -0
  158. ahuora_builder/tests/test_solver/configurations/solar_solved.json +50 -0
  159. ahuora_builder/tests/test_solver/configurations/vapor_frac_target.json +67 -0
  160. ahuora_builder/tests/test_solver/configurations/vapor_frac_target_solved.json +68 -0
  161. ahuora_builder/tests/test_solver/test_solve_models.py +250 -0
  162. ahuora_builder/timing.py +65 -0
  163. ahuora_builder/types/__init__.py +1 -0
  164. ahuora_builder/unit_model_manager.py +48 -0
  165. ahuora_builder-0.1.0.dist-info/METADATA +14 -0
  166. ahuora_builder-0.1.0.dist-info/RECORD +167 -0
  167. 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
+