##############################################################################
# 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".
##############################################################################
"""
Basic IDAES plug flow reactor model.
"""
from __future__ import division
# Import Python libraries
import logging
# Import Pyomo libraries
from pyomo.environ import Constraint, Var
from pyomo.common.config import ConfigValue, In
# Import IDAES cores
from idaes.core import UnitBlockData, declare_process_block_class, \
Holdup1D, CONFIG_Base_1D
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("PFR")
class PFRData(UnitBlockData):
"""
Compressor/Expander Unit Class
"""
CONFIG = CONFIG_Base_1D()
# Set default values of inherited attributes
CONFIG.get("has_rate_reactions")._default = True
CONFIG.get("has_equilibrium_reactions")._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 indication 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(
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 indication 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("discretization_method", ConfigValue(
default='OCLR',
domain=In(['OCLR', 'OCLL', 'BFD', 'FFD']),
description="Discretization method to apply to length domain",
doc="""Method to be used by DAE transformation when discretizing length
domain (default = `OCLR`).
- 'OCLR' - orthogonal collocation (Radau roots)
- 'OCLL' - orthogonal collocation (Legendre roots)
- 'BFD' - backwards finite difference (1st order)
- 'FFD' - forwards finite difference (1st order)"""))
CONFIG.declare("finite_elements", ConfigValue(
default=20,
domain=int,
description="Number of finite elements length domain",
doc="""Number of finite elements to use when discretizing length
domain (default=20)"""))
CONFIG.declare("collocation_points", ConfigValue(
default=3,
domain=int,
description="Number of collocation points per finite element",
doc="""Number of collocation points to use per finite element when
discretizing length domain (default=3)"""))
CONFIG.declare("has_mass_diffusion", ConfigValue(
default=False,
domain=In([True, False]),
description="Mass diffusion flag",
doc="""Flag indicating whether mass diffusion/dispersion should be
included in material balance equations (default=False)"""))
CONFIG.declare("has_energy_diffusion", ConfigValue(
default=False,
domain=In([True, False]),
description="Energy diffusion flag",
doc="""Flag indicating whether energy diffusion/dispersion should be
included in energy balance equations (default=False)"""))
[docs] def build(self):
"""
Begin building model (pre-DAE transformation).
Args:
None
Returns:
None
"""
# Call UnitModel.build to setup dynamics
super(PFRData, self).build()
# Build Holdup Block
self.holdup = Holdup1D(
discretization_method=self.config.discretization_method,
finite_elements=self.config.finite_elements,
collocation_points=self.config.collocation_points,
has_mass_diffusion=self.config.has_mass_diffusion,
has_energy_diffusion=self.config.has_energy_diffusion)
# Set Unit Geometry and holdup Volume
self._set_geometry()
# Construct performance equations
self._make_performance()
[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
self.build_outlets(outlets=self.config.outlet_list,
num_outlets=self.config.num_outlets)
def _set_geometry(self):
"""
Define the geometry of the unit as necessary, and link to holdup volume
Args:
None
Returns:
None
"""
# Create references to all geometry variables
add_object_ref(self, "volume", self.holdup.volume)
add_object_ref(self, "area", self.holdup.area)
add_object_ref(self, "length", self.holdup.length)
# Create reference to spatial domain
add_object_ref(self, "ldomain", self.holdup.ldomain)
# Create reference to velocity if constructed
if self.config.velocity_type != 'none':
add_object_ref(self, "velocity", self.holdup.velocity)
# If property package has a velocity var, create linking constraint
if hasattr(self.holdup.properties[0, 1], "velocity"):
@self.Constraint(self.time,
self.ldomain,
doc="Link velocity to properties")
def link_velocity(b, t, x):
if hasattr(b.holdup.properties[t, x], "velocity"):
return (b.velocity[t, x] ==
b.holdup.properties[t, x].velocity)
else:
return Constraint.Skip
def _make_performance(self):
"""
Define constraints which describe the behaviour of the unit model.
Args:
None
Returns:
None
"""
# Add constraint relating extents to rate of reaction
if hasattr(self.holdup, "rate_reaction_idx"):
add_object_ref(self, "rate_reaction_idx",
self.holdup.rate_reaction_idx)
@self.Constraint(self.time,
self.ldomain,
self.rate_reaction_idx,
doc="Extents of reaction")
def rate_reaction_extents(b, t, x, k):
return b.holdup.rate_reaction_extent[t, x, k] == (
b.area*b.holdup.properties[t, x].reaction_rate[k])
# 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)