##############################################################################
# Institute for the Design of Advanced Energy Systems Process Systems
# Engineering Framework (IDAES PSE Framework) Copyright (c) 2018-2019, 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".
##############################################################################
"""
Example property package for the saponification of Ethyl Acetate with NaOH
Assumes dilute solutions with properties of H2O.
"""
# Chages the divide behavior to not do integer division
from __future__ import division
# Import Python libraries
import logging
# Import Pyomo libraries
from pyomo.environ import (Constraint,
exp,
Param,
PositiveReals,
Set,
value,
Var)
# Import IDAES cores
from idaes.core import (declare_process_block_class,
MaterialFlowBasis,
ReactionParameterBlock,
ReactionBlockDataBase,
ReactionBlockBase)
from idaes.core.util.misc import add_object_reference
# Some more inforation about this module
__author__ = "Andrew Lee"
# Set up logger
_log = logging.getLogger(__name__)
[docs]@declare_process_block_class("SaponificationReactionParameterBlock")
class ReactionParameterData(ReactionParameterBlock):
"""
Property Parameter Block Class
Contains parameters and indexing sets associated with properties for
superheated steam.
"""
[docs] def build(self):
'''
Callable method for Block construction.
'''
super(ReactionParameterData, self).build()
self.reaction_block_class = ReactionBlock
# List of valid phases in property package
self.phase_list = Set(initialize=['Liq'])
# Component list - a list of component identifiers
self.component_list = Set(initialize=['H2O', 'NaOH',
'EthylAcetate',
'SodiumAcetate',
'Ethanol'])
# Reaction Index
self.rate_reaction_idx = Set(initialize=["R1"])
# Reaction Stoichiometry
self.rate_reaction_stoichiometry = {("R1", "Liq", "NaOH"): -1,
("R1", "Liq", "EthylAcetate"): -1,
("R1", "Liq", "SodiumAcetate"): 1,
("R1", "Liq", "Ethanol"): 1,
("R1", "Liq", "H2O"): 0}
# Arrhenius Constant
self.arrhenius = Param(default=3.132e6,
doc="Arrhenius constant")
# Activation Energy
self.energy_activation = Param(default=43000,
doc="Activation energy [J/mol]")
# Heat of Reaction
dh_rxn_dict = {"R1": -49000}
self.dh_rxn = Param(self.rate_reaction_idx,
initialize=dh_rxn_dict,
doc="Heat of reaction [J/mol]")
# Gas Constant
self.gas_const = Param(within=PositiveReals,
mutable=False,
default=8.314,
doc='Gas Constant [J/mol.K]')
class _ReactionBlock(ReactionBlockBase):
"""
This Class contains methods which should be applied to Reaction Blocks as a
whole, rather than individual elements of indexed Reaction Blocks.
"""
def initialize(blk, outlvl=0, **kwargs):
'''
Initialisation routine for reaction package.
Keyword Arguments:
outlvl : sets output level of initialisation routine
* 0 = no output (default)
* 1 = report after each step
Returns:
None
'''
if outlvl > 0:
if outlvl > 0:
_log.info('{} Initialisation Complete.'.format(blk.name))
[docs]@declare_process_block_class("ReactionBlock",
block_class=_ReactionBlock)
class ReactionBlockData(ReactionBlockDataBase):
"""
An example reaction package for saponification of ethyl acetate
"""
[docs] def build(self):
"""
Callable method for Block construction
"""
super(ReactionBlockData, self).build()
# Create references to state vars
# Concentration
add_object_reference(self,
"conc_mol_comp_ref",
self.state_ref.conc_mol_comp)
# Temperature
add_object_reference(self,
"temperature_ref",
self.state_ref.temperature)
# Heat of reaction - no _ref as this is the actual property
add_object_reference(
self,
"dh_rxn",
self.config.parameters.dh_rxn)
# Rate constant method
def _rate_constant(self):
self.k_rxn = Var(initialize=1,
doc="Rate constant [m^3/mol.s]")
try:
self.arrhenius_eqn = Constraint(
expr=self.k_rxn == self._params.arrhenius *
exp(-self._params.energy_activation /
(self._params.gas_const*self.temperature_ref)))
except AttributeError:
# If constraint fails, clean up so that DAE can try again later
self.del_component(self.k_rxn)
self.del_component(self.arrhenius_eqn)
raise
# Rate of reaction method
def _rxn_rate(self):
self.reaction_rate = Var(self._params.rate_reaction_idx,
initialize=0,
doc="Rate of reaction [mol/m^3.s]")
try:
def rate_rule(b, r):
return b.reaction_rate[r] == (
b.k_rxn*b.conc_mol_comp_ref["EthylAcetate"] *
b.conc_mol_comp_ref["NaOH"])
self.rate_expression = Constraint(self._params.rate_reaction_idx,
rule=rate_rule)
except AttributeError:
# If constraint fails, clean up so that DAE can try again later
self.del_component(self.reaction_rate)
self.del_component(self.rate_expression)
raise
[docs] def get_reaction_rate_basis(b):
return MaterialFlowBasis.molar
[docs] def model_check(blk):
"""
Model checks for property block
"""
# Check temperature bounds
if value(blk.temperature) < blk.temperature.lb:
_log.error('{} Temperature set below lower bound.'
.format(blk.name))
if value(blk.temperature) > blk.temperature.ub:
_log.error('{} Temperature set above upper bound.'
.format(blk.name))
# Check pressure bounds
if value(blk.pressure) < blk.pressure.lb:
_log.error('{} Pressure set below lower bound.'.format(blk.name))
if value(blk.pressure) > blk.pressure.ub:
_log.error('{} Pressure set above upper bound.'.format(blk.name))