def test_CV1D_w_inherent_rxns_comp_phase(self, frame): frame.fs.cv = ControlVolume1DBlock( default={ "property_package": frame.fs.params, "transformation_method": "dae.finite_difference", "transformation_scheme": "BACKWARD", "finite_elements": 2 }) frame.fs.cv.add_geometry() frame.fs.cv.add_state_blocks(has_phase_equilibrium=False) frame.fs.cv.add_material_balances( balance_type=MaterialBalanceType.componentPhase) frame.fs.cv.add_energy_balances() frame.fs.cv.add_momentum_balances() frame.fs.cv.apply_transformation() frame.fs.cv.properties[0, 0].flow_mol.fix(100) frame.fs.cv.properties[0, 0].mole_frac_comp.fix(0.25) frame.fs.cv.properties[0, 0].temperature.fix(350) frame.fs.cv.properties[0, 0].pressure.fix(101325) frame.fs.cv.area.fix(1) frame.fs.cv.length.fix(1) assert (degrees_of_freedom(frame)) == 0 frame.fs.cv.initialize() solver = get_solver() results = solver.solve(frame) assert check_optimal_termination(results) assert pytest.approx(2, rel=1e-8) == value( frame.fs.cv.properties[0, 1].k_eq["e1"]) assert (value( frame.fs.cv.properties[0, 1].k_eq["e1"]) == pytest.approx( value(frame.fs.cv.properties[0, 1].mole_frac_comp["b"] / frame.fs.cv.properties[0, 1].mole_frac_comp["a"]), rel=1e-5)) assert (value( frame.fs.cv.properties[0, 1].mole_frac_comp["a"]) == pytest.approx( 1 / 6, rel=1e-5)) assert (value( frame.fs.cv.properties[0, 1].mole_frac_comp["b"]) == pytest.approx( 1 / 3, rel=1e-5)) assert (value( frame.fs.cv.properties[0, 1].mole_frac_comp["c"]) == pytest.approx( 1 / 4, rel=1e-5)) assert (value( frame.fs.cv.properties[0, 1].mole_frac_comp["d"]) == pytest.approx( 1 / 4, rel=1e-5)) assert (value(frame.fs.cv.properties[0, 1].flow_mol) == pytest.approx( 100, rel=1e-5)) assert (value(frame.fs.cv.properties[0, 1].temperature) == pytest.approx( 350, rel=1e-5)) assert (value(frame.fs.cv.properties[0, 1].pressure) == pytest.approx( 101325, rel=1e-5))
def build(self): """ Begin building model (pre-DAE transformation). Args: None Returns: None """ # Call UnitModel.build to setup dynamics super(HeatExchanger1DData, self).build() # Set flow directions for the control volume blocks and specify # dicretisation if not specified. if self.config.flow_type == HeatExchangerFlowPattern.cocurrent: set_direction_shell = FlowDirection.forward set_direction_tube = FlowDirection.forward if (self.config.shell_side.transformation_method != self.config.tube_side.transformation_method) or ( self.config.shell_side.transformation_scheme != self.config.tube_side.transformation_scheme): raise ConfigurationError( "HeatExchanger1D only supports similar transformation " "schemes on the shell side and tube side domains for " "both cocurrent and countercurrent flow patterns.") if self.config.shell_side.transformation_method is useDefault: _log.warning("Discretization method was " "not specified for the shell side of the " "co-current heat exchanger. " "Defaulting to finite " "difference method on the shell side.") self.config.shell_side.transformation_method = "dae.finite_difference" if self.config.tube_side.transformation_method is useDefault: _log.warning("Discretization method was " "not specified for the tube side of the " "co-current heat exchanger. " "Defaulting to finite " "difference method on the tube side.") self.config.tube_side.transformation_method = "dae.finite_difference" if self.config.shell_side.transformation_scheme is useDefault: _log.warning("Discretization scheme was " "not specified for the shell side of the " "co-current heat exchanger. " "Defaulting to backward finite " "difference on the shell side.") self.config.shell_side.transformation_scheme = "BACKWARD" if self.config.tube_side.transformation_scheme is useDefault: _log.warning("Discretization scheme was " "not specified for the tube side of the " "co-current heat exchanger. " "Defaulting to backward finite " "difference on the tube side.") self.config.tube_side.transformation_scheme = "BACKWARD" elif self.config.flow_type == HeatExchangerFlowPattern.countercurrent: set_direction_shell = FlowDirection.forward set_direction_tube = FlowDirection.backward if self.config.shell_side.transformation_method is useDefault: _log.warning("Discretization method was " "not specified for the shell side of the " "counter-current heat exchanger. " "Defaulting to finite " "difference method on the shell side.") self.config.shell_side.transformation_method = "dae.finite_difference" if self.config.tube_side.transformation_method is useDefault: _log.warning("Discretization method was " "not specified for the tube side of the " "counter-current heat exchanger. " "Defaulting to finite " "difference method on the tube side.") self.config.tube_side.transformation_method = "dae.finite_difference" if self.config.shell_side.transformation_scheme is useDefault: _log.warning("Discretization scheme was " "not specified for the shell side of the " "counter-current heat exchanger. " "Defaulting to backward finite " "difference on the shell side.") self.config.shell_side.transformation_scheme = "BACKWARD" if self.config.tube_side.transformation_scheme is useDefault: _log.warning("Discretization scheme was " "not specified for the tube side of the " "counter-current heat exchanger. " "Defaulting to forward finite " "difference on the tube side.") self.config.tube_side.transformation_scheme = "BACKWARD" else: raise ConfigurationError( "{} HeatExchanger1D only supports cocurrent and " "countercurrent flow patterns, but flow_type configuration" " argument was set to {}.".format(self.name, self.config.flow_type)) # Control volume 1D for shell self.shell = ControlVolume1DBlock( default={ "dynamic": self.config.shell_side.dynamic, "has_holdup": self.config.shell_side.has_holdup, "property_package": self.config.shell_side.property_package, "property_package_args": self.config.shell_side.property_package_args, "transformation_method": self.config.shell_side.transformation_method, "transformation_scheme": self.config.shell_side.transformation_scheme, "finite_elements": self.config.finite_elements, "collocation_points": self.config.collocation_points, }) self.tube = ControlVolume1DBlock( default={ "dynamic": self.config.tube_side.dynamic, "has_holdup": self.config.tube_side.has_holdup, "property_package": self.config.tube_side.property_package, "property_package_args": self.config.tube_side.property_package_args, "transformation_method": self.config.tube_side.transformation_method, "transformation_scheme": self.config.tube_side.transformation_scheme, "finite_elements": self.config.finite_elements, "collocation_points": self.config.collocation_points, }) self.shell.add_geometry(flow_direction=set_direction_shell) self.tube.add_geometry(flow_direction=set_direction_tube) self.shell.add_state_blocks( information_flow=set_direction_shell, has_phase_equilibrium=self.config.shell_side.has_phase_equilibrium, ) self.tube.add_state_blocks( information_flow=set_direction_tube, has_phase_equilibrium=self.config.tube_side.has_phase_equilibrium, ) # Populate shell self.shell.add_material_balances( balance_type=self.config.shell_side.material_balance_type, has_phase_equilibrium=self.config.shell_side.has_phase_equilibrium, ) self.shell.add_energy_balances( balance_type=self.config.shell_side.energy_balance_type, has_heat_transfer=True, ) self.shell.add_momentum_balances( balance_type=self.config.shell_side.momentum_balance_type, has_pressure_change=self.config.shell_side.has_pressure_change, ) self.shell.apply_transformation() # Populate tube self.tube.add_material_balances( balance_type=self.config.tube_side.material_balance_type, has_phase_equilibrium=self.config.tube_side.has_phase_equilibrium, ) self.tube.add_energy_balances( balance_type=self.config.tube_side.energy_balance_type, has_heat_transfer=True, ) self.tube.add_momentum_balances( balance_type=self.config.tube_side.momentum_balance_type, has_pressure_change=self.config.tube_side.has_pressure_change, ) self.tube.apply_transformation() # Add Ports for shell side self.add_inlet_port(name="shell_inlet", block=self.shell) self.add_outlet_port(name="shell_outlet", block=self.shell) # Add Ports for tube side self.add_inlet_port(name="tube_inlet", block=self.tube) self.add_outlet_port(name="tube_outlet", block=self.tube) # Add reference to control volume geometry add_object_reference(self, "shell_area", self.shell.area) add_object_reference(self, "shell_length", self.shell.length) add_object_reference(self, "tube_area", self.tube.area) add_object_reference(self, "tube_length", self.tube.length) self._make_performance()
def build(self): """ Begin building model (pre-DAE transformation). Args: None Returns: None """ # Call UnitModel.build to setup dynamics super(PFRData, self).build() # Build Control Volume self.control_volume = ControlVolume1DBlock( default={ "dynamic": self.config.dynamic, "has_holdup": self.config.has_holdup, "property_package": self.config.property_package, "property_package_args": self.config.property_package_args, "reaction_package": self.config.reaction_package, "reaction_package_args": self.config.reaction_package_args, "transformation_method": self.config.transformation_method, "transformation_scheme": self.config.transformation_scheme, "finite_elements": self.config.finite_elements, "collocation_points": self.config.collocation_points }) self.control_volume.add_geometry( length_domain_set=self.config.length_domain_set) self.control_volume.add_state_blocks( has_phase_equilibrium=self.config.has_phase_equilibrium) self.control_volume.add_reaction_blocks( has_equilibrium=self.config.has_equilibrium_reactions) self.control_volume.add_material_balances( balance_type=self.config.material_balance_type, has_rate_reactions=True, has_equilibrium_reactions=self.config.has_equilibrium_reactions, has_phase_equilibrium=self.config.has_phase_equilibrium) self.control_volume.add_energy_balances( balance_type=self.config.energy_balance_type, has_heat_of_reaction=self.config.has_heat_of_reaction, has_heat_transfer=self.config.has_heat_transfer) self.control_volume.add_momentum_balances( balance_type=self.config.momentum_balance_type, has_pressure_change=self.config.has_pressure_change) self.control_volume.apply_transformation() # Add Ports self.add_inlet_port() self.add_outlet_port() # Add PFR performance equation @self.Constraint(self.flowsheet().config.time, self.control_volume.length_domain, self.config.reaction_package.rate_reaction_idx, doc="PFR performance equation") def performance_eqn(b, t, x, r): return b.control_volume.rate_reaction_extent[t, x, r] == ( b.control_volume.reactions[t, x].reaction_rate[r] * b.control_volume.area) # Set references to balance terms at unit level add_object_reference(self, "length", self.control_volume.length) add_object_reference(self, "area", self.control_volume.area) # Add volume variable for full reactor # TODO : Need to add units self.volume = Var(initialize=1, doc="Reactor Volume") self.geometry = Constraint(expr=self.volume == self.area * self.length) if (self.config.has_heat_transfer is True and self.config.energy_balance_type != 'none'): add_object_reference(self, "heat_duty", self.control_volume.heat) if (self.config.has_pressure_change is True and self.config.momentum_balance_type != 'none'): add_object_reference(self, "deltaP", self.control_volume.deltaP)
def build(self): """ Build 1D RO model (pre-DAE transformation). Args: None Returns: None """ # Call UnitModel.build to setup dynamics super().build() # Check configuration errors self._process_config() # Build 1D Control volume for feed side self.feed_side = feed_side = ControlVolume1DBlock( default={ "dynamic": self.config.dynamic, "has_holdup": self.config.has_holdup, "area_definition": self.config.area_definition, "property_package": self.config.property_package, "property_package_args": self.config.property_package_args, "transformation_method": self.config.transformation_method, "transformation_scheme": self.config.transformation_scheme, "finite_elements": self.config.finite_elements, "collocation_points": self.config.collocation_points, } ) # Add geometry to feed side feed_side.add_geometry() # Add state blocks to feed side feed_side.add_state_blocks(has_phase_equilibrium=False) # Populate feed side feed_side.add_material_balances( balance_type=self.config.material_balance_type, has_mass_transfer=True ) feed_side.add_momentum_balances( balance_type=self.config.momentum_balance_type, has_pressure_change=self.config.has_pressure_change, ) # Apply transformation to feed side feed_side.apply_transformation() add_object_reference(self, "length_domain", self.feed_side.length_domain) self.first_element = self.length_domain.first() self.difference_elements = Set( ordered=True, initialize=(x for x in self.length_domain if x != self.first_element), ) # Add inlet/outlet ports for feed side self.add_inlet_port(name="inlet", block=feed_side) self.add_outlet_port(name="retentate", block=feed_side) # Make indexed stateblock and separate stateblock for permeate-side and permeate outlet, respectively. tmp_dict = dict(**self.config.property_package_args) tmp_dict["has_phase_equilibrium"] = False tmp_dict["parameters"] = self.config.property_package tmp_dict["defined_state"] = False # these blocks are not inlets self.permeate_side = self.config.property_package.state_block_class( self.flowsheet().config.time, self.length_domain, doc="Material properties of permeate along permeate channel", default=tmp_dict, ) self.mixed_permeate = self.config.property_package.state_block_class( self.flowsheet().config.time, doc="Material properties of mixed permeate exiting the module", default=tmp_dict, ) # Membrane interface: indexed state block self.feed_side.properties_interface = ( self.config.property_package.state_block_class( self.flowsheet().config.time, self.length_domain, doc="Material properties of feed-side membrane interface", default=tmp_dict, ) ) # Add port to mixed_permeate self.add_port(name="permeate", block=self.mixed_permeate) # ========================================================================== """ Add references to control volume geometry.""" add_object_reference(self, "length", feed_side.length) add_object_reference(self, "area_cross", feed_side.area) # Add reference to pressure drop for feed side only if ( self.config.has_pressure_change is True and self.config.momentum_balance_type != MomentumBalanceType.none ): add_object_reference(self, "dP_dx", feed_side.deltaP) self._make_performance() self._add_expressions()
def build(self): """ Begin building model (pre-DAE transformation). Args: None Returns: None """ # Call UnitModel.build to build default attributes super(MBRData, self).build() # Set flow directions for the control volume blocks # Gas flows from 0 to 1, solid flows from 1 to 0 # An if statement is used here despite only one option to allow for # future extensions to other flow configurations if self.config.flow_type == "counter_current": set_direction_gas = FlowDirection.forward set_direction_solid = FlowDirection.backward # Set transformation scheme to be in the "opposite # direction" as flow. self.GAS_TRANSFORM_SCHEME = "BACKWARD" self.SOLID_TRANSFORM_SCHEME = "FORWARD" else: raise BurntToast("{} encountered unrecognized argument " "for flow type. Please contact the IDAES" " developers with this bug.".format(self.name)) # Set arguments for gas sides if homoogeneous reaction block if self.config.gas_phase_config.reaction_package is not None: has_rate_reaction_gas_phase = True else: has_rate_reaction_gas_phase = False # Set arguments for gas and solid sides if heterogeneous reaction block if self.config.solid_phase_config.reaction_package is not None: has_rate_reaction_solid_phase = True has_mass_transfer_gas_phase = True else: has_rate_reaction_solid_phase = False has_mass_transfer_gas_phase = False # Set heat transfer terms if self.config.energy_balance_type != EnergyBalanceType.none: has_heat_transfer = True else: has_heat_transfer = False # Set heat of reaction terms if (self.config.energy_balance_type != EnergyBalanceType.none and self.config.gas_phase_config.reaction_package is not None): has_heat_of_reaction_gas_phase = True else: has_heat_of_reaction_gas_phase = False if (self.config.energy_balance_type != EnergyBalanceType.none and self.config.solid_phase_config.reaction_package is not None): has_heat_of_reaction_solid_phase = True else: has_heat_of_reaction_solid_phase = False # Create two different length domains; one for each phase. # Add them to this block so I can use one of them to index # all the variables on this block. # I can then call discretization on this block, which will # discretize the variables on the control volume blocks. self.solid_length_domain = ContinuousSet( bounds=(0.0, 1.0), initialize=self.config.length_domain_set, doc="Normalized length domain", ) self.gas_length_domain = ContinuousSet( bounds=(0.0, 1.0), initialize=self.config.length_domain_set, doc="Normalized length domain", ) self.bed_height = Var(domain=Reals, initialize=1, doc="Bed length [m]") super(_BlockData, self).__setattr__( 'length_domain', self.solid_length_domain, ) # ========================================================================= """ Build Control volume 1D for gas phase and populate gas control volume""" self.gas_phase = ControlVolume1DBlock( default={ "transformation_method": self.config.transformation_method, #"transformation_scheme": self.config.transformation_scheme, "transformation_scheme": self.GAS_TRANSFORM_SCHEME, "finite_elements": self.config.finite_elements, "collocation_points": self.config.collocation_points, "dynamic": self.config.dynamic, "has_holdup": self.config.has_holdup, "area_definition": DistributedVars.variant, "property_package": self.config.gas_phase_config.property_package, "property_package_args": self.config.gas_phase_config.property_package_args, "reaction_package": self.config.gas_phase_config.reaction_package, "reaction_package_args": self.config.gas_phase_config.reaction_package_args }) # Pass gas_length_domain to the gas phase control volume # Note that length_domain_set is redundant as the set is # already initialized. self.gas_phase.add_geometry( length_domain=self.gas_length_domain, length_domain_set=self.config.length_domain_set, flow_direction=set_direction_gas, ) self.gas_phase.add_state_blocks(information_flow=set_direction_gas, has_phase_equilibrium=False) if self.config.gas_phase_config.reaction_package is not None: self.gas_phase.add_reaction_blocks( has_equilibrium=self.config.gas_phase_config. has_equilibrium_reactions) self.gas_phase.add_material_balances( balance_type=self.config.material_balance_type, has_phase_equilibrium=False, has_mass_transfer=has_mass_transfer_gas_phase, has_rate_reactions=has_rate_reaction_gas_phase) self.gas_phase.add_energy_balances( balance_type=self.config.energy_balance_type, has_heat_transfer=has_heat_transfer, has_heat_of_reaction=has_heat_of_reaction_gas_phase) self.gas_phase.add_momentum_balances( balance_type=self.config.momentum_balance_type, has_pressure_change=self.config.has_pressure_change) # ========================================================================= """ Build Control volume 1D for solid phase and populate solid control volume""" # Set argument for heterogeneous reaction block self.solid_phase = ControlVolume1DBlock( default={ "transformation_method": self.config.transformation_method, "transformation_scheme": self.SOLID_TRANSFORM_SCHEME, "finite_elements": self.config.finite_elements, "collocation_points": self.config.collocation_points, # ^ These arguments have no effect as the transformation # is applied in this class. "dynamic": self.config.dynamic, "has_holdup": self.config.has_holdup, "area_definition": DistributedVars.variant, "property_package": self.config.solid_phase_config.property_package, "property_package_args": self.config.solid_phase_config.property_package_args, "reaction_package": self.config.solid_phase_config.reaction_package, "reaction_package_args": self.config.solid_phase_config.reaction_package_args }) # Same comment as made for the gas phase. # Pass in the set we've constructed for this purpose. self.solid_phase.add_geometry( length_domain=self.solid_length_domain, length_domain_set=self.config.length_domain_set, flow_direction=set_direction_solid) # These constraints no longer are created by make_performance, # So I make them here... # Length of gas side, and solid side @self.Constraint(doc="Gas side length") def gas_phase_length(b): return (b.gas_phase.length == b.bed_height) @self.Constraint(doc="Solid side length") def solid_phase_length(b): return (b.solid_phase.length == b.bed_height) # Many other methods of the MBR base class actually rely on this # attribute, so here I slap on a reference. The particular set # I use shouldn't matter, as long as the derivatives are constructed # wrt the correct set. self.solid_phase.add_state_blocks(information_flow=set_direction_solid, has_phase_equilibrium=False) if self.config.solid_phase_config.reaction_package is not None: # TODO - a generalization of the heterogeneous reaction block # The heterogeneous reaction block does not use the # add_reaction_blocks in control volumes as control volumes are # currently setup to handle only homogeneous reaction properties. # Thus appending the heterogeneous reaction block to the # solid state block is currently hard coded here. tmp_dict = dict( **self.config.solid_phase_config.reaction_package_args) tmp_dict["gas_state_block"] = self.gas_phase.properties tmp_dict["solid_state_block"] = self.solid_phase.properties tmp_dict["has_equilibrium"] = ( self.config.solid_phase_config.has_equilibrium_reactions) tmp_dict["parameters"] = ( self.config.solid_phase_config.reaction_package) self.solid_phase.reactions = ( self.config.solid_phase_config.reaction_package. reaction_block_class( self.flowsheet().config.time, self.length_domain, doc="Reaction properties in control volume", default=tmp_dict)) self.solid_phase.add_material_balances( balance_type=self.config.material_balance_type, has_phase_equilibrium=False, has_mass_transfer=False, has_rate_reactions=has_rate_reaction_solid_phase) self.solid_phase.add_energy_balances( balance_type=self.config.energy_balance_type, has_heat_transfer=has_heat_transfer, has_heat_of_reaction=has_heat_of_reaction_solid_phase) self.solid_phase.add_momentum_balances( balance_type=MomentumBalanceType.none, has_pressure_change=False) # ========================================================================= """ Add ports""" # Add Ports for gas side self.add_inlet_port(name="gas_inlet", block=self.gas_phase) self.add_outlet_port(name="gas_outlet", block=self.gas_phase) # Add Ports for solid side self.add_inlet_port(name="solid_inlet", block=self.solid_phase) self.add_outlet_port(name="solid_outlet", block=self.solid_phase) # ========================================================================= """ Add performace equation method""" self._apply_transformation() self._make_performance()