Beispiel #1
0
    def _validate_property_parameter_properties(self):
        """
        Checks that the property parameter block associated with the
        reaction block supports the necessary properties with correct units.
        """
        req_props = self.get_metadata().required_properties
        supp_props = self.config.property_package.get_metadata().properties

        for p in req_props:
            if p not in supp_props:
                raise PropertyPackageError(
                    '{} the property package associated with this '
                    'reaction package does not support the necessary '
                    'property, {}. Please choose a property package '
                    'which supports all required properties.'.format(
                        self.name, p))
            elif supp_props[p]['method'] is False:
                raise PropertyPackageError(
                    '{} the property package associated with this '
                    'reaction package does not support the necessary '
                    'property, {}. Please choose a property package '
                    'which supports all required properties.'.format(
                        self.name, p))

            # Check property units
            if req_props[p]['units'] != supp_props[p]['units']:
                raise PropertyPackageError(
                    '{} the units associated with property {} in this '
                    'reaction package ({}) do not match with the units '
                    'used in the assoicated property package ({}). Please '
                    'choose a property package which used the same '
                    'units for all properties.'.format(self.name, p,
                                                       req_props[p]['units'],
                                                       supp_props[p]['units']))
Beispiel #2
0
    def _get_indexing_sets(self):
        """
        This method collects all necessary indexing sets from property
        parameter block and makes references to these for use within the
        control volume block. Collected indexing sets are phase_list and
        component_list.

        Args:
            None

        Returns:
            None
        """
        # Check for phase list(s)
        if not hasattr(self.config.property_package, "phase_list"):
            raise PropertyPackageError(
                '{} property_package provided does not '
                'contain a phase_list. '
                'Please contact the developer of the property package.'.format(
                    self.name))

        # Check for component list(s)
        if not hasattr(self.config.property_package, "component_list"):
            raise PropertyPackageError(
                '{} property_package provided does not '
                'contain a component_list. '
                'Please contact the developer of the property package.'.format(
                    self.name))
Beispiel #3
0
    def _validate_parameter_block(self):
        """
        Backwards compatability checks.

        This is code to check for old-style property packages and create
        the necessary Phase and Component objects.

        It also tries to catch some possible mistakes and provide the user with
        useful error messages.
        """
        try:
            # Check names in component list have matching Component objects
            for c in self.component_list:
                try:
                    obj = getattr(self, str(c))
                    if not isinstance(obj, ComponentData):
                        raise TypeError(
                            "Property package {} has an object {} whose "
                            "name appears in component_list but is not an "
                            "instance of Component".format(self.name, c))
                except AttributeError:
                    # No object with name c, must be old-style package
                    self._make_component_objects()
                    break
        except AttributeError:
            # No component list
            raise PropertyPackageError("Property package {} has not defined a "
                                       "component list.".format(self.name))

        try:
            # Valdiate that names in phase list have matching Phase objects
            for p in self.phase_list:
                try:
                    obj = getattr(self, str(p))
                    if not isinstance(obj, PhaseData):
                        raise TypeError(
                            "Property package {} has an object {} whose "
                            "name appears in phase_list but is not an "
                            "instance of Phase".format(self.name, p))
                except AttributeError:
                    # No object with name p, must be old-style package
                    self._make_phase_objects()
                    break
        except AttributeError:
            # No phase list
            raise PropertyPackageError("Property package {} has not defined a "
                                       "phase list.".format(self.name))

        # Also check that the phase-component set has been created.
        self.get_phase_component_set()
Beispiel #4
0
    def add_default_units(self, u):
        """Add a dict with keys for the
        quantities used in the property package (as strings) and values of
        their default units as unit objects or strings.

        The quantities used by the framework are in constants
        defined in :class:`UnitNames`, aliased here in the class
        attribute `U`.

        Args:
            u (dict): Key=property, Value=units

        Returns:
            None
        """
        self._default_units.update(u)

        # Validate values. Pyomo units are all-or-nothing, so check to see that
        # this is the case
        for q, u in self._default_units.items():
            if u is None and (q == "luminous intensity" or q == "current"):
                # these units are infrequently used in PSE, so allow users
                # to skip these
                continue
            elif not isinstance(u, _PyomoUnit):
                raise PropertyPackageError(
                    f"Unrecognized units of measurment for quantity {q} ({u})")
    def build(self):
        # TODO: Need a different error here
        super(GenericReactionBlockData, self).build()

        if self.config.has_equilibrium:
            if len(self.params.config.equilibrium_reactions) == 0:
                raise PropertyPackageError(
                    "{} Generic Reaction Block was set to include equilibrium "
                    "reactions, however no equilibrium reactions were "
                    "defined. Either set has_equilibrium to be False, or "
                    "include equilibrium reactions in the package definition.".
                    format(self.name))
            self._equilibrium_constraint()
Beispiel #6
0
    def _validate_parameter_block(self):
        """
        Tries to catch some possible mistakes and provide the user with
        useful error messages.
        """
        try:
            # Check names in component list have matching Component objects
            for c in self.component_list:
                obj = getattr(self, str(c))
                if not isinstance(obj, ComponentData):
                    raise TypeError(
                        "Property package {} has an object {} whose "
                        "name appears in component_list but is not an "
                        "instance of Component".format(self.name, c))
        except AttributeError:
            # No component list
            raise PropertyPackageError(
                f"Property package {self.name} has not defined any "
                f"Components.")

        try:
            # Valdiate that names in phase list have matching Phase objects
            for p in self.phase_list:
                obj = getattr(self, str(p))
                if not isinstance(obj, PhaseData):
                    raise TypeError(
                        "Property package {} has an object {} whose "
                        "name appears in phase_list but is not an "
                        "instance of Phase".format(self.name, p))
        except AttributeError:
            # No phase list
            raise PropertyPackageError(
                f"Property package {self.name} has not defined any Phases.")

        # Also check that the phase-component set has been created.
        self.get_phase_component_set()
Beispiel #7
0
    def _make_component_objects(self):
        _log.warning("DEPRECATED: {} appears to be an old-style property "
                     "package. It will be automatically converted to a "
                     "new-style package, however users are strongly encouraged"
                     " to convert their property packages to use phase and "
                     "component objects.".format(self.name))
        for c in self.component_list:
            if hasattr(self, c):
                # An object with this name already exists, raise exception
                raise PropertyPackageError(
                    "{} could not add Component object {} - an object with "
                    "that name already exists.".format(self.name, c))

            self.add_component(
                str(c), Component(default={"_component_list_exists": True}))
Beispiel #8
0
    def get_phase(self, phase):
        """
        Method to retrieve a Phase object based on a name from the phase_list.

        Args:
            phase: name of Phase object to retrieve

        Returns:
            Phase object
        """
        obj = getattr(self, phase)
        if not isinstance(obj, PhaseData):
            raise PropertyPackageError(
                "{} get_phase found an attribute {}, but it does not "
                "appear to be an instance of a Phase object.".format(
                    self.name, phase))
        return obj
Beispiel #9
0
 def _validate_property_parameter_units(self):
     """
     Checks that the property parameter block associated with the
     reaction block uses the same set of default units.
     """
     r_units = self.get_metadata().default_units
     prop_units = self.config.property_package.get_metadata().default_units
     for u in r_units:
         try:
             if prop_units[u] != r_units[u]:
                 raise KeyError()
         except KeyError:
             raise PropertyPackageError(
                 '{} the property package associated with this '
                 'reaction package does not use the same set of '
                 'units of measurement ({}). Please choose a '
                 'property package which uses the same units.'.format(
                     self.name, u))
Beispiel #10
0
    def get_component(self, comp):
        """
        Method to retrieve a Component object based on a name from the
        component_list.

        Args:
            comp: name of Component object to retrieve

        Returns:
            Component object
        """
        obj = getattr(self, comp)
        if not isinstance(obj, ComponentData):
            raise PropertyPackageError(
                "{} get_component found an attribute {}, but it does not "
                "appear to be an instance of a Component object.".format(
                    self.name, comp))
        return obj
Beispiel #11
0
        def clear_call_list(self, attr):
            """Local method for cleaning up call list when a call is handled.

                Args:
                    attr: attribute currently being handled
            """
            if self.__getattrcalls[-1] == attr:
                if len(self.__getattrcalls) <= 1:
                    del self.__getattrcalls
                else:
                    del self.__getattrcalls[-1]
            else:
                raise PropertyPackageError(
                    "{} Trying to remove call {} from __getattr__"
                    " call list, however this is not the most "
                    "recent call in the list ({}). This indicates"
                    " a bug in the __getattr__ calls. Please "
                    "contact the IDAES developers with this bug.".format(
                        self.name, attr, self.__getattrcalls[-1]))
Beispiel #12
0
    def _validate_state_block(self):
        """
        Method to validate that the associated state block matches with the
        PropertyParameterBlock assoicated with the ReactionParameterBlock.
        """
        # Add a reference to the corresponding state block data for later use
        add_object_reference(self, "state_ref",
                             self.config.state_block[self.index()])

        # Validate that property package of state matches that of reaction pack
        if (self.config.parameters.config.property_package !=
                self.state_ref.config.parameters):
            raise PropertyPackageError(
                '{} the StateBlock associated with this '
                'ReactionBlock does not match with the '
                'PropertyParamterBlock associated with the '
                'ReactionParameterBlock. The modelling framework '
                'does not support mixed associations of property '
                'and reaction packages.'.format(self.name))
Beispiel #13
0
    def add_default_units(self, u):
        """Add a dict with keys for the
        quantities used in the property package (as strings) and values of
        their default units as unit objects or strings.

        The quantities used by the framework are in constants
        defined in :class:`UnitNames`, aliased here in the class
        attribute `U`.

        Args:
            u (dict): Key=property, Value=units

        Returns:
            None
        """
        self._default_units.update(u)

        # Validate values. Pyomo units are all-or-nothing, so check to see that
        # this is the case
        _units = 0
        for q, u in self._default_units.items():
            if isinstance(u, _PyomoUnit):
                _units += 1
            elif u is None and (q == "luminous intensity" or q == "current"):
                # these units are infrequently used in PSE, so allow users
                # to skip these
                continue
            elif _units > 0:
                # Mix of units and non-unit objects
                raise PropertyPackageError(
                    "default_units ({}: {}): if using Pyomo Units objects, "
                    "all units must be defined using Units objects (not "
                    "compount units).".format(q, u))

        # Take opportunity to log a deprecation warning if units are not used
        if _units == 0:
            _log.warning("DEPRECATED: IDAES is moving to using Pyomo Units "
                         "when defining default units, which are used "
                         "to automatically determine units of measurement "
                         "for quantities and convert where necessary. "
                         "Users are strongly encouraged to convert their "
                         "property packages to use Pyomo Units objects.")
Beispiel #14
0
    def _make_phase_objects(self):
        _log.warning("DEPRECATED: {} appears to be an old-style property "
                     "package. It will be automatically converted to a "
                     "new-style package, however users are strongly encouraged"
                     " to convert their property packages to use phase and "
                     "component objects."
                     .format(self.name))
        for p in self.phase_list:
            if hasattr(self, p):
                # An object with this name already exists, raise exception
                raise PropertyPackageError(
                    "{} could not add Phase object {} - an object with "
                    "that name already exists.".format(self.name, p))

            try:
                pc_list = self.phase_comp[p]
            except AttributeError:
                pc_list = None
            self.add_component(str(p), Phase(
                default={"component_list": pc_list,
                         "_phase_list_exists": True}))
Beispiel #15
0
 def _validate_property_parameter_units(self):
     """
     Checks that the property parameter block associated with the
     reaction block uses the same set of default units.
     """
     r_units = self.get_metadata().default_units
     prop_units = self.config.property_package.get_metadata().default_units
     for u in r_units:
         try:
             # TODO: This check is for backwards compatability with
             # pre-units property packages. It can be removed once these are
             # fully deprecated.
             if isinstance(prop_units[u],
                           str) and (prop_units[u] != r_units[u]):
                 raise KeyError()
             elif prop_units[u] is not r_units[u]:
                 raise KeyError()
         except KeyError:
             raise PropertyPackageError(
                 '{} the property package associated with this '
                 'reaction package does not use the same set of '
                 'units of measurement ({}). Please choose a '
                 'property package which uses the same units.'.format(
                     self.name, u))
Beispiel #16
0
    def build(self):
        '''
        Callable method for Block construction.
        '''
        # Call super.build() to initialize Block
        # In this case we are replicating the super.build to get around a
        # chicken-and-egg problem
        # The super.build tries to validate units, but they have not been set
        # and cannot be set until the config block is created by super.build
        super(ReactionParameterBlock, self).build()
        self.default_scaling_factor = {}

        # Set base units of measurement
        self.get_metadata().add_default_units(self.config.base_units)

        # TODO: Need way to tie reaction package to a specfic property package
        self._validate_property_parameter_units()
        self._validate_property_parameter_properties()

        # Call configure method to set construction arguments
        self.configure()

        # Build core components
        self._reaction_block_class = GenericReactionBlock

        # Alias associated property package to keep line length down
        ppack = self.config.property_package

        if not hasattr(ppack, "_electrolyte") or not ppack._electrolyte:
            pc_set = ppack._phase_component_set
        elif ppack.config.state_components.name == "true":
            pc_set = ppack.true_phase_component_set
        elif ppack.config.state_components.name == "apparent":
            pc_set = ppack.apparent_phase_component_set
        else:
            raise BurntToast()

        # Construct rate reaction attributes if required
        if len(self.config.rate_reactions) > 0:
            # Construct rate reaction index
            self.rate_reaction_idx = Set(
                initialize=self.config.rate_reactions.keys())

            # Construct rate reaction stoichiometry dict
            self.rate_reaction_stoichiometry = {}
            for r, rxn in self.config.rate_reactions.items():
                for p, j in pc_set:
                    self.rate_reaction_stoichiometry[(r, p, j)] = 0

                if rxn.stoichiometry is None:
                    raise ConfigurationError(
                        "{} rate reaction {} was not provided with a "
                        "stoichiometry configuration argument.".format(
                            self.name, r))
                else:
                    for k, v in rxn.stoichiometry.items():
                        if k[0] not in ppack.phase_list:
                            raise ConfigurationError(
                                "{} stoichiometry for rate reaction {} "
                                "included unrecognised phase {}.".format(
                                    self.name, r, k[0]))
                        if k[1] not in ppack.component_list:
                            raise ConfigurationError(
                                "{} stoichiometry for rate reaction {} "
                                "included unrecognised component {}.".format(
                                    self.name, r, k[1]))
                        self.rate_reaction_stoichiometry[(r, k[0], k[1])] = v

                # Check that a method was provided for the rate form
                if rxn.rate_form is None:
                    _log.debug(
                        "{} rate reaction {} was not provided with a "
                        "rate_form configuration argument. This is suitable "
                        "for processes using stoichiometric reactors, but not "
                        "for those using unit operations which rely on "
                        "reaction rate.".format(self.name, r))

        # Construct equilibrium reaction attributes if required
        if len(self.config.equilibrium_reactions) > 0:
            # Construct equilibrium reaction index
            self.equilibrium_reaction_idx = Set(
                initialize=self.config.equilibrium_reactions.keys())

            # Construct equilibrium reaction stoichiometry dict
            self.equilibrium_reaction_stoichiometry = {}
            for r, rxn in self.config.equilibrium_reactions.items():
                for p, j in pc_set:
                    self.equilibrium_reaction_stoichiometry[(r, p, j)] = 0

                if rxn.stoichiometry is None:
                    raise ConfigurationError(
                        "{} equilibrium reaction {} was not provided with a "
                        "stoichiometry configuration argument.".format(
                            self.name, r))
                else:
                    for k, v in rxn.stoichiometry.items():
                        if k[0] not in ppack.phase_list:
                            raise ConfigurationError(
                                "{} stoichiometry for equilibrium reaction {} "
                                "included unrecognised phase {}.".format(
                                    self.name, r, k[0]))
                        if k[1] not in ppack.component_list:
                            raise ConfigurationError(
                                "{} stoichiometry for equilibrium reaction {} "
                                "included unrecognised component {}.".format(
                                    self.name, r, k[1]))
                        self.equilibrium_reaction_stoichiometry[(r, k[0],
                                                                 k[1])] = v

                # Check that a method was provided for the equilibrium form
                if rxn.equilibrium_form is None:
                    raise ConfigurationError(
                        "{} equilibrium reaction {} was not provided with a "
                        "equilibrium_form configuration argument.".format(
                            self.name, r))

        # Add a master reaction index which includes both types of reactions
        if (len(self.config.rate_reactions) > 0
                and len(self.config.equilibrium_reactions) > 0):
            self.reaction_idx = Set(
                initialize=(self.rate_reaction_idx
                            | self.equilibrium_reaction_idx))
        elif len(self.config.rate_reactions) > 0:
            self.reaction_idx = Set(initialize=self.rate_reaction_idx)
        elif len(self.config.equilibrium_reactions) > 0:
            self.reaction_idx = Set(initialize=self.equilibrium_reaction_idx)
        else:
            raise BurntToast("{} Generic property package failed to construct "
                             "master reaction Set. This should not happen. "
                             "Please contact the IDAES developers with this "
                             "bug".format(self.name))

        # Construct blocks to contain parameters for each reaction
        for r in self.reaction_idx:
            self.add_component("reaction_" + str(r), Block())

        # Build parameters
        if len(self.config.rate_reactions) > 0:
            for r in self.rate_reaction_idx:
                rblock = getattr(self, "reaction_" + r)
                r_config = self.config.rate_reactions[r]

                order_init = {}
                for p, j in pc_set:
                    if "reaction_order" in r_config.parameter_data:
                        try:
                            order_init[p, j] = r_config.parameter_data[
                                "reaction_order"][p, j]
                        except KeyError:
                            order_init[p, j] = 0
                    else:
                        # Assume elementary reaction and use stoichiometry
                        try:
                            if r_config.stoichiometry[p, j] < 0:
                                # These are reactants, but order is -ve stoic
                                order_init[p,
                                           j] = -r_config.stoichiometry[p, j]
                            else:
                                # Anything else is a product, not be included
                                order_init[p, j] = 0
                        except KeyError:
                            order_init[p, j] = 0

                rblock.reaction_order = Var(pc_set,
                                            initialize=order_init,
                                            doc="Reaction order",
                                            units=None)

                for val in self.config.rate_reactions[r].values():
                    try:
                        val.build_parameters(rblock,
                                             self.config.rate_reactions[r])
                    except AttributeError:
                        pass

        if len(self.config.equilibrium_reactions) > 0:
            for r in self.equilibrium_reaction_idx:
                rblock = getattr(self, "reaction_" + r)
                r_config = self.config.equilibrium_reactions[r]

                order_init = {}
                for p, j in pc_set:
                    if "reaction_order" in r_config.parameter_data:
                        try:
                            order_init[p, j] = r_config.parameter_data[
                                "reaction_order"][p, j]
                        except KeyError:
                            order_init[p, j] = 0
                    else:
                        # Assume elementary reaction and use stoichiometry
                        try:
                            # Here we use the stoic. coeff. directly
                            # However, solids should be excluded as they
                            # normally do not appear in the equilibrium
                            # relationship
                            pobj = ppack.get_phase(p)
                            if not pobj.is_solid_phase():
                                order_init[p, j] = r_config.stoichiometry[p, j]
                            else:
                                order_init[p, j] = 0
                        except KeyError:
                            order_init[p, j] = 0

                rblock.reaction_order = Var(pc_set,
                                            initialize=order_init,
                                            doc="Reaction order",
                                            units=None)

                for val in self.config.equilibrium_reactions[r].values():
                    try:
                        val.build_parameters(
                            rblock, self.config.equilibrium_reactions[r])
                    except AttributeError:
                        pass
                    except KeyError as err:
                        # This likely arises from mismatched true and apparent
                        # species sets. Reaction packages must use the same
                        # basis as the associated thermo properties
                        # Raise an exception to inform the user
                        raise PropertyPackageError(
                            "{} KeyError encountered whilst constructing "
                            "reaction parameters. This may be due to "
                            "mismatched state_components between the "
                            "Reaction Package and the associated Physical "
                            "Property Package - Reaction Packages must use the"
                            "same basis (true or apparent species) as the "
                            "Physical Property Package.".format(self.name),
                            err)

        # As a safety check, make sure all Vars in reaction blocks are fixed
        for v in self.component_objects(Var, descend_into=True):
            for i in v:
                if v[i].value is None:
                    raise ConfigurationError(
                        "{} parameter {} was not assigned"
                        " a value. Please check your configuration "
                        "arguments.".format(self.name, v.local_name))
                v[i].fix()

        # Set default scaling factors
        if self.config.default_scaling_factors is not None:
            self.default_scaling_factor.update(
                self.config.default_scaling_factors)
        # Finally, call populate_default_scaling_factors method to fill blanks
        iscale.populate_default_scaling_factors(self)
Beispiel #17
0
    def _make_phase_split(self,
                          port=None,
                          phase=None,
                          has_liquid_side_draw=False,
                          has_vapor_side_draw=False,
                          side_sf=None):
        """Method to split and populate the outlet ports with corresponding
           phase values from the mixed stream outlet block."""

        member_list = self.properties_out[0].define_port_members()

        for k in member_list:

            local_name = member_list[k].local_name

            # Create references and populate the intensive variables
            if "flow" not in local_name and "frac" not in local_name \
                    and "enth" not in local_name:
                if not member_list[k].is_indexed():
                    var = self.properties_out[:].\
                        component(local_name)
                else:
                    var = self.properties_out[:].\
                        component(local_name)[...]

                # add the reference and variable name to the port
                ref = Reference(var)
                setattr(self, "_" + k + "_ref", ref)
                port.add(ref, k)

            elif "frac" in local_name:

                # Mole/mass frac is typically indexed
                index_set = member_list[k].index_set()

                # if state var is not mole/mass frac by phase
                if "phase" not in local_name:
                    if "mole" in local_name:  # check mole basis/mass basis

                        # The following conditionals are required when a
                        # mole frac or mass frac is a state var i.e. will be
                        # a port member. This gets a bit tricky when handling
                        # non-conventional systems when you have more than one
                        # liquid or vapor phase. Hence, the logic here is that
                        # the mole frac that should be present in the liquid or
                        # vapor port should be computed by accounting for
                        # multiple liquid or vapor phases if present. For the
                        # classical VLE system, this holds too.
                        if hasattr(self.properties_out[0],
                                   "mole_frac_phase_comp") and \
                            hasattr(self.properties_out[0],
                                    "flow_mol_phase"):
                            flow_phase_comp = False
                            local_name_frac = "mole_frac_phase_comp"
                            local_name_flow = "flow_mol_phase"
                        elif hasattr(self.properties_out[0],
                                     "flow_mol_phase_comp"):
                            flow_phase_comp = True
                            local_name_flow = "flow_mol_phase_comp"
                        else:
                            raise PropertyNotSupportedError(
                                "No mole_frac_phase_comp or flow_mol_phase or"
                                " flow_mol_phase_comp variables encountered "
                                "while building ports for the condenser. ")
                    elif "mass" in local_name:
                        if hasattr(self.properties_out[0],
                                   "mass_frac_phase_comp") and \
                            hasattr(self.properties_out[0],
                                    "flow_mass_phase"):
                            flow_phase_comp = False
                            local_name_frac = "mass_frac_phase_comp"
                            local_name_flow = "flow_mass_phase"
                        elif hasattr(self.properties_out[0],
                                     "flow_mass_phase_comp"):
                            flow_phase_comp = True
                            local_name_flow = "flow_mass_phase_comp"
                        else:
                            raise PropertyNotSupportedError(
                                "No mass_frac_phase_comp or flow_mass_phase or"
                                " flow_mass_phase_comp variables encountered "
                                "while building ports for the condenser.")
                    else:
                        raise PropertyNotSupportedError(
                            "No mass frac or mole frac variables encountered "
                            " while building ports for the condenser. "
                            "phase_frac as a state variable is not "
                            "supported with distillation unit models.")

                    # Rule for mole fraction
                    def rule_mole_frac(self, t, i):
                        if not flow_phase_comp:
                            sum_flow_comp = sum(
                                self.properties_out[t].component(
                                    local_name_frac)[p, i] *
                                self.properties_out[t].component(
                                    local_name_flow)[p] for p in phase)

                            return sum_flow_comp / sum(
                                self.properties_out[t].component(
                                    local_name_flow)[p] for p in phase)
                        else:
                            sum_flow_comp = sum(
                                self.properties_out[t].component(
                                    local_name_flow)[p, i] for p in phase)

                            return sum_flow_comp / sum(
                                self.properties_out[t].component(
                                    local_name_flow)[p, i] for p in phase for i
                                in self.config.property_package.component_list)

                    # add the reference and variable name to the port
                    expr = Expression(self.flowsheet().time,
                                      index_set,
                                      rule=rule_mole_frac)
                    self.add_component("e_mole_frac_" + port.local_name, expr)
                    port.add(expr, k)
                else:

                    # Assumes mole_frac_phase or mass_frac_phase exist as
                    # state vars in the port and therefore access directly
                    # from the state block.
                    var = self.properties_out[:].\
                        component(local_name)[...]

                    # add the reference and variable name to the port
                    ref = Reference(var)
                    setattr(self, "_" + k + "_" + port.local_name + "_ref",
                            ref)
                    port.add(ref, k)
            elif "flow" in local_name:
                if "phase" not in local_name:

                    # Assumes that here the var is total flow or component
                    # flow. However, need to extract the flow by phase from
                    # the state block. Expects to find the var
                    # flow_mol_phase or flow_mass_phase in the state block.

                    # Check if it is not indexed by component list and this
                    # is total flow
                    if not member_list[k].is_indexed():
                        # if state var is not flow_mol/flow_mass
                        # by phase
                        local_name_flow = local_name + "_phase"

                        # Rule to link the flow to the port
                        def rule_flow(self, t):
                            return sum(self.properties_out[t].component(
                                local_name_flow)[p] for p in phase) * (side_sf)

                        # add the reference and variable name to the port
                        expr = Expression(self.flowsheet().time,
                                          rule=rule_flow)
                        self.add_component("e_flow_" + port.local_name, expr)
                        port.add(expr, k)
                    else:
                        # when it is flow comp indexed by component list
                        str_split = local_name.split("_")
                        if len(str_split) == 3 and str_split[-1] == "comp":
                            local_name_flow = str_split[0] + "_" + \
                                str_split[1] + "_phase_" + "comp"

                        # Get the indexing set i.e. component list
                        index_set = member_list[k].index_set()

                        # Rule to link the flow to the port
                        def rule_flow(self, t, i):
                            return sum(self.properties_out[t].component(
                                local_name_flow)[p, i]
                                       for p in phase) * (side_sf)

                        expr = Expression(self.flowsheet().time,
                                          index_set,
                                          rule=rule_flow)
                        self.add_component("e_flow_" + port.local_name, expr)
                        port.add(expr, local_name)
                elif "phase" in local_name:
                    # flow is indexed by phase and comp
                    # Get the indexing sets i.e. component list and phase list
                    component_set = self.config.\
                        property_package.component_list

                    phase_set = self.config.\
                        property_package.phase_list

                    def rule_flow(self, t, p, i):
                        if (phase is self._liquid_set and
                                p in self._liquid_set) or \
                                (phase is self._vapor_set and
                                 p in self._vapor_set) :
                            # pass appropriate phase flow values to port
                            return (self.properties_out[t].component(
                                local_name)[p, i]) * (side_sf)
                        else:
                            # return small number for phase that should not
                            # be in the appropriate port. For example,
                            # the state vars will be flow_mol_phase_comp
                            # which will include all phases. The liq port
                            # should have the correct references to the liq
                            # phase flow but the vapor phase flow should be 0.
                            return 1e-8

                    expr = Expression(self.flowsheet().time,
                                      phase_set,
                                      component_set,
                                      rule=rule_flow)
                    self.add_component("e_" + local_name + port.local_name,
                                       expr)
                    port.add(expr, k)
                else:
                    raise PropertyPackageError(
                        "Unrecognized flow state variable encountered "
                        "while building ports for the tray. Please follow "
                        "the naming convention outlined in the documentation "
                        "for state variables.")
            elif "enth" in local_name:
                if "phase" not in local_name:
                    # assumes total mixture enthalpy (enth_mol or enth_mass)
                    if not member_list[k].is_indexed():
                        # if state var is not enth_mol/enth_mass
                        # by phase, add _phase string to extract the right
                        # value from the state block
                        local_name_phase = local_name + "_phase"
                    else:
                        raise PropertyPackageError(
                            "Enthalpy is indexed but the variable "
                            "name does not reflect the presence of an index. "
                            "Please follow the naming convention outlined "
                            "in the documentation for state variables.")

                    # Rule to link the phase enthalpy to the port.
                    def rule_enth(self, t):
                        return sum(self.properties_out[t].component(
                            local_name_phase)[p] for p in phase)

                    expr = Expression(self.flowsheet().time, rule=rule_enth)
                    self.add_component("e_enth_" + port.local_name, expr)
                    # add the reference and variable name to the port
                    port.add(expr, k)

                elif "phase" in local_name:
                    # assumes enth_mol_phase or enth_mass_phase.
                    # This is an intensive property, you create a direct
                    # reference irrespective of the reflux, distillate and
                    # vap_outlet

                    if not member_list[k].is_indexed():
                        var = self.properties_out[:].\
                            component(local_name)
                    else:
                        var = self.properties_out[:].\
                            component(local_name)[...]

                    # add the reference and variable name to the port
                    ref = Reference(var)
                    setattr(self, "_" + k + "_" + port.local_name + "_ref",
                            ref)
                    port.add(ref, k)
                else:
                    raise PropertyNotSupportedError(
                        "Unrecognized enthalpy state variable encountered "
                        "while building ports for the tray. Only total "
                        "mixture enthalpy or enthalpy by phase are supported.")
Beispiel #18
0
    def _make_splits_reboiler(self):
        # Get dict of Port members and names
        member_list = self.control_volume.\
            properties_out[0].define_port_members()

        # Create references and populate the reflux, distillate ports
        for k in member_list:
            # Create references and populate the intensive variables
            if "flow" not in k and "frac" not in k and "enth" not in k:
                if not member_list[k].is_indexed():
                    var = self.control_volume.properties_out[:].\
                        component(member_list[k].local_name)
                else:
                    var = self.control_volume.properties_out[:].\
                        component(member_list[k].local_name)[...]

                # add the reference and variable name to the reflux port
                self.bottoms.add(Reference(var), k)

                # add the reference and variable name to the
                # vapor outlet port
                self.vapor_reboil.add(Reference(var), k)

            elif "frac" in k and ("mole" in k or "mass" in k):

                # Mole/mass frac is typically indexed
                index_set = member_list[k].index_set()

                # if state var is not mole/mass frac by phase
                if "phase" not in k:
                    # Assuming the state block has the var
                    # "mole_frac_phase_comp". Valid if VLE is supported
                    # Create a string "mole_frac_phase_comp" or
                    # "mass_frac_phase_comp". Cannot directly append phase to
                    # k as the naming convention is phase followed by comp

                    str_split = k.split('_')
                    local_name = '_'.join(str_split[0:2]) + \
                        "_phase" + "_" + str_split[2]

                    # Rule for liquid fraction
                    def rule_liq_frac(self, t, i):
                        return self.control_volume.properties_out[t].\
                            component(local_name)["Liq", i]
                    self.e_liq_frac = Expression(
                        self.flowsheet().time, index_set,
                        rule=rule_liq_frac)

                    # Rule for vapor fraction
                    def rule_vap_frac(self, t, i):
                        return self.control_volume.properties_out[t].\
                            component(local_name)["Vap", i]
                    self.e_vap_frac = Expression(
                        self.flowsheet().time, index_set,
                        rule=rule_vap_frac)

                    # add the reference and variable name to the
                    # distillate port
                    self.bottoms.add(self.e_liq_frac, k)

                    # add the reference and variable name to the
                    # vapor port
                    self.vapor_reboil.add(self.e_vap_frac, k)

                else:

                    # Assumes mole_frac_phase or mass_frac_phase exist as
                    # state vars in the port and therefore access directly
                    # from the state block.
                    var = self.control_volume.properties_out[:].\
                        component(member_list[k].local_name)[...]

                    # add the reference and variable name to the distillate port
                    self.bottoms.add(Reference(var), k)

                    # add the reference and variable name to the boil up port
                    self.vapor_reboil.add(Reference(var), k)
            elif "flow" in k:
                if "phase" not in k:

                    # Assumes that here the var is total flow or component
                    # flow. However, need to extract the flow by phase from
                    # the state block. Expects to find the var
                    # flow_mol_phase or flow_mass_phase in the state block.

                    # Check if it is not indexed by component list and this
                    # is total flow
                    if not member_list[k].is_indexed():
                        # if state var is not flow_mol/flow_mass
                        # by phase
                        local_name = str(member_list[k].local_name) + \
                            "_phase"

                        # Rule for vap flow
                        def rule_vap_flow(self, t):
                            return self.control_volume.properties_out[t].\
                                component(local_name)["Vap"]
                        self.e_vap_flow = Expression(
                            self.flowsheet().time,
                            rule=rule_vap_flow)

                        # Rule to link the liq flow to the distillate
                        def rule_bottoms_flow(self, t):
                            return self.control_volume.properties_out[t].\
                                component(local_name)["Liq"]
                        self.e_bottoms_flow = Expression(
                            self.flowsheet().time,
                            rule=rule_bottoms_flow)

                    else:
                        # when it is flow comp indexed by component list
                        str_split = \
                            str(member_list[k].local_name).split("_")
                        if len(str_split) == 3 and str_split[-1] == "comp":
                            local_name = str_split[0] + "_" + \
                                str_split[1] + "_phase_" + "comp"

                        # Get the indexing set i.e. component list
                        index_set = member_list[k].index_set()

                        # Rule for vap flow
                        def rule_vap_flow(self, t, i):
                            return self.control_volume.properties_out[t].\
                                component(local_name)["Vap", i]
                        self.e_vap_flow = Expression(
                            self.flowsheet().time, index_set,
                            rule=rule_vap_flow)

                        # Rule to link the liq flow to the distillate
                        def rule_bottoms_flow(self, t, i):
                            return self.control_volume.properties_out[t].\
                                component(local_name)["Liq", i]
                        self.e_bottoms_flow = Expression(
                            self.flowsheet().time, index_set,
                            rule=rule_bottoms_flow)

                    # add the reference and variable name to the
                    # distillate port
                    self.bottoms.add(self.e_bottoms_flow, k)

                    # add the reference and variable name to the
                    # distillate port
                    self.vapor_reboil.add(self.e_vap_flow, k)
            elif "enth" in k:
                if "phase" not in k:
                    # assumes total mixture enthalpy (enth_mol or enth_mass)
                    if not member_list[k].is_indexed():
                        # if state var is not enth_mol/enth_mass
                        # by phase, add _phase string to extract the right
                        # value from the state block
                        local_name = str(member_list[k].local_name) + \
                            "_phase"
                    else:
                        raise PropertyPackageError(
                            "Enthalpy is indexed but the variable "
                            "name does not reflect the presence of an index. "
                            "Please follow the naming convention outlined "
                            "in the documentation for state variables.")

                    # Rule for vap enthalpy. Setting the enthalpy to the
                    # enth_mol_phase['Vap'] value from the state block
                    def rule_vap_enth(self, t):
                        return self.control_volume.properties_out[t].\
                            component(local_name)["Vap"]
                    self.e_vap_enth = Expression(
                        self.flowsheet().time,
                        rule=rule_vap_enth)

                    # Rule to link the liq flow to the distillate.
                    # Setting the enthalpy to the
                    # enth_mol_phase['Liq'] value from the state block
                    def rule_bottoms_enth(self, t):
                        return self.control_volume.properties_out[t].\
                            component(local_name)["Liq"]
                    self.e_bottoms_enth = Expression(
                        self.flowsheet().time,
                        rule=rule_bottoms_enth)

                    # add the reference and variable name to the
                    # distillate port
                    self.bottoms.add(self.e_bottoms_enth, k)

                    # add the reference and variable name to the
                    # distillate port
                    self.vapor_reboil.add(self.e_vap_enth, k)
                elif "phase" in k:
                    # assumes enth_mol_phase or enth_mass_phase.
                    # This is an intensive property, you create a direct
                    # reference irrespective of the reflux, distillate and
                    # vap_outlet

                    # Rule for vap flow
                    if not member_list[k].is_indexed():
                        var = self.control_volume.properties_out[:].\
                            component(member_list[k].local_name)
                    else:
                        var = self.control_volume.properties_out[:].\
                            component(member_list[k].local_name)[...]

                    # add the reference and variable name to the distillate port
                    self.bottoms.add(Reference(var), k)

                    # add the reference and variable name to the
                    # vapor outlet port
                    self.vapor_reboil.add(Reference(var), k)
                else:
                    raise PropertyNotSupportedError(
                        "Unrecognized enthalpy state variable encountered "
                        "while building ports for the reboiler. Only total "
                        "mixture enthalpy or enthalpy by phase are supported.")
Beispiel #19
0
    def initialize(
        self,
        state_args=None,
        state_vars_fixed=False,
        hold_state=False,
        outlvl=idaeslog.NOTSET,
        solver=None,
        optarg=None,
    ):
        """
        Initialization routine for property package.
        Keyword Arguments:
            state_args : Dictionary with initial guesses for the state vars
                         chosen. Note that if this method is triggered
                         through the control volume, and if initial guesses
                         were not provided at the unit model level, the
                         control volume passes the inlet values as initial
                         guess.The keys for the state_args dictionary are:

                         flow_mass_phase_comp : value at which to initialize
                                               phase component flows
                         pressure : value at which to initialize pressure
                         temperature : value at which to initialize temperature
            outlvl : sets output level of initialization routine (default=idaeslog.NOTSET)
            optarg : solver options dictionary object (default=None)
            state_vars_fixed: Flag to denote if state vars have already been
                              fixed.
                              - True - states have already been fixed by the
                                       control volume 1D. Control volume 0D
                                       does not fix the state vars, so will
                                       be False if this state block is used
                                       with 0D blocks.
                             - False - states have not been fixed. The state
                                       block will deal with fixing/unfixing.
            solver : Solver object to use during initialization if None is provided
                     it will use the default solver for IDAES (default = None)
            hold_state : flag indicating whether the initialization routine
                         should unfix any state variables fixed during
                         initialization (default=False).
                         - True - states variables are not unfixed, and
                                 a dict of returned containing flags for
                                 which states were fixed during
                                 initialization.
                        - False - state variables are unfixed after
                                 initialization by calling the
                                 release_state method
        Returns:
            If hold_states is True, returns a dict containing flags for
            which states were fixed during initialization.
        """
        # Get loggers
        init_log = idaeslog.getInitLogger(self.name, outlvl, tag="properties")
        solve_log = idaeslog.getSolveLogger(self.name,
                                            outlvl,
                                            tag="properties")

        # Set solver and options
        opt = get_solver(solver, optarg)

        # Fix state variables
        flags = fix_state_vars(self, state_args)
        # Check when the state vars are fixed already result in dof 0
        for k in self.keys():
            dof = degrees_of_freedom(self[k])
            if dof != 0:
                raise PropertyPackageError(
                    "\nWhile initializing {sb_name}, the degrees of freedom "
                    "are {dof}, when zero is required. \nInitialization assumes "
                    "that the state variables should be fixed and that no other "
                    "variables are fixed. \nIf other properties have a "
                    "predetermined value, use the calculate_state method "
                    "before using initialize to determine the values for "
                    "the state variables and avoid fixing the property variables."
                    "".format(sb_name=self.name, dof=dof))

        # ---------------------------------------------------------------------
        skip_solve = True  # skip solve if only state variables are present
        for k in self.keys():
            if number_unfixed_variables(self[k]) != 0:
                skip_solve = False

        if not skip_solve:
            # Initialize properties
            with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc:
                results = solve_indexed_blocks(opt, [self], tee=slc.tee)
            init_log.info_high("Property initialization: {}.".format(
                idaeslog.condition(results)))

        # ---------------------------------------------------------------------
        # If input block, return flags, else release state
        if state_vars_fixed is False:
            if hold_state is True:
                return flags
            else:
                self.release_state(flags)
Beispiel #20
0
 def parameters(self):
     raise PropertyPackageError(
         "{} User defined property package failed to define a "
         "parameters method. Please contact the developer of the "
         "property package with this error.".format(self.name))
Beispiel #21
0
    def __getattr__(self, attr):
        """
        This method is used to avoid generating unnecessary property
        calculations in state blocks. __getattr__ is called whenever a
        property is called for, and if a propery does not exist, it looks for
        a method to create the required property, and any associated
        components.

        Create a property calculation if needed. Return an attrbute error if
        attr == 'domain' or starts with a _ . The error for _ prevents a
        recursion error if trying to get a function to create a property and
        that function doesn't exist.  Pyomo also ocasionally looks for things
        that start with _ and may not exist.  Pyomo also looks for the domain
        attribute, and it may not exist.
        This works by creating a property calculation by calling the "_"+attr
        function.

        A list of __getattr__ calls is maintained in self.__getattrcalls to
        check for recursive loops which maybe useful for debugging. This list
        is cleared after __getattr__ completes successfully.

        Args:
            attr: an attribute to create and return. Should be a property
                  component.
        """
        if self._lock_attribute_creation:
            raise AttributeError(
                f"{attr} does not exist, and attribute creation is locked.")

        def clear_call_list(self, attr):
            """Local method for cleaning up call list when a call is handled.

                Args:
                    attr: attribute currently being handled
            """
            if self.__getattrcalls[-1] == attr:
                if len(self.__getattrcalls) <= 1:
                    del self.__getattrcalls
                else:
                    del self.__getattrcalls[-1]
            else:
                raise PropertyPackageError(
                    "{} Trying to remove call {} from __getattr__"
                    " call list, however this is not the most "
                    "recent call in the list ({}). This indicates"
                    " a bug in the __getattr__ calls. Please "
                    "contact the IDAES developers with this bug.".format(
                        self.name, attr, self.__getattrcalls[-1]))

        # Check that attr is not something we shouldn't touch
        if attr == "domain" or attr.startswith("_"):
            # Don't interfere with anything by getting attributes that are
            # none of my business
            raise PropertyPackageError(
                '{} {} does not exist, but is a protected '
                'attribute. Check the naming of your '
                'components to avoid any reserved names'.format(
                    self.name, attr))

        if attr == "config":
            try:
                self._get_config_args()
                return self.config
            except:
                raise BurntToast("{} getattr method was triggered by a call "
                                 "to the config block, but _get_config_args "
                                 "failed. This should never happen.")

        # Check for recursive calls
        try:
            # Check if __getattrcalls is initialized
            self.__getattrcalls
        except AttributeError:
            # Initialize it
            self.__getattrcalls = [attr]
        else:
            # Check to see if attr already appears in call list
            if attr in self.__getattrcalls:
                # If it does, indicates a recursive loop.
                if attr == self.__getattrcalls[-1]:
                    # attr method is calling itself
                    self.__getattrcalls.append(attr)
                    raise PropertyPackageError(
                        '{} _{} made a recursive call to '
                        'itself, indicating a potential '
                        'recursive loop. This is generally '
                        'caused by the {} method failing to '
                        'create the {} component.'.format(
                            self.name, attr, attr, attr))
                else:
                    self.__getattrcalls.append(attr)
                    raise PropertyPackageError(
                        '{} a potential recursive loop has been '
                        'detected whilst trying to construct {}. '
                        'A method was called, but resulted in a '
                        'subsequent call to itself, indicating a '
                        'recursive loop. This may be caused by a '
                        'method trying to access a component out '
                        'of order for some reason (e.g. it is '
                        'declared later in the same method). See '
                        'the __getattrcalls object for a list of '
                        'components called in the __getattr__ '
                        'sequence.'.format(self.name, attr))
            # If not, add call to list
            self.__getattrcalls.append(attr)

        # Get property information from properties metadata
        try:
            m = self.config.parameters.get_metadata().properties

            if m is None:
                raise PropertyPackageError(
                    '{} property package get_metadata()'
                    ' method returned None when trying to create '
                    '{}. Please contact the developer of the '
                    'property package'.format(self.name, attr))
        except KeyError:
            # If attr not in metadata, assume package does not
            # support property
            clear_call_list(self, attr)
            raise PropertyNotSupportedError(
                '{} {} is not supported by property package (property is '
                'not listed in package metadata properties).'.format(
                    self.name, attr))

        # Get method name from resulting properties
        try:
            if m[attr]['method'] is None:
                # If method is none, property should be constructed
                # by property package, so raise PropertyPackageError
                clear_call_list(self, attr)
                raise PropertyPackageError(
                    '{} {} should be constructed automatically '
                    'by property package, but is not present. '
                    'This can be caused by methods being called '
                    'out of order.'.format(self.name, attr))
            elif m[attr]['method'] is False:
                # If method is False, package does not support property
                # Raise NotImplementedError
                clear_call_list(self, attr)
                raise PropertyNotSupportedError(
                    '{} {} is not supported by property package '
                    '(property method is listed as False in '
                    'package property metadata).'.format(self.name, attr))
            elif isinstance(m[attr]['method'], str):
                # Try to get method name in from PropertyBlock object
                try:
                    f = getattr(self, m[attr]['method'])
                except AttributeError:
                    # If fails, method does not exist
                    clear_call_list(self, attr)
                    raise PropertyPackageError(
                        '{} {} package property metadata method '
                        'returned a name that does not correspond'
                        ' to any method in the property package. '
                        'Please contact the developer of the '
                        'property package.'.format(self.name, attr))
            else:
                # Otherwise method name is invalid
                clear_call_list(self, attr)
                raise PropertyPackageError(
                    '{} {} package property metadata method '
                    'returned invalid value for method name. '
                    'Please contact the developer of the '
                    'property package.'.format(self.name, attr))
        except KeyError:
            # No method key - raise Exception
            # Need to use an AttributeError so Pyomo.DAE will handle this
            clear_call_list(self, attr)
            raise PropertyNotSupportedError(
                '{} package property metadata method '
                'does not contain a method for {}. '
                'Please select a package which supports '
                'the necessary properties for your process.'.format(
                    self.name, attr))

        # Call attribute if it is callable
        # If this fails, it should return a meaningful error.
        if callable(f):
            try:
                f()
            except Exception:
                # Clear call list and reraise error
                clear_call_list(self, attr)
                raise
        else:
            # If f is not callable, inform the user and clear call list
            clear_call_list(self, attr)
            raise PropertyPackageError(
                '{} tried calling attribute {} in order to create '
                'component {}. However the method is not callable.'.format(
                    self.name, f, attr))

        # Clear call list, and return
        comp = getattr(self, attr)
        clear_call_list(self, attr)
        return comp
Beispiel #22
0
    def _make_splits_reboiler(self):
        # Get dict of Port members and names
        member_list = self.control_volume.\
            properties_out[0].define_port_members()

        # Create references and populate the reflux, distillate ports
        for k in member_list:
            # Create references and populate the intensive variables
            if "flow" not in k and "frac" not in k and "enth" not in k:
                if not member_list[k].is_indexed():
                    var = self.control_volume.properties_out[:].\
                        component(member_list[k].local_name)
                else:
                    var = self.control_volume.properties_out[:].\
                        component(member_list[k].local_name)[...]

                # add the reference and variable name to the reflux port
                self.bottoms.add(Reference(var), k)

                # add the reference and variable name to the
                # vapor outlet port
                self.vapor_reboil.add(Reference(var), k)

            elif "frac" in k:

                # Mole/mass frac is typically indexed
                index_set = member_list[k].index_set()

                # if state var is not mole/mass frac by phase
                if "phase" not in k:
                    if "mole" in k:  # check mole basis/mass basis

                        # The following conditionals are required when a
                        # mole frac or mass frac is a state var i.e. will be
                        # a port member. This gets a bit tricky when handling
                        # non-conventional systems when you have more than one
                        # liquid or vapor phase. Hence, the logic here is that
                        # the mole frac that should be present in the liquid or
                        # vapor port should be computed by accounting for
                        # multiple liquid or vapor phases if present. For the
                        # classical VLE system, this holds too.
                        if hasattr(self.control_volume.properties_out[0],
                                   "mole_frac_phase_comp") and \
                            hasattr(self.control_volume.properties_out[0],
                                    "flow_mol_phase"):
                            flow_phase_comp = False
                            local_name_frac = "mole_frac_phase_comp"
                            local_name_flow = "flow_mol_phase"
                        elif hasattr(self.control_volum.properties_out[0],
                                     "flow_mol_phase_comp"):
                            flow_phase_comp = True
                            local_name_flow = "flow_mol_phase_comp"
                        else:
                            raise PropertyNotSupportedError(
                                "No mole_frac_phase_comp or flow_mol_phase or"
                                " flow_mol_phase_comp variables encountered "
                                "while building ports for the reboiler. ")
                    elif "mass" in k:
                        if hasattr(self.control_volume.properties_out[0],
                                   "mass_frac_phase_comp") and \
                            hasattr(self.control_volume.properties_out[0],
                                    "flow_mass_phase"):
                            flow_phase_comp = False
                            local_name_frac = "mass_frac_phase_comp"
                            local_name_flow = "flow_mass_phase"
                        elif hasattr(self.control_volum.properties_out[0],
                                     "flow_mass_phase_comp"):
                            flow_phase_comp = True
                            local_name_flow = "flow_mass_phase_comp"
                        else:
                            raise PropertyNotSupportedError(
                                "No mass_frac_phase_comp or flow_mass_phase or"
                                " flow_mass_phase_comp variables encountered "
                                "while building ports for the reboiler.")
                    else:
                        raise PropertyNotSupportedError(
                            "No mass frac or mole frac variables encountered "
                            " while building ports for the reboiler. "
                            "phase_frac as a state variable is not "
                            "supported with distillation unit models.")

                    # Rule for liquid phase mole fraction
                    def rule_liq_frac(self, t, i):
                        if not flow_phase_comp:
                            sum_flow_comp = sum(
                                self.control_volume.properties_out[t].
                                component(local_name_frac)[p, i] *
                                self.control_volume.properties_out[t].
                                component(local_name_flow)[p]
                                for p in self._liquid_set)

                            return sum_flow_comp / sum(
                                self.control_volume.properties_out[t].
                                component(local_name_flow)[p]
                                for p in self._liquid_set)
                        else:
                            sum_flow_comp = sum(
                                self.control_volume.properties_out[t].
                                component(local_name_flow)[p, i]
                                for p in self._liquid_set)

                            return sum_flow_comp / sum(
                                self.control_volume.properties_out[t].
                                component(local_name_flow)[p, i]
                                for p in self._liquid_set for i in
                                self.config.property_package.component_list)

                    self.e_liq_frac = Expression(self.flowsheet().time,
                                                 index_set,
                                                 rule=rule_liq_frac)

                    # Rule for vapor phase mass/mole fraction
                    def rule_vap_frac(self, t, i):
                        if not flow_phase_comp:
                            sum_flow_comp = sum(
                                self.control_volume.properties_out[t].
                                component(local_name_frac)[p, i] *
                                self.control_volume.properties_out[t].
                                component(local_name_flow)[p]
                                for p in self._vapor_set)
                            return sum_flow_comp / sum(
                                self.control_volume.properties_out[t].
                                component(local_name_flow)[p]
                                for p in self._vapor_set)
                        else:
                            sum_flow_comp = sum(
                                self.control_volume.properties_out[t].
                                component(local_name_flow)[p, i]
                                for p in self._vapor_set)

                            return sum_flow_comp / sum(
                                self.control_volume.properties_out[t].
                                component(local_name_flow)[p, i]
                                for p in self._vapor_set for i in
                                self.config.property_package.component_list)

                    self.e_vap_frac = Expression(self.flowsheet().time,
                                                 index_set,
                                                 rule=rule_vap_frac)

                    # add the reference and variable name to the
                    # distillate port
                    self.bottoms.add(self.e_liq_frac, k)

                    # add the reference and variable name to the
                    # vapor port
                    self.vapor_reboil.add(self.e_vap_frac, k)
                else:
                    # Assumes mole_frac_phase or mass_frac_phase exist as
                    # state vars in the port and therefore access directly
                    # from the state block.
                    var = self.control_volume.properties_out[:].\
                        component(member_list[k].local_name)[...]

                    # add the reference and variable name to the distillate port
                    self.bottoms.add(Reference(var), k)

                    # add the reference and variable name to the boil up port
                    self.vapor_reboil.add(Reference(var), k)
            elif "flow" in k:
                if "phase" not in k:

                    # Assumes that here the var is total flow or component
                    # flow. However, need to extract the flow by phase from
                    # the state block. Expects to find the var
                    # flow_mol_phase or flow_mass_phase in the state block.

                    # Check if it is not indexed by component list and this
                    # is total flow
                    if not member_list[k].is_indexed():
                        # if state var is not flow_mol/flow_mass
                        # by phase
                        local_name = str(member_list[k].local_name) + \
                            "_phase"

                        # Rule for vap flow
                        def rule_vap_flow(self, t):
                            return sum(self.control_volume.properties_out[t].
                                       component(local_name)[p]
                                       for p in self._vapor_set)

                        self.e_vap_flow = Expression(self.flowsheet().time,
                                                     rule=rule_vap_flow)

                        # Rule to link the liq flow to the distillate
                        def rule_bottoms_flow(self, t):
                            return sum(self.control_volume.properties_out[t].
                                       component(local_name)[p]
                                       for p in self._liquid_set)

                        self.e_bottoms_flow = Expression(
                            self.flowsheet().time, rule=rule_bottoms_flow)

                    else:
                        # when it is flow comp indexed by component list
                        str_split = \
                            str(member_list[k].local_name).split("_")
                        if len(str_split) == 3 and str_split[-1] == "comp":
                            local_name = str_split[0] + "_" + \
                                str_split[1] + "_phase_" + "comp"

                        # Get the indexing set i.e. component list
                        index_set = member_list[k].index_set()

                        # Rule for vap phase flow to the vapor outlet
                        def rule_vap_flow(self, t, i):
                            return sum(self.control_volume.properties_out[t].
                                       component(local_name)[p, i]
                                       for p in self._vapor_set)

                        self.e_vap_flow = Expression(self.flowsheet().time,
                                                     index_set,
                                                     rule=rule_vap_flow)

                        # Rule for liq phase flow to the liquid outlet
                        def rule_bottoms_flow(self, t, i):
                            return sum(self.control_volume.properties_out[t].
                                       component(local_name)[p, i]
                                       for p in self._liquid_set)

                        self.e_bottoms_flow = Expression(
                            self.flowsheet().time,
                            index_set,
                            rule=rule_bottoms_flow)

                    # add the reference and variable name to the
                    # distillate port
                    self.bottoms.add(self.e_bottoms_flow, k)

                    # add the reference and variable name to the
                    # distillate port
                    self.vapor_reboil.add(self.e_vap_flow, k)
            elif "enth" in k:
                if "phase" not in k:
                    # assumes total mixture enthalpy (enth_mol or enth_mass)
                    if not member_list[k].is_indexed():
                        # if state var is not enth_mol/enth_mass
                        # by phase, add _phase string to extract the right
                        # value from the state block
                        local_name = str(member_list[k].local_name) + \
                            "_phase"
                    else:
                        raise PropertyPackageError(
                            "Enthalpy is indexed but the variable "
                            "name does not reflect the presence of an index. "
                            "Please follow the naming convention outlined "
                            "in the documentation for state variables.")

                    # Rule for vap enthalpy. Setting the enthalpy to the
                    # enth_mol_phase['Vap'] value from the state block
                    def rule_vap_enth(self, t):
                        return sum(
                            self.control_volume.properties_out[t].component(
                                local_name)[p] for p in self._vapor_set)

                    self.e_vap_enth = Expression(self.flowsheet().time,
                                                 rule=rule_vap_enth)

                    # Rule to link the liq flow to the distillate.
                    # Setting the enthalpy to the
                    # enth_mol_phase['Liq'] value from the state block
                    def rule_bottoms_enth(self, t):
                        return sum(
                            self.control_volume.properties_out[t].component(
                                local_name)[p] for p in self._liquid_set)

                    self.e_bottoms_enth = Expression(self.flowsheet().time,
                                                     rule=rule_bottoms_enth)

                    # add the reference and variable name to the
                    # distillate port
                    self.bottoms.add(self.e_bottoms_enth, k)

                    # add the reference and variable name to the
                    # distillate port
                    self.vapor_reboil.add(self.e_vap_enth, k)
                elif "phase" in k:
                    # assumes enth_mol_phase or enth_mass_phase.
                    # This is an intensive property, you create a direct
                    # reference irrespective of the reflux, distillate and
                    # vap_outlet

                    # Rule for vap flow
                    if not member_list[k].is_indexed():
                        var = self.control_volume.properties_out[:].\
                            component(member_list[k].local_name)
                    else:
                        var = self.control_volume.properties_out[:].\
                            component(member_list[k].local_name)[...]

                    # add the reference and variable name to the distillate port
                    self.bottoms.add(Reference(var), k)

                    # add the reference and variable name to the
                    # vapor outlet port
                    self.vapor_reboil.add(Reference(var), k)
                else:
                    raise PropertyNotSupportedError(
                        "Unrecognized enthalpy state variable encountered "
                        "while building ports for the reboiler. Only total "
                        "mixture enthalpy or enthalpy by phase are supported.")
Beispiel #23
0
    def initialize(
        self,
        state_args=None,
        state_vars_fixed=False,
        hold_state=False,
        outlvl=idaeslog.NOTSET,
        solver=None,
        optarg=None,
    ):
        """
        Initialization routine for property package.
        Keyword Arguments:
            state_args : Dictionary with initial guesses for the state vars
                         chosen. Note that if this method is triggered
                         through the control volume, and if initial guesses
                         were not provided at the unit model level, the
                         control volume passes the inlet values as initial
                         guess.The keys for the state_args dictionary are:

                         flow_mass_phase_comp : value at which to initialize
                                               phase component flows
                         pressure : value at which to initialize pressure
                         temperature : value at which to initialize temperature
            outlvl : sets output level of initialization routine
            optarg : solver options dictionary object (default={})
            state_vars_fixed: Flag to denote if state vars have already been
                              fixed.
                              - True - states have already been fixed by the
                                       control volume 1D. Control volume 0D
                                       does not fix the state vars, so will
                                       be False if this state block is used
                                       with 0D blocks.
                             - False - states have not been fixed. The state
                                       block will deal with fixing/unfixing.
            solver : Solver object to use during initialization if None is provided
                     it will use the default solver for IDAES (default = None)
            hold_state : flag indicating whether the initialization routine
                         should unfix any state variables fixed during
                         initialization (default=False).
                         - True - states variables are not unfixed, and
                                 a dict of returned containing flags for
                                 which states were fixed during
                                 initialization.
                        - False - state variables are unfixed after
                                 initialization by calling the
                                 release_state method
        Returns:
            If hold_states is True, returns a dict containing flags for
            which states were fixed during initialization.
        """
        # Get loggers
        init_log = idaeslog.getInitLogger(self.name, outlvl, tag="properties")
        solve_log = idaeslog.getSolveLogger(self.name,
                                            outlvl,
                                            tag="properties")

        # Set solver and options
        opt = get_solver(solver, optarg)

        # Fix state variables
        flags = fix_state_vars(self, state_args)
        # Check when the state vars are fixed already result in dof 0
        for k in self.keys():
            dof = degrees_of_freedom(self[k])
            if dof != 0:
                raise PropertyPackageError("State vars fixed but degrees of "
                                           "freedom for state block is not "
                                           "zero during initialization.")

        # ---------------------------------------------------------------------
        # Initialize properties
        with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc:
            results = solve_indexed_blocks(opt, [self], tee=slc.tee)
        init_log.info("Property initialization: {}.".format(
            idaeslog.condition(results)))

        if not check_optimal_termination(results):
            raise InitializationError(
                f"{self.name} failed to initialize successfully. Please check "
                f"the output logs for more information.")

        # ---------------------------------------------------------------------
        # If input block, return flags, else release state
        if state_vars_fixed is False:
            if hold_state is True:
                return flags
            else:
                self.release_state(flags)
Beispiel #24
0
 def _create_derived_units(self):
     try:
         self._derived_units = {
             "time":
             self.default_units["time"],
             "length":
             self.default_units["length"],
             "mass":
             self.default_units["mass"],
             "amount":
             self.default_units["amount"],
             "temperature":
             self.default_units["temperature"],
             "current":
             self.default_units["current"],
             "luminous intensity":
             self.default_units["luminous intensity"],
             "area":
             self.default_units["length"]**2,
             "volume":
             self.default_units["length"]**3,
             "flow_mass":
             (self.default_units["mass"] * self.default_units["time"]**-1),
             "flow_mole": (self.default_units["amount"] *
                           self.default_units["time"]**-1),
             "density_mass": (self.default_units["mass"] *
                              self.default_units["length"]**-3),
             "density_mole": (self.default_units["amount"] *
                              self.default_units["length"]**-3),
             "energy":
             (self.default_units["mass"] * self.default_units["length"]**2 *
              self.default_units["time"]**-2),
             "energy_mass": (self.default_units["length"]**2 *
                             self.default_units["time"]**-2),
             "energy_mole":
             (self.default_units["mass"] * self.default_units["length"]**2 *
              self.default_units["time"]**-2 *
              self.default_units["amount"]**-1),
             "entropy":
             (self.default_units["mass"] * self.default_units["length"]**2 *
              self.default_units["time"]**-2 *
              self.default_units["temperature"]**-1),
             "entropy_mass": (self.default_units["length"]**2 *
                              self.default_units["time"]**-2 *
                              self.default_units["temperature"]**-1),
             "entropy_mole":
             (self.default_units["mass"] * self.default_units["length"]**2 *
              self.default_units["time"]**-2 *
              self.default_units["temperature"]**-1 *
              self.default_units["amount"]**-1),
             "power":
             (self.default_units["mass"] * self.default_units["length"]**2 *
              self.default_units["time"]**-3),
             "pressure": (self.default_units["mass"] *
                          self.default_units["length"]**-1 *
                          self.default_units["time"]**-2),
             "heat_capacity_mass": (self.default_units["length"]**2 *
                                    self.default_units["time"]**-2 *
                                    self.default_units["temperature"]**-1),
             "heat_capacity_mole":
             (self.default_units["mass"] * self.default_units["length"]**2 *
              self.default_units["time"]**-2 *
              self.default_units["temperature"]**-1 *
              self.default_units["amount"]**-1),
             "heat_transfer_coefficient":
             (self.default_units["mass"] * self.default_units["time"]**-3 *
              self.default_units["temperature"]**-1),
             "gas_constant":
             (self.default_units["mass"] * self.default_units["length"]**2 *
              self.default_units["time"]**-2 *
              self.default_units["temperature"]**-1 *
              self.default_units["amount"]**-1)
         }
     except TypeError:
         raise PropertyPackageError(
             "{} cannot determine derived units, as property package has "
             "not defined a set of base units.".format(self.name))
Beispiel #25
0
    def add_outlet_port(blk, name=None, block=None, doc=None):
        """
        This is a method to build outlet Port objects in a unit model and
        connect these to a specified control volume or state block.

        The name and block arguments are optional, but must be used together.
        i.e. either both arguments are provided or neither.

        Keyword Args:
            name : name to use for Port object (default = "outlet").
            block : an instance of a ControlVolume or StateBlock to use as the
                    source to populate the Port object. If a ControlVolume is
                    provided, the method will use the outlet state block as
                    defined by the ControlVolume. If not provided, method will
                    attempt to default to an object named control_volume.
            doc : doc string for Port object (default = "Outlet Port")

        Returns:
            A Pyomo Port object and associated components.
        """
        if block is None:
            # Check that name is None
            if name is not None:
                raise ConfigurationError(
                        "{} add_outlet_port was called without a block "
                        "argument  but a name argument was provided. Either "
                        "both a name and a block must be provided or neither."
                        .format(blk.name))
            else:
                name = "outlet"
            # Try for default ControlVolume name
            try:
                block = blk.control_volume
            except AttributeError:
                raise ConfigurationError(
                        "{} add_outlet_port was called without a block "
                        "argument but no default ControlVolume exists "
                        "(control_volume). Please provide block to which the "
                        "Port should be associated.".format(blk.name))
        else:
            # Check that name is not None
            if name is None:
                raise ConfigurationError(
                        "{} add_outlet_port was called with a block argument, "
                        "but a name argument was not provided. Either both "
                        "a name and a block must be provided or neither."
                        .format(blk.name))

        if doc is None:
            doc = "Outlet Port"

        # Create empty Port
        p = Port(doc=doc)
        setattr(blk, name, p)

        # Get dict of Port members and names
        if isinstance(block, ControlVolumeBlockData):
            try:
                member_list = (block.properties_out[
                                    block.flowsheet().time.first()]
                               .define_port_members())
            except AttributeError:
                try:
                    member_list = (block.properties[
                                    block.flowsheet().time.first(), 0]
                                   .define_port_members())
                except AttributeError:
                    raise PropertyPackageError(
                            "{} property package does not appear to have "
                            "implemented a define_port_members method. "
                            "Please contact the developer of the property "
                            "package.".format(blk.name))
        elif isinstance(block, StateBlock):
            member_list = block[
                    blk.flowsheet().time.first()].define_port_members()
        else:
            raise ConfigurationError(
                    "{} block provided to add_inlet_port "
                    "method was not an instance of a "
                    "ControlVolume or a StateBlock."
                    .format(blk.name))

        # Create References for port members
        for s in member_list:
            if not member_list[s].is_indexed():
                if isinstance(block, ControlVolumeBlockData):
                    try:
                        slicer = block.properties_out[:].component(
                                        member_list[s].local_name)
                    except AttributeError:
                        if block._flow_direction == FlowDirection.forward:
                            _idx = block.length_domain.last()
                        elif block._flow_direction == FlowDirection.backward:
                            _idx = block.length_domain.first()
                        else:
                            raise BurntToast(
                                    "{} flow_direction argument received "
                                    "invalid value. This should never "
                                    "happen, so please contact the IDAES "
                                    "developers with this bug."
                                    .format(blk.name))
                        slicer = (block.properties[:, _idx]
                                  .component(member_list[s].local_name))
                elif isinstance(block, StateBlock):
                    slicer = block[:].component(member_list[s].local_name)
                else:
                    raise ConfigurationError(
                            "{} block provided to add_inlet_port "
                            "method was not an instance of a "
                            "ControlVolume or a StateBlock."
                            .format(blk.name))
            else:
                # Need to use slice notation on indexed comenent as well
                if isinstance(block, ControlVolumeBlockData):
                    try:
                        slicer = block.properties_out[:].component(
                                        member_list[s].local_name)[...]
                    except AttributeError:
                        if block._flow_direction == FlowDirection.forward:
                            _idx = block.length_domain.last()
                        elif block._flow_direction == FlowDirection.backward:
                            _idx = block.length_domain.first()
                        else:
                            raise BurntToast(
                                    "{} flow_direction argument received "
                                    "invalid value. This should never "
                                    "happen, so please contact the IDAES "
                                    "developers with this bug."
                                    .format(blk.name))
                        slicer = (block.properties[:, _idx].component(
                                    member_list[s].local_name))[...]
                elif isinstance(block, StateBlock):
                    slicer = block[:].component(member_list[s].local_name)[...]
                else:
                    raise ConfigurationError(
                            "{} block provided to add_inlet_port "
                            "method was not an instance of a "
                            "ControlVolume or a StateBlock."
                            .format(blk.name))

            r = Reference(slicer)
            setattr(blk, "_"+s+"_"+name+"_ref", r)

            # Add Reference to Port
            p.add(r, s)

        return p
Beispiel #26
0
    def calculate_scaling_factors(self):
        super().calculate_scaling_factors()

        # scaling factors for parameters
        if iscale.get_scaling_factor(self.params.cp) is None:
            iscale.set_scaling_factor(self.params.cp, 1e-3)

        # these variables should have user input
        if iscale.get_scaling_factor(self.flow_mass_phase_comp['Liq',
                                                               'H2O']) is None:
            sf = iscale.get_scaling_factor(self.flow_mass_phase_comp['Liq',
                                                                     'H2O'],
                                           default=1,
                                           warning=True)
            iscale.set_scaling_factor(self.flow_mass_phase_comp['Liq', 'H2O'],
                                      sf)

        if iscale.get_scaling_factor(self.flow_mass_phase_comp['Liq',
                                                               'TSS']) is None:
            sf = iscale.get_scaling_factor(self.flow_mass_phase_comp['Liq',
                                                                     'TSS'],
                                           default=1e2,
                                           warning=True)
            iscale.set_scaling_factor(self.flow_mass_phase_comp['Liq', 'TSS'],
                                      sf)

        if iscale.get_scaling_factor(self.flow_mass_phase_comp['Liq',
                                                               'TDS']) is None:
            sf = iscale.get_scaling_factor(self.flow_mass_phase_comp['Liq',
                                                                     'TDS'],
                                           default=1e2,
                                           warning=True)
            iscale.set_scaling_factor(self.flow_mass_phase_comp['Liq', 'TDS'],
                                      sf)

        if iscale.get_scaling_factor(
                self.flow_mass_phase_comp['Liq', 'Sludge']) is None:
            sf = iscale.get_scaling_factor(self.flow_mass_phase_comp['Liq',
                                                                     'Sludge'],
                                           default=1e3,
                                           warning=True)
            iscale.set_scaling_factor(
                self.flow_mass_phase_comp['Liq', 'Sludge'], sf)

        # these variables do not typically require user input,
        # will not override if the user does provide the scaling factor

        if self.is_property_constructed('mass_frac_phase_comp'):
            #Apply variable scaling
            for pair in self.seawater_mass_frac_dict.keys():
                if iscale.get_scaling_factor(
                        self.mass_frac_phase_comp[pair]) is None:
                    if pair[1] == 'H2O':
                        iscale.set_scaling_factor(
                            self.mass_frac_phase_comp[pair], 1)
                    else:
                        sf = (iscale.get_scaling_factor(
                            self.flow_mass_phase_comp[pair]) /
                              iscale.get_scaling_factor(
                                  self.flow_mass_phase_comp['Liq', 'H2O']))
                        iscale.set_scaling_factor(
                            self.mass_frac_phase_comp[pair], sf)

            #Apply constraint scaling
            for pair in self.seawater_mass_frac_dict.keys():
                sf = iscale.get_scaling_factor(self.mass_frac_phase_comp[pair],
                                               default=1,
                                               warning=True)
                iscale.constraint_scaling_transform(
                    self.eq_mass_frac_phase_comp[pair], sf)

        if self.is_property_constructed('dens_mass_phase'):
            if iscale.get_scaling_factor(self.dens_mass_phase) is None:
                iscale.set_scaling_factor(self.dens_mass_phase, 1e-3)

            # transforming constraints
            sf = iscale.get_scaling_factor(self.dens_mass_phase)
            iscale.constraint_scaling_transform(self.eq_dens_mass_phase['Liq'],
                                                sf)

        if self.is_property_constructed('flow_vol_phase'):
            sf = (iscale.get_scaling_factor(self.flow_mass_phase_comp['Liq',
                                                                      'H2O']) /
                  iscale.get_scaling_factor(self.dens_mass_phase['Liq']))
            iscale.set_scaling_factor(self.flow_vol_phase['Liq'], sf)
            # transforming constraints
            iscale.constraint_scaling_transform(self.eq_flow_vol_phase['Liq'],
                                                sf)

        if self.is_property_constructed('conc_mass_phase_comp'):
            #Apply variable scaling
            for pair in self.seawater_mass_frac_dict.keys():
                if iscale.get_scaling_factor(
                        self.conc_mass_phase_comp[pair]) is None:
                    if pair[0] == 'Liq':
                        sf = (iscale.get_scaling_factor(
                            self.mass_frac_phase_comp[pair]) *
                              iscale.get_scaling_factor(
                                  self.dens_mass_phase['Liq']))
                        iscale.set_scaling_factor(
                            self.conc_mass_phase_comp[pair], sf)
                    else:
                        raise PropertyPackageError(
                            "Unsupported phase for CoagulationParameterData property package"
                        )

            #Apply constraint scaling
            for pair in self.seawater_mass_frac_dict.keys():
                sf = iscale.get_scaling_factor(self.conc_mass_phase_comp[pair],
                                               default=1,
                                               warning=True)
                iscale.constraint_scaling_transform(
                    self.eq_conc_mass_phase_comp[pair], sf)

        if self.is_property_constructed('enth_flow'):
            if iscale.get_scaling_factor(self.enth_flow) is None:
                sf = (iscale.get_scaling_factor(self.params.cp) *
                      iscale.get_scaling_factor(
                          self.flow_mass_phase_comp['Liq', 'H2O']) * 1e-1)
                iscale.set_scaling_factor(self.enth_flow, sf)