##############################################################################
# Institute for the Design of Advanced Energy Systems Process Systems
# Engineering Framework (IDAES PSE Framework) Copyright (c) 2018-2020, by the
# software owners: The Regents of the University of California, through
# Lawrence Berkeley National Laboratory, National Technology & Engineering
# Solutions of Sandia, LLC, Carnegie Mellon University, West Virginia
# University Research Corporation, et al. All rights reserved.
#
# Please see the files COPYRIGHT.txt and LICENSE.txt for full copyright and
# license information, respectively. Both files are also available online
# at the URL "https://github.com/IDAES/idaes-pse".
##############################################################################
"""
IDAES Component objects
@author: alee
"""
from pyomo.environ import Set, Param, Var
from pyomo.common.config import ConfigBlock, ConfigValue
from .process_base import (declare_process_block_class,
ProcessBlockData)
from .phases import PhaseType as PT
from .util.config import list_of_phase_types
from .util.exceptions import ConfigurationError
@declare_process_block_class("Component")
class ComponentData(ProcessBlockData):
CONFIG = ConfigBlock()
CONFIG.declare("valid_phase_types", ConfigValue(
domain=list_of_phase_types,
doc="List of valid PhaseTypes (Enums) for this Component."))
CONFIG.declare("elemental_composition", ConfigValue(
domain=dict,
description="Elemental composition of component",
doc="Dict containing elemental composition in the form element "
": stoichiometry"))
CONFIG.declare("henry_component", ConfigValue(
domain=dict,
description="Phases in which component follows Henry's Law",
doc="Dict indicating phases in which component follows Herny's "
"Law (keys) with values indicating form of law."))
CONFIG.declare("dens_mol_liq_comp", ConfigValue(
description="Method to use to calculate liquid phase molar density"))
CONFIG.declare("enth_mol_liq_comp", ConfigValue(
description="Method to calculate liquid component molar enthalpies"))
CONFIG.declare("enth_mol_ig_comp", ConfigValue(
description="Method to calculate ideal gas component molar enthalpies"
))
CONFIG.declare("entr_mol_liq_comp", ConfigValue(
description="Method to calculate liquid component molar entropies"))
CONFIG.declare("entr_mol_ig_comp", ConfigValue(
description="Method to calculate ideal gas component molar entropies"))
CONFIG.declare("pressure_sat_comp", ConfigValue(
description="Method to use to calculate saturation pressure"))
CONFIG.declare("phase_equilibrium_form", ConfigValue(
domain=dict,
description="Form of phase equilibrium constraints for component"))
CONFIG.declare("parameter_data", ConfigValue(
default={},
domain=dict,
description="Dict containing initialization data for parameters"))
CONFIG.declare("_component_list_exists", ConfigValue(
default=False,
doc="Internal config argument indicating whether component_list "
"needs to be populated."))
def build(self):
super(ComponentData, self).build()
# If the component_list does not exist, add reference to new Component
# The IF is mostly for backwards compatability, to allow for old-style
# property packages where the component_list already exists but we
# need to add new Component objects
if not self.config._component_list_exists:
self.__add_to_component_list()
# Create Param for molecular weight if provided
if "mw" in self.config.parameter_data:
self.mw = Param(initialize=self.config.parameter_data["mw"])
# Create Vars for common parameters
for p in ["pressure_crit", "temperature_crit", "omega"]:
if p in self.config.parameter_data:
self.add_component(p, Var(
initialize=self.config.parameter_data[p]))
def is_solute(self):
raise TypeError(
"{} Generic Component objects do not support is_solute() method. "
"Use a Solvent or Solute Component instead."
.format(self.name))
def is_solvent(self):
raise TypeError(
"{} Generic Component objects do not support is_solvent() method. "
"Use a Solvent or Solute Component instead."
.format(self.name))
def __add_to_component_list(self):
"""
Method to add reference to new Component in component_list
"""
parent = self.parent_block()
try:
comp_list = getattr(parent, "component_list")
comp_list.add(self.local_name)
except AttributeError:
# Parent does not have a component_list yet, so create one
parent.component_list = Set(initialize=[self.local_name],
ordered=True)
def _is_phase_valid(self, phase):
# If no valid phases assigned, assume all are valid
if self.config.valid_phase_types is None:
return True
# Check for behaviour of phase, and see if that is a valid behaviour
# for component.
if (phase.is_liquid_phase() and
PT.liquidPhase in self.config.valid_phase_types):
return True
elif (phase.is_vapor_phase() and
PT.vaporPhase in self.config.valid_phase_types):
return True
elif (phase.is_solid_phase() and
PT.solidPhase in self.config.valid_phase_types):
return True
else:
return False
# TODO : What about LLE systems where a species is a solvent in one liquid
# phase, but a solute in another?
@declare_process_block_class("Solute")
class SoluteData(ComponentData):
"""
Component type for species which should be considered as solutes in
LiquidPhases.
"""
def is_solute(self):
return True
def is_solvent(self):
return False
# TODO : What about LLE systems where a species is a solvent in one liquid
# phase, but a solute in another?
@declare_process_block_class("Solvent")
class SolventData(ComponentData):
"""
Component type for species which should be considered as solvents in
LiquidPhases.
"""
def is_solute(self):
return False
def is_solvent(self):
return True
@declare_process_block_class("Ion")
class IonData(SoluteData):
"""
Component type for ionic species. These can exist only in LiquidPhases,
and are always solutes.
"""
CONFIG = SoluteData.CONFIG()
# Remove valid_phase_types argument, as ions are liquid phase only
CONFIG.__delitem__("valid_phase_types")
CONFIG.declare("charge", ConfigValue(
domain=int,
doc="Charge of ionic species."))
def _is_phase_valid(self, phase):
return phase.is_liquid_phase()
@declare_process_block_class("Anion")
class AnionData(IonData):
"""
Component type for anionic species. These can exist only in LiquidPhases,
and are always solutes.
"""
CONFIG = IonData.CONFIG()
def build(self):
super().build()
# Validate charge config argument
if self.config.charge is None:
raise ConfigurationError(
"{} was not provided with a value for charge."
.format(self.name))
elif self.config.charge >= 0:
raise ConfigurationError(
"{} received invalid value for charge configuration argument."
" Anions must have a negative charge.".format(self.name))
@declare_process_block_class("Cation")
class CationData(IonData):
"""
Component type for cationic species. These can exist only in LiquidPhases,
and are always solutes.
"""
CONFIG = IonData.CONFIG()
def build(self):
super().build()
# Validate charge config argument
if self.config.charge is None:
raise ConfigurationError(
"{} was not provided with a value for charge."
.format(self.name))
elif self.config.charge <= 0:
raise ConfigurationError(
"{} received invalid value for charge configuration argument."
" Cations must have a positive charge.".format(self.name))
__all_components__ = [Component, Solute, Solvent, Ion, Anion, Cation]