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
|
File without changes
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from pyomo.environ import Reference, Var, units
|
|
2
|
+
from pyomo.dae import DerivativeVar
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def add_initial_dynamics(unit_model):
|
|
6
|
+
"""
|
|
7
|
+
Adds the reference variables for initial holdup and initial accumulation.
|
|
8
|
+
Makes it easier for us to set initial conditions in the frontend, as we can reference them directly.
|
|
9
|
+
"""
|
|
10
|
+
if unit_model.config.dynamic:
|
|
11
|
+
# add initial holdup reference
|
|
12
|
+
unit_model.initial_material_holdup = Reference(unit_model.control_volume.material_holdup[0,:,:])
|
|
13
|
+
unit_model.initial_energy_holdup = Reference(unit_model.control_volume.energy_holdup[0,:])
|
|
14
|
+
|
|
15
|
+
# For some reason we can't do references to the initialaccumulation variables,
|
|
16
|
+
# Error ( Can only take the derivative of a Varcomponent.)
|
|
17
|
+
# so we create them as vars
|
|
18
|
+
unit_model.initial_material_accumulation = Var(unit_model.config.property_package.phase_list, unit_model.config.property_package.component_list, initialize=0,units=units.mol/units.s)
|
|
19
|
+
unit_model.initial_energy_accumulation = Var(unit_model.config.property_package.phase_list, initialize=0,units=units.kW)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@unit_model.Constraint(
|
|
23
|
+
unit_model.config.property_package.phase_list,
|
|
24
|
+
unit_model.config.property_package.component_list,
|
|
25
|
+
doc="Initial material accumulation constraint"
|
|
26
|
+
)
|
|
27
|
+
def initial_material_accumulation_constraint(b, p, j):
|
|
28
|
+
return b.initial_material_accumulation[p, j] == b.control_volume.material_accumulation[0, p, j]
|
|
29
|
+
|
|
30
|
+
@unit_model.Constraint(
|
|
31
|
+
unit_model.config.property_package.phase_list,
|
|
32
|
+
doc="Initial energy accumulation constraint"
|
|
33
|
+
)
|
|
34
|
+
def initial_energy_accumulation_constraint(b, p):
|
|
35
|
+
return b.initial_energy_accumulation[p] == b.control_volume.energy_accumulation[0, p]
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# Import Pyomo libraries
|
|
2
|
+
from pyomo.environ import (
|
|
3
|
+
Var,
|
|
4
|
+
Suffix,
|
|
5
|
+
units as pyunits,
|
|
6
|
+
)
|
|
7
|
+
from pyomo.common.config import ConfigBlock, ConfigValue, In
|
|
8
|
+
from idaes.core.util.tables import create_stream_table_dataframe
|
|
9
|
+
from idaes.core.util.exceptions import ConfigurationError
|
|
10
|
+
# Import IDAES cores
|
|
11
|
+
from idaes.core import (
|
|
12
|
+
declare_process_block_class,
|
|
13
|
+
UnitModelBlockData,
|
|
14
|
+
useDefault,
|
|
15
|
+
)
|
|
16
|
+
from idaes.core.util.config import is_physical_parameter_block
|
|
17
|
+
import idaes.core.util.scaling as iscale
|
|
18
|
+
import idaes.logger as idaeslog
|
|
19
|
+
|
|
20
|
+
from ..custom.updated_pressure_changer import (
|
|
21
|
+
|
|
22
|
+
CompressorData,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# When using this file the name "CustomCompressor" is what is imported
|
|
29
|
+
@declare_process_block_class("CustomCompressor")
|
|
30
|
+
class CustomCompressorData(CompressorData):
|
|
31
|
+
"""
|
|
32
|
+
Zero order Load model
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
# CONFIG are options for the unit model, this simple model only has the mandatory config options
|
|
36
|
+
CONFIG = CompressorData.CONFIG()
|
|
37
|
+
|
|
38
|
+
CONFIG.declare(
|
|
39
|
+
"power_property_package",
|
|
40
|
+
ConfigValue(
|
|
41
|
+
default=useDefault,
|
|
42
|
+
domain=is_physical_parameter_block,
|
|
43
|
+
description="Property package to use for power",
|
|
44
|
+
doc="""Power Property parameter object used to define power calculations,
|
|
45
|
+
**default** - useDefault.
|
|
46
|
+
**Valid values:** {
|
|
47
|
+
**useDefault** - use default package from parent model or flowsheet,
|
|
48
|
+
**PhysicalParameterObject** - a PhysicalParameterBlock object.}""",
|
|
49
|
+
),
|
|
50
|
+
)
|
|
51
|
+
CONFIG.declare(
|
|
52
|
+
"power_property_package_args",
|
|
53
|
+
ConfigBlock(
|
|
54
|
+
implicit=True,
|
|
55
|
+
description="Arguments to use for constructing power property packages",
|
|
56
|
+
doc="""A ConfigBlock with arguments to be passed to a property block(s)
|
|
57
|
+
and used when constructing these,
|
|
58
|
+
**default** - None.
|
|
59
|
+
**Valid values:** {
|
|
60
|
+
see property package for documentation.}""",
|
|
61
|
+
),
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
def build(self):
|
|
65
|
+
# build always starts by calling super().build()
|
|
66
|
+
# This triggers a lot of boilerplate in the background for you
|
|
67
|
+
super().build()
|
|
68
|
+
|
|
69
|
+
# This creates blank scaling factors, which are populated later
|
|
70
|
+
self.scaling_factor = Suffix(direction=Suffix.EXPORT)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
# Add state blocks for inlet, outlet, and waste
|
|
74
|
+
# These include the state variables and any other properties on demand
|
|
75
|
+
|
|
76
|
+
tmp_dict = dict(**self.config.property_package_args)
|
|
77
|
+
tmp_dict["parameters"] = self.config.property_package
|
|
78
|
+
tmp_dict["defined_state"] = True # inlet block is an inlet
|
|
79
|
+
# Add inlet block
|
|
80
|
+
# self.properties_in = self.config.property_package.state_block_class(
|
|
81
|
+
# self.flowsheet().config.time, doc="Material properties of inlet", **tmp_dict
|
|
82
|
+
# )
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
# Add outlet and waste block
|
|
86
|
+
tmp_dict["defined_state"] = False # outlet and waste block is not an inlet
|
|
87
|
+
self.power_properties_out = self.config.power_property_package.state_block_class(
|
|
88
|
+
self.flowsheet().config.time,
|
|
89
|
+
doc="Material properties of outlet",
|
|
90
|
+
**tmp_dict
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
# Add ports - oftentimes users interact with these rather than the state blocks
|
|
94
|
+
self.add_port(name="power_outlet", block=self.power_properties_out)
|
|
95
|
+
|
|
96
|
+
# Add constraints
|
|
97
|
+
# Usually unit models use a control volume to do the mass, energy, and momentum
|
|
98
|
+
# balances, however, they will be explicitly written out in this example
|
|
99
|
+
@self.Constraint(
|
|
100
|
+
self.flowsheet().time,
|
|
101
|
+
doc="Power out",
|
|
102
|
+
)
|
|
103
|
+
def eq_power_out(b, t):
|
|
104
|
+
return (
|
|
105
|
+
self.power_properties_out[t].power == self.work_mechanical[t] * -1
|
|
106
|
+
)
|
|
107
|
+
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from idaes.models.unit_models.heater import HeaterData
|
|
2
|
+
from .inverted import add_inverted, initialise_inverted, enable_inverted, disable_inverted
|
|
3
|
+
from idaes.core import declare_process_block_class
|
|
4
|
+
|
|
5
|
+
@declare_process_block_class("CustomCooler")
|
|
6
|
+
class CustomCoolerData(HeaterData):
|
|
7
|
+
"""
|
|
8
|
+
Custom Cooler model that includes inverted deltaP and Heat Added properties.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
def build(self, *args, **kwargs):
|
|
12
|
+
super().build(*args, **kwargs)
|
|
13
|
+
|
|
14
|
+
# add deltaP_inverted as a property
|
|
15
|
+
add_inverted(self, "heat_duty")
|
|
16
|
+
if hasattr(self,"deltaP"):
|
|
17
|
+
add_inverted(self, "deltaP")
|
|
18
|
+
|
|
19
|
+
def initialize_build(
|
|
20
|
+
self,*args,**kwargs,
|
|
21
|
+
):
|
|
22
|
+
initialise_inverted(self, "heat_duty")
|
|
23
|
+
disable_inverted(self, "heat_duty")
|
|
24
|
+
|
|
25
|
+
if hasattr(self,"deltaP"):
|
|
26
|
+
initialise_inverted(self, "deltaP")
|
|
27
|
+
disable_inverted(self, "deltaP")
|
|
28
|
+
|
|
29
|
+
super().initialize_build(*args, **kwargs)
|
|
30
|
+
|
|
31
|
+
enable_inverted(self, "heat_duty")
|
|
32
|
+
if hasattr(self,"deltaP"):
|
|
33
|
+
enable_inverted(self, "deltaP")
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
# Import Pyomo libraries
|
|
4
|
+
from pyomo.environ import (
|
|
5
|
+
Block,
|
|
6
|
+
Var,
|
|
7
|
+
Param,
|
|
8
|
+
log,
|
|
9
|
+
Reference,
|
|
10
|
+
PositiveReals,
|
|
11
|
+
ExternalFunction,
|
|
12
|
+
units as pyunits,
|
|
13
|
+
check_optimal_termination,
|
|
14
|
+
)
|
|
15
|
+
from pyomo.common.config import ConfigBlock, ConfigValue, In
|
|
16
|
+
|
|
17
|
+
# Import IDAES cores
|
|
18
|
+
from idaes.core import (
|
|
19
|
+
declare_process_block_class,
|
|
20
|
+
UnitModelBlockData,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
import idaes.logger as idaeslog
|
|
24
|
+
from idaes.core.util.functions import functions_lib
|
|
25
|
+
from idaes.core.util.tables import create_stream_table_dataframe
|
|
26
|
+
from idaes.models.unit_models.heater import (
|
|
27
|
+
_make_heater_config_block,
|
|
28
|
+
_make_heater_control_volume,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
from idaes.core.util.misc import add_object_reference
|
|
32
|
+
from idaes.core.util import scaling as iscale
|
|
33
|
+
from idaes.core.solvers import get_solver
|
|
34
|
+
from idaes.core.util.exceptions import ConfigurationError, InitializationError
|
|
35
|
+
from idaes.core.initialization import SingleControlVolumeUnitInitializer
|
|
36
|
+
from idaes.models.unit_models.heat_exchanger import HX0DInitializer, _make_heat_exchanger_config, HeatExchangerData
|
|
37
|
+
from .inverted import add_inverted, initialise_inverted
|
|
38
|
+
_log = idaeslog.getLogger(__name__)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@declare_process_block_class("CustomHeatExchanger", doc="Simple 0D heat exchanger model.")
|
|
42
|
+
class CustomHeatExchangerData(HeatExchangerData):
|
|
43
|
+
|
|
44
|
+
def build(self,*args,**kwargs) -> None:
|
|
45
|
+
"""
|
|
46
|
+
Begin building model.
|
|
47
|
+
"""
|
|
48
|
+
super().build(*args,**kwargs)
|
|
49
|
+
# Add an inverted DeltaP
|
|
50
|
+
add_inverted(self.hot_side, "deltaP")
|
|
51
|
+
add_inverted(self.cold_side, "deltaP")
|
|
52
|
+
|
|
53
|
+
def initialize_build(
|
|
54
|
+
self,
|
|
55
|
+
state_args_1=None,
|
|
56
|
+
state_args_2=None,
|
|
57
|
+
outlvl=idaeslog.NOTSET,
|
|
58
|
+
solver=None,
|
|
59
|
+
optarg=None,
|
|
60
|
+
duty=None,
|
|
61
|
+
):
|
|
62
|
+
"""
|
|
63
|
+
Heat exchanger initialization method.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
state_args_1 : a dict of arguments to be passed to the property
|
|
67
|
+
initialization for the hot side (see documentation of the specific
|
|
68
|
+
property package) (default = {}).
|
|
69
|
+
state_args_2 : a dict of arguments to be passed to the property
|
|
70
|
+
initialization for the cold side (see documentation of the specific
|
|
71
|
+
property package) (default = {}).
|
|
72
|
+
outlvl : sets output level of initialization routine
|
|
73
|
+
optarg : solver options dictionary object (default=None, use
|
|
74
|
+
default solver options)
|
|
75
|
+
solver : str indicating which solver to use during
|
|
76
|
+
initialization (default = None, use default solver)
|
|
77
|
+
duty : an initial guess for the amount of heat transferred. This
|
|
78
|
+
should be a tuple in the form (value, units), (default
|
|
79
|
+
= (1000 J/s))
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
None
|
|
83
|
+
|
|
84
|
+
"""
|
|
85
|
+
# So, when solving with a correct area, there can be problems
|
|
86
|
+
# That's because if the area's even slightly too large, it becomes infeasible
|
|
87
|
+
if not self.area.fixed:
|
|
88
|
+
self.area.value = self.area.value * 0.8
|
|
89
|
+
|
|
90
|
+
initialise_inverted(self.hot_side, "deltaP")
|
|
91
|
+
initialise_inverted(self.cold_side, "deltaP")
|
|
92
|
+
|
|
93
|
+
# Set solver options
|
|
94
|
+
init_log = idaeslog.getInitLogger(self.name, outlvl, tag="unit")
|
|
95
|
+
solve_log = idaeslog.getSolveLogger(self.name, outlvl, tag="unit")
|
|
96
|
+
|
|
97
|
+
# Create solver
|
|
98
|
+
opt = get_solver(solver, optarg)
|
|
99
|
+
|
|
100
|
+
flags1 = self.hot_side.initialize(
|
|
101
|
+
outlvl=outlvl, optarg=optarg, solver=solver, state_args=state_args_1
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
init_log.info_high("Initialization Step 1a (hot side) Complete.")
|
|
105
|
+
|
|
106
|
+
flags2 = self.cold_side.initialize(
|
|
107
|
+
outlvl=outlvl, optarg=optarg, solver=solver, state_args=state_args_2
|
|
108
|
+
)
|
|
109
|
+
init_log.info_high("Initialization Step 1b (cold side) Complete.")
|
|
110
|
+
# ---------------------------------------------------------------------
|
|
111
|
+
# Solve unit without heat transfer equation
|
|
112
|
+
self.heat_transfer_equation.deactivate()
|
|
113
|
+
if hasattr( self.cold_side.properties_out[0], "constraints"):
|
|
114
|
+
self.cold_side.properties_out[0].constraints.deactivate()
|
|
115
|
+
if hasattr( self.hot_side.properties_out[0], "constraints"):
|
|
116
|
+
self.hot_side.properties_out[0].constraints.deactivate()
|
|
117
|
+
|
|
118
|
+
# Get side 1 and side 2 heat units, and convert duty as needed
|
|
119
|
+
s1_units = self.hot_side.heat.get_units()
|
|
120
|
+
s2_units = self.cold_side.heat.get_units()
|
|
121
|
+
|
|
122
|
+
# Check to see if heat duty is fixed
|
|
123
|
+
# WE will assume that if the first point is fixed, it is fixed at all points
|
|
124
|
+
if not self.cold_side.heat[self.flowsheet().time.first()].fixed:
|
|
125
|
+
cs_fixed = False
|
|
126
|
+
if duty is None:
|
|
127
|
+
# Assume 1000 J/s and check for unitless properties
|
|
128
|
+
if s1_units is None and s2_units is None:
|
|
129
|
+
# Backwards compatibility for unitless properties
|
|
130
|
+
s1_duty = -1000
|
|
131
|
+
s2_duty = 1000
|
|
132
|
+
else:
|
|
133
|
+
s1_duty = pyunits.convert_value(
|
|
134
|
+
-1000, from_units=pyunits.W, to_units=s1_units
|
|
135
|
+
)
|
|
136
|
+
s2_duty = pyunits.convert_value(
|
|
137
|
+
1000, from_units=pyunits.W, to_units=s2_units
|
|
138
|
+
)
|
|
139
|
+
else:
|
|
140
|
+
# Duty provided with explicit units
|
|
141
|
+
s1_duty = -pyunits.convert_value(
|
|
142
|
+
duty[0], from_units=duty[1], to_units=s1_units
|
|
143
|
+
)
|
|
144
|
+
s2_duty = pyunits.convert_value(
|
|
145
|
+
duty[0], from_units=duty[1], to_units=s2_units
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
self.cold_side.heat.fix(s2_duty)
|
|
149
|
+
for i in self.hot_side.heat:
|
|
150
|
+
self.hot_side.heat[i].value = s1_duty
|
|
151
|
+
else:
|
|
152
|
+
cs_fixed = True
|
|
153
|
+
for i in self.hot_side.heat:
|
|
154
|
+
self.hot_side.heat[i].set_value(self.cold_side.heat[i])
|
|
155
|
+
with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc:
|
|
156
|
+
res = opt.solve(self, tee=slc.tee)
|
|
157
|
+
init_log.info_high("Initialization Step 2 {}.".format(idaeslog.condition(res)))
|
|
158
|
+
if not cs_fixed:
|
|
159
|
+
self.cold_side.heat.unfix()
|
|
160
|
+
if hasattr( self.cold_side.properties_out[0], "constraints"):
|
|
161
|
+
self.cold_side.properties_out[0].constraints.activate()
|
|
162
|
+
if hasattr( self.hot_side.properties_out[0], "constraints"):
|
|
163
|
+
self.hot_side.properties_out[0].constraints.activate()
|
|
164
|
+
self.heat_transfer_equation.activate()
|
|
165
|
+
|
|
166
|
+
# ---------------------------------------------------------------------
|
|
167
|
+
# Solve unit
|
|
168
|
+
with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc:
|
|
169
|
+
res = opt.solve(self, tee=slc.tee)
|
|
170
|
+
init_log.info_high("Initialization Step 3 {}.".format(idaeslog.condition(res)))
|
|
171
|
+
# ---------------------------------------------------------------------
|
|
172
|
+
|
|
173
|
+
# Release Inlet state
|
|
174
|
+
self.hot_side.release_state(flags1, outlvl=outlvl)
|
|
175
|
+
self.cold_side.release_state(flags2, outlvl=outlvl)
|
|
176
|
+
|
|
177
|
+
init_log.info("Initialization Completed, {}".format(idaeslog.condition(res)))
|
|
178
|
+
|
|
179
|
+
if not check_optimal_termination(res):
|
|
180
|
+
raise InitializationError(
|
|
181
|
+
f"{self.name} failed to initialize successfully. Please check "
|
|
182
|
+
f"the output logs for more information."
|
|
183
|
+
)
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
from pyomo.environ import Var, Constraint, Reference, units as pyunits
|
|
2
|
+
from pyomo.common.numeric_types import value
|
|
3
|
+
from pyomo.opt.results.solver import check_optimal_termination
|
|
4
|
+
from idaes.core import declare_process_block_class
|
|
5
|
+
from idaes.core.util import scaling as iscale
|
|
6
|
+
from idaes.models.unit_models.heat_exchanger_1D import HeatExchanger1DData, HX1DInitializer
|
|
7
|
+
from idaes.models.unit_models.heat_exchanger import HeatExchangerFlowPattern
|
|
8
|
+
from idaes.core.solvers import get_solver
|
|
9
|
+
from idaes.core.util.exceptions import InitializationError
|
|
10
|
+
import idaes.logger as idaeslog
|
|
11
|
+
|
|
12
|
+
class CustomHX1DInitializer(HX1DInitializer):
|
|
13
|
+
"""
|
|
14
|
+
Use our custom control-volume initialize (no source port-member fixing).
|
|
15
|
+
"""
|
|
16
|
+
def initialize_control_volume(self, cv, state_args=None):
|
|
17
|
+
return initialize(
|
|
18
|
+
cv,
|
|
19
|
+
state_args=state_args,
|
|
20
|
+
outlvl=self.get_output_level(),
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
@declare_process_block_class(
|
|
24
|
+
"CustomHeatExchanger1D",
|
|
25
|
+
doc="1D Heat Exchanger with overall U tied to local heat_transfer_coefficient.",
|
|
26
|
+
)
|
|
27
|
+
class CustomHeatExchanger1DData(HeatExchanger1DData):
|
|
28
|
+
# Use our initializer so both sides use the custom CV initialize
|
|
29
|
+
default_initializer = CustomHX1DInitializer
|
|
30
|
+
|
|
31
|
+
CONFIG = HeatExchanger1DData.CONFIG()
|
|
32
|
+
|
|
33
|
+
def build(self):
|
|
34
|
+
super().build()
|
|
35
|
+
# Ends of the tube along the length axis (start and end positions)
|
|
36
|
+
x_first = self.hot_side.length_domain.first()
|
|
37
|
+
x_last = self.hot_side.length_domain.last()
|
|
38
|
+
|
|
39
|
+
# Hot side: inlet at start, outlet at end
|
|
40
|
+
x_hot_in, x_hot_out = x_first, x_last
|
|
41
|
+
|
|
42
|
+
# Cold side: depends on flow pattern
|
|
43
|
+
if self.config.flow_type == HeatExchangerFlowPattern.cocurrent:
|
|
44
|
+
x_cold_in, x_cold_out = x_first, x_last
|
|
45
|
+
else:
|
|
46
|
+
x_cold_in, x_cold_out = x_last, x_first
|
|
47
|
+
|
|
48
|
+
# Time-only inlet/outlet views of the boundary states (no extra vars/cons)
|
|
49
|
+
self.hot_side.properties_in = Reference(self.hot_side.properties[:, x_hot_in])
|
|
50
|
+
self.hot_side.properties_out = Reference(self.hot_side.properties[:, x_hot_out])
|
|
51
|
+
self.cold_side.properties_in = Reference(self.cold_side.properties[:, x_cold_in])
|
|
52
|
+
self.cold_side.properties_out = Reference(self.cold_side.properties[:, x_cold_out])
|
|
53
|
+
|
|
54
|
+
# Overall U
|
|
55
|
+
self.overall_heat_transfer_coefficient = Var(
|
|
56
|
+
self.flowsheet().time,
|
|
57
|
+
initialize=500.0,
|
|
58
|
+
bounds=(1.0, 1e5),
|
|
59
|
+
units=pyunits.W / pyunits.m**2 / pyunits.K,
|
|
60
|
+
doc="Overall (constant along length) heat transfer coefficient U.",
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
@self.Constraint(self.flowsheet().time, self.hot_side.length_domain)
|
|
64
|
+
def overall_heat_transfer_coefficient_def(b, t, x):
|
|
65
|
+
return b.overall_heat_transfer_coefficient[t] == b.heat_transfer_coefficient[t, x]
|
|
66
|
+
|
|
67
|
+
iscale.set_scaling_factor(self.overall_heat_transfer_coefficient, 1e-3)
|
|
68
|
+
|
|
69
|
+
def initialize_build(
|
|
70
|
+
self,
|
|
71
|
+
hot_side_state_args=None,
|
|
72
|
+
cold_side_state_args=None,
|
|
73
|
+
outlvl=idaeslog.NOTSET,
|
|
74
|
+
solver=None,
|
|
75
|
+
optarg=None,
|
|
76
|
+
duty=None,
|
|
77
|
+
):
|
|
78
|
+
init_log = idaeslog.getInitLogger(self.name, outlvl, tag="unit")
|
|
79
|
+
solve_log = idaeslog.getSolveLogger(self.name, outlvl, tag="unit")
|
|
80
|
+
opt = get_solver(solver, optarg)
|
|
81
|
+
|
|
82
|
+
# Sync length values
|
|
83
|
+
if self.length.fixed:
|
|
84
|
+
self.cold_side.length.set_value(self.length)
|
|
85
|
+
elif self.cold_side.length.fixed:
|
|
86
|
+
self.length.set_value(self.cold_side.length)
|
|
87
|
+
|
|
88
|
+
# Initialize control volumes with length fixed
|
|
89
|
+
Lfix = self.hot_side.length.fixed
|
|
90
|
+
self.hot_side.length.fix()
|
|
91
|
+
flags_hot_side = initialize(
|
|
92
|
+
self.hot_side,
|
|
93
|
+
outlvl=outlvl,
|
|
94
|
+
optarg=optarg,
|
|
95
|
+
solver=solver,
|
|
96
|
+
state_args=hot_side_state_args,
|
|
97
|
+
)
|
|
98
|
+
if not Lfix:
|
|
99
|
+
self.hot_side.length.unfix()
|
|
100
|
+
|
|
101
|
+
Lfix = self.cold_side.length.fixed
|
|
102
|
+
self.cold_side.length.fix()
|
|
103
|
+
# Use our custom CV initialize here as well
|
|
104
|
+
flags_cold_side = initialize(
|
|
105
|
+
self.cold_side,
|
|
106
|
+
outlvl=outlvl,
|
|
107
|
+
optarg=optarg,
|
|
108
|
+
solver=solver,
|
|
109
|
+
state_args=cold_side_state_args,
|
|
110
|
+
)
|
|
111
|
+
if not Lfix:
|
|
112
|
+
self.cold_side.length.unfix()
|
|
113
|
+
|
|
114
|
+
init_log.info_high("Initialization Step 1 Complete.")
|
|
115
|
+
|
|
116
|
+
# Fixed-duty solve
|
|
117
|
+
hot_units = self.hot_side.config.property_package.get_metadata().get_derived_units
|
|
118
|
+
cold_units = self.cold_side.config.property_package.get_metadata().get_derived_units
|
|
119
|
+
t0 = self.flowsheet().time.first()
|
|
120
|
+
|
|
121
|
+
# Use inlet indices for each side
|
|
122
|
+
x_hot_in = self.hot_side.length_domain.first()
|
|
123
|
+
x_cold_in = self.cold_side.length_domain.first() if self.config.flow_type == HeatExchangerFlowPattern.cocurrent else self.cold_side.length_domain.last()
|
|
124
|
+
|
|
125
|
+
if duty is None:
|
|
126
|
+
duty = value(
|
|
127
|
+
0.25
|
|
128
|
+
* self.heat_transfer_coefficient[t0, x_hot_in]
|
|
129
|
+
* self.area
|
|
130
|
+
* (
|
|
131
|
+
self.hot_side.properties[t0, x_hot_in].temperature
|
|
132
|
+
- pyunits.convert(
|
|
133
|
+
self.cold_side.properties[t0, x_cold_in].temperature,
|
|
134
|
+
to_units=hot_units("temperature"),
|
|
135
|
+
)
|
|
136
|
+
)
|
|
137
|
+
)
|
|
138
|
+
else:
|
|
139
|
+
duty = pyunits.convert_value(duty[0], from_units=duty[1], to_units=hot_units("power"))
|
|
140
|
+
|
|
141
|
+
duty_per_length = value(duty / self.length)
|
|
142
|
+
|
|
143
|
+
# Fix heat duties
|
|
144
|
+
for v in self.hot_side.heat.values():
|
|
145
|
+
v.fix(-duty_per_length)
|
|
146
|
+
for v in self.cold_side.heat.values():
|
|
147
|
+
v.fix(pyunits.convert_value(duty_per_length, to_units=cold_units("power")/cold_units("length"), from_units=hot_units("power")/hot_units("length")))
|
|
148
|
+
|
|
149
|
+
# Deactivate heat duty constraints and solve
|
|
150
|
+
self.heat_transfer_eq.deactivate()
|
|
151
|
+
self.heat_conservation.deactivate()
|
|
152
|
+
with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc:
|
|
153
|
+
res = opt.solve(self, tee=slc.tee)
|
|
154
|
+
init_log.info_high("Initialization Step 2 {}.".format(idaeslog.condition(res)))
|
|
155
|
+
|
|
156
|
+
# Unfix heat duties and re-activate constraints
|
|
157
|
+
for v in self.hot_side.heat.values():
|
|
158
|
+
v.unfix()
|
|
159
|
+
for v in self.cold_side.heat.values():
|
|
160
|
+
v.unfix()
|
|
161
|
+
self.heat_transfer_eq.activate()
|
|
162
|
+
self.heat_conservation.activate()
|
|
163
|
+
with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc:
|
|
164
|
+
res = opt.solve(self, tee=slc.tee)
|
|
165
|
+
init_log.info_high("Initialization Step 3 {}.".format(idaeslog.condition(res)))
|
|
166
|
+
|
|
167
|
+
release_state(self.hot_side, flags_hot_side)
|
|
168
|
+
release_state(self.cold_side, flags_cold_side)
|
|
169
|
+
|
|
170
|
+
if res is not None and not check_optimal_termination(res):
|
|
171
|
+
raise InitializationError(f"{self.name} failed to initialize successfully. See logs.")
|
|
172
|
+
|
|
173
|
+
init_log.info("Initialization Complete.")
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def initialize(
|
|
177
|
+
blk,
|
|
178
|
+
state_args=None,
|
|
179
|
+
outlvl=idaeslog.NOTSET,
|
|
180
|
+
optarg=None,
|
|
181
|
+
solver=None,
|
|
182
|
+
hold_state=True,
|
|
183
|
+
):
|
|
184
|
+
"""
|
|
185
|
+
Initialization routine for 1D control volume.
|
|
186
|
+
|
|
187
|
+
Keyword Arguments:
|
|
188
|
+
state_args: a dict of arguments to be passed to the property
|
|
189
|
+
package(s) to provide an initial state for initialization
|
|
190
|
+
(see documentation of the specific property package) (default = {}).
|
|
191
|
+
outlvl: sets output level of initialization routine
|
|
192
|
+
optarg: solver options dictionary object (default=None, use
|
|
193
|
+
default solver options)
|
|
194
|
+
solver: str indicating which solver to use during initialization
|
|
195
|
+
(default = None)
|
|
196
|
+
hold_state: flag indicating whether the initialization routine
|
|
197
|
+
should unfix any state variables fixed during initialization,
|
|
198
|
+
(default = True). **Valid values:**
|
|
199
|
+
**True** - states variables are not unfixed, and a dict of
|
|
200
|
+
returned containing flags for which states were fixed
|
|
201
|
+
during initialization, **False** - state variables are
|
|
202
|
+
unfixed after initialization by calling the release_state
|
|
203
|
+
method.
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
If hold_states is True, returns a dict containing flags for which
|
|
207
|
+
states were fixed during initialization else the release state is
|
|
208
|
+
triggered.
|
|
209
|
+
"""
|
|
210
|
+
if optarg is None:
|
|
211
|
+
optarg = {}
|
|
212
|
+
|
|
213
|
+
# Get inlet state if not provided
|
|
214
|
+
init_log = idaeslog.getInitLogger(blk.name, outlvl, tag="control_volume")
|
|
215
|
+
|
|
216
|
+
# Provide guesses if none
|
|
217
|
+
if state_args is None:
|
|
218
|
+
blk.estimate_states(always_estimate=True)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
if state_args is None:
|
|
222
|
+
# If no initial guesses provided, estimate values for states
|
|
223
|
+
blk.estimate_states(always_estimate=True)
|
|
224
|
+
|
|
225
|
+
# Initialize state blocks
|
|
226
|
+
flags = blk.properties.initialize(
|
|
227
|
+
state_args=state_args,
|
|
228
|
+
outlvl=outlvl,
|
|
229
|
+
optarg=optarg,
|
|
230
|
+
solver=solver,
|
|
231
|
+
hold_state=True,
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
try:
|
|
235
|
+
# TODO: setting state_vars_fixed may not work for heterogeneous
|
|
236
|
+
# systems where a second control volume is involved, as we cannot
|
|
237
|
+
# assume those state vars are also fixed. For now, heterogeneous
|
|
238
|
+
# reactions should ignore the state_vars_fixed argument and always
|
|
239
|
+
# check their state_vars.
|
|
240
|
+
blk.reactions.initialize(
|
|
241
|
+
outlvl=outlvl,
|
|
242
|
+
optarg=optarg,
|
|
243
|
+
solver=solver,
|
|
244
|
+
state_vars_fixed=True,
|
|
245
|
+
)
|
|
246
|
+
except AttributeError:
|
|
247
|
+
pass
|
|
248
|
+
|
|
249
|
+
init_log.info("Initialization Complete")
|
|
250
|
+
|
|
251
|
+
# Unfix state variables except for source block
|
|
252
|
+
blk.properties.release_state(flags)
|
|
253
|
+
|
|
254
|
+
return {}
|
|
255
|
+
|
|
256
|
+
def release_state(blk, flags, outlvl=idaeslog.NOTSET):
|
|
257
|
+
# No-op: nothing was fixed at the CV level in our custom initialize
|
|
258
|
+
return
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from idaes.core import declare_process_block_class
|
|
2
|
+
from idaes.models.unit_models.heater import HeaterData
|
|
3
|
+
from .add_initial_dynamics import add_initial_dynamics
|
|
4
|
+
from .inverted import add_inverted, initialise_inverted, disable_inverted, enable_inverted
|
|
5
|
+
|
|
6
|
+
@declare_process_block_class("DynamicHeater")
|
|
7
|
+
class DynamicHeaterData(HeaterData):
|
|
8
|
+
"""
|
|
9
|
+
Dynamic Heater unit model class.
|
|
10
|
+
This extends the Heater class to include reference variables for initial holdup and initial accumulation.
|
|
11
|
+
Which makes it easier for us to set initial conditions in the frontend.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def build(self,*args, **kwargs):
|
|
15
|
+
"""
|
|
16
|
+
Build method for the DynamicHeaterData class.
|
|
17
|
+
This method initializes the control volume and sets up the model.
|
|
18
|
+
"""
|
|
19
|
+
super().build(*args, **kwargs)
|
|
20
|
+
|
|
21
|
+
if hasattr(self,"deltaP"):
|
|
22
|
+
# else has_pressure_change is false
|
|
23
|
+
add_inverted(self, "deltaP")
|
|
24
|
+
|
|
25
|
+
add_initial_dynamics(self)
|
|
26
|
+
|
|
27
|
+
def initialize_build(
|
|
28
|
+
self,*args,**kwargs,
|
|
29
|
+
):
|
|
30
|
+
"""
|
|
31
|
+
Initialize method for the DynamicHeaterData class.
|
|
32
|
+
This method initializes the control volume and sets up the model.
|
|
33
|
+
"""
|
|
34
|
+
if hasattr(self,"deltaP"):
|
|
35
|
+
initialise_inverted(self, "deltaP")
|
|
36
|
+
disable_inverted(self, "deltaP")
|
|
37
|
+
|
|
38
|
+
super().initialize_build(*args, **kwargs)
|
|
39
|
+
|
|
40
|
+
if hasattr(self,"deltaP"):
|
|
41
|
+
enable_inverted(self, "deltaP")
|