How to use inherent reactions

In How to setup simple chemistry, we showed an example of setting up a thermo_config and reaction_config and put all reactions in that reaction_config. However, it is possible to place reactions into the thermo_config itself using Inherent Reactions. This guide will perform the same walk through and setup from How to setup simple chemistry, but will place reactions into the thermo_config itself.

Inherent Reaction vs Other Reactions

The Inherent Reactions are reactions that will be common to all unit processes within a flowsheet. Thus, it is convenient to put those common reactions into the thermo_config as inherent. Then, the non-inherent reactions in the reaction_config will be unique to a specific unit process.

Note

It is possible to mix inherent reactions and non-inherent reactions within the same unit process. However, user’s need to take care and make sure that if a reaction is in the thermo_config as inherent, then it SHOULD NOT also show up in the reaction_config. This would create a degeneracy in the model.

Example thermo_config

In this example, we will define a simple thermo-properties configuration dictionary for a chemical system that contains only water. The water dissociation reaction will be declared as inherent and, thus, be a part of this configuration dictionary.

# Importing the object for units from pyomo
from pyomo.environ import units as pyunits

# Imports from idaes core
from idaes.core import AqueousPhase
from idaes.core.base.components import Solvent, Cation, Anion

# Imports from idaes generic models
import idaes.models.properties.modular_properties.pure.Perrys as Perrys
from idaes.models.properties.modular_properties.pure.ConstantProperties import Constant
from idaes.models.properties.modular_properties.state_definitions import FTPx
from idaes.models.properties.modular_properties.eos.ideal import Ideal

# Importing the object for units from pyomo
from pyomo.environ import units as pyunits

# Import the object/function for heat of reaction
from idaes.models.properties.modular_properties.reactions.dh_rxn import constant_dh_rxn

# Import built-in Gibb's Energy function
from idaes.models.properties.modular_properties.reactions.equilibrium_constant import van_t_hoff

# Import safe log power law equation
from idaes.models.properties.modular_properties.reactions.equilibrium_forms import log_power_law_equil

# Importing the enum for concentration unit basis used in the 'get_concentration_term' function
from idaes.models.properties.modular_properties.base.generic_reaction import ConcentrationForm

# Configuration dictionary
thermo_config = {
    "components": {
        'H2O': {"type": Solvent,
              # Define the methods used to calculate the following properties
              "dens_mol_liq_comp": Perrys,
              "enth_mol_liq_comp": Perrys,
              "cp_mol_liq_comp": Perrys,
              "entr_mol_liq_comp": Perrys,
              # Parameter data is always associated with the methods defined above
              "parameter_data": {
                    "mw": (18.0153, pyunits.g/pyunits.mol),
                    # Parameters here come from Perry's Handbook:  p. 2-98
                    "dens_mol_liq_comp_coeff": {
                        'eqn_type': 1,
                        '1': (5.459, pyunits.kmol*pyunits.m**-3),
                        '2': (0.30542, pyunits.dimensionless),
                        '3': (647.13, pyunits.K),
                        '4': (0.081, pyunits.dimensionless)},
                    "enth_mol_form_liq_comp_ref": (-285.830, pyunits.kJ/pyunits.mol),
                    "enth_mol_form_vap_comp_ref": (0, pyunits.kJ/pyunits.mol),
                    # Parameters here come Perry's Handbook:  p. 2-174
                    "cp_mol_liq_comp_coeff": {
                        '1': (2.7637E5, pyunits.J/pyunits.kmol/pyunits.K),
                        '2': (-2.0901E3, pyunits.J/pyunits.kmol/pyunits.K**2),
                        '3': (8.125, pyunits.J/pyunits.kmol/pyunits.K**3),
                        '4': (-1.4116E-2, pyunits.J/pyunits.kmol/pyunits.K**4),
                        '5': (9.3701E-6, pyunits.J/pyunits.kmol/pyunits.K**5)},
                    "cp_mol_ig_comp_coeff": {
                        'A': (30.09200, pyunits.J/pyunits.mol/pyunits.K),
                        'B': (6.832514, pyunits.J*pyunits.mol**-1*pyunits.K**-1*pyunits.kiloK**-1),
                        'C': (6.793435, pyunits.J*pyunits.mol**-1*pyunits.K**-1*pyunits.kiloK**-2),
                        'D': (-2.534480, pyunits.J*pyunits.mol**-1*pyunits.K**-1*pyunits.kiloK**-3),
                        'E': (0.082139, pyunits.J*pyunits.mol**-1*pyunits.K**-1*pyunits.kiloK**2),
                        'F': (-250.8810, pyunits.kJ/pyunits.mol),
                        'G': (223.3967, pyunits.J/pyunits.mol/pyunits.K),
                        'H': (0, pyunits.kJ/pyunits.mol)},
                    "entr_mol_form_liq_comp_ref": (69.95, pyunits.J/pyunits.K/pyunits.mol)
                    # End parameter_data
                    }},
        'H_+': {"type": Cation, "charge": 1,
              # Define the methods used to calculate the following properties
              "dens_mol_liq_comp": Constant,
              "enth_mol_liq_comp": Constant,
              "cp_mol_liq_comp": Constant,
              "entr_mol_liq_comp": Constant,
              # Parameter data is always associated with the methods defined above
              "parameter_data": {
                    "mw": (1.00784, pyunits.g/pyunits.mol),
                    "dens_mol_liq_comp_coeff": (55, pyunits.kmol*pyunits.m**-3),
                    "enth_mol_form_liq_comp_ref": (0, pyunits.kJ/pyunits.mol),
                    "cp_mol_liq_comp_coeff": (75000, pyunits.J/pyunits.kmol/pyunits.K),
                    "entr_mol_form_liq_comp_ref": (0, pyunits.J/pyunits.K/pyunits.mol)
                                },
                    # End parameter_data
                    },
        'OH_-': {"type": Anion, "charge": -1,
              # Define the methods used to calculate the following properties
              "dens_mol_liq_comp": Constant,
              "enth_mol_liq_comp": Constant,
              "cp_mol_liq_comp": Constant,
              "entr_mol_liq_comp": Constant,
              # Parameter data is always associated with the methods defined above
              "parameter_data": {
                    "mw": (17.008, pyunits.g/pyunits.mol),
                    "dens_mol_liq_comp_coeff": (55, pyunits.kmol*pyunits.m**-3),
                    "enth_mol_form_liq_comp_ref": (-230.000, pyunits.kJ/pyunits.mol),
                    "cp_mol_liq_comp_coeff": (75000, pyunits.J/pyunits.kmol/pyunits.K),
                    "entr_mol_form_liq_comp_ref": (-10.75, pyunits.J/pyunits.K/pyunits.mol)
                                },
                    # End parameter_data
                    }
              },
              # End Component list

        "phases":  {'Liq': {"type": AqueousPhase,
                            "equation_of_state": Ideal},
                    },

        "state_definition": FTPx,

        # This is an optional dictionary to setup bounds on
        #   the state variables. Names below MUST correspond
        #   to the 'FTPx' type state definition
        "state_bounds": {"flow_mol": (0, 50, 100),
                         "temperature": (273.15, 300, 650),
                         "pressure": (5e4, 1e5, 1e6)
                     },

        # These are generally optional parameters, however, because we
        #   are using the Perry's model to calculate temperature dependent
        #   properties, we MUST provide these here.
        "pressure_ref": 1e5,
        "temperature_ref": 300,

        # Our dictionary for base units MUST define the following
        "base_units": {"time": pyunits.s,
                       "length": pyunits.m,
                       "mass": pyunits.kg,
                       "amount": pyunits.mol,
                       "temperature": pyunits.K},

         # Inherent reactions
         #    These are added just like any other equilibrium reaction
         #    would be defined in a reaction config
         "inherent_reactions": {
             "H2O_Kw": {
                     "stoichiometry": {("Liq", "H2O"): -1,
                                      ("Liq", "H_+"): 1,
                                      ("Liq", "OH_-"): 1},
                    "heat_of_reaction": constant_dh_rxn,
                    "equilibrium_constant": van_t_hoff,
                    "equilibrium_form": log_power_law_equil,
                    "concentration_form": ConcentrationForm.moleFraction,
                    "parameter_data": {
                        "dh_rxn_ref": (55.830, pyunits.J/pyunits.mol),
                        "k_eq_ref": (10**-14/55.2/55.2, pyunits.dimensionless),
                        "T_eq_ref": (298, pyunits.K),

                        # By default, reaction orders follow stoichiometry
                        #    manually set reaction order here to override
                        "reaction_order": {("Liq", "H2O"): 0,
                                         ("Liq", "H_+"): 1,
                                         ("Liq", "OH_-"): 1}
                         }
                         # End parameter_data
                    }
              }
              # End inherent reactions
    }
    # End thermo_config definition

For a detailed analysis of everything from above, see How to setup simple chemistry.

Note

Even if your system only involves inherent reactions, you may still be required to provide a reaction configuration dictionary to construct certain unit models (such as EquilibriumReactor). However, your reaction configuration may be a blank or a dummy config (see below)

Example of a dummy reaction_config

# Importing the object for units from pyomo
from pyomo.environ import units as pyunits

# Import safe log power law equation
from idaes.models.properties.modular_properties.reactions.equilibrium_forms import log_power_law_equil

# This config is REQUIRED to use EquilibriumReactor even if we have no equilibrium reactions
reaction_config = {
    "base_units": {"time": pyunits.s,
                   "length": pyunits.m,
                   "mass": pyunits.kg,
                   "amount": pyunits.mol,
                   "temperature": pyunits.K},
    "equilibrium_reactions": {
        "dummy": {
                "stoichiometry": {},
                "equilibrium_form": log_power_law_equil,
               }
               # End reaction
         }
         # End equilibrium_reactions
    }
    # End reaction_config definition

Example: Using our configuration dictionaries in an EquilibriumReactor

Recall, we had named our configuration dictionaries as thermo_config and reaction_config. We will reference those dictionary names in the example code below.

# Import specific pyomo objects
from pyomo.environ import ConcreteModel

# Import the core idaes objects for Flowsheets and types of balances
from idaes.core import FlowsheetBlock

# Import the idaes objects for Generic Properties and Reactions
from idaes.models.properties.modular_properties.base.generic_property import GenericParameterBlock
from idaes.models.properties.modular_properties.base.generic_reaction import GenericReactionParameterBlock

# Import the idaes object for the EquilibriumReactor unit model
from idaes.models.unit_models.equilibrium_reactor import EquilibriumReactor

# Create an instance of a pyomo model
model = ConcreteModel()

# Add an IDAES flowsheet to that model
model.fs = FlowsheetBlock(dynamic=False)

# Add a thermo parameter block to that flowsheet
#   Here, we are passing our 'thermo_config' dictionary we created earlier
model.fs.thermo_params = GenericParameterBlock(**thermo_config)

# Add a reaction parameter block to that flowsheet
#   Here, we are passing our thermo block created above as the property package
#   and then giving our 'reaction_config' as the instructions for how the
#   reactions will be constructed from the thermo package.
model.fs.rxn_params = GenericReactionParameterBlock(property_package=model.fs.thermo_params, **reaction_config)

# Add an EquilibriumReactor object as the unit model
#   Here, we pass both the thermo package and reaction package, as well
#   as a number of other arguments to help define how this unit process
#   will behave.
#
# NOTE: What is different here is now we state that there are no
#       equilibrium reactions in this unit model because we defined
#       those reactions as inherent.
model.fs.unit = EquilibriumReactor(property_package=model.fs.thermo_params,
                                   reaction_package=model.fs.rxn_params,
                                   has_rate_reactions=False,
                                   has_equilibrium_reactions=False,
                                   has_heat_transfer=False,
                                   has_heat_of_reaction=False,
                                   has_pressure_change=False)

# At this point, you can 'fix' your inlet/outlet state conditions,
#     setup scaling factors, initialize the model, then solve the model
#     just as you would with any other IDAES flowsheet