Carbonic Acid Example

STEPS

  1. Connect to database

  2. Create a base ‘thermo’ config for liquid only system with FpcTP state variables

  3. Add all components for carbonic acid problem to the base ‘thermo’ config

  4. Create a base ‘reaction’ config

  5. Find and add reactions to ‘reaction’ config based on the component list

  6. Build an IDAES model from the database generated configs

  7. Check the IDAES model for errors

1. Connect to database

[1]:
from watertap.edb import ElectrolyteDB
print("connecting to " + str(ElectrolyteDB.DEFAULT_URL))
db = ElectrolyteDB()
connecting to mongodb://localhost:27017

2. Create base ‘thermo’ config

Here, we grab the “thermo_Liq_FpcTP” base, which will likely be the most common for simple acid systems.

[2]:
thermo_base = db.get_base("thermo_Liq_FpcTP")

3. Add components to ‘thermo’ base

In this case, we know that our system is water + carbonic acid, which will produce the follow species.

[3]:
comp_list = ["H2O", "H_+", "OH_-", "H2CO3", "HCO3_-", "CO3_2-"]
comps = db.get_components(component_names=comp_list)
for comp_obj in comps:
    print("Adding " + str(comp_obj.name) + "" )
    thermo_base.add(comp_obj)
Adding CO3_2-
Adding H2CO3
Adding HCO3_-
Adding H_+
Adding OH_-
Adding H2O

4. Create base ‘reaction’ config

Unlike in the prior example, here we are going to place all reactions in a separate configuration dictionary and declare those reactions as equilibrium. This is likely the most common way to handle reactions in WaterTAP.

[4]:
react_base = db.get_base("reaction")

5. Find and add reactions to ‘reaction’ base

The reactions that should be found include ‘H2O_Kw’, ‘H2CO3_Ka1’, and ‘H2CO3_Ka2’. These are the deprotonation reactions of the acids in the system.

[5]:
react_obj = db.get_reactions(component_names=comp_list)
for r in react_obj:
    print("Found reaction: " + str(r.name))
    react_base.add(r)
Found reaction: H2CO3_Ka2
Found reaction: H2CO3_Ka1
Found reaction: H2O_Kw

6. Build an IDAES model

After we have grabbed all necessary information from the database, the formatted configuration dictionaries can be obtained from the ‘base’ objects we created in steps 2 & 4. The configurations are accessible via *_base.idaes_config. Passing those configuration dictionaries to the IDAES objects (GenericParameterBlock and GenericReactionParameterBlock) allows us to build the IDAES model. In this case, we build an EquilibriumReactor model from those property blocks.

[6]:
# Import specific pyomo objects
from pyomo.environ import (
    ConcreteModel,
)
# Import the idaes objects for Generic Properties and Reactions
from idaes.generic_models.properties.core.generic.generic_property import (
    GenericParameterBlock,
)
from idaes.generic_models.properties.core.generic.generic_reaction import (
    GenericReactionParameterBlock,
)

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

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

thermo_config = thermo_base.idaes_config
reaction_config = react_base.idaes_config

model = ConcreteModel()
model.fs = FlowsheetBlock(default={"dynamic": False})
model.fs.thermo_params = GenericParameterBlock(default=thermo_config)
model.fs.rxn_params = GenericReactionParameterBlock(
            default={"property_package": model.fs.thermo_params, **reaction_config}
        )

model.fs.unit = EquilibriumReactor(
        default={
            "property_package": model.fs.thermo_params,
            "reaction_package": model.fs.rxn_params,
            "has_rate_reactions": False,
            "has_equilibrium_reactions": True,
            "has_heat_transfer": False,
            "has_heat_of_reaction": False,
            "has_pressure_change": False,
        }
    )

7. Check IDAES model for errors

In this last step, we probe the created model to make sure everything is ok. We first check to make sure that the units of the model are consistent, then we can check the degrees of freedom. In this particular case, we expect 8 degrees of freedom.

The number of degrees of freedom will be problem dependent. In this case, our degrees stem from (1) pressure, (2) temperature, and (3-8) the individual species-phase pairs:

  1. (H2O , Liq)

  2. (H_+ , Liq)

  3. (OH_- , Liq)

  4. (H2CO3 , Liq)

  5. (HCO3_- , Liq)

  6. (CO3_2- , Liq)

[7]:
from pyomo.util.check_units import assert_units_consistent
from idaes.core.util.model_statistics import (
    degrees_of_freedom,
)

assert_units_consistent(model)
assert degrees_of_freedom(model) == 8