예제 #1
0
    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))
예제 #2
0
    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()
예제 #3
0
    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)
예제 #4
0
    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()