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']))
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))
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()
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()
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()
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}))
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
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))
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
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]))
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))
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.")
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}))
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))
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)
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.")
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.")
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)
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))
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
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.")
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)
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))
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
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)