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
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")