##############################################################################
# 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".
##############################################################################
"""
IDAES Translator Block for connecting between differnt property packages.
"""
from __future__ import division
# Import Python libraries
import logging
# Import Pyomo libraries
from pyomo.environ import SolverFactory
from pyomo.network import Port
from pyomo.common.config import ConfigValue, In
# Import IDAES cores
from idaes.core import UnitBlockData, declare_process_block_class
from idaes.core.util.config import is_parameter_block
__author__ = "Andrew Lee <andrew.lee@netl.doe.gov>"
__version__ = "1.0.0"
# Set up logger
logger = logging.getLogger('idaes.unit_model')
[docs]@declare_process_block_class("Translator")
class TranslatorData(UnitBlockData):
"""
This class constructs the basic framework for an IDAES block to "translate"
between differnt property packages. This class constructs two property
blocks using two different property packages - one for the incoming package
and one for the outgoing package - along with the assoicated inlet and
outlet Connector objects. Users will then need to provide a set of
Constraints to connect the states in the incoming properties to those in
the outgoing properties.
"""
CONFIG = UnitBlockData.CONFIG()
# Add translator block attributes
CONFIG.declare("inlet_property_package", ConfigValue(
default=None,
domain=is_parameter_block,
description="Property package for inlet side of block",
doc="""Inlet property parameter object to be converted"""))
CONFIG.declare("inlet_property_package_args", ConfigValue(
default={},
description="Arguments to use for constructing inlet property package",
doc="""A dict of arguments to be passed to the inlet PropertyBlockData
to be used for construction."""))
CONFIG.declare("outlet_property_package", ConfigValue(
default=None,
domain=is_parameter_block,
description="Property package for outlet side of block",
doc="""Outlet property parameter object to be converted"""))
CONFIG.declare("outlet_property_package_args", ConfigValue(
default={},
description="Arguments for constructing outlet property package",
doc="""A dict of arguments to be passed to the outlet PropertyBlockData
to be used for construction."""))
CONFIG.declare("has_equilibrium_reactions", ConfigValue(
default=False,
domain=In([True, False]),
description="Equilibrium reaction construction flag",
doc="""Indicates whether terms for equilibrium controlled reactions
should be constructed, **default** - 'use_parent_value'. **Valid values:** {
**'use_parent_value'** - get flag from parent (default = False),
**True** - include equilibrium reaction terms,
**False** - exclude equilibrium reaction terms.}"""))
CONFIG.declare("has_phase_equilibrium", ConfigValue(
default=False,
domain=In([True, False]),
description="Phase equilibrium construction flag",
doc="""Indicates whether terms for phase equilibrium should be
constructed (default = 'use_parent_value')
- 'use_parent_value' - get flag from parent (default = False)
- True - include phase equilibrium terms
- False - exclude phase equilibrium terms"""))
[docs] def build(self):
"""
Begin building model (pre-DAE transformation).
Args:
None
Returns:
None
"""
# Call UnitModel.build to setup dynamics
super(TranslatorData, self).build()
# Create Property Blocks
self._create_property_blocks()
[docs] def post_transform_build(self):
"""
Continue model construction after DAE transformation.
Args:
None
Returns:
None
"""
# Construct Inlets
def inlet_rule(b, t):
return b.inlet_properties[t].declare_port_members()
self.inlet = Port(self.time,
rule=inlet_rule,
doc="Inlet connector object")
# Build Outlets
def outlet_rule(b, t):
return b.outlet_properties[t].declare_port_members()
self.outlet = Port(self.time,
rule=outlet_rule,
doc="outlet connector object")
[docs] def model_check(blk):
"""
This is a general purpose model_check routine for translator blocks.
This method tries to call the model_check method of the inlet and
outlet property blocks. If an AttributeError is raised, the check is
passed.
Args:
None
Returns:
None
"""
try:
blk.inlet_properties.model_check()
except AttributeError:
pass
try:
blk.outlet_properties.model_check()
except AttributeError:
pass
[docs] def initialize(blk, outlvl=0,
solver='ipopt', optarg={'tol': 1e-6}):
'''
This is a basic initialization routine for translator blocks. This
method assumes both property blocks have good initial values and calls
the initialization method of each block.
Users will liekly need to overload this method with a customised
routine suited to their particular translation.
Keyword Arguments:
outlvl : sets output level of initialisation routine
* 0 = no output (default)
* 1 = return solver state for each step in routine
* 2 = return solver state for each step in subroutines
* 3 = include solver output infomation (tee=True)
optarg : solver options dictionary object (default={'tol': 1e-6})
solver : str indicating whcih solver to use during
initialization (default = 'ipopt')
Returns:
None
'''
opt = SolverFactory(solver)
opt.options = optarg
# ---------------------------------------------------------------------
# Initialize inlet properties
blk.inlet_properties.initialize(outlvl=outlvl-1,
optarg=optarg,
solver=solver)
if outlvl > 0:
logger.info('{} Initialisation Step 1 Complete.'.format(blk.name))
# ---------------------------------------------------------------------
# Initialize outlet properties
blk.outlet_properties.initialize(outlvl=outlvl-1,
optarg=optarg,
solver=solver)
if outlvl > 0:
logger.info('{} Initialisation Step 2 Complete.'.format(blk.name))
if outlvl > 0:
logger.info('{} Initialisation Complete.'.format(blk.name))
def _create_property_blocks(self):
"""
Create inlet and outlet property blocks for translation.
Args:
None
Returns:
None
"""
(self.config.inlet_property_package,
self.inlet_property_module,
self.config.inlet_property_package_args) = \
self._get_property_package(self.config.inlet_property_package,
self.config.inlet_property_package_args)
(self.config.outlet_property_package,
self.outlet_property_module,
self.config.outlet_property_package_args) = \
self._get_property_package(
self.config.outlet_property_package,
self.config.outlet_property_package_args)
self.inlet_properties = self.inlet_property_module.PropertyBlock(
self.time,
doc="Material properties at inlet",
has_sum_fractions=False,
calculate_equilibrium_reactions=
self.config.has_equilibrium_reactions,
calculate_phase_equilibrium=self.config.has_phase_equilibrium,
parameters=self.config.inlet_property_package,
**self.config.inlet_property_package_args)
self.outlet_properties = self.outlet_property_module.PropertyBlock(
self.time,
doc="Material properties at outlet",
has_sum_fractions=True,
calculate_equilibrium_reactions=
self.config.has_equilibrium_reactions,
calculate_phase_equilibrium=self.config.has_phase_equilibrium,
parameters=self.config.outlet_property_package,
**self.config.outlet_property_package_args)
def _get_property_package(self, package, package_args):
"""
This method gathers the necessary information about the property
package to be used in the holdup block.
If a property package has not been provided by the user, the method
searches up the model tree until it finds an object with the
'default_property_package' attribute and uses this package for the
holdup block.
The method also gathers any default construction arguments specified
for the property package and combines these with any arguments
specified by the user for the holdup block (user specified arguments
take priority over defaults).
Args:
package : reference to property parameter block from config block
package_args : unit level construction argumnets for property pack
Returns:
package : reference to property parameter block from config block
property_module : reference to the source module for package
arg_dict : combined dictionary of construction arguments from
unit and flowsheet level
"""
# Get property_package block if not provided in arguments
if package is None:
parent = self.parent_block()
while True:
if hasattr(parent.config, "default_property_package"):
break
else:
if parent.parent_block() is None:
raise AttributeError(
'{} no property package provided and '
'no default defined. Found end of '
'parent tree.'.format(self.name))
elif parent.parent_block() == parent:
raise ValueError(
'{} no property package provided and '
'no default defined. Found recursive '
'loop in parent tree.'.format(self.name))
parent = parent.parent_block()
logger.info('{} Using default property package'
.format(self.name))
if parent.config.default_property_package is None:
raise ValueError('{} no default property package has been '
'specified at flowsheet level ('
'default_property_package = None)'
.format(self.name))
package = parent.config.default_property_package
# Check for any flowsheet level build arguments
try:
# If flowsheet arguments exist, merge with local arguments
# Local arguments take precedence
arg_dict = package.config.default_arguments
arg_dict.update(package_args)
except AttributeError:
# Otherwise, just use local arguments
arg_dict = package_args
return package, package.property_module, arg_dict