{ "cells": [ { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "# Chemical equilibrium with constraints\n", "\n", "
Written by Allan Leal (ETH Zurich) on Jan 6th, 2022
\n", "\n", "```{attention}\n", "Always make sure you are using the [latest version of Reaktoro](https://anaconda.org/conda-forge/reaktoro). Otherwise, some new features documented on this website will not work on your machine and you may receive unintuitive errors. Follow these [update instructions](updating_reaktoro_via_conda) to get the latest version of Reaktoro!\n", "```\n", "\n", "As we mentioned in the previous section, Reaktoro can be used for a wide range of chemical equilibrium problems. In this section, we show:\n", "\n", "* the fundamentals of specifying different equilibrium constraints when calculating chemical equilibrium; and\n", "* how the equilibrium solver can be configured to allow the system to be open to one or more substances.\n", "\n", "We learned that a chemical equilibrium solver can be created with the class {{EquilibriumSolver}}, as shown in the example below:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "from reaktoro import *\n", "\n", "db = NasaDatabase(\"nasa-cea\")\n", "\n", "gases = GaseousPhase(\"CH4 O2 CO2 CO H2O H2\")\n", "\n", "system = ChemicalSystem(db, gases)\n", "\n", "solver = EquilibriumSolver(system)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "{{EquilibriumSolver}} objects created this way (i.e., as in `solver = EquilibriumSolver(system)`), are limited to chemical equilibrium calculations where:\n", "\n", "* temperature and pressure are given;\n", "* the system is closed (no mass transfer in or out of the system).\n", "\n", "Thus, `solver = EquilibriumSolver(system)` creates a **Gibbs energy minimization solver**. \n", "\n", "This is not always the desired behavior, however. If we want to impose the temperature and volume of the system, we will need to construct a specialized {{EquilibriumSolver}} object that can handle these constraint specifications.\n", "\n", "```{note}\n", "Chemical equilibrium problems in which temperature and volume are prescribed belong to the class of *Helmholtz energy minimization problems* {cite}`Smith1982`. Reaktoro, however, does not implement a Helmholtz energy minimization solver. Instead, Reaktoro implements a single **parametric Gibbs energy minimization solver that accepts general equilibrium constraints**, which can be configured to efficiently solve all other classes of equilibrium problems (including Helmholtz energy minimization problems!).\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Specifying constrained properties\n", "\n", "Let's create an equilibrium solver that can solve problems with prescribed **temperature** and **volume**:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "specs = EquilibriumSpecs(system)\n", "specs.temperature()\n", "specs.volume()\n", "\n", "solver = EquilibriumSolver(specs)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "That's it! We use {{EquilibriumSpecs}} to provide the constraint specifications for our {{EquilibriumSolver}}.\n", "\n", "```{tip}\n", "Access the link {{EquilibriumSpecs}} to find out all currently existing constraints you can impose! It is even **possible to define your own constraints**, instead of predefined ones, which we'll cover in a dedicated tutorial given this is a more advanced use case. \n", "```\n", "\n", "It's now time to demonstrate how we use this specialized equilibrium solver. We want to model the combustion of 1 mol of CH{{_4}} and 0.5 mol of O{{_2}} in a chamber of 10 cm³ at 1000 °C. We create below an initial chemical state with this gas composition:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "INITIAL STATE\n", "+-----------------+------------+------+\n", "| Property | Value | Unit |\n", "+-----------------+------------+------+\n", "| Temperature | 298.1500 | K |\n", "| Pressure | 1.0000 | bar |\n", "| Charge: | 0.0000e+00 | mol |\n", "| Element Amount: | | |\n", "| :: H | 4.0000e+00 | mol |\n", "| :: C | 1.0000e+00 | mol |\n", "| :: O | 1.0000e+00 | mol |\n", "| Species Amount: | | |\n", "| :: CH4 | 1.0000e+00 | mol |\n", "| :: O2 | 5.0000e-01 | mol |\n", "| :: CO2 | 1.0000e-16 | mol |\n", "| :: CO | 1.0000e-16 | mol |\n", "| :: H2O | 1.0000e-16 | mol |\n", "| :: H2 | 1.0000e-16 | mol |\n", "+-----------------+------------+------+\n" ] } ], "source": [ "state = ChemicalState(system)\n", "state.set(\"CH4\", 1.0, \"mol\")\n", "state.set(\"O2\", 0.5, \"mol\")\n", "\n", "print(\"INITIAL STATE\")\n", "print(state)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This state is in disequilibrium. We use {{EquilibriumConditions}} below to specify the desired conditions of temperature and volume at the equilibrium state, and then equilibrate the `state` object:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "FINAL STATE\n", "+-----------------+------------+------+\n", "| Property | Value | Unit |\n", "+-----------------+------------+------+\n", "| Temperature | 1273.1500 | K |\n", "| Pressure | 16603.2449 | bar |\n", "| Charge: | 0.0000e+00 | mol |\n", "| Element Amount: | | |\n", "| :: H | 4.0000e+00 | mol |\n", "| :: C | 1.0000e+00 | mol |\n", "| :: O | 1.0000e+00 | mol |\n", "| Species Amount: | | |\n", "| :: CH4 | 7.1576e-01 | mol |\n", "| :: O2 | 1.0000e-16 | mol |\n", "| :: CO2 | 2.2509e-01 | mol |\n", "| :: CO | 5.9148e-02 | mol |\n", "| :: H2O | 4.9067e-01 | mol |\n", "| :: H2 | 7.7814e-02 | mol |\n", "+-----------------+------------+------+\n" ] } ], "source": [ "conditions = EquilibriumConditions(specs)\n", "conditions.temperature(1000.0, \"celsius\")\n", "conditions.volume(10.0, \"cm3\")\n", "\n", "solver.solve(state, conditions)\n", "\n", "print(\"FINAL STATE\")\n", "print(state)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This printed state does not show volume. Let's check the volume in the {{ChemicalProps}} object attached by {{EquilibriumSolver}} to the {{ChemicalState}} object `state` at the end of the computation. Let's also print the computed pressure:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Volume at equilibrium state is 10 cm³!\n", "Pressure at equilibrium state is 1.66032 GPa!\n" ] } ], "source": [ "V = state.props().volume() # volume in m³\n", "P = state.pressure() # pressure in Pa\n", "\n", "V = units.convert(V, \"m3\", \"cm3\") # convert volume from m³ to cm³\n", "P = units.convert(P, \"Pa\", \"GPa\") # convert pressure from Pa to GPa\n", "\n", "print(\"Volume at equilibrium state is\", V, \"cm³!\")\n", "print(\"Pressure at equilibrium state is\", P, \"GPa!\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that the volume at the computed equilibrium state is exactly what we enforced in the {{EquilibriumConditions}} object: 10 cm³." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```{attention}\n", "Don't forget to set the expected conditions in the {{EquilibriumConditions}} object. For every condition marked to be known in the {{EquilibriumSpecs}} object (e.g., `specs.temperature()` and `specs.volume()`), make sure you give a value for each of them in the associated {{EquilibriumConditions}} object (e.g., `conditions.temperature(1000.0, \"celsius\")` and `conditions.volume(10.0, \"cm3\")`)! Not doing this can cause unexpected behavior or a runtime error.\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```{admonition} Did you know?\n", "The Gibbs energy minimization computation performed with:\n", "\n", "~~~py\n", "solver = EquilibriumSolver(system)\n", "solver.solve(state)\n", "~~~\n", "\n", "is executed in the background with the following code:\n", "\n", "~~~py\n", "specs = EquilibriumSpecs(system)\n", "specs.temperature()\n", "specs.pressure()\n", "\n", "conditions = EquilibriumConditions(specs)\n", "conditions.temperature(state.temperature())\n", "conditions.pressure(state.pressure())\n", "\n", "solver = EquilibriumSolver(specs)\n", "solver.solve(state, conditions)\n", "~~~\n", "\n", "so you can type a lot less if all you need is equilibrium calculations with specified temperatures and pressures! \n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Specifying open conditions\n", "\n", "Let's say we would like to find out how many mols of CH{{_4}} must react with 0.5 mol of O{{_2}} in the same chamber (with volume 10 cm³ and temperature 1000 °C) to obtain pressure 1 GPa (which, let's assume, is the maximum pressure supported by the chamber material).\n", "\n", "In this new equilibrium calculation, the system is open to the mass transfer of CH{{_4}}, and this creates a new *degree of freedom* in the problem: the amount added/removed of this substance. That's why we can simultaneously impose a value for temperature (1000 °C), volume (10 cm³), and pressure (1 GPa), which would be thermodynamically difficult otherwise.\n", "\n", "The code below starts by creating a chemical state in which there is only 0.5 mols of O{{_2}}:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "state = ChemicalState(system)\n", "state.set(\"O2\", 0.5, \"mol\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next, we create an {{EquilibriumSpecs}} object in which we specify that **temperature**, **volume**, and **pressure** are constrained properties. We also specify that the system is open to CH{{_4}}. This is all followed by the creation of a specialized and optimized {{EquilibriumSolver}} object to handle this kind of equilibrium calculations:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "\n", "specs = EquilibriumSpecs(system)\n", "specs.temperature()\n", "specs.volume()\n", "specs.pressure()\n", "specs.openTo(\"CH4\")\n", "\n", "solver = EquilibriumSolver(specs)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can now create the {{EquilibriumConditions}} object in which we provide the values for temperature, volume, and pressure, and then perform the calculation:" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "tags": [ "scroll-output" ] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "+----------------------------------------+--------------+-----------+\n", "| Property | Value | Unit |\n", "+----------------------------------------+--------------+-----------+\n", "| Temperature | 1273.1500 | K |\n", "| Pressure | 10000.0000 | bar |\n", "| Volume | 1.0000e-05 | m3 |\n", "| Gibbs Energy | -488420.1304 | J |\n", "| Enthalpy | -184792.5765 | J |\n", "| Entropy | 238.4853 | J/K |\n", "| Internal Energy | -194792.5765 | J |\n", "| Helmholtz Energy | -498420.1304 | J |\n", "| Charge | 0.0000e+00 | mol |\n", "| Element Amount: | | |\n", "| :: H | 1.5995e+00 | mol |\n", "| :: C | 3.9988e-01 | mol |\n", "| :: O | 1.0000e+00 | mol |\n", "| Species Amount: | | |\n", "| :: CH4 | 1.2748e-01 | mol |\n", "| :: O2 | 1.0000e-16 | mol |\n", "| :: CO2 | 2.3311e-01 | mol |\n", "| :: CO | 3.9293e-02 | mol |\n", "| :: H2O | 4.9449e-01 | mol |\n", "| :: H2 | 5.0306e-02 | mol |\n", "| Mole Fraction: | | |\n", "| :: CH4 | 1.3495e-01 | mol/mol |\n", "| :: O2 | 1.0586e-16 | mol/mol |\n", "| :: CO2 | 2.4676e-01 | mol/mol |\n", "| :: CO | 4.1594e-02 | mol/mol |\n", "| :: H2O | 5.2345e-01 | mol/mol |\n", "| :: H2 | 5.3252e-02 | mol/mol |\n", "| Activity Coefficient: | | |\n", "| :: CH4 | 1.0000 | - |\n", "| :: O2 | 1.0000 | - |\n", "| :: CO2 | 1.0000 | - |\n", "| :: CO | 1.0000 | - |\n", "| :: H2O | 1.0000 | - |\n", "| :: H2 | 1.0000 | - |\n", "| Activity: | | |\n", "| :: CH4 | 1.3495e+03 | - |\n", "| :: O2 | 1.0586e-12 | - |\n", "| :: CO2 | 2.4676e+03 | - |\n", "| :: CO | 4.1594e+02 | - |\n", "| :: H2O | 5.2345e+03 | - |\n", "| :: H2 | 5.3252e+02 | - |\n", "| lg(Activity): | | |\n", "| :: CH4 | 3.1302 | - |\n", "| :: O2 | -11.9753 | - |\n", "| :: CO2 | 3.3923 | - |\n", "| :: CO | 2.6190 | - |\n", "| :: H2O | 3.7189 | - |\n", "| :: H2 | 2.7263 | - |\n", "| ln(Activity): | | |\n", "| :: CH4 | 7.2075 | - |\n", "| :: O2 | -27.5741 | - |\n", "| :: CO2 | 7.8110 | - |\n", "| :: CO | 6.0305 | - |\n", "| :: H2O | 8.5630 | - |\n", "| :: H2 | 6.2776 | - |\n", "| Chemical Potential: | | |\n", "| :: CH4 | -2.7843e+05 | J/mol |\n", "| :: O2 | -5.8051e+05 | J/mol |\n", "| :: CO2 | -6.2217e+05 | J/mol |\n", "| :: CO | -3.2477e+05 | J/mol |\n", "| :: H2O | -4.2294e+05 | J/mol |\n", "| :: H2 | -1.2553e+05 | J/mol |\n", "| Standard Volume: | | |\n", "| :: CH4 | 0.0000e+00 | m3/mol |\n", "| :: O2 | 0.0000e+00 | m3/mol |\n", "| :: CO2 | 0.0000e+00 | m3/mol |\n", "| :: CO | 0.0000e+00 | m3/mol |\n", "| :: H2O | 0.0000e+00 | m3/mol |\n", "| :: H2 | 0.0000e+00 | m3/mol |\n", "| Standard Gibbs Energy (formation): | | |\n", "| :: CH4 | -354726.7026 | J/mol |\n", "| :: O2 | -288624.1836 | J/mol |\n", "| :: CO2 | -704857.1814 | J/mol |\n", "| :: CO | -388605.1648 | J/mol |\n", "| :: H2O | -513583.4128 | J/mol |\n", "| :: H2 | -191986.0517 | J/mol |\n", "| Standard Enthalpy (formation): | | |\n", "| :: CH4 | -14294.0889 | J/mol |\n", "| :: O2 | 32390.4240 | J/mol |\n", "| :: CO2 | -344886.6903 | J/mol |\n", "| :: CO | -79597.1200 | J/mol |\n", "| :: H2O | -204066.9932 | J/mol |\n", "| :: H2 | 29072.1299 | J/mol |\n", "| Standard Entropy (formation): | | |\n", "| :: CH4 | 267.3940 | J/(mol*K) |\n", "| :: O2 | 252.1420 | J/(mol*K) |\n", "| :: CO2 | 282.7400 | J/(mol*K) |\n", "| :: CO | 242.7114 | J/(mol*K) |\n", "| :: H2O | 243.1107 | J/(mol*K) |\n", "| :: H2 | 173.6309 | J/(mol*K) |\n", "| Standard Internal Energy (formation): | | |\n", "| :: CH4 | -14294.0889 | J/mol |\n", "| :: O2 | 32390.4240 | J/mol |\n", "| :: CO2 | -344886.6903 | J/mol |\n", "| :: CO | -79597.1200 | J/mol |\n", "| :: H2O | -204066.9932 | J/mol |\n", "| :: H2 | 29072.1299 | J/mol |\n", "| Standard Helmholtz Energy (formation): | | |\n", "| :: CH4 | -354726.7026 | J/mol |\n", "| :: O2 | -288624.1836 | J/mol |\n", "| :: CO2 | -704857.1814 | J/mol |\n", "| :: CO | -388605.1648 | J/mol |\n", "| :: H2O | -513583.4128 | J/mol |\n", "| :: H2 | -191986.0517 | J/mol |\n", "| Standard Heat Capacity (constant P): | | |\n", "| :: CH4 | 84.1523 | J/(mol*K) |\n", "| :: O2 | 35.9257 | J/(mol*K) |\n", "| :: CO2 | 56.9320 | J/(mol*K) |\n", "| :: CO | 34.4671 | J/(mol*K) |\n", "| :: H2O | 44.7425 | J/(mol*K) |\n", "| :: H2 | 31.3023 | J/(mol*K) |\n", "| Standard Heat Capacity (constant V): | | |\n", "| :: CH4 | 84.1523 | J/(mol*K) |\n", "| :: O2 | 35.9257 | J/(mol*K) |\n", "| :: CO2 | 56.9320 | J/(mol*K) |\n", "| :: CO | 34.4671 | J/(mol*K) |\n", "| :: H2O | 44.7425 | J/(mol*K) |\n", "| :: H2 | 31.3023 | J/(mol*K) |\n", "+----------------------------------------+--------------+-----------+\n" ] } ], "source": [ "\n", "conditions = EquilibriumConditions(specs)\n", "conditions.temperature(1000.0, \"celsius\")\n", "conditions.volume(10.0, \"cm3\")\n", "conditions.pressure(1.0, \"GPa\")\n", "\n", "solver.solve(state, conditions)\n", "\n", "print(state.props())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The above table shows that we obtained an equilibrium state with desired values for temperature, pressure, and volume (in SI units). We can also see below the amount of CH{{_4}} that entered the system so that all these prescribed conditions could be attained:" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Amount of CH4 titrated in: 0.399884 mol\n" ] } ], "source": [ "print(f\"Amount of CH4 titrated in: {state.equilibrium().titrantAmount('CH4')} mol\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Recap\n", "\n", "Reaktoro can solve chemical equilibrium problems with a variety of specifications. This guide demonstrated how this can be done for some relatively simple cases using classes:\n", "\n", "* {{EquilibriumSolver}}\n", "* {{EquilibriumSpecs}}\n", "* {{EquilibriumConditions}}\n", "\n", "We will later learn how these classes can be used to model more complicated equilibrium problems. " ] } ], "metadata": { "interpreter": { "hash": "e4e8b2f3ae27709963f14fd23a6560d362beea55eaec742263828e04d814e23c" }, "kernelspec": { "display_name": "Python 3.9.7 64-bit ('reaktoro-jupyter-book': conda)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.9" }, "orig_nbformat": 4 }, "nbformat": 4, "nbformat_minor": 2 }