##############################################################################
# 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 Gibbs reactor model.
"""
from __future__ import division
# Import Python libraries
import logging
# Import Pyomo libraries
from pyomo.environ import log, Reals, Var
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__ = "Jinliang Ma, Andrew Lee"
# Set up logger
logger = logging.getLogger('idaes.unit_model')
[docs]@declare_process_block_class("GibbsReactor")
class GibbsReactorData(UnitBlockData):
"""
Standard Gibbs Reactor Unit Model Class
This model assume all possible reactions reach equilibrium such that the
system partial molar Gibbs free energy is minimized.
Since some species mole flow rate might be very small,
the natural log of the species molar flow rate is used.
Instead of specifying the system Gibbs free energy as an objective
function, the equations for zero partial derivatives of the grand function
with Lagrangian multiple terms with repect to product species mole flow
rates and the multiples are specified as constraints.
"""
CONFIG = CONFIG_Base()
# TODO: Does a dynamic equilibrium reactor make sense at all?
# Set dynamic flag, as model is only steady-state
CONFIG.dynamic = False
CONFIG.get('dynamic')._default = False
CONFIG.get('dynamic')._domain = In([False])
# Set default values of inherited attributes
CONFIG.get("material_balance_type")._default = 'element_total'
CONFIG.get("has_equilibrium_reactions")._default = True
CONFIG.get("has_phase_equilibrium")._default = True
CONFIG.get("has_heat_transfer")._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"""))
[docs] def build(self):
"""
Begin building model (pre-DAE transformation).
Args:
None
Returns:
None
"""
# Call UnitModel.build to setup dynamics
super(GibbsReactorData, self).build()
# Build Holdup Block
self.holdup = Holdup0D()
# 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
"""
# 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 _make_performance(self):
"""
Define constraints which describe the behaviour of the unit model.
Args:
None
Returns:
None
"""
add_object_ref(self, "component_list", self.holdup.component_list)
add_object_ref(self, "element_list", self.holdup.element_list)
# Add Lagrangian multiplier variables
self.lagrange_mult = Var(self.time,
self.element_list,
domain=Reals,
initialize=100,
doc="Lagrangian multipliers")
# TODO: Need to add support for multiple phases
# Use Lagrangian multiple method to derive equations for Out_Fi
# Use RT*lagrange as the Lagrangian multiple such that lagrange is in
# a similar order of magnitude as log(Yi)
@self.Constraint(self.time,
self.component_list,
doc="Gibbs energy minimisation constraint")
def gibbs_minimization(b, t, j):
# Use natural log of species mole flow to avoid Pyomo solver
# warnings of reaching infeasible point
return 0 == (b.holdup.properties_out[t].gibbs_pc[j] +
b.holdup.properties_out[t].gas_const *
b.holdup.properties_out[t].temperature *
(log(b.holdup.properties_out[t].mole_frac[j]) +
sum(b.lagrange_mult[t, e] *
b.holdup.properties_out[t].element_comp[j][e]
for e in b.element_list)))
# 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)