Coverage for backend/idaes_service/solver/custom/custom_tank.py: 93%
53 statements
« prev ^ index » next coverage.py v7.10.7, created at 2025-11-06 23:27 +0000
« prev ^ index » next coverage.py v7.10.7, created at 2025-11-06 23:27 +0000
1from idaes.core import declare_process_block_class, MaterialBalanceType
2from .water_tank_with_units import WaterTankData
3from .add_initial_dynamics import add_initial_dynamics
4from pyomo.environ import Reference, Var
5from pyomo.dae import DerivativeVar
6from pyomo.environ import units as pyunit
7from .inverted import add_inverted, initialise_inverted
10def CustomTank(*args, **kwargs):
11 # In dynamics mode, we need to use the default material balance type of componentPhase.
12 # This does balances for liquid and vapor phases separately. This is needed becasue
13 # accumulation terms are phase specific.
14 # However, in steady-state, this is not necessary, because we don't have accumulation terms.
15 # So this would make the system over-defined, as the state block defines the phase equilibrium already.
16 is_dynamic = kwargs.get("dynamic")
17 if is_dynamic:
18 kwargs["material_balance_type"] = MaterialBalanceType.componentPhase
19 else:
20 kwargs["material_balance_type"] = MaterialBalanceType.componentTotal
22 return DynamicTank(*args, **kwargs)
25@declare_process_block_class("DynamicTank")
26class DynamicTankData(WaterTankData):
27 """
28 Water tank model with dynamic capabilities.
29 Some extra properties are added to IDAES's tank model to allow for easier specification of initial conditions.
30 """
32 def build(self, *args, **kwargs):
33 """
34 Build method for the DynamicHeaterData class.
35 This method initializes the control volume and sets up the model.
36 """
38 super().build(*args, **kwargs)
40 self.flow_rate_out = Reference(self.outlet.flow_mol)
42 # add deltaP_inverted as a property
43 add_inverted(self,"heat_duty")
45 # Because it's hard to specify the initial conditions directly,
46 # Create a state block for the initial conditions.
48 if not self.config.dynamic:
49 return # There is no need to add these extra properties.
51 self.initial_block = self.config.property_package.build_state_block(
52 [0],
53 defined_state=True,
54 )
56 if len(self.config.property_package.component_list) > 1: 56 ↛ 60line 56 didn't jump to line 60 because the condition on line 56 was never true
58 # We are assuming that the composition does not change at the initial time step. In theory it could, but
59 # we can worry about that later.
60 @self.Constraint(
61 self.config.property_package.component_list,
62 doc="Initial composition constraint",
63 )
64 def initial_composition_constraint(b, j):
65 return (
66 b.initial_block[0].mole_frac_comp[j]
67 == b.control_volume.properties_in[0].mole_frac_comp[j]
68 )
70 # The initial temperature, pressure, and flow amount is set by the user.
71 self.initial_pressure = Var(initialize=101325, units=pyunit.Pa)
72 @self.Constraint(doc="Initial pressure constraint")
73 def initial_pressure_constraint(b):
74 return b.initial_block[0].pressure == b.initial_pressure
76 self.initial_holdup = Var(initialize=300, units=pyunit.mol)
77 @self.Constraint(doc="Initial flow constraint")
78 def initial_holdup_mol_constraint(b):
79 return (
80 b.initial_block[0].flow_mol * pyunit.s == b.initial_holdup
81 ) # cancel out the seconds as we are using it for holdup not accumulation.
84 self.initial_level = Var(initialize=300, units=pyunit.m)
85 @self.Constraint(doc="Initial level constraint")
86 def initial_level_constraint(b):
87 return b.initial_holdup == b.tank_cross_sect_area * b.initial_level * b.initial_block[0].dens_mol
89 self.initial_temperature = Var(initialize=300, units=pyunit.K)
90 @self.Constraint(doc="Initial temperature constraint")
91 def initial_temperature_constraint(b):
92 return b.initial_block[0].temperature == b.initial_temperature
94 # The temperature, pressure and flow are used to calculate the other properties.
95 @self.Constraint(
96 self.config.property_package.phase_list,
97 self.config.property_package.component_list,
98 doc="Defining accumulation",
99 )
100 def initial_material_conditions_constraint(b, p, j):
101 return (
102 b.initial_block[0].flow_mol
103 * b.initial_block[0].mole_frac_phase_comp[p, j]
104 + b.control_volume.material_accumulation[0, p, j]
105 == b.control_volume.material_holdup[0, p, j]
106 )
108 @self.Constraint(
109 self.config.property_package.phase_list, doc="Defining accumulation"
110 )
111 def initial_energy_conditions_constraint(b, p):
112 return (
113 b.initial_block[0].flow_mol
114 * b.initial_block[0].phase_frac[p]
115 * b.initial_block[0].enth_mol_phase[p]
116 + b.control_volume.energy_accumulation[0, p]
117 == b.control_volume.energy_holdup[0, p]
118 )
122 def initialize(self, *args, **kwargs):
123 """
124 Initialization method for the DynamicTankData class.
125 This method initializes the control volume and sets up the model.
126 """
127 # Copy initial conditions from inverted properties
128 initialise_inverted(self,"heat_duty")
130 if self.config.dynamic:
131 self.initial_block.initialize()
133 super().initialize(*args, **kwargs)