Source code for idaes.models.flash

##############################################################################
# Institute for the Design of Advanced Energy Systems Process Systems
# Engineering Framework (IDAES PSE Framework) Copyright (c) 2018, 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".
##############################################################################
"""
Standard IDAES flash model.
"""
from __future__ import division

# Import Python libraries
import logging

# Import Pyomo libraries
from pyomo.environ import Expression, Set
from pyomo.network import Port
from pyomo.common.config import ConfigValue, In

# Import IDAES cores
from idaes.core import UnitBlockData, declare_process_block_class, \
                        Holdup0D, CONFIG_Base
from idaes.core.util.config import is_parameter_block, list_of_strings
from idaes.core.util.misc import add_object_ref

__author__ = "Andrew Lee"


# Set up logger
logger = logging.getLogger('idaes.unit_model')


[docs]@declare_process_block_class("Flash") class FlashData(UnitBlockData): """ Standard Flash Unit Model Class """ CONFIG = CONFIG_Base() # Set default values of inherited attributes CONFIG.get("has_phase_equilibrium")._default = True CONFIG.get("has_heat_transfer")._default = True CONFIG.get("has_pressure_change")._default = True # Add unit model attributes CONFIG.declare("property_package", ConfigValue( default=None, domain=is_parameter_block, description="Property package to use for holdup", doc="""Property parameter object used to define property calculations (default = 'use_parent_value') - 'use_parent_value' - get package from parent (default = None) - a ParameterBlock object""")) CONFIG.declare("property_package_args", ConfigValue( default={}, description="Arguments to use for constructing property packages", doc="""A dict of arguments to be passed to the PropertyBlockData and used when constructing these (default = 'use_parent_value') - 'use_parent_value' - get package from parent (default = None) - a dict (see property package for documentation)""")) CONFIG.declare("inlet_list", ConfigValue( domain=list_of_strings, description="List of inlet names", doc="""A list containing names of inlets (default = None) - None - default single inlet - list - a list of names for inlets""")) CONFIG.declare("num_inlets", ConfigValue( domain=int, description="Number of inlets to unit", doc="""Argument indicating number (int) of inlets to construct (default = None). Not used if inlet_list arg is provided. - None - use inlet_list arg instead - int - Inlets will be named with sequential numbers from 1 to num_inlets""")) CONFIG.declare("outlet_list", ConfigValue( default=["Vap", "Liq"], domain=list_of_strings, description="List of outlet names", doc="""A list containing names of outlets (default = None) - None - default single outlet - list - a list of names for outlets""")) CONFIG.declare("num_outlets", ConfigValue( domain=int, description="Number of outlets to unit", doc="""Argument indicating number (int) of outlets to construct (default = None). Not used if outlet_list arg is provided. - None - use outlet_list arg instead - int - Outlets will be named with sequential numbers from 1 to num_outlets""")) CONFIG.declare("outlet_type", ConfigValue( default='generic', domain=In(['generic', 'direct']), description="Outlet splitter type", doc="""Argument indicating whether to use a generic OutletSplitter block (default) or a direct link to the properties_out block.""")) CONFIG.declare("energy_split_type", ConfigValue( default='temperature', domain=In(['temperature', 'enth_mol', 'enth_mass', 'energy_balance']), description="Energy splitting method", doc="""Argument indiciating method to use when splitting material flows between outlets, **default** - 'temperature'. **Valid values:** { **'temperature'** - equate temperatures in outlet streams, **'enth_mol'** - equate specific molar enthalpies in each outlet, **'enth_mass'** - equate specific mass enthalpies in each outlet, **'energy_balance'** - split using an energy balance with a split fraction.}"""))
[docs] def build(self): """ Begin building model (pre-DAE transformation). Args: None Returns: None """ # Call UnitModel.build to setup dynamics super(FlashData, self).build() # Build Holdup Block self.holdup = Holdup0D() # Set Unit Geometry and holdup Volume self._set_geometry()
[docs] def post_transform_build(self): """ Continue model construction after DAE transformation. Args: None Returns: None """ # Construct Inlets self.build_inlets(inlets=self.config.inlet_list, num_inlets=self.config.num_inlets) # Build Outlets if self.config.outlet_type == "direct": self._build_direct_outlet() else: self._build_generic_outlet() # Construct performance equations self._make_performance()
def _set_geometry(self): """ Define the geometry of the unit as necessary, and link to holdup volume Args: None Returns: None """ # For this case, just create a reference to holdup volume if self.config.include_holdup is True: add_object_ref(self, "volume", self.holdup.volume) def _build_generic_outlet(self): """ Build a generic OutletSplitter block Args: None Returns: None """ self.build_outlets(outlets=self.config.outlet_list, num_outlets=self.config.num_outlets, material_split_type="phase", energy_split_type=self.config.energy_split_type) def _build_direct_outlet(self): """ Build outlets with direct links to properties_out block. Args: None Returns: None """ def _state_error(self): # Method to log a warning and construct OutletSplitter if # state variables do not appear to be suitable for direct splitting logger.warning("{} property package does not appear to " "use a set of state variables suitable for direct " "outlet creation. An OutletSplitter " "block will be created instead. Please refer " "to the documentation for more details." .format(self.name)) self.config.outlet_type = 'generic' self._build_generic_outlet() return def _prop_error(self): # Method to log a warning and construct OutletSplitter if prop pack # does not support properties necessary for direct splitting logger.warning("{} property package does not support properties " "necessary for direct outlet creation. An " "OutletSplitter block will be created instead. " "Please refer to the documentation for more " "details.".format(self.name)) self.config.outlet_type = 'generic' self._build_generic_outlet() return # Get list of state variables and supported properties from prop pack svars = self.holdup.properties_out[0].declare_port_members() sprops = self.config.property_package.get_supported_properties() # Check that all state vars are recognised if not all(k in ["flow_mol", "flow_mol_phase", "flow_mol_comp", "flow_mol_phase_comp", "mole_frac", "mole_frac_phase", "flow_mass", "flow_mass_phase", "flow_mass_comp", "flow_mass_phase_comp", "mass_frac", "mass_frac_phase", "temperature", "enth_mol", "enth_mass", "enth_mol_phase", "enth_mass_phase", "pressure"] for k in svars): return _state_error(self) # Check that Property Package uses a recognised set of state vars and # supports the necessary property components pcount = 0 for b in ['mol', 'mass']: if (("flow_"+b in svars) and ((b == "mol" and "mole_frac" in svars) or ("mass_frac" in svars))): stype = ('', b) fb = 'mole' if b == 'mol' else 'mass' if not all(k in sprops for k in ("flow_"+b+"_phase", fb+"_frac_phase")): return _prop_error(self) break elif "flow_"+b+"_comp" in svars: stype = ('_comp', b) if "flow_"+b+"_phase_comp" not in sprops: return _prop_error(self) break elif "flow_"+b+"_phase_comp" in svars: stype = ('_phase_comp', b) if "flow_"+b+"_phase_comp" not in sprops: return _prop_error(self) break elif (("flow_"+b+"_phase" in svars) and ((b == "mol" and "mole_frac_phase" in svars) or ("mass_frac_phase" in svars))): stype = ('_phase', b) fb = 'mole' if b == 'mol' else 'mass' if not all(k in sprops for k in ("flow_"+b+"_phase", fb+"_frac_phase")): return _prop_error(self) break else: pcount = pcount+1 if pcount > 1: return _state_error(self) if 'enth_mol' in svars or 'enth_mol_phase' in svars: if 'enth_mol_phase' not in sprops: return _prop_error(self) if 'enth_mass' in svars or 'enth_mass_phase' in svars: if 'enth_mass_phase' not in sprops: return _prop_error(self) elif 'temperature' in svars: if 'temperature' not in sprops: return _prop_error(self) # Check outlet construction arguments and raise warning if not defaults if ((self.config.outlet_list != ['Vap', 'Liq']) and ( self.config.num_outlets is not None)): logger.warning("{} outlet_list and/or num_oulets arguments have " "been provided whilst using outlet_type = 'direct'." " When using direct outlets, only two outlets will " "be created, and other arguments will be ignored" .format(self.name)) self.config.outlet_list = ['Vap', 'Liq'] self.config.num_outlets = None # Create outlet object self.outlet_idx = Set(initialize=self.config.outlet_list) self.outlet = Port(self.time, self.outlet_idx, noruleinit=True, doc="Outlet port object") # Populate outlet object for t in self.time: if "pressure" in svars: self.outlet[t, "Vap"].add( self.holdup.properties_out[t].pressure, "pressure") self.outlet[t, "Liq"].add( self.holdup.properties_out[t].pressure, "pressure") if "temperature" in svars: self.outlet[t, "Vap"].add( self.holdup.properties_out[t].temperature, "temperature") self.outlet[t, "Liq"].add( self.holdup.properties_out[t].temperature, "temperature") if "enth_mol" in svars: self.outlet[t, "Vap"].add( self.holdup.properties_out[t].enth_mol_phase['Vap'], "enth_mol") self.outlet[t, "Liq"].add( self.holdup.properties_out[t].enth_mol_phase['Liq'], "enth_mol") if "enth_mass" in svars: self.outlet[t, "Vap"].add( self.holdup.properties_out[t].enth_mass_phase['Vap'], "enth_mass") self.outlet[t, "Liq"].add( self.holdup.properties_out[t].enth_mass_phase['Liq'], "enth_mass") if "enth_mol_phase" in svars: self.outlet[t, "Vap"].add( self.holdup.properties_out[t].enth_mol_phase, "enth_mol_phase") self.outlet[t, "Liq"].add( self.holdup.properties_out[t].enth_mol_phase, "enth_mol_phase") if "enth_mass_phase" in svars: self.outlet[t, "Vap"].add( self.holdup.properties_out[t].enth_mass_phase, "enth_mass_phase") self.outlet[t, "Liq"].add( self.holdup.properties_out[t].enth_mass_phase, "enth_mass_phase") # If using component based flows, add only a flow term if stype[0] in ["_phase_comp", "_comp"]: vobj = getattr(self.holdup.properties_out[t], "flow_"+stype[1]+"_phase_comp") # Create Expressions in Property Blocks to hold variables if stype[0] == "_comp": def rule_vap(b, j): return vobj["Vap", j] self.holdup.properties_out[t]._vapor_outlet_flow = ( Expression( self.holdup.properties_out[t].component_list, rule=rule_vap)) def rule_liq(b, j): return vobj["Liq", j] self.holdup.properties_out[t]._liquid_outlet_flow = ( Expression( self.holdup.properties_out[t].component_list, rule=rule_liq)) else: def rule_vap(b, p, j): if p == "Vap": return vobj["Vap", j] else: return 0 self.holdup.properties_out[t]._vapor_outlet_flow = ( Expression( self.holdup.properties_out[t].phase_list, self.holdup.properties_out[t].component_list, rule=rule_vap)) def rule_liq(b, p, j): if p == "Liq": return vobj["Liq", j] else: return 0 self.holdup.properties_out[t]._liquid_outlet_flow = ( Expression( self.holdup.properties_out[t].phase_list, self.holdup.properties_out[t].component_list, rule=rule_liq)) # Add Expressions to Port self.outlet[t, "Vap"].add( self.holdup.properties_out[t]._vapor_outlet_flow, "flow_"+stype[1]+stype[0]) self.outlet[t, "Liq"].add( self.holdup.properties_out[t]._liquid_outlet_flow, "flow_"+stype[1]+stype[0]) # Otherwise, need both flow and mass/mole fraction else: vobj = getattr(self.holdup.properties_out[t], "flow_"+stype[1]+"_phase") fb = 'mole' if stype[1] == 'mol' else 'mass' fobj = getattr(self.holdup.properties_out[t], fb+"_frac_phase") # Create Expressions in Property Blocks to hold variables if stype[0] == "_phase": def rule_vap_flow(b, p): if p == "Vap": return vobj["Vap"] else: return 0 self.holdup.properties_out[t]._vapor_outlet_flow = ( Expression( self.holdup.properties_out[t].phase_list, rule=rule_vap_flow)) def rule_liq_flow(b, p): if p == "Liq": return vobj["Liq"] else: return 0 self.holdup.properties_out[t]._liquid_outlet_flow = ( Expression( self.holdup.properties_out[t].phase_list, rule=rule_liq_flow)) def rule_vap_frac(b, p, j): if p == "Vap": return fobj["Vap", j] else: return 0 self.holdup.properties_out[t]._vapor_outlet_frac = ( Expression( self.holdup.properties_out[t].phase_list, self.holdup.properties_out[t].component_list, rule=rule_vap_frac)) def rule_liq_frac(b, p, j): if p == "Liq": return fobj["Liq", j] else: return 0 self.holdup.properties_out[t]._liquid_outlet_frac = ( Expression( self.holdup.properties_out[t].phase_list, self.holdup.properties_out[t].component_list, rule=rule_liq_frac)) else: def rule_vap_flow(b): return vobj["Vap"] self.holdup.properties_out[t]._vapor_outlet_flow = ( Expression(rule=rule_vap_flow)) def rule_liq_flow(b): return vobj["Liq"] self.holdup.properties_out[t]._liquid_outlet_flow = ( Expression(rule=rule_liq_flow)) def rule_vap_frac(b, j): return fobj["Vap", j] self.holdup.properties_out[t]._vapor_outlet_frac = ( Expression( self.holdup.properties_out[t].component_list, rule=rule_vap_frac)) def rule_liq_frac(b, j): return fobj["Liq", j] self.holdup.properties_out[t]._liquid_outlet_frac = ( Expression( self.holdup.properties_out[t].component_list, rule=rule_liq_frac)) # Add Expressions to Port self.outlet[t, "Vap"].add( self.holdup.properties_out[t]._vapor_outlet_flow, "flow_"+stype[1]+stype[0]) self.outlet[t, "Liq"].add( self.holdup.properties_out[t]._liquid_outlet_flow, "flow_"+stype[1]+stype[0]) self.outlet[t, "Vap"].add( self.holdup.properties_out[t]._vapor_outlet_frac, fb+"_frac"+stype[0]) self.outlet[t, "Liq"].add( self.holdup.properties_out[t]._liquid_outlet_frac, fb+"_frac"+stype[0]) def _make_performance(self): """ Define constraints which describe the behaviour of the unit model. Args: None Returns: None """ # Set split fractions ol = self.config.outlet_list if (ol is not None and "Vap" in ol and "Liq" in ol and len(ol) == 2 and self.config.outlet_type != 'direct'): for t in self.time: self.holdup.outlet_splitter.split_fraction[t, "Vap", "Vap"].fix(1.0) self.holdup.outlet_splitter.split_fraction[t, "Liq", "Liq"].fix(1.0) # Set references to balance terms at unit level if (self.config.has_heat_transfer is True and self.config.energy_balance_type != 'none'): add_object_ref(self, "heat", self.holdup.heat) if (self.config.has_pressure_change is True and self.config.momentum_balance_type != 'none'): add_object_ref(self, "deltaP", self.holdup.deltaP)