<?xml version="1.0" encoding="UTF-8"?>

<rss version="2.0"
     xmlns:content="http://purl.org/rss/1.0/modules/content/"
     xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
     xmlns:atom="http://www.w3.org/2005/Atom"
     xmlns:dc="http://purl.org/dc/elements/1.1/"
     xmlns:wfw="http://wellformedweb.org/CommentAPI/"
     >
  <channel>
    <atom:link href="http://kitchingroup.cheme.cmu.edu/blog/feed/index.xml" rel="self" type="application/rss+xml" />
    <title>The Kitchin Research Group</title>
    <link>https://kitchingroup.cheme.cmu.edu/blog</link>
    <description>Chemical Engineering at Carnegie Mellon University</description>
    <pubDate>Sat, 01 Nov 2025 13:47:46 GMT</pubDate>
    <generator>Blogofile</generator>
    <sy:updatePeriod>hourly</sy:updatePeriod>
    <sy:updateFrequency>1</sy:updateFrequency>
    
    <item>
      <title>Gibbs energy minimization and the NIST webbook</title>
      <link>https://kitchingroup.cheme.cmu.edu/blog/2013/03/01/Gibbs-energy-minimization-and-the-NIST-webbook</link>
      <pubDate>Fri, 01 Mar 2013 13:11:58 EST</pubDate>
      <category><![CDATA[optimization]]></category>
      <guid isPermaLink="false">2cPyJ1dhosp_-EMfiMiuxmC2KMM=</guid>
      <description>Gibbs energy minimization and the NIST webbook</description>
      <content:encoded><![CDATA[


&lt;p&gt;
&lt;a href="http://matlab.cheme.cmu.edu/2011/12/25/gibbs-energy-minimization-and-the-nist-webbook/" &gt;Matlab post&lt;/a&gt;
In Post 1536 we used the NIST webbook to compute a temperature dependent Gibbs energy of reaction, and then used a reaction extent variable to compute the equilibrium concentrations of each species for the water gas shift reaction.
&lt;/p&gt;

&lt;p&gt;
Today, we look at the direct minimization of the Gibbs free energy of the species, with no assumptions about stoichiometry of reactions. We only apply the constraint of conservation of atoms. We use the NIST Webbook to provide the data for the Gibbs energy of each species.
&lt;/p&gt;

&lt;p&gt;
As a reminder we consider equilibrium between the species \(CO\), \(H_2O\), \(CO_2\) and \(H_2\), at 1000K, and 10 atm total pressure with an initial equimolar molar flow rate of \(CO\) and \(H_2O\).
&lt;/p&gt;

&lt;div class="org-src-container"&gt;

&lt;pre class="src src-python"&gt;&lt;span style="color: #8b0000;"&gt;import&lt;/span&gt; numpy &lt;span style="color: #8b0000;"&gt;as&lt;/span&gt; np

T = 1000  &lt;span style="color: #ff0000; font-weight: bold;"&gt;# K&lt;/span&gt;
R = 8.314e-3 &lt;span style="color: #ff0000; font-weight: bold;"&gt;# kJ/mol/K&lt;/span&gt;

P = 10.0 &lt;span style="color: #ff0000; font-weight: bold;"&gt;# atm, this is the total pressure in the reactor&lt;/span&gt;
Po = 1.0 &lt;span style="color: #ff0000; font-weight: bold;"&gt;# atm, this is the standard state pressure&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
We are going to store all the data and calculations in vectors, so we need to assign each position in the vector to a species. Here are the definitions we use in this work.
&lt;/p&gt;

&lt;pre class="example"&gt;
1  CO
2  H2O
3  CO2
4  H2
&lt;/pre&gt;

&lt;div class="org-src-container"&gt;

&lt;pre class="src src-python"&gt;species = [&lt;span style="color: #228b22;"&gt;'CO'&lt;/span&gt;, &lt;span style="color: #228b22;"&gt;'H2O'&lt;/span&gt;, &lt;span style="color: #228b22;"&gt;'CO2'&lt;/span&gt;, &lt;span style="color: #228b22;"&gt;'H2'&lt;/span&gt;]

&lt;span style="color: #ff0000; font-weight: bold;"&gt;# Heats of formation at 298.15 K&lt;/span&gt;

Hf298 = [
    -110.53,  &lt;span style="color: #ff0000; font-weight: bold;"&gt;# CO&lt;/span&gt;
    -241.826, &lt;span style="color: #ff0000; font-weight: bold;"&gt;# H2O&lt;/span&gt;
    -393.51,  &lt;span style="color: #ff0000; font-weight: bold;"&gt;# CO2&lt;/span&gt;
       0.0]   &lt;span style="color: #ff0000; font-weight: bold;"&gt;# H2&lt;/span&gt;

&lt;span style="color: #ff0000; font-weight: bold;"&gt;# Shomate parameters for each species&lt;/span&gt;
&lt;span style="color: #ff0000; font-weight: bold;"&gt;#           A          B           C          D          E            F          G       H&lt;/span&gt;
WB = [[25.56759,  6.096130,     4.054656,  -2.671301,  0.131021, -118.0089, 227.3665,   -110.5271],  &lt;span style="color: #ff0000; font-weight: bold;"&gt;# CO&lt;/span&gt;
      [30.09200,  6.832514,     6.793435,  -2.534480,  0.082139, -250.8810, 223.3967,   -241.8264],  &lt;span style="color: #ff0000; font-weight: bold;"&gt;# H2O&lt;/span&gt;
      [24.99735,  55.18696,   -33.69137,    7.948387, -0.136638, -403.6075, 228.2431,   -393.5224],  &lt;span style="color: #ff0000; font-weight: bold;"&gt;# CO2&lt;/span&gt;
      [33.066178, -11.363417,  11.432816,  -2.772874, -0.158558, -9.980797, 172.707974,    0.0]]     &lt;span style="color: #ff0000; font-weight: bold;"&gt;# H2&lt;/span&gt;

WB = np.array(WB)

&lt;span style="color: #ff0000; font-weight: bold;"&gt;# Shomate equations&lt;/span&gt;
t = T/1000
T_H = np.array([t,  t**2 / 2.0, t**3 / 3.0, t**4 / 4.0, -1.0 / t, 1.0, 0.0, -1.0])
T_S = np.array([np.log(t), t,  t**2 / 2.0,  t**3 / 3.0, -1.0 / (2.0 * t**2), 0.0, 1.0, 0.0])

H = np.dot(WB, T_H)        &lt;span style="color: #ff0000; font-weight: bold;"&gt;# (H - H_298.15) kJ/mol&lt;/span&gt;
S = np.dot(WB, T_S/1000.0) &lt;span style="color: #ff0000; font-weight: bold;"&gt;# absolute entropy kJ/mol/K&lt;/span&gt;

Gjo = Hf298 + H - T*S      &lt;span style="color: #ff0000; font-weight: bold;"&gt;# Gibbs energy of each component at 1000 K&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
Now, construct the Gibbs free energy function, accounting for the change in activity due to concentration changes (ideal mixing).
&lt;/p&gt;
&lt;div class="org-src-container"&gt;

&lt;pre class="src src-python"&gt;&lt;span style="color: #8b0000;"&gt;def&lt;/span&gt; &lt;span style="color: #8b2323;"&gt;func&lt;/span&gt;(nj):
    nj = np.array(nj)
    Enj = np.sum(nj);
    Gj =  Gjo / (R * T) + np.log(nj / Enj * P / Po)
    &lt;span style="color: #8b0000;"&gt;return&lt;/span&gt; np.dot(nj, Gj)
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
We impose the constraint that all atoms are conserved from the initial conditions to the equilibrium distribution of species. These constraints are in the form of \(A_{eq} n = b_{eq}\), where \(n\) is the vector of mole numbers for each species.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;

&lt;pre class="src src-python"&gt;Aeq = np.array([[ 1,    0,    1,    0],  &lt;span style="color: #ff0000; font-weight: bold;"&gt;# C balance&lt;/span&gt;
                [ 1,    1,    2,    0],  &lt;span style="color: #ff0000; font-weight: bold;"&gt;# O balance&lt;/span&gt;
                [ 0,    2,    0,    2]]) &lt;span style="color: #ff0000; font-weight: bold;"&gt;# H balance&lt;/span&gt;

&lt;span style="color: #ff0000; font-weight: bold;"&gt;# equimolar feed of 1 mol H2O and 1 mol CO&lt;/span&gt;
beq = np.array([1,  &lt;span style="color: #ff0000; font-weight: bold;"&gt;# mol C fed&lt;/span&gt;
                2,  &lt;span style="color: #ff0000; font-weight: bold;"&gt;# mol O fed&lt;/span&gt;
                2]) &lt;span style="color: #ff0000; font-weight: bold;"&gt;# mol H fed&lt;/span&gt;

&lt;span style="color: #8b0000;"&gt;def&lt;/span&gt; &lt;span style="color: #8b2323;"&gt;ec1&lt;/span&gt;(nj):
    &lt;span style="color: #228b22;"&gt;'conservation of atoms constraint'&lt;/span&gt;
    &lt;span style="color: #8b0000;"&gt;return&lt;/span&gt; np.dot(Aeq, nj) - beq
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
Now we are ready to solve the problem. 
&lt;/p&gt;

&lt;div class="org-src-container"&gt;

&lt;pre class="src src-python"&gt;&lt;span style="color: #8b0000;"&gt;from&lt;/span&gt; scipy.optimize &lt;span style="color: #8b0000;"&gt;import&lt;/span&gt; fmin_slsqp

n0 = [0.5, 0.5, 0.5, 0.5]  &lt;span style="color: #ff0000; font-weight: bold;"&gt;# initial guesses&lt;/span&gt;
N = fmin_slsqp(func, n0, f_eqcons=ec1)
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; N
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
&amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; Optimization terminated successfully.    (Exit mode 0)
            Current function value: -91.204832308
            Iterations: 2
            Function evaluations: 13
            Gradient evaluations: 2
[ 0.45502309  0.45502309  0.54497691  0.54497691]
&lt;/pre&gt;

&lt;div id="outline-container-1" class="outline-2"&gt;
&lt;h2 id="sec-1"&gt;&lt;span class="section-number-2"&gt;1&lt;/span&gt; Compute mole fractions and partial pressures&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-1"&gt;
&lt;p&gt;
The pressures here are in good agreement with the pressures found by other methods. The minor disagreement (in the third or fourth decimal place) is likely due to convergence tolerances in the different algorithms used.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;

&lt;pre class="src src-python"&gt;yj = N / np.sum(N)
Pj = yj * P

&lt;span style="color: #8b0000;"&gt;for&lt;/span&gt; s, y, p &lt;span style="color: #8b0000;"&gt;in&lt;/span&gt; &lt;span style="color: #8b0000;"&gt;zip&lt;/span&gt;(species, yj, Pj):
    &lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; &lt;span style="color: #228b22;"&gt;'{0:10s}: {1:1.2f} {2:1.2f}'&lt;/span&gt;.format(s, y, p)
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
&amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; ... ... CO        : 0.23 2.28
H2O       : 0.23 2.28
CO2       : 0.27 2.72
H2        : 0.27 2.72
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id="outline-container-2" class="outline-2"&gt;
&lt;h2 id="sec-2"&gt;&lt;span class="section-number-2"&gt;2&lt;/span&gt; Computing equilibrium constants&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-2"&gt;
&lt;p&gt;
We can compute the equilibrium constant for the reaction \(CO + H_2O \rightleftharpoons CO_2 + H_2\). Compared to the value of K = 1.44 we found at the end of Post 1536 , the agreement is excellent. Note, that to define an equilibrium constant it is necessary to specify a reaction, even though it is not necessary to even consider a reaction to obtain the equilibrium distribution of species!
&lt;/p&gt;

&lt;div class="org-src-container"&gt;

&lt;pre class="src src-python"&gt;nuj = np.array([-1, -1, 1, 1])  &lt;span style="color: #ff0000; font-weight: bold;"&gt;# stoichiometric coefficients of the reaction&lt;/span&gt;
K = np.prod(yj**nuj)
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; K
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
&amp;gt;&amp;gt;&amp;gt; 1.43446295961
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Copyright (C) 2013 by John Kitchin. See the &lt;a href="/copying.html"&gt;License&lt;/a&gt; for information about copying.&lt;p&gt;&lt;p&gt;&lt;a href="/org/2013/03/01/Gibbs-energy-minimization-and-the-NIST-webbook.org"&gt;org-mode source&lt;/a&gt;&lt;p&gt;]]></content:encoded>
    </item>
    <item>
      <title>Finding equilibrium composition by direct minimization of Gibbs free energy on mole numbers</title>
      <link>https://kitchingroup.cheme.cmu.edu/blog/2013/03/01/Finding-equilibrium-composition-by-direct-minimization-of-Gibbs-free-energy-on-mole-numbers</link>
      <pubDate>Fri, 01 Mar 2013 12:27:48 EST</pubDate>
      <category><![CDATA[optimization]]></category>
      <guid isPermaLink="false">j0YUTyQ_GaQ-VsNlprCKzxr8qH8=</guid>
      <description>Finding equilibrium composition by direct minimization of Gibbs free energy on mole numbers</description>
      <content:encoded><![CDATA[


&lt;div id="table-of-contents"&gt;
&lt;h2&gt;Table of Contents&lt;/h2&gt;
&lt;div id="text-table-of-contents"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#sec-1"&gt;1. The Gibbs energy of a mixture&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#sec-2"&gt;2. Linear equality constraints for atomic mass conservation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#sec-3"&gt;3. Equilibrium constant based on mole numbers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#sec-4"&gt;4. Summary&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
&lt;a href="http://matlab.cheme.cmu.edu/2011/12/25/finding-equilibrium-composition-by-direct-minimization-of-gibbs-free-energy-on-mole-numbers/"&gt;Matlab post&lt;/a&gt; 
Adapted from problem 4.5 in Cutlip and Shacham
Ethane and steam are fed to a steam cracker at a total pressure of 1 atm and at 1000K at a ratio of 4 mol H2O to 1 mol ethane. Estimate the equilibrium distribution of products (CH4, C2H4, C2H2, CO2, CO, O2, H2, H2O, and C2H6).
&lt;/p&gt;

&lt;p&gt;
Solution method: We will construct a Gibbs energy function for the mixture, and obtain the equilibrium composition by minimization of the function subject to elemental mass balance constraints.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;

&lt;pre class="src src-python"&gt;&lt;span style="color: #8b0000;"&gt;import&lt;/span&gt; numpy &lt;span style="color: #8b0000;"&gt;as&lt;/span&gt; np

&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;R&lt;/span&gt; = &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;00198588&lt;/span&gt; &lt;span style="color: #ff0000; font-weight: bold;"&gt;# kcal/mol/K&lt;/span&gt;
&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;T&lt;/span&gt; = &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;1000&lt;/span&gt; &lt;span style="color: #ff0000; font-weight: bold;"&gt;# K&lt;/span&gt;

&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;species&lt;/span&gt; = [&lt;span style="color: #228b22;"&gt;'CH4'&lt;/span&gt;, &lt;span style="color: #228b22;"&gt;'C2H4'&lt;/span&gt;, &lt;span style="color: #228b22;"&gt;'C2H2'&lt;/span&gt;, &lt;span style="color: #228b22;"&gt;'CO2'&lt;/span&gt;, &lt;span style="color: #228b22;"&gt;'CO'&lt;/span&gt;, &lt;span style="color: #228b22;"&gt;'O2'&lt;/span&gt;, &lt;span style="color: #228b22;"&gt;'H2'&lt;/span&gt;, &lt;span style="color: #228b22;"&gt;'H2O'&lt;/span&gt;, &lt;span style="color: #228b22;"&gt;'C2H6'&lt;/span&gt;]

# &lt;span style="color: #ff0000; font-weight: bold;"&gt;$G_^\circ for each species. These are the heats of formation for each&lt;/span&gt;
# &lt;span style="color: #ff0000; font-weight: bold;"&gt;species.&lt;/span&gt;
&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;Gjo&lt;/span&gt; = np.array([&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;4&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;61&lt;/span&gt;, &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;28&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;249&lt;/span&gt;, &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;40&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;604&lt;/span&gt;, -&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;94&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;61&lt;/span&gt;, -&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;47&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;942&lt;/span&gt;, &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;, &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;, -&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;46&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;03&lt;/span&gt;, &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;26&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;13&lt;/span&gt;]) &lt;span style="color: #ff0000; font-weight: bold;"&gt;# kcal/mol&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;

&lt;div id="outline-container-sec-1" class="outline-2"&gt;
&lt;h2 id="sec-1"&gt;&lt;span class="section-number-2"&gt;1&lt;/span&gt; The Gibbs energy of a mixture&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-1"&gt;
&lt;p&gt;
We start with \(G=\sum\limits_j n_j \mu_j\). Recalling that we define \(\mu_j = G_j^\circ + RT \ln a_j\), and in the ideal gas limit, \(a_j = y_j P/P^\circ\), and that \(y_j = \frac{n_j}{\sum n_j}\). Since in this problem, P = 1 atm, this leads to the function \(\frac{G}{RT} = \sum\limits_{j=1}^n n_j\left(\frac{G_j^\circ}{RT} + \ln \frac{n_j}{\sum n_j}\right)\).
&lt;/p&gt;

&lt;div class="org-src-container"&gt;

&lt;pre class="src src-python"&gt;&lt;span style="color: #8b0000;"&gt;import&lt;/span&gt; numpy &lt;span style="color: #8b0000;"&gt;as&lt;/span&gt; np

&lt;span style="color: #8b0000;"&gt;def&lt;/span&gt; &lt;span style="color: #8b2323;"&gt;func&lt;/span&gt;(nj):
    &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;nj&lt;/span&gt; = np.array(nj)
    &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;Enj&lt;/span&gt; = np.sum(nj);
    &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;G&lt;/span&gt; = np.sum(nj * (Gjo / R / T + np.log(nj / Enj)))
    &lt;span style="color: #8b0000;"&gt;return&lt;/span&gt; G
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;div id="outline-container-sec-2" class="outline-2"&gt;
&lt;h2 id="sec-2"&gt;&lt;span class="section-number-2"&gt;2&lt;/span&gt; Linear equality constraints for atomic mass conservation&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-2"&gt;
&lt;p&gt;
The total number of each type of atom must be the same as what entered the reactor. These form equality constraints on the equilibrium composition. We express these constraints as: \(A_{eq} n = b\) where \(n\) is a vector of the moles of each species present in the mixture. CH4 C2H4 C2H2 CO2 CO O2 H2 H2O C2H6
&lt;/p&gt;

&lt;div class="org-src-container"&gt;

&lt;pre class="src src-python"&gt;&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;Aeq&lt;/span&gt; = np.array([[&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;,   &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;,    &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;,   &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;2&lt;/span&gt;,   &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;1&lt;/span&gt;,  &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;2&lt;/span&gt;,  &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;,  &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;1&lt;/span&gt;,   &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;],      &lt;span style="color: #ff0000; font-weight: bold;"&gt;# oxygen balance&lt;/span&gt;
                [&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;4&lt;/span&gt;,   &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;4&lt;/span&gt;,    &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;2&lt;/span&gt;,   &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;,   &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;,  &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;,  &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;2&lt;/span&gt;,  &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;2&lt;/span&gt;,   &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;6&lt;/span&gt;],      &lt;span style="color: #ff0000; font-weight: bold;"&gt;# hydrogen balance&lt;/span&gt;
                [&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;1&lt;/span&gt;,   &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;2&lt;/span&gt;,    &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;2&lt;/span&gt;,   &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;1&lt;/span&gt;,   &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;1&lt;/span&gt;,  &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;,  &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;,  &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;,   &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;2&lt;/span&gt;]])     &lt;span style="color: #ff0000; font-weight: bold;"&gt;# carbon balance&lt;/span&gt;

# &lt;span style="color: #ff0000; font-weight: bold;"&gt;the incoming feed was 4 mol H2O and 1 mol ethane&lt;/span&gt;
&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;beq&lt;/span&gt; = np.array([&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;4&lt;/span&gt;,  &lt;span style="color: #ff0000; font-weight: bold;"&gt;# moles of oxygen atoms coming in&lt;/span&gt;
                &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;14&lt;/span&gt;, &lt;span style="color: #ff0000; font-weight: bold;"&gt;# moles of hydrogen atoms coming in&lt;/span&gt;
                &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;2&lt;/span&gt;]) &lt;span style="color: #ff0000; font-weight: bold;"&gt;# moles of carbon atoms coming in&lt;/span&gt;

&lt;span style="color: #8b0000;"&gt;def&lt;/span&gt; &lt;span style="color: #8b2323;"&gt;ec1&lt;/span&gt;(n):
    &lt;span style="color: #228b22;"&gt;'equality constraint'&lt;/span&gt;
    &lt;span style="color: #8b0000;"&gt;return&lt;/span&gt; np.dot(Aeq, n) - beq

&lt;span style="color: #8b0000;"&gt;def&lt;/span&gt; &lt;span style="color: #8b2323;"&gt;ic1&lt;/span&gt;(n):
    &lt;span style="color: #228b22;"&gt;'''inequality constraint&lt;/span&gt;
&lt;span style="color: #228b22;"&gt;       all n&amp;gt;=0&lt;/span&gt;
&lt;span style="color: #228b22;"&gt;    '''&lt;/span&gt;   
    &lt;span style="color: #8b0000;"&gt;return&lt;/span&gt; n
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
Now we solve the problem.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;

&lt;pre class="src src-python"&gt;# &lt;span style="color: #ff0000; font-weight: bold;"&gt;initial guess suggested in the example&lt;/span&gt;
&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;n0&lt;/span&gt; = [1e-&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;3&lt;/span&gt;, 1e-&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;3&lt;/span&gt;, 1e-&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;3&lt;/span&gt;, &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;993&lt;/span&gt;, &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;1&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;, 1e-&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;4&lt;/span&gt;, &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;5&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;992&lt;/span&gt;, &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;1&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;, 1e-&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;3&lt;/span&gt;] 

&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;n0&lt;/span&gt; = [&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;066&lt;/span&gt;, &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;8&lt;/span&gt;.7e-&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;08&lt;/span&gt;, &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;2&lt;/span&gt;.1e-&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;14&lt;/span&gt;, &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;545&lt;/span&gt;, &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;1&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;39&lt;/span&gt;, &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;5&lt;/span&gt;.7e-&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;14&lt;/span&gt;, &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;5&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;346&lt;/span&gt;, &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;1&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;521&lt;/span&gt;, &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;1&lt;/span&gt;.58e-&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;7&lt;/span&gt;]

&lt;span style="color: #8b0000;"&gt;from&lt;/span&gt; scipy.optimize &lt;span style="color: #8b0000;"&gt;import&lt;/span&gt; fmin_slsqp

&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;X&lt;/span&gt; = fmin_slsqp(func, n0, &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;f_eqcons&lt;/span&gt;=ec1,&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;f_ieqcons&lt;/span&gt;=ic1,&lt;span style="color: #cd0000;"&gt; iter&lt;/span&gt;=&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;300&lt;/span&gt;, &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;acc&lt;/span&gt;=1e-&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;12&lt;/span&gt;)

&lt;span style="color: #8b0000;"&gt;for&lt;/span&gt; s,x &lt;span style="color: #8b0000;"&gt;in&lt;/span&gt;&lt;span style="color: #cd0000;"&gt; zip&lt;/span&gt;(species, X):
    &lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; &lt;span style="color: #228b22;"&gt;'{0:10s} {1:1.4g}'&lt;/span&gt;.format(s, x)

# &lt;span style="color: #ff0000; font-weight: bold;"&gt;check that constraints were met&lt;/span&gt;
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; np.dot(Aeq, X) - beq
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; np.all( np.abs( np.dot(Aeq, X) - beq) &amp;lt; 1e-&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;12&lt;/span&gt;)
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
&amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; Optimization terminated successfully.    (Exit mode 0)
            Current function value: -104.403951524
            Iterations: 16
            Function evaluations: 193
            Gradient evaluations: 15
&amp;gt;&amp;gt;&amp;gt; ... ... CH4        0.06644
C2H4       9.48e-08
C2H2       1.487e-13
CO2        0.545
CO         1.389
O2         3.096e-13
H2         5.346
H2O        1.521
C2H6       1.581e-07
... [  0.00000000e+00   0.00000000e+00   4.44089210e-16]
True
&lt;/pre&gt;

&lt;p&gt;
I found it necessary to tighten the accuracy parameter to get pretty good matches to the solutions found in Matlab. It was also necessary to increase the number of iterations. Even still, not all of the numbers match well, especially the very small numbers. You can, however, see that the constraints were satisfied pretty well.
&lt;/p&gt;


&lt;p&gt;
Interestingly there is a distribution of products! That is interesting because only steam and ethane enter the reactor, but a small fraction of methane is formed! The main product is hydrogen. The stoichiometry of steam reforming is ideally \(C_2H_6 + 4H_2O \rightarrow 2CO_2 + 7 H2\). Even though nearly all the ethane is consumed, we do not get the full yield of hydrogen. It appears that another equilibrium, one between CO, CO2, H2O and H2, may be limiting that, since the rest of the hydrogen is largely in the water. It is also of great importance that we have not said anything about reactions, i.e. how these products were formed. 
&lt;/p&gt;

&lt;p&gt;
The water gas shift reaction is: \(CO + H_2O \rightleftharpoons CO_2 + H_2\). We can compute the Gibbs free energy of the reaction from the heats of formation of each species. Assuming these are the formation energies at 1000K, this is the reaction free energy at 1000K.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;

&lt;pre class="src src-python"&gt;&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;G_wgs&lt;/span&gt; = Gjo[&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;3&lt;/span&gt;] + Gjo[&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;6&lt;/span&gt;] - Gjo[&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;4&lt;/span&gt;] - Gjo[&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;7&lt;/span&gt;]
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; G_wgs

&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;K&lt;/span&gt; = np.exp(-G_wgs / (R*T))
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; K
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
-0.638
&amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; 1.37887528109
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;div id="outline-container-sec-3" class="outline-2"&gt;
&lt;h2 id="sec-3"&gt;&lt;span class="section-number-2"&gt;3&lt;/span&gt; Equilibrium constant based on mole numbers&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-3"&gt;
&lt;p&gt;
One normally uses activities to define the equilibrium constant. Since there are the same number of moles on each side of the reaction all factors that convert mole numbers to activity, concentration or pressure cancel, so we simply consider the ratio of mole numbers here.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;

&lt;pre class="src src-python"&gt;&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; (X[&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;3&lt;/span&gt;] * X[&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;6&lt;/span&gt;]) / (X[&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;4&lt;/span&gt;] * X[&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;7&lt;/span&gt;])
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
1.37887525547
&lt;/pre&gt;

&lt;p&gt;
This is very close to the equilibrium constant computed above. 
&lt;/p&gt;

&lt;p&gt;
Clearly, there is an equilibrium between these species that prevents the complete reaction of steam reforming.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;div id="outline-container-sec-4" class="outline-2"&gt;
&lt;h2 id="sec-4"&gt;&lt;span class="section-number-2"&gt;4&lt;/span&gt; Summary&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-4"&gt;
&lt;p&gt;
This is an appealing way to minimize the Gibbs energy of a mixture. No assumptions about reactions are necessary, and the constraints are easy to identify. The Gibbs energy function is especially easy to code.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Copyright (C) 2014 by John Kitchin. See the &lt;a href="/copying.html"&gt;License&lt;/a&gt; for information about copying.&lt;p&gt;&lt;p&gt;&lt;a href="/org/2013/03/01/Finding-equilibrium-composition-by-direct-minimization-of-Gibbs-free-energy-on-mole-numbers.org"&gt;org-mode source&lt;/a&gt;&lt;p&gt;&lt;p&gt;Org-mode version = 8.2.7c&lt;/p&gt;]]></content:encoded>
    </item>
    <item>
      <title>Meet the steam tables</title>
      <link>https://kitchingroup.cheme.cmu.edu/blog/2013/02/28/Meet-the-steam-tables</link>
      <pubDate>Thu, 28 Feb 2013 22:09:29 EST</pubDate>
      <category><![CDATA[uncategorized]]></category>
      <guid isPermaLink="false">a9q2uEoIwVNXU6iFwbuJlBvHks4=</guid>
      <description>Meet the steam tables</description>
      <content:encoded><![CDATA[


&lt;div id="table-of-contents"&gt;
&lt;h2&gt;Table of Contents&lt;/h2&gt;
&lt;div id="text-table-of-contents"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#sec-1"&gt;1. Starting point in the Rankine cycle in condenser.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#sec-2"&gt;2. Isentropic compression of liquid to point 2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#sec-3"&gt;3. Isobaric heating to T3 in boiler where we make steam&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#sec-4"&gt;4. Isentropic expansion through turbine to point 4&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#sec-5"&gt;5. To get from point 4 to point 1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#sec-6"&gt;6. Efficiency&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#sec-7"&gt;7. Entropy-temperature chart&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#sec-8"&gt;8. Summary&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
&lt;a href="http://matlab.cheme.cmu.edu/2011/10/31/matlab-meets-the-steam-tables/"&gt;Matlab post&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;
We will use the &lt;a href="https://pypi.python.org/pypi/iapws"&gt;iapws&lt;/a&gt; module. Install it like this:
&lt;/p&gt;

&lt;div class="org-src-container"&gt;

&lt;pre class="src src-sh"&gt;pip install iapws
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
Problem statement: A Rankine cycle operates using steam with the condenser at 100 degC, a pressure of 3.0 MPa and temperature of 600 degC in the boiler. Assuming the compressor and turbine operate reversibly, estimate the efficiency of the cycle.
&lt;/p&gt;

&lt;p&gt;
Starting point in the Rankine cycle in condenser.
&lt;/p&gt;

&lt;p&gt;
we have saturated liquid here, and we get the thermodynamic properties for the given temperature. In this python module, these properties are all in attributes of an IAPWS object created at a set of conditions.
&lt;/p&gt;

&lt;div id="outline-container-sec-1" class="outline-2"&gt;
&lt;h2 id="sec-1"&gt;&lt;span class="section-number-2"&gt;1&lt;/span&gt; Starting point in the Rankine cycle in condenser.&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-1"&gt;
&lt;p&gt;
We have saturated liquid here, and we get the thermodynamic properties for the given temperature.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;

&lt;pre class="src src-python"&gt;&lt;span style="color: #8b0000;"&gt;from&lt;/span&gt; iapws &lt;span style="color: #8b0000;"&gt;import&lt;/span&gt; IAPWS97

T1 = 100 + 273.15 &lt;span style="color: #ff0000; font-weight: bold;"&gt;#in K&lt;/span&gt;

sat_liquid1  = IAPWS97(T=T1, x=0) &lt;span style="color: #ff0000; font-weight: bold;"&gt;# x is the steam quality. 0 = liquid&lt;/span&gt;

P1 = sat_liquid1.P
s1 = sat_liquid1.s
h1 = sat_liquid1.h
v1 = sat_liquid1.v
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id="outline-container-sec-2" class="outline-2"&gt;
&lt;h2 id="sec-2"&gt;&lt;span class="section-number-2"&gt;2&lt;/span&gt; Isentropic compression of liquid to point 2&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-2"&gt;
&lt;p&gt;
The final pressure is given, and we need to compute the new temperatures, and enthalpy.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;

&lt;pre class="src src-python"&gt;P2 = 3.0 &lt;span style="color: #ff0000; font-weight: bold;"&gt;# MPa&lt;/span&gt;
s2 = s1 &lt;span style="color: #ff0000; font-weight: bold;"&gt;# this is what isentropic means&lt;/span&gt;

sat_liquid2 = IAPWS97(P=P2, s=s1)
T2, = sat_liquid2.T
h2 = sat_liquid2.h

&lt;span style="color: #ff0000; font-weight: bold;"&gt;# work done to compress liquid. This is an approximation, since the&lt;/span&gt;
&lt;span style="color: #ff0000; font-weight: bold;"&gt;# volume does change a little with pressure, but the overall work here&lt;/span&gt;
&lt;span style="color: #ff0000; font-weight: bold;"&gt;# is pretty small so we neglect the volume change.&lt;/span&gt;
WdotP = v1*(P2 - P1);
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt;
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt;(&lt;span style="color: #228b22;"&gt;'The compressor work is: {0:1.4f} kJ/kg'&lt;/span&gt;.format(WdotP))
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
&amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; ... ... ... &amp;gt;&amp;gt;&amp;gt;
The compressor work is: 0.0030 kJ/kg
&lt;/pre&gt;

&lt;p&gt;
The compression work is almost negligible. This number is 1000 times smaller than we computed with Xsteam. I wonder what the units of v1 actually are.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id="outline-container-sec-3" class="outline-2"&gt;
&lt;h2 id="sec-3"&gt;&lt;span class="section-number-2"&gt;3&lt;/span&gt; Isobaric heating to T3 in boiler where we make steam&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-3"&gt;
&lt;div class="org-src-container"&gt;

&lt;pre class="src src-python"&gt;T3 = 600 + 273.15 &lt;span style="color: #ff0000; font-weight: bold;"&gt;# K&lt;/span&gt;
P3 = P2 &lt;span style="color: #ff0000; font-weight: bold;"&gt;# definition of isobaric&lt;/span&gt;
steam = IAPWS97(P=P3, T=T3)

h3 = steam.h
s3 = steam.s

Qb, = h3 - h2 &lt;span style="color: #ff0000; font-weight: bold;"&gt;# heat required to make the steam&lt;/span&gt;

&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt;
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; &lt;span style="color: #228b22;"&gt;'The boiler heat duty is: {0:1.2f} kJ/kg'&lt;/span&gt;.format(Qb)
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
&amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt;
The boiler heat duty is: 3260.69 kJ/kg
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id="outline-container-sec-4" class="outline-2"&gt;
&lt;h2 id="sec-4"&gt;&lt;span class="section-number-2"&gt;4&lt;/span&gt; Isentropic expansion through turbine to point 4&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-4"&gt;
&lt;div class="org-src-container"&gt;

&lt;pre class="src src-python"&gt;steam =  IAPWS97(P=P1, s=s3)
T4, = steam.T
h4 = steam.h
s4 = s3 &lt;span style="color: #ff0000; font-weight: bold;"&gt;# isentropic&lt;/span&gt;
Qc, = h4 - h1 &lt;span style="color: #ff0000; font-weight: bold;"&gt;# work required to cool from T4 to T1&lt;/span&gt;
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; 
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; &lt;span style="color: #228b22;"&gt;'The condenser heat duty is {0:1.2f} kJ/kg'&lt;/span&gt;.format(Qc)
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
&amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt;
The condenser heat duty is 2317.00 kJ/kg
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id="outline-container-sec-5" class="outline-2"&gt;
&lt;h2 id="sec-5"&gt;&lt;span class="section-number-2"&gt;5&lt;/span&gt; To get from point 4 to point 1&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-5"&gt;
&lt;div class="org-src-container"&gt;

&lt;pre class="src src-python"&gt;WdotTurbine, = h4 - h3 &lt;span style="color: #ff0000; font-weight: bold;"&gt;# work extracted from the expansion&lt;/span&gt;
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt;(&lt;span style="color: #228b22;"&gt;'The turbine work is: {0:1.2f} kJ/kg'&lt;/span&gt;.format(WdotTurbine))
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
The turbine work is: -946.71 kJ/kg
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id="outline-container-sec-6" class="outline-2"&gt;
&lt;h2 id="sec-6"&gt;&lt;span class="section-number-2"&gt;6&lt;/span&gt; Efficiency&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-6"&gt;
&lt;p&gt;
This is a ratio of the work put in to make the steam, and the net work obtained from the turbine. The answer here agrees with the efficiency calculated in Sandler on page 135.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;

&lt;pre class="src src-python"&gt;eta = -(WdotTurbine - WdotP) / Qb
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt;(&lt;span style="color: #228b22;"&gt;'The overall efficiency is {0:1.2%}.'&lt;/span&gt;.format(eta))
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
The overall efficiency is 29.03%.
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id="outline-container-sec-7" class="outline-2"&gt;
&lt;h2 id="sec-7"&gt;&lt;span class="section-number-2"&gt;7&lt;/span&gt; Entropy-temperature chart&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-7"&gt;
&lt;p&gt;
The IAPWS module makes it pretty easy to generate figures of the steam tables. Here we generate an entropy-Temperature graph. We do this to illustrate the path of the Rankine cycle. We need to compute the values of steam entropy for a range of pressures and temperatures.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;

&lt;pre class="src src-python"&gt;&lt;span style="color: #8b0000;"&gt;import&lt;/span&gt; numpy &lt;span style="color: #8b0000;"&gt;as&lt;/span&gt; np
&lt;span style="color: #8b0000;"&gt;import&lt;/span&gt; matplotlib.pyplot &lt;span style="color: #8b0000;"&gt;as&lt;/span&gt; plt

plt.figure()
plt.clf()
T = np.linspace(300, 372+273, 200) &lt;span style="color: #ff0000; font-weight: bold;"&gt;# range of temperatures&lt;/span&gt;
&lt;span style="color: #8b0000;"&gt;for&lt;/span&gt; P &lt;span style="color: #8b0000;"&gt;in&lt;/span&gt; [0.1, 1, 2, 5, 10, 20]: &lt;span style="color: #ff0000; font-weight: bold;"&gt;#MPa&lt;/span&gt;
    steam = [IAPWS97(T=t, P=P) &lt;span style="color: #8b0000;"&gt;for&lt;/span&gt; t &lt;span style="color: #8b0000;"&gt;in&lt;/span&gt; T]
    S = [s.s &lt;span style="color: #8b0000;"&gt;for&lt;/span&gt; s &lt;span style="color: #8b0000;"&gt;in&lt;/span&gt; steam]
    plt.plot(S, T, &lt;span style="color: #228b22;"&gt;'k-'&lt;/span&gt;)

&lt;span style="color: #ff0000; font-weight: bold;"&gt;# saturated vapor and liquid entropy lines&lt;/span&gt;
svap = [s.s &lt;span style="color: #8b0000;"&gt;for&lt;/span&gt; s &lt;span style="color: #8b0000;"&gt;in&lt;/span&gt; [IAPWS97(T=t, x=1) &lt;span style="color: #8b0000;"&gt;for&lt;/span&gt; t &lt;span style="color: #8b0000;"&gt;in&lt;/span&gt; T]]
sliq = [s.s &lt;span style="color: #8b0000;"&gt;for&lt;/span&gt; s &lt;span style="color: #8b0000;"&gt;in&lt;/span&gt; [IAPWS97(T=t, x=0) &lt;span style="color: #8b0000;"&gt;for&lt;/span&gt; t &lt;span style="color: #8b0000;"&gt;in&lt;/span&gt; T]]

plt.plot(svap, T, &lt;span style="color: #228b22;"&gt;'r-'&lt;/span&gt;)
plt.plot(sliq, T, &lt;span style="color: #228b22;"&gt;'b-'&lt;/span&gt;)

plt.xlabel(&lt;span style="color: #228b22;"&gt;'Entropy (kJ/(kg K)'&lt;/span&gt;)
plt.ylabel(&lt;span style="color: #228b22;"&gt;'Temperature (K)'&lt;/span&gt;)
plt.savefig(&lt;span style="color: #228b22;"&gt;'images/iawps-steam.png'&lt;/span&gt;)
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
&amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; &amp;lt;matplotlib.figure.Figure object at 0x000000000638BC18&amp;gt;
&amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; ... ... ... ... [&amp;lt;matplotlib.lines.Line2D object at 0x0000000007F9C208&amp;gt;]
[&amp;lt;matplotlib.lines.Line2D object at 0x0000000007F9C400&amp;gt;]
[&amp;lt;matplotlib.lines.Line2D object at 0x0000000007F9C8D0&amp;gt;]
[&amp;lt;matplotlib.lines.Line2D object at 0x0000000007F9CD30&amp;gt;]
[&amp;lt;matplotlib.lines.Line2D object at 0x0000000007F9E1D0&amp;gt;]
[&amp;lt;matplotlib.lines.Line2D object at 0x0000000007F9E630&amp;gt;]
... &amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; [&amp;lt;matplotlib.lines.Line2D object at 0x0000000001FDCEB8&amp;gt;]
[&amp;lt;matplotlib.lines.Line2D object at 0x0000000007F9EA90&amp;gt;]
&amp;gt;&amp;gt;&amp;gt; &amp;lt;matplotlib.text.Text object at 0x0000000007F7BE48&amp;gt;
&amp;lt;matplotlib.text.Text object at 0x0000000007F855F8&amp;gt;
&lt;/pre&gt;


&lt;p&gt;&lt;img src="/img/./images/iawps-steam.png"&gt;&lt;p&gt;


&lt;p&gt;
We can plot our Rankine cycle path like this. We compute the entropies along the non-isentropic paths.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;

&lt;pre class="src src-python"&gt;T23 = np.linspace(T2, T3)
S23 = [s.s &lt;span style="color: #8b0000;"&gt;for&lt;/span&gt; s &lt;span style="color: #8b0000;"&gt;in&lt;/span&gt; [IAPWS97(P=P2, T=t) &lt;span style="color: #8b0000;"&gt;for&lt;/span&gt; t &lt;span style="color: #8b0000;"&gt;in&lt;/span&gt; T23]]

T41 = np.linspace(T4, T1 - 0.01) &lt;span style="color: #ff0000; font-weight: bold;"&gt;# subtract a tiny bit to make sure we get a liquid&lt;/span&gt;
S41 = [s.s &lt;span style="color: #8b0000;"&gt;for&lt;/span&gt; s &lt;span style="color: #8b0000;"&gt;in&lt;/span&gt; [IAPWS97(P=P1, T=t) &lt;span style="color: #8b0000;"&gt;for&lt;/span&gt; t &lt;span style="color: #8b0000;"&gt;in&lt;/span&gt; T41]]
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
And then we plot the paths.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;

&lt;pre class="src src-python"&gt;plt.plot([s1, s2], [T1, T2], &lt;span style="color: #228b22;"&gt;'r-'&lt;/span&gt;, lw=4) &lt;span style="color: #ff0000; font-weight: bold;"&gt;# Path 1 to 2&lt;/span&gt;
plt.plot(S23, T23, &lt;span style="color: #228b22;"&gt;'b-'&lt;/span&gt;, lw=4) &lt;span style="color: #ff0000; font-weight: bold;"&gt;# path from 2 to 3 is isobaric&lt;/span&gt;
plt.plot([s3, s4], [T3, T4], &lt;span style="color: #228b22;"&gt;'g-'&lt;/span&gt;, lw=4) &lt;span style="color: #ff0000; font-weight: bold;"&gt;# path from 3 to 4 is isentropic&lt;/span&gt;
plt.plot(S41, T41, &lt;span style="color: #228b22;"&gt;'k-'&lt;/span&gt;, lw=4) &lt;span style="color: #ff0000; font-weight: bold;"&gt;# and from 4 to 1 is isobaric&lt;/span&gt;
plt.savefig(&lt;span style="color: #228b22;"&gt;'images/iawps-steam-2.png'&lt;/span&gt;)
plt.savefig(&lt;span style="color: #228b22;"&gt;'images/iawps-steam-2.svg'&lt;/span&gt;)
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
[&amp;lt;matplotlib.lines.Line2D object at 0x0000000008350908&amp;gt;]
[&amp;lt;matplotlib.lines.Line2D object at 0x00000000083358D0&amp;gt;]
[&amp;lt;matplotlib.lines.Line2D object at 0x000000000835BEB8&amp;gt;]
[&amp;lt;matplotlib.lines.Line2D object at 0x0000000008357160&amp;gt;]
&lt;/pre&gt;

&lt;p&gt;&lt;img src="/img/./images/iawps-steam-2.png"&gt;&lt;p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id="outline-container-sec-8" class="outline-2"&gt;
&lt;h2 id="sec-8"&gt;&lt;span class="section-number-2"&gt;8&lt;/span&gt; Summary&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-8"&gt;
&lt;p&gt;
This was an interesting exercise. On one hand, the tedium of interpolating the steam tables is gone. On the other hand, you still have to know exactly what to ask for to get an answer that is correct. The iapws interface is a little clunky, and takes some getting used to. It does not seem as robust as the Xsteam module I used in Matlab.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Copyright (C) 2013 by John Kitchin. See the &lt;a href="/copying.html"&gt;License&lt;/a&gt; for information about copying.&lt;p&gt;&lt;p&gt;&lt;a href="/org/2013/02/28/Meet-the-steam-tables.org"&gt;org-mode source&lt;/a&gt;&lt;p&gt;]]></content:encoded>
    </item>
    <item>
      <title>Reading parameter database text files in python</title>
      <link>https://kitchingroup.cheme.cmu.edu/blog/2013/02/27/Reading-parameter-database-text-files-in-python</link>
      <pubDate>Wed, 27 Feb 2013 10:52:22 EST</pubDate>
      <category><![CDATA[io]]></category>
      <guid isPermaLink="false">n66LCHdthWBW60A44kRyS5yJa3w=</guid>
      <description>Reading parameter database text files in python</description>
      <content:encoded><![CDATA[


&lt;p&gt;
&lt;a href="http://matlab.cheme.cmu.edu/2011/09/10/reading-parameter-database-text-files-in-matlab/" &gt;Matlab post&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;
The datafile at &lt;a href="http://terpconnect.umd.edu/~nsw/ench250/antoine.dat" &gt;http://terpconnect.umd.edu/~nsw/ench250/antoine.dat&lt;/a&gt; (dead link) contains data that can be used to estimate the vapor pressure of about 700 pure compounds using the Antoine equation
&lt;/p&gt;

&lt;p&gt;
The data file has the following contents:
&lt;/p&gt;

&lt;pre class="example"&gt;
Antoine Coefficients
  log(P) = A-B/(T+C) where P is in mmHg and T is in Celsius
Source of data: Yaws and Yang (Yaws, C.  L.  and Yang, H.  C.,
"To estimate vapor pressure easily. antoine coefficients relate vapor pressure to temperature for almost 700 major organic compounds", Hydrocarbon Processing, 68(10), p65-68, 1989.

ID  formula  compound name                  A       B       C     Tmin Tmax ??    ?
-----------------------------------------------------------------------------------
  1 CCL4     carbon-tetrachloride        6.89410 1219.580 227.170  -20  101 Y2    0
  2 CCL3F    trichlorofluoromethane      6.88430 1043.010 236.860  -33   27 Y2    0
  3 CCL2F2   dichlorodifluoromethane     6.68619  782.072 235.377 -119  -30 Y6    0
&lt;/pre&gt;

&lt;p&gt;
To use this data, you find the line that has the compound you want, and read off the data. You could do that manually for each component you want but that is tedious, and error prone. Today we will see how to retrieve the file, then read the data into python to create a database we can use to store and retrieve the data.
&lt;/p&gt;

&lt;p&gt;
We will use the data to find the temperature at which the vapor pressure of acetone is 400 mmHg. 
&lt;/p&gt;

&lt;p&gt;
We use numpy.loadtxt to read the file, and tell the function the format of each column. This creates a special kind of record array which we can access data by field name.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;

&lt;pre class="src src-python"&gt;&lt;span style="color: #8b0000;"&gt;import&lt;/span&gt; numpy &lt;span style="color: #8b0000;"&gt;as&lt;/span&gt; np
&lt;span style="color: #8b0000;"&gt;import&lt;/span&gt; matplotlib.pyplot &lt;span style="color: #8b0000;"&gt;as&lt;/span&gt; plt

data = np.loadtxt(&lt;span style="color: #228b22;"&gt;'data/antoine_data.dat'&lt;/span&gt;,
                  dtype=[(&lt;span style="color: #228b22;"&gt;'id'&lt;/span&gt;, np.int),
                         (&lt;span style="color: #228b22;"&gt;'formula'&lt;/span&gt;, &lt;span style="color: #228b22;"&gt;'S8'&lt;/span&gt;),
                         (&lt;span style="color: #228b22;"&gt;'name'&lt;/span&gt;, &lt;span style="color: #228b22;"&gt;'S28'&lt;/span&gt;),
                         (&lt;span style="color: #228b22;"&gt;'A'&lt;/span&gt;, np.float),
                         (&lt;span style="color: #228b22;"&gt;'B'&lt;/span&gt;, np.float),
                         (&lt;span style="color: #228b22;"&gt;'C'&lt;/span&gt;, np.float),
                         (&lt;span style="color: #228b22;"&gt;'Tmin'&lt;/span&gt;, np.float),
                         (&lt;span style="color: #228b22;"&gt;'Tmax'&lt;/span&gt;, np.float),
                         (&lt;span style="color: #228b22;"&gt;'??'&lt;/span&gt;, &lt;span style="color: #228b22;"&gt;'S4'&lt;/span&gt;),
                         (&lt;span style="color: #228b22;"&gt;'?'&lt;/span&gt;, &lt;span style="color: #228b22;"&gt;'S4'&lt;/span&gt;)],
                  skiprows=7)

names = data[&lt;span style="color: #228b22;"&gt;'name'&lt;/span&gt;]

acetone, = data[names == &lt;span style="color: #228b22;"&gt;'acetone'&lt;/span&gt;]

&lt;span style="color: #ff0000; font-weight: bold;"&gt;# for readability we unpack the array into variables&lt;/span&gt;
id, formula, name, A, B, C, Tmin, Tmax, u1, u2 = acetone

T = np.linspace(Tmin, Tmax)
P = 10**(A - B / ( T + C))
plt.plot(T, P)
plt.xlabel(&lt;span style="color: #228b22;"&gt;'T ($^\circ$C)'&lt;/span&gt;)
plt.ylabel(&lt;span style="color: #228b22;"&gt;'P$_{vap}$ (mmHg)'&lt;/span&gt;)

&lt;span style="color: #ff0000; font-weight: bold;"&gt;# Find T at which Pvap = 400 mmHg&lt;/span&gt;
&lt;span style="color: #ff0000; font-weight: bold;"&gt;# from our graph we might guess T ~ 40 ^{\circ}C&lt;/span&gt;

&lt;span style="color: #8b0000;"&gt;def&lt;/span&gt; &lt;span style="color: #8b2323;"&gt;objective&lt;/span&gt;(T):
    &lt;span style="color: #8b0000;"&gt;return&lt;/span&gt; 400 - 10**(A - B / (T + C))

&lt;span style="color: #8b0000;"&gt;from&lt;/span&gt; scipy.optimize &lt;span style="color: #8b0000;"&gt;import&lt;/span&gt; fsolve
Tsol, = fsolve(objective, 40)
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; Tsol
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; &lt;span style="color: #228b22;"&gt;'The vapor pressure is 400 mmHg at T = {0:1.1f} degC'&lt;/span&gt;.format(Tsol)

&lt;span style="color: #ff0000; font-weight: bold;"&gt;#Plot CRC data http://en.wikipedia.org/wiki/Acetone_%28data_page%29#Vapor_pressure_of_liquid&lt;/span&gt;
&lt;span style="color: #ff0000; font-weight: bold;"&gt;# We only include the data for the range where the Antoine fit is valid.&lt;/span&gt;

Tcrc  = [-59.4,         -31.1,  -9.4,   7.7,    39.5,   56.5]
Pcrc = [        1,      10,     40,     100,    400,    760]

plt.plot(Tcrc, Pcrc, &lt;span style="color: #228b22;"&gt;'bo'&lt;/span&gt;)
plt.legend([&lt;span style="color: #228b22;"&gt;'Antoine'&lt;/span&gt;,&lt;span style="color: #228b22;"&gt;'CRC Handbook'&lt;/span&gt;], loc=&lt;span style="color: #228b22;"&gt;'best'&lt;/span&gt;)
plt.savefig(&lt;span style="color: #228b22;"&gt;'images/antoine-2.png'&lt;/span&gt;)
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
38.6138198197
The vapor pressure is 400 mmHg at T = 38.6 degC
&lt;/pre&gt;

&lt;p&gt;&lt;img src="/img/images/antoine-1.png"&gt;&lt;p&gt;

&lt;p&gt;
This result is close to the value reported &lt;a href="http://en.wikipedia.org/wiki/Acetone_(data_page)#Vapor_pressure_of_liquid" &gt; here&lt;/a&gt; (39.5 degC), from the CRC Handbook. The difference is probably that the value reported in the CRC is an actual experimental number.
&lt;/p&gt;

&lt;p&gt;&lt;img src="/img/./images/antoine-2.png"&gt;&lt;p&gt;
&lt;p&gt;Copyright (C) 2013 by John Kitchin. See the &lt;a href="/copying.html"&gt;License&lt;/a&gt; for information about copying.&lt;p&gt;&lt;p&gt;&lt;a href="/org/2013/02/27/Reading-parameter-database-text-files-in-python.org"&gt;org-mode source&lt;/a&gt;&lt;p&gt;]]></content:encoded>
    </item>
    <item>
      <title>Calculating a bubble point pressure of a mixture</title>
      <link>https://kitchingroup.cheme.cmu.edu/blog/2013/02/18/Calculating-a-bubble-point-pressure-of-a-mixture</link>
      <pubDate>Mon, 18 Feb 2013 09:00:00 EST</pubDate>
      <category><![CDATA[nonlinear algebra]]></category>
      <guid isPermaLink="false">pRZlFP3yQwJs7jwDKG2E5hSQrsU=</guid>
      <description>Calculating a bubble point pressure of a mixture</description>
      <content:encoded><![CDATA[


&lt;p&gt;
&lt;a href="http://matlab.cheme.cmu.edu/2011/09/15/calculating-a-bubble-point-pressure/" &gt;Matlab post&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;
Adapted from &lt;a href="http://terpconnect.umd.edu/~nsw/ench250/bubpnt.htm" &gt;http://terpconnect.umd.edu/~nsw/ench250/bubpnt.htm&lt;/a&gt; (dead link)
&lt;/p&gt;

&lt;p&gt;
We previously learned to read a datafile containing lots of Antoine coefficients into a database, and use the coefficients to estimate vapor pressure of a single compound. Here we use those coefficents to compute a bubble point pressure of a mixture. 
&lt;/p&gt;

&lt;p&gt;
The bubble point is the temperature at which the sum of the component vapor pressures is equal to the the total pressure. This is where a bubble of vapor will first start forming, and the mixture starts to boil.
&lt;/p&gt;

&lt;p&gt;
Consider an equimolar mixture of benzene, toluene, chloroform, acetone and methanol. Compute the bubble point at 760 mmHg, and the gas phase composition. The gas phase composition is given by: \(y_i = x_i*P_i/P_T\).
&lt;/p&gt;

&lt;div class="org-src-container"&gt;

&lt;pre class="src src-python"&gt;&lt;span style="color: #8b0000;"&gt;import&lt;/span&gt; numpy &lt;span style="color: #8b0000;"&gt;as&lt;/span&gt; np
&lt;span style="color: #8b0000;"&gt;from&lt;/span&gt; scipy.optimize &lt;span style="color: #8b0000;"&gt;import&lt;/span&gt; fsolve

&lt;span style="color: #ff0000; font-weight: bold;"&gt;# load our thermodynamic data&lt;/span&gt;
data = np.loadtxt(&lt;span style="color: #228b22;"&gt;'data/antoine_data.dat'&lt;/span&gt;,
                  dtype=[(&lt;span style="color: #228b22;"&gt;'id'&lt;/span&gt;, np.int),
                         (&lt;span style="color: #228b22;"&gt;'formula'&lt;/span&gt;, &lt;span style="color: #228b22;"&gt;'S8'&lt;/span&gt;),
                         (&lt;span style="color: #228b22;"&gt;'name'&lt;/span&gt;, &lt;span style="color: #228b22;"&gt;'S28'&lt;/span&gt;),
                         (&lt;span style="color: #228b22;"&gt;'A'&lt;/span&gt;, np.float),
                         (&lt;span style="color: #228b22;"&gt;'B'&lt;/span&gt;, np.float),
                         (&lt;span style="color: #228b22;"&gt;'C'&lt;/span&gt;, np.float),
                         (&lt;span style="color: #228b22;"&gt;'Tmin'&lt;/span&gt;, np.float),
                         (&lt;span style="color: #228b22;"&gt;'Tmax'&lt;/span&gt;, np.float),
                         (&lt;span style="color: #228b22;"&gt;'??'&lt;/span&gt;, &lt;span style="color: #228b22;"&gt;'S4'&lt;/span&gt;),
                         (&lt;span style="color: #228b22;"&gt;'?'&lt;/span&gt;, &lt;span style="color: #228b22;"&gt;'S4'&lt;/span&gt;)],
                  skiprows=7)

compounds = [&lt;span style="color: #228b22;"&gt;'benzene'&lt;/span&gt;, &lt;span style="color: #228b22;"&gt;'toluene'&lt;/span&gt;, &lt;span style="color: #228b22;"&gt;'chloroform'&lt;/span&gt;, &lt;span style="color: #228b22;"&gt;'acetone'&lt;/span&gt;, &lt;span style="color: #228b22;"&gt;'methanol'&lt;/span&gt;]

&lt;span style="color: #ff0000; font-weight: bold;"&gt;# extract the data we want&lt;/span&gt;
A = np.array([data[data[&lt;span style="color: #228b22;"&gt;'name'&lt;/span&gt;] == x][&lt;span style="color: #228b22;"&gt;'A'&lt;/span&gt;][0] &lt;span style="color: #8b0000;"&gt;for&lt;/span&gt; x &lt;span style="color: #8b0000;"&gt;in&lt;/span&gt; compounds])
B = np.array([data[data[&lt;span style="color: #228b22;"&gt;'name'&lt;/span&gt;] == x][&lt;span style="color: #228b22;"&gt;'B'&lt;/span&gt;][0] &lt;span style="color: #8b0000;"&gt;for&lt;/span&gt; x &lt;span style="color: #8b0000;"&gt;in&lt;/span&gt; compounds])
C = np.array([data[data[&lt;span style="color: #228b22;"&gt;'name'&lt;/span&gt;] == x][&lt;span style="color: #228b22;"&gt;'C'&lt;/span&gt;][0] &lt;span style="color: #8b0000;"&gt;for&lt;/span&gt; x &lt;span style="color: #8b0000;"&gt;in&lt;/span&gt; compounds])
Tmin = np.array([data[data[&lt;span style="color: #228b22;"&gt;'name'&lt;/span&gt;] == x][&lt;span style="color: #228b22;"&gt;'Tmin'&lt;/span&gt;][0] &lt;span style="color: #8b0000;"&gt;for&lt;/span&gt; x &lt;span style="color: #8b0000;"&gt;in&lt;/span&gt; compounds])
Tmax = np.array([data[data[&lt;span style="color: #228b22;"&gt;'name'&lt;/span&gt;] == x][&lt;span style="color: #228b22;"&gt;'Tmax'&lt;/span&gt;][0] &lt;span style="color: #8b0000;"&gt;for&lt;/span&gt; x &lt;span style="color: #8b0000;"&gt;in&lt;/span&gt; compounds])


&lt;span style="color: #ff0000; font-weight: bold;"&gt;# we have an equimolar mixture&lt;/span&gt;
x = np.array([0.2, 0.2, 0.2, 0.2, 0.2])

&lt;span style="color: #ff0000; font-weight: bold;"&gt;# Given a T, we can compute the pressure of each species like this:&lt;/span&gt;

T = 67 &lt;span style="color: #ff0000; font-weight: bold;"&gt;# degC&lt;/span&gt;
P = 10**(A - B / (T + C))
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; P
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; np.dot(x, P)  &lt;span style="color: #ff0000; font-weight: bold;"&gt;# total mole-fraction weighted pressure&lt;/span&gt;

Tguess = 67
Ptotal = 760

&lt;span style="color: #8b0000;"&gt;def&lt;/span&gt; &lt;span style="color: #8b2323;"&gt;func&lt;/span&gt;(T):
    P = 10**(A - B / (T + C))
    &lt;span style="color: #8b0000;"&gt;return&lt;/span&gt; Ptotal - np.dot(x, P)
    
Tbubble, = fsolve(func, Tguess)

&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; &lt;span style="color: #228b22;"&gt;'The bubble point is {0:1.2f} degC'&lt;/span&gt;.format(Tbubble)

&lt;span style="color: #ff0000; font-weight: bold;"&gt;# double check answer is in a valid T range&lt;/span&gt;
&lt;span style="color: #8b0000;"&gt;if&lt;/span&gt; np.any(Tbubble &amp;lt; Tmin) &lt;span style="color: #8b0000;"&gt;or&lt;/span&gt; np.any(Tbubble &amp;gt; Tmax):
    &lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; &lt;span style="color: #228b22;"&gt;'T_bubble is out of range!'&lt;/span&gt;

&lt;span style="color: #ff0000; font-weight: bold;"&gt;# print gas phase composition&lt;/span&gt;
y = x * 10**(A - B / (Tbubble + C))/Ptotal

&lt;span style="color: #8b0000;"&gt;for&lt;/span&gt; cmpd, yi &lt;span style="color: #8b0000;"&gt;in&lt;/span&gt; &lt;span style="color: #8b0000;"&gt;zip&lt;/span&gt;(compounds, y):
    &lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; &lt;span style="color: #228b22;"&gt;'y_{0:&amp;lt;10s} = {1:1.3f}'&lt;/span&gt;.format(cmpd, yi)
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
[  498.4320267    182.16010994   898.31061294  1081.48181768   837.88860027]
699.654633507
The bubble point is 69.46 degC
y_benzene    = 0.142
y_toluene    = 0.053
y_chloroform = 0.255
y_acetone    = 0.308
y_methanol   = 0.242
&lt;/pre&gt;
&lt;p&gt;Copyright (C) 2013 by John Kitchin. See the &lt;a href="/copying.html"&gt;License&lt;/a&gt; for information about copying.&lt;p&gt;&lt;p&gt;&lt;a href="/org/2013/02/18/Calculating-a-bubble-point-pressure-of-a-mixture.org"&gt;org-mode source&lt;/a&gt;&lt;p&gt;]]></content:encoded>
    </item>
    <item>
      <title>The Gibbs free energy of a reacting mixture and the equilibrium composition</title>
      <link>https://kitchingroup.cheme.cmu.edu/blog/2013/02/18/The-Gibbs-free-energy-of-a-reacting-mixture-and-the-equilibrium-composition</link>
      <pubDate>Mon, 18 Feb 2013 09:00:00 EST</pubDate>
      <category><![CDATA[optimization]]></category>
      <guid isPermaLink="false">6p4az1VK91uyv4s50T5N3Bl2Do4=</guid>
      <description>The Gibbs free energy of a reacting mixture and the equilibrium composition</description>
      <content:encoded><![CDATA[


&lt;div id="table-of-contents"&gt;
&lt;h2&gt;Table of Contents&lt;/h2&gt;
&lt;div id="text-table-of-contents"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#sec-1"&gt;1. Summary&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
&lt;a href="http://matlab.cheme.cmu.edu/2011/12/20/the-gibbs-free-energy-of-a-reacting-mixture-and-the-equilibrium-composition/"&gt;Matlab post&lt;/a&gt; 
&lt;/p&gt;

&lt;p&gt;
In this post we derive the equations needed to find the equilibrium composition of a reacting mixture. We use the method of direct minimization of the Gibbs free energy of the reacting mixture.
&lt;/p&gt;

&lt;p&gt;
The Gibbs free energy of a mixture is defined as \(G = \sum\limits_j \mu_j n_j\) where \(\mu_j\) is the chemical potential of species \(j\), and it is temperature and pressure dependent, and \(n_j\) is the number of moles of species \(j\).
&lt;/p&gt;

&lt;p&gt;
We define the chemical potential as \(\mu_j = G_j^\circ + RT\ln a_j\), where \(G_j^\circ\) is the Gibbs energy in a standard state, and \(a_j\) is the activity of species \(j\) if the pressure and temperature are not at standard state conditions.
&lt;/p&gt;

&lt;p&gt;
If a reaction is occurring, then the number of moles of each species are related to each other through the reaction extent \(\epsilon\) and stoichiometric coefficients: \(n_j = n_{j0} + \nu_j \epsilon\). Note that the reaction extent has units of moles.
&lt;/p&gt;

&lt;p&gt;
Combining these three equations and expanding the terms leads to:
&lt;/p&gt;

&lt;p&gt;
$$G = \sum\limits_j n_{j0}G_j^\circ +\sum\limits_j \nu_j G_j^\circ \epsilon +RT\sum\limits_j(n_{j0} + \nu_j\epsilon)\ln a_j $$
&lt;/p&gt;

&lt;p&gt;
The first term is simply the initial Gibbs free energy that is present before any reaction begins, and it is a constant. It is difficult to evaluate, so we will move it to the left side of the equation in the next step, because it does not matter what its value is since it is a constant. The second term is related to the Gibbs free energy of reaction: \(\Delta_rG = \sum\limits_j \nu_j G_j^\circ\). With these observations we rewrite the equation as:
&lt;/p&gt;

&lt;p&gt;
$$G - \sum\limits_j n_{j0}G_j^\circ = \Delta_rG \epsilon +RT\sum\limits_j(n_{j0} + \nu_j\epsilon)\ln a_j $$
&lt;/p&gt;

&lt;p&gt;
Now, we have an equation that allows us to compute the change in Gibbs free energy as a function of the reaction extent, initial number of moles of each species, and the activities of each species. This difference in Gibbs free energy has no natural scale, and depends on the size of the system, i.e. on \(n_{j0}\). It is desirable to avoid this, so we now rescale the equation by the total initial moles present, \(n_{T0}\) and define a new variable \(\epsilon' = \epsilon/n_{T0}\), which is dimensionless. This leads to:
&lt;/p&gt;

&lt;p&gt;
$$ \frac{G - \sum\limits_j n_{j0}G_j^\circ}{n_{T0}} = \Delta_rG \epsilon' + RT \sum\limits_j(y_{j0} + \nu_j\epsilon')\ln a_j $$
&lt;/p&gt;

&lt;p&gt;
where \(y_{j0}\) is the initial mole fraction of species \(j\) present. The mole fractions are intensive properties that do not depend on the system size. Finally, we need to address \(a_j\). For an ideal gas, we know that \(A_j = \frac{y_j P}{P^\circ}\), where the numerator is the partial pressure of species \(j\) computed from the mole fraction of species \(j\) times the total pressure. To get the mole fraction we note:
&lt;/p&gt;

&lt;p&gt;
$$y_j = \frac{n_j}{n_T} = \frac{n_{j0} + \nu_j \epsilon}{n_{T0} + \epsilon \sum\limits_j \nu_j} = \frac{y_{j0} + \nu_j \epsilon'}{1 + \epsilon'\sum\limits_j \nu_j} $$
&lt;/p&gt;

&lt;p&gt;
This finally leads us to an equation that we can evaluate as a function of reaction extent:
&lt;/p&gt;

&lt;p&gt;
$$ \frac{G - \sum\limits_j n_{j0}G_j^\circ}{n_{T0}} = \widetilde{\widetilde{G}} = \Delta_rG \epsilon' + RT\sum\limits_j(y_{j0} + \nu_j\epsilon') \ln\left(\frac{y_{j0}+\nu_j\epsilon'}{1+\epsilon'\sum\limits_j\nu_j} \frac{P}{P^\circ}\right) $$
&lt;/p&gt;

&lt;p&gt;
we use a double tilde notation to distinguish this quantity from the quantity derived by Rawlings and Ekerdt which is further normalized by a factor of \(RT\). This additional scaling makes the quantities dimensionless, and makes the quantity have a magnitude of order unity, but otherwise has no effect on the shape of the graph.
&lt;/p&gt;

&lt;p&gt;
Finally, if we know the initial mole fractions, the initial total pressure, the Gibbs energy of reaction, and the stoichiometric coefficients, we can plot the scaled reacting mixture energy as a function of reaction extent. At equilibrium, this energy will be a minimum. We consider the example in Rawlings and Ekerdt where isobutane (I) reacts with 1-butene (B) to form 2,2,3-trimethylpentane (P). The reaction occurs at a total pressure of 2.5 atm at 400K, with equal molar amounts of I and B. The standard Gibbs free energy of reaction at 400K is -3.72 kcal/mol. Compute the equilibrium composition.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;

&lt;pre class="src src-python"&gt;&lt;span style="color: #8b0000;"&gt;import&lt;/span&gt; numpy &lt;span style="color: #8b0000;"&gt;as&lt;/span&gt; np

&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;R&lt;/span&gt; = &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;8&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;314&lt;/span&gt;
&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;P&lt;/span&gt; = &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;250000&lt;/span&gt;  # &lt;span style="color: #ff0000; font-weight: bold;"&gt;Pa&lt;/span&gt;
&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;P0&lt;/span&gt; = &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;100000&lt;/span&gt; # &lt;span style="color: #ff0000; font-weight: bold;"&gt;Pa, approximately 1 atm&lt;/span&gt;
&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;T&lt;/span&gt; = &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;400&lt;/span&gt; # &lt;span style="color: #ff0000; font-weight: bold;"&gt;K&lt;/span&gt;

&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;Grxn&lt;/span&gt; = -&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;15564&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt; #&lt;span style="color: #ff0000; font-weight: bold;"&gt;J/mol&lt;/span&gt;
&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;yi0&lt;/span&gt; = &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;5&lt;/span&gt;; &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;yb0&lt;/span&gt; = &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;5&lt;/span&gt;; &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;yp0&lt;/span&gt; = &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;; # &lt;span style="color: #ff0000; font-weight: bold;"&gt;initial mole fractions&lt;/span&gt;

&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;yj0&lt;/span&gt; = np.array([yi0, yb0, yp0])
&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;nu_j&lt;/span&gt; = np.array([-&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;1&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;, -&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;1&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;, &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;1&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;])   # &lt;span style="color: #ff0000; font-weight: bold;"&gt;stoichiometric coefficients&lt;/span&gt;

&lt;span style="color: #8b0000;"&gt;def&lt;/span&gt; &lt;span style="color: #8b2323;"&gt;Gwigglewiggle&lt;/span&gt;(extentp):
    &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;diffg&lt;/span&gt; = Grxn * extentp
    &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;sum_nu_j&lt;/span&gt; = np.sum(nu_j)
    &lt;span style="color: #8b0000;"&gt;for&lt;/span&gt; i,y &lt;span style="color: #8b0000;"&gt;in&lt;/span&gt;&lt;span style="color: #cd0000;"&gt; enumerate&lt;/span&gt;(yj0):
        &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;x1&lt;/span&gt; = yj0[i] + nu_j[i] * extentp
        &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;x2&lt;/span&gt; = x1 / (&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;1&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt; + extentp*sum_nu_j)
        &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;diffg&lt;/span&gt; += R * T * x1 * np.log(x2 * P / P0)
    &lt;span style="color: #8b0000;"&gt;return&lt;/span&gt; diffg
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
There are bounds on how large \(\epsilon'\) can be. Recall that \(n_j = n_{j0} + \nu_j \epsilon\), and that \(n_j \ge 0\). Thus, \(\epsilon_{max} = -n_{j0}/\nu_j\), and the maximum value that \(\epsilon'\) can have is therefore \(-y_{j0}/\nu_j\) where \(y_{j0}&gt;0\). When there are multiple species, you need the smallest \(epsilon'_{max}\) to avoid getting negative mole numbers.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;

&lt;pre class="src src-python"&gt;&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;epsilonp_max&lt;/span&gt; =&lt;span style="color: #cd0000;"&gt; min&lt;/span&gt;(-yj0[yj0 &amp;gt; &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;] / nu_j[yj0 &amp;gt; &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;])
&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;epsilonp&lt;/span&gt; = np.linspace(1e-&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;6&lt;/span&gt;, epsilonp_max, &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;1000&lt;/span&gt;);

&lt;span style="color: #8b0000;"&gt;import&lt;/span&gt; matplotlib.pyplot &lt;span style="color: #8b0000;"&gt;as&lt;/span&gt; plt

plt.plot(epsilonp,Gwigglewiggle(epsilonp))
plt.xlabel(&lt;span style="color: #228b22;"&gt;'$\epsilon$'&lt;/span&gt;)
plt.ylabel(&lt;span style="color: #228b22;"&gt;'Gwigglewiggle'&lt;/span&gt;)
plt.savefig(&lt;span style="color: #228b22;"&gt;'images/gibbs-minim-1.png'&lt;/span&gt;)
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
&amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; __main__:7: RuntimeWarning: divide by zero encountered in log
__main__:7: RuntimeWarning: invalid value encountered in multiply
[&amp;lt;matplotlib.lines.Line2D object at 0x10b1c7710&amp;gt;]
&amp;lt;matplotlib.text.Text object at 0x10b1c3d10&amp;gt;
&amp;lt;matplotlib.text.Text object at 0x10b1c9b90&amp;gt;
&lt;/pre&gt;


&lt;div class="figure"&gt;
&lt;p&gt;&lt;img src="/media/2013-02-18-The-Gibbs-free-energy-of-a-reacting-mixture-and-the-equilibrium-composition/gibbs-minim-1.png"&gt; 
&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;
Now we simply minimize our Gwigglewiggle function. Based on the figure above, the miminum is near 0.45.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;

&lt;pre class="src src-python"&gt;&lt;span style="color: #8b0000;"&gt;from&lt;/span&gt; scipy.optimize &lt;span style="color: #8b0000;"&gt;import&lt;/span&gt; fminbound

&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;epsilonp_eq&lt;/span&gt; = fminbound(Gwigglewiggle, &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;4&lt;/span&gt;, &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;5&lt;/span&gt;)
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; epsilonp_eq

plt.plot([epsilonp_eq], [Gwigglewiggle(epsilonp_eq)], &lt;span style="color: #228b22;"&gt;'ro'&lt;/span&gt;)
plt.savefig(&lt;span style="color: #228b22;"&gt;'images/gibbs-minim-2.png'&lt;/span&gt;)
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
&amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; 0.46959618249
&amp;gt;&amp;gt;&amp;gt; [&amp;lt;matplotlib.lines.Line2D object at 0x10d4d3e50&amp;gt;]
&lt;/pre&gt;


&lt;div class="figure"&gt;
&lt;p&gt;&lt;img src="/media/2013-02-18-The-Gibbs-free-energy-of-a-reacting-mixture-and-the-equilibrium-composition/gibbs-minim-2.png"&gt; 
&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;
To compute equilibrium mole fractions we do this:
&lt;/p&gt;
&lt;div class="org-src-container"&gt;

&lt;pre class="src src-python"&gt;&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;yi&lt;/span&gt; = (yi0 + nu_j[&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;]*epsilonp_eq) / (&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;1&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt; + epsilonp_eq*np.sum(nu_j))
&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;yb&lt;/span&gt; = (yb0 + nu_j[&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;1&lt;/span&gt;]*epsilonp_eq) / (&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;1&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt; + epsilonp_eq*np.sum(nu_j))
&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;yp&lt;/span&gt; = (yp0 + nu_j[&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;2&lt;/span&gt;]*epsilonp_eq) / (&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;1&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt; + epsilonp_eq*np.sum(nu_j))

&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; yi, yb, yp

# &lt;span style="color: #ff0000; font-weight: bold;"&gt;or this&lt;/span&gt;
&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;y_j&lt;/span&gt; = (yj0 + np.dot(nu_j, epsilonp_eq)) / (&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;1&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt; + epsilonp_eq*np.sum(nu_j))
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; y_j
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
&amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; 0.0573220186324 0.0573220186324 0.885355962735
&amp;gt;&amp;gt;&amp;gt; ... &amp;gt;&amp;gt;&amp;gt; [ 0.05732202  0.05732202  0.88535596]
&lt;/pre&gt;

&lt;p&gt;
\(K = \frac{a_P}{a_I a_B} = \frac{y_p P/P^\circ}{y_i P/P^\circ y_b P/P^\circ} = \frac{y_P}{y_i y_b}\frac{P^\circ}{P}\).
&lt;/p&gt;

&lt;p&gt;
We can express the equilibrium constant like this :\(K = \prod\limits_j a_j^{\nu_j}\), and compute it with a single line of code.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;

&lt;pre class="src src-python"&gt;&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;K&lt;/span&gt; = np.exp(-Grxn/R/T)
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; &lt;span style="color: #228b22;"&gt;'K from delta G '&lt;/span&gt;,K
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; &lt;span style="color: #228b22;"&gt;'K as ratio of mole fractions '&lt;/span&gt;,yp / (yi * yb) * P0 / P
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; &lt;span style="color: #228b22;"&gt;'compact notation: '&lt;/span&gt;,np.prod((y_j * P / P0)**nu_j)
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
K from delta G  107.776294742
K as ratio of mole fractions  107.779200065
compact notation:  107.779200065
&lt;/pre&gt;


&lt;p&gt;
These results are very close, and only disagree because of the default tolerance used in identifying the minimum of our function. You could tighten the tolerances by setting options to the fminbnd function.
&lt;/p&gt;

&lt;div id="outline-container-sec-1" class="outline-2"&gt;
&lt;h2 id="sec-1"&gt;&lt;span class="section-number-2"&gt;1&lt;/span&gt; Summary&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-1"&gt;
&lt;p&gt;
In this post we derived an equation for the Gibbs free energy of a reacting mixture and used it to find the equilibrium composition. In future posts we will examine some alternate forms of the equations that may be more useful in some circumstances.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Copyright (C) 2014 by John Kitchin. See the &lt;a href="/copying.html"&gt;License&lt;/a&gt; for information about copying.&lt;p&gt;&lt;p&gt;&lt;a href="/org/2013/02/18/The-Gibbs-free-energy-of-a-reacting-mixture-and-the-equilibrium-composition.org"&gt;org-mode source&lt;/a&gt;&lt;p&gt;&lt;p&gt;Org-mode version = 8.2.7c&lt;/p&gt;]]></content:encoded>
    </item>
    <item>
      <title>The equal area method for the van der Waals equation</title>
      <link>https://kitchingroup.cheme.cmu.edu/blog/2013/02/15/The-equal-area-method-for-the-van-der-Waals-equation</link>
      <pubDate>Fri, 15 Feb 2013 09:00:00 EST</pubDate>
      <category><![CDATA[plotting]]></category>
      <guid isPermaLink="false">y4-e4AzPJtxZMR6YOH6t_k2TGdk=</guid>
      <description>The equal area method for the van der Waals equation</description>
      <content:encoded><![CDATA[


&lt;div id="table-of-contents"&gt;
&lt;h2&gt;Table of Contents&lt;/h2&gt;
&lt;div id="text-table-of-contents"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#sec-1"&gt;1. Compute areas&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
&lt;a href="http://matlab.cheme.cmu.edu/2011/09/11/the-equal-area-method-for-the-van-der-waals-equation/"&gt;Matlab post&lt;/a&gt; 
&lt;/p&gt;

&lt;p&gt;
When a gas is below its Tc the van der Waal equation oscillates. In the portion of the isotherm where \(\partial P_R/\partial V_r &gt; 0\), the isotherm fails to describe real materials, which phase separate into a liquid and gas in this region.
&lt;/p&gt;

&lt;p&gt;
Maxwell proposed to replace this region by a flat line, where the area above and below the curves are equal. Today, we examine how to identify where that line should be.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;

&lt;pre class="src src-python"&gt;&lt;span style="color: #8b0000;"&gt;import&lt;/span&gt; numpy &lt;span style="color: #8b0000;"&gt;as&lt;/span&gt; np
&lt;span style="color: #8b0000;"&gt;import&lt;/span&gt; matplotlib.pyplot &lt;span style="color: #8b0000;"&gt;as&lt;/span&gt; plt

&lt;span style="color: #8b008b;"&gt;Tr&lt;/span&gt; = 0.9 &lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;A Tr below Tc:  Tr = T/Tc&lt;/span&gt;
&lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;analytical equation for Pr. This is the reduced form of the van der Waal&lt;/span&gt;
&lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;equation.&lt;/span&gt;
&lt;span style="color: #8b0000;"&gt;def&lt;/span&gt; &lt;span style="color: #8b2323;"&gt;Prfh&lt;/span&gt;(Vr):
    &lt;span style="color: #8b0000;"&gt;return&lt;/span&gt;  8.0 / 3.0 * Tr / (Vr - 1.0 / 3.0) - 3.0 / (Vr**2)

&lt;span style="color: #8b008b;"&gt;Vr&lt;/span&gt; = np.linspace(0.5, 4, 100)  &lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;vector of reduced volume&lt;/span&gt;
&lt;span style="color: #8b008b;"&gt;Pr&lt;/span&gt; = Prfh(Vr)                 &lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;vector of reduced pressure&lt;/span&gt;

plt.plot(Vr,Pr)
plt.ylim([0, 2])
plt.xlabel(&lt;span style="color: #228b22;"&gt;'$V_R$'&lt;/span&gt;)
plt.ylabel(&lt;span style="color: #228b22;"&gt;'$P_R$'&lt;/span&gt;)
plt.savefig(&lt;span style="color: #228b22;"&gt;'images/maxwell-eq-area-1.png'&lt;/span&gt;)
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
&amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; ... ... ... ... &amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; [&amp;lt;matplotlib.lines.Line2D object at 0x042FDAF0&amp;gt;]
(0, 2)
&amp;lt;matplotlib.text.Text object at 0x04237CB0&amp;gt;
&amp;lt;matplotlib.text.Text object at 0x042DC030&amp;gt;
&lt;/pre&gt;


&lt;div class="figure"&gt;
&lt;p&gt;&lt;p&gt;&lt;img src="/img/./images/maxwell-eq-area-1.png"&gt;&lt;p&gt;
&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;
The idea is to pick a Pr and draw a line through the EOS. We want the areas between the line and EOS to be equal on each side of the middle intersection. Let us draw a line on the figure at y = 0.65.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;

&lt;pre class="src src-python"&gt;&lt;span style="color: #8b008b;"&gt;y&lt;/span&gt; = 0.65

plt.plot([0.5, 4.0], [y, y], &lt;span style="color: #228b22;"&gt;'k--'&lt;/span&gt;)
plt.savefig(&lt;span style="color: #228b22;"&gt;'images/maxwell-eq-area-2.png'&lt;/span&gt;)
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
&amp;gt;&amp;gt;&amp;gt; [&amp;lt;matplotlib.lines.Line2D object at 0x042FDCD0&amp;gt;]
&lt;/pre&gt;


&lt;div class="figure"&gt;
&lt;p&gt;&lt;p&gt;&lt;img src="/img/./images/maxwell-eq-area-2.png"&gt;&lt;p&gt;
&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;
To find the areas, we need to know where the intersection of the vdW eqn with the horizontal line. This is the same as asking what are the roots of the vdW equation at that Pr. We need all three intersections so we can integrate from the first root to the middle root, and then the middle root to the third root. We take advantage of the polynomial nature of the vdW equation, which allows us to use the roots command to get all the roots at once. The polynomial is \(V_R^3 - \frac{1}{3}(1+8 T_R/P_R) + 3/P_R - 1/P_R = 0\). We use the coefficients t0 get the roots like this.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;

&lt;pre class="src src-python"&gt;&lt;span style="color: #8b008b;"&gt;vdWp&lt;/span&gt; = [1.0, -1. / 3.0 * (1.0 + 8.0 * Tr / y), 3.0 / y, - 1.0 / y]
&lt;span style="color: #8b008b;"&gt;v&lt;/span&gt; = np.roots(vdWp)
v.sort()
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; v

plt.plot(v[0], y, &lt;span style="color: #228b22;"&gt;'bo'&lt;/span&gt;, v[1], y, &lt;span style="color: #228b22;"&gt;'bo'&lt;/span&gt;, v[2], y, &lt;span style="color: #228b22;"&gt;'bo'&lt;/span&gt;)
plt.savefig(&lt;span style="color: #228b22;"&gt;'images/maxwell-eq-area-3.png'&lt;/span&gt;)
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
&amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; [ 0.60286812  1.09743234  2.32534056]
&amp;gt;&amp;gt;&amp;gt; [&amp;lt;matplotlib.lines.Line2D object at 0x0439C570&amp;gt;, &amp;lt;matplotlib.lines.Line2D object at 0x043933B0&amp;gt;, &amp;lt;matplotlib.lines.Line2D object at 0x04393CB0&amp;gt;]
&lt;/pre&gt;


&lt;div class="figure"&gt;
&lt;p&gt;&lt;p&gt;&lt;img src="/img/./images/maxwell-eq-area-3.png"&gt;&lt;p&gt;
&lt;/p&gt;
&lt;/div&gt;

&lt;div id="outline-container-sec-1" class="outline-2"&gt;
&lt;h2 id="sec-1"&gt;&lt;span class="section-number-2"&gt;1&lt;/span&gt; Compute areas&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-1"&gt;
&lt;p&gt;
for A1, we need the area under the line minus the area under the vdW curve. That is the area between the curves. For A2, we want the area under the vdW curve minus the area under the line. The area under the line between root 2 and root 1 is just the width (root2 - root1)*y
&lt;/p&gt;

&lt;div class="org-src-container"&gt;

&lt;pre class="src src-python"&gt;&lt;span style="color: #8b0000;"&gt;from&lt;/span&gt; scipy.integrate &lt;span style="color: #8b0000;"&gt;import&lt;/span&gt; quad

&lt;span style="color: #8b008b;"&gt;A1&lt;/span&gt;, &lt;span style="color: #8b008b;"&gt;e1&lt;/span&gt; = (v[1] - v[0]) * y - quad(Prfh,  v[0], v[1])
&lt;span style="color: #8b008b;"&gt;A2&lt;/span&gt;, &lt;span style="color: #8b008b;"&gt;e2&lt;/span&gt; = quad(Prfh, v[1], v[2]) - (v[2] - v[1])* y 

&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; A1, A2
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; e1, e2  &lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;interesting these look so large&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
&amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; 0.063225945606 0.0580212098122
0.321466743765 -0.798140339268
&lt;/pre&gt;

&lt;div class="org-src-container"&gt;

&lt;pre class="src src-python"&gt;&lt;span style="color: #8b0000;"&gt;from&lt;/span&gt; scipy.optimize &lt;span style="color: #8b0000;"&gt;import&lt;/span&gt; fsolve

&lt;span style="color: #8b0000;"&gt;def&lt;/span&gt; &lt;span style="color: #8b2323;"&gt;equal_area&lt;/span&gt;(y):
    &lt;span style="color: #8b008b;"&gt;Tr&lt;/span&gt; = 0.9
    &lt;span style="color: #8b008b;"&gt;vdWp&lt;/span&gt; = [1, -1.0 / 3 * ( 1.0 + 8.0 * Tr / y), 3.0 / y,  -1.0 / y]
    &lt;span style="color: #8b008b;"&gt;v&lt;/span&gt; = np.roots(vdWp)
    v.sort()
    &lt;span style="color: #8b008b;"&gt;A1&lt;/span&gt; = (v[1] - v[0]) * y - quad(Prfh, v[0], v[1])
    &lt;span style="color: #8b008b;"&gt;A2&lt;/span&gt; = quad(Prfh, v[1], v[2]) - (v[2] - v[1]) * y
    &lt;span style="color: #8b0000;"&gt;return&lt;/span&gt;  A1 - A2

y_eq, = fsolve(equal_area, 0.65)
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; y_eq

&lt;span style="color: #8b008b;"&gt;Tr&lt;/span&gt; = 0.9
&lt;span style="color: #8b008b;"&gt;vdWp&lt;/span&gt; = [1, -1.0 / 3 * ( 1.0 + 8.0 * Tr / y_eq), 3.0 / y_eq,  -1.0 / y_eq]
&lt;span style="color: #8b008b;"&gt;v&lt;/span&gt; = np.roots(vdWp)
v.sort()

&lt;span style="color: #8b008b;"&gt;A1&lt;/span&gt;, &lt;span style="color: #8b008b;"&gt;e1&lt;/span&gt; = (v[1] - v[0]) * y_eq - quad(Prfh,  v[0], v[1])
&lt;span style="color: #8b008b;"&gt;A2&lt;/span&gt;, &lt;span style="color: #8b008b;"&gt;e2&lt;/span&gt; = quad(Prfh, v[1], v[2]) - (v[2] - v[1]) * y_eq

&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; A1, A2
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
&amp;gt;&amp;gt;&amp;gt; ... ... ... ... ... ... ... ... &amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; 0.646998351872
&amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; 0.0617526473994 0.0617526473994
&lt;/pre&gt;

&lt;p&gt;
Now let us plot the equal areas and indicate them by shading.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;

&lt;pre class="src src-python"&gt;&lt;span style="color: #8b008b;"&gt;fig&lt;/span&gt; = plt.gcf()
&lt;span style="color: #8b008b;"&gt;ax&lt;/span&gt; = fig.add_subplot(111)

ax.plot(Vr,Pr)

&lt;span style="color: #8b008b;"&gt;hline&lt;/span&gt; = np.ones(Vr.size) * y_eq

ax.plot(Vr, hline)
ax.fill_between(Vr, hline, Pr, where=(Vr &amp;gt;= v[0]) &amp;amp; (Vr &amp;lt;= v[1]), facecolor=&lt;span style="color: #228b22;"&gt;'gray'&lt;/span&gt;)
ax.fill_between(Vr, hline, Pr, where=(Vr &amp;gt;= v[1]) &amp;amp; (Vr &amp;lt;= v[2]), facecolor=&lt;span style="color: #228b22;"&gt;'gray'&lt;/span&gt;)

plt.text(v[0], 1, &lt;span style="color: #228b22;"&gt;'A1 = {0}'&lt;/span&gt;.&lt;span style="color: #cd0000;"&gt;format&lt;/span&gt;(A1))
plt.text(v[2], 1, &lt;span style="color: #228b22;"&gt;'A2 = {0}'&lt;/span&gt;.&lt;span style="color: #cd0000;"&gt;format&lt;/span&gt;(A2))
plt.xlabel(&lt;span style="color: #228b22;"&gt;'$V_R$'&lt;/span&gt;)
plt.ylabel(&lt;span style="color: #228b22;"&gt;'$P_R$'&lt;/span&gt;)
plt.title(&lt;span style="color: #228b22;"&gt;'$T_R$ = 0.9'&lt;/span&gt;)

plt.savefig(&lt;span style="color: #228b22;"&gt;'images/maxwell-eq-area-4.png'&lt;/span&gt;)
plt.savefig(&lt;span style="color: #228b22;"&gt;'images/maxwell-eq-area-4.svg'&lt;/span&gt;)
&lt;/pre&gt;
&lt;/div&gt;


&lt;pre class="example"&gt;
&amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; [&amp;lt;matplotlib.lines.Line2D object at 0x043939D0&amp;gt;]
&amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; [&amp;lt;matplotlib.lines.Line2D object at 0x043A7230&amp;gt;]
&amp;lt;matplotlib.collections.PolyCollection object at 0x047ADE70&amp;gt;
&amp;lt;matplotlib.collections.PolyCollection object at 0x047ADAB0&amp;gt;
&amp;gt;&amp;gt;&amp;gt; &amp;lt;matplotlib.text.Text object at 0x0438E730&amp;gt;
&amp;lt;matplotlib.text.Text object at 0x047B7930&amp;gt;
&amp;lt;matplotlib.text.Text object at 0x04237CB0&amp;gt;
&amp;lt;matplotlib.text.Text object at 0x042DC030&amp;gt;
&amp;lt;matplotlib.text.Text object at 0x042EBCD0&amp;gt;
&lt;/pre&gt;


&lt;div class="figure"&gt;
&lt;p&gt;&lt;p&gt;&lt;img src="/img/./images/maxwell-eq-area-4.png"&gt;&lt;p&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Copyright (C) 2013 by John Kitchin. See the &lt;a href="/copying.html"&gt;License&lt;/a&gt; for information about copying.&lt;p&gt;&lt;p&gt;&lt;a href="/org/2013/02/15/The-equal-area-method-for-the-van-der-Waals-equation.org"&gt;org-mode source&lt;/a&gt;&lt;p&gt;]]></content:encoded>
    </item>
    <item>
      <title>Constrained minimization to find equilibrium compositions</title>
      <link>https://kitchingroup.cheme.cmu.edu/blog/2013/02/05/Constrained-minimization-to-find-equilibrium-compositions</link>
      <pubDate>Tue, 05 Feb 2013 09:00:00 EST</pubDate>
      <category><![CDATA[optimization]]></category>
      <guid isPermaLink="false">HB-uY8lu7q22beBW__fqM0My8HY=</guid>
      <description>Constrained minimization to find equilibrium compositions</description>
      <content:encoded><![CDATA[



&lt;p&gt;
adapated from Chemical Reactor analysis and design fundamentals, Rawlings and Ekerdt, appendix A.2.3.
&lt;/p&gt;

&lt;p&gt;
&lt;a href="http://matlab.cheme.cmu.edu/2011/08/12/constrained-minimization-to-find-equilibrium-compositions/"&gt;Matlab post&lt;/a&gt; 
&lt;/p&gt;

&lt;p&gt;
The equilibrium composition of a reaction is the one that minimizes the total Gibbs free energy. The Gibbs free energy of a reacting ideal gas mixture depends on the mole fractions of each species, which are determined by the initial mole fractions of each species, the extent of reactions that convert each species, and the equilibrium constants.
&lt;/p&gt;

&lt;p&gt;
Reaction 1: \(I + B \rightleftharpoons P1\)
&lt;/p&gt;

&lt;p&gt;
Reaction 2: \(I + B \rightleftharpoons P2\)
&lt;/p&gt;

&lt;p&gt;
Here we define the Gibbs free energy of the mixture as a function of the reaction extents.
&lt;/p&gt;
&lt;div class="org-src-container"&gt;

&lt;pre class="src src-python"&gt;&lt;span style="color: #0000FF;"&gt;import&lt;/span&gt; numpy &lt;span style="color: #0000FF;"&gt;as&lt;/span&gt; np

&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;gibbs&lt;/span&gt;(E):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #036A07;"&gt;'function defining Gibbs free energy as a function of reaction extents'&lt;/span&gt;
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;e1&lt;/span&gt; = E[0]
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;e2&lt;/span&gt; = E[1]
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;known equilibrium constants and initial amounts&lt;/span&gt;
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;K1&lt;/span&gt; = 108; &lt;span style="color: #BA36A5;"&gt;K2&lt;/span&gt; = 284; &lt;span style="color: #BA36A5;"&gt;P&lt;/span&gt; = 2.5
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;yI0&lt;/span&gt; = 0.5; &lt;span style="color: #BA36A5;"&gt;yB0&lt;/span&gt; = 0.5; &lt;span style="color: #BA36A5;"&gt;yP10&lt;/span&gt; = 0.0; &lt;span style="color: #BA36A5;"&gt;yP20&lt;/span&gt; = 0.0
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;compute mole fractions&lt;/span&gt;
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;d&lt;/span&gt; = 1 - e1 - e2
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;yI&lt;/span&gt; = (yI0 - e1 - e2) / d
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;yB&lt;/span&gt; = (yB0 - e1 - e2) / d
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;yP1&lt;/span&gt; = (yP10 + e1) / d
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;yP2&lt;/span&gt; = (yP20 + e2) / d
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;G&lt;/span&gt; = (-(e1 * np.log(K1) + e2 * np.log(K2)) +
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;d * np.log(P) + yI * d * np.log(yI) +
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;yB * d * np.log(yB) + yP1 * d * np.log(yP1) + yP2 * d * np.log(yP2))
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; G
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
The equilibrium constants for these reactions are known, and we seek to find the equilibrium reaction extents so we can determine equilibrium compositions. The equilibrium reaction extents are those that minimize the Gibbs free energy.  We have the following constraints, written in standard less than or equal to form:
&lt;/p&gt;

&lt;p&gt;
\(-\epsilon_1 \le 0\)
&lt;/p&gt;

&lt;p&gt;
\(-\epsilon_2 \le 0\)
&lt;/p&gt;

&lt;p&gt;
\(\epsilon_1 + \epsilon_2 \le 0.5\)
&lt;/p&gt;

&lt;p&gt;
In Matlab we express this in matrix form as Ax=b where
&lt;/p&gt;
\begin{equation}
A = \left[ \begin{array}{cc} -1 &amp; 0 \\ 0 &amp; -1 \\ 1 &amp; 1 \end{array} \right]
\end{equation}

&lt;p&gt;
and
&lt;/p&gt;

\begin{equation}
b = \left[ \begin{array}{c} 0 \\ 0 \\ 0.5\end{array} \right]
\end{equation}

&lt;p&gt;
Unlike in Matlab, in python we construct the inequality constraints as functions that are greater than or equal to zero when the constraint is met.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;

&lt;pre class="src src-python"&gt;&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;constraint1&lt;/span&gt;(E):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;e1&lt;/span&gt; = E[0]
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; e1


&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;constraint2&lt;/span&gt;(E):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;e2&lt;/span&gt; = E[1]
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; e2


&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;constraint3&lt;/span&gt;(E):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;e1&lt;/span&gt; = E[0]
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;e2&lt;/span&gt; = E[1]
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; 0.5 - (e1 + e2)
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
Now, we minimize.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;

&lt;pre class="src src-python"&gt;&lt;span style="color: #0000FF;"&gt;from&lt;/span&gt; scipy.optimize &lt;span style="color: #0000FF;"&gt;import&lt;/span&gt; fmin_slsqp

&lt;span style="color: #BA36A5;"&gt;X0&lt;/span&gt; = [0.2, 0.2]
&lt;span style="color: #BA36A5;"&gt;X&lt;/span&gt; = fmin_slsqp(gibbs, X0, ieqcons=[constraint1, constraint2, constraint3],
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;  bounds=((0.001, 0.499),
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;  (0.001, 0.499)))
&lt;span style="color: #0000FF;"&gt;print&lt;/span&gt;(X)

&lt;span style="color: #0000FF;"&gt;print&lt;/span&gt;(gibbs(X))
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
Optimization terminated successfully.    (Exit mode 0)
            Current function value: -2.55942394906
            Iterations: 7
            Function evaluations: 31
            Gradient evaluations: 7
[ 0.13336503  0.35066486]
-2.55942394906
&lt;/pre&gt;



&lt;p&gt;
One way we can verify our solution is to plot the gibbs function and see where the minimum is, and whether there is more than one minimum. We start by making grids over the range of 0 to 0.5. Note we actually start slightly above zero because at zero there are some numerical imaginary elements of the gibbs function or it is numerically not defined since there are logs of zero there. We also set all elements where the sum of the two extents is greater than 0.5 to near zero, since those regions violate the constraints.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;

&lt;pre class="src src-python"&gt;&lt;span style="color: #0000FF;"&gt;import&lt;/span&gt; numpy &lt;span style="color: #0000FF;"&gt;as&lt;/span&gt; np
&lt;span style="color: #0000FF;"&gt;import&lt;/span&gt; matplotlib.pyplot &lt;span style="color: #0000FF;"&gt;as&lt;/span&gt; plt

&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;gibbs&lt;/span&gt;(E):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #036A07;"&gt;'function defining Gibbs free energy as a function of reaction extents'&lt;/span&gt;
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;e1&lt;/span&gt; = E[0]
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;e2&lt;/span&gt; = E[1]
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;known equilibrium constants and initial amounts&lt;/span&gt;
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;K1&lt;/span&gt; = 108; &lt;span style="color: #BA36A5;"&gt;K2&lt;/span&gt; = 284; &lt;span style="color: #BA36A5;"&gt;P&lt;/span&gt; = 2.5;
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;yI0&lt;/span&gt; = 0.5; &lt;span style="color: #BA36A5;"&gt;yB0&lt;/span&gt; = 0.5; &lt;span style="color: #BA36A5;"&gt;yP10&lt;/span&gt; = 0.0; &lt;span style="color: #BA36A5;"&gt;yP20&lt;/span&gt; = 0.0;
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;compute mole fractions&lt;/span&gt;
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;d&lt;/span&gt; = 1 - e1 - e2;
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;yI&lt;/span&gt; = (yI0 - e1 - e2)/d;
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;yB&lt;/span&gt; = (yB0 - e1 - e2)/d;
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;yP1&lt;/span&gt; = (yP10 + e1)/d;
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;yP2&lt;/span&gt; = (yP20 + e2)/d;
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;G&lt;/span&gt; = (-(e1 * np.log(K1) + e2 * np.log(K2)) +
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;d * np.log(P) + yI * d * np.log(yI) +
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;yB * d * np.log(yB) + yP1 * d * np.log(yP1) + yP2 * d * np.log(yP2))
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; G


&lt;span style="color: #BA36A5;"&gt;a&lt;/span&gt; = np.linspace(0.001, 0.5, 100)
&lt;span style="color: #BA36A5;"&gt;E1&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;E2&lt;/span&gt; = np.meshgrid(a,a)

&lt;span style="color: #BA36A5;"&gt;sumE&lt;/span&gt; = E1 + E2
&lt;span style="color: #BA36A5;"&gt;E1&lt;/span&gt;[sumE &amp;gt;= 0.5] = 0.00001
&lt;span style="color: #BA36A5;"&gt;E2&lt;/span&gt;[sumE &amp;gt;= 0.5] = 0.00001

&lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;now evaluate gibbs&lt;/span&gt;
&lt;span style="color: #BA36A5;"&gt;G&lt;/span&gt; = np.zeros(E1.shape)
&lt;span style="color: #BA36A5;"&gt;m&lt;/span&gt;,&lt;span style="color: #BA36A5;"&gt;n&lt;/span&gt; = E1.shape

&lt;span style="color: #BA36A5;"&gt;G&lt;/span&gt; = gibbs([E1, E2])

&lt;span style="color: #BA36A5;"&gt;CS&lt;/span&gt; = plt.contour(E1, E2, G, levels=np.linspace(G.&lt;span style="color: #006FE0;"&gt;min&lt;/span&gt;(),G.&lt;span style="color: #006FE0;"&gt;max&lt;/span&gt;(),100))
plt.xlabel(&lt;span style="color: #008000;"&gt;'$\epsilon_1$'&lt;/span&gt;)
plt.ylabel(&lt;span style="color: #008000;"&gt;'$\epsilon_2$'&lt;/span&gt;)
plt.colorbar()

plt.plot([0.13336503],  [0.35066486], &lt;span style="color: #008000;"&gt;'ro'&lt;/span&gt;)

plt.savefig(&lt;span style="color: #008000;"&gt;'images/gibbs-minimization-1.png'&lt;/span&gt;)
plt.savefig(&lt;span style="color: #008000;"&gt;'images/gibbs-minimization-1.svg'&lt;/span&gt;)
plt.show()
&lt;/pre&gt;
&lt;/div&gt;


&lt;div class="figure"&gt;
&lt;p&gt;&lt;img src="/media/2013-02-05-Constrained-minimization-to-find-equilibrium-compositions/gibbs-minimization-1.png"&gt; 
&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;
You can see we found the minimum. We can compute the mole fractions pretty easily.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;

&lt;pre class="src src-python"&gt;&lt;span style="color: #BA36A5;"&gt;e1&lt;/span&gt; = X[0];
&lt;span style="color: #BA36A5;"&gt;e2&lt;/span&gt; = X[1];

&lt;span style="color: #BA36A5;"&gt;yI0&lt;/span&gt; = 0.5; &lt;span style="color: #BA36A5;"&gt;yB0&lt;/span&gt; = 0.5; &lt;span style="color: #BA36A5;"&gt;yP10&lt;/span&gt; = 0; &lt;span style="color: #BA36A5;"&gt;yP20&lt;/span&gt; = 0; &lt;span style="color: #8D8D84;"&gt;#&lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;initial mole fractions&lt;/span&gt;

&lt;span style="color: #BA36A5;"&gt;d&lt;/span&gt; = 1 - e1 - e2;
&lt;span style="color: #BA36A5;"&gt;yI&lt;/span&gt; = (yI0 - e1 - e2) / d
&lt;span style="color: #BA36A5;"&gt;yB&lt;/span&gt; = (yB0 - e1 - e2) / d
&lt;span style="color: #BA36A5;"&gt;yP1&lt;/span&gt; = (yP10 + e1) / d
&lt;span style="color: #BA36A5;"&gt;yP2&lt;/span&gt; = (yP20 + e2) / d

&lt;span style="color: #0000FF;"&gt;print&lt;/span&gt;(&lt;span style="color: #008000;"&gt;'y_I = {0:1.3f} y_B = {1:1.3f} y_P1 = {2:1.3f} y_P2 = {3:1.3f}'&lt;/span&gt;.&lt;span style="color: #006FE0;"&gt;format&lt;/span&gt;(yI,yB,yP1,yP2))
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
y_I = 0.031 y_B = 0.031 y_P1 = 0.258 y_P2 = 0.680
&lt;/pre&gt;

&lt;div id="outline-container-sec-1" class="outline-2"&gt;
&lt;h2 id="sec-1"&gt;&lt;span class="section-number-2"&gt;1&lt;/span&gt; summary&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-1"&gt;
&lt;p&gt;
I found setting up the constraints in this example to be more confusing than the Matlab syntax.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Copyright (C) 2016 by John Kitchin. See the &lt;a href="/copying.html"&gt;License&lt;/a&gt; for information about copying.&lt;p&gt;
&lt;p&gt;&lt;a href="/org/2013/02/05/Constrained-minimization-to-find-equilibrium-compositions.org"&gt;org-mode source&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Org-mode version = 8.2.10&lt;/p&gt;]]></content:encoded>
    </item>
    <item>
      <title>Estimating the boiling point of water</title>
      <link>https://kitchingroup.cheme.cmu.edu/blog/2013/02/04/Estimating-the-boiling-point-of-water</link>
      <pubDate>Mon, 04 Feb 2013 09:00:00 EST</pubDate>
      <category><![CDATA[uncategorized]]></category>
      <guid isPermaLink="false">COTcdDHZhHVGtw1uWV6EAzpjRwg=</guid>
      <description>Estimating the boiling point of water</description>
      <content:encoded><![CDATA[


&lt;p&gt;
&lt;a href="http://matlab.cheme.cmu.edu/2012/01/01/estimating-the-boiling-point-of-water/" &gt;Matlab post&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;
I got distracted looking for Shomate parameters for ethane today, and came across this &lt;a href="http://senese.wordpress.com/2010/01/26/notebook-3-2-predicting-boiling-points-from-liquidvapor-gibbs-free-energy-functions/" &gt;website&lt;/a&gt; on predicting the boiling point of water using the Shomate equations. The basic idea is to find the temperature where the Gibbs energy of water as a vapor is equal to the Gibbs energy of the liquid.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;

&lt;pre class="src src-python"&gt;&lt;span style="color: #8b0000;"&gt;import&lt;/span&gt; matplotlib.pyplot &lt;span style="color: #8b0000;"&gt;as&lt;/span&gt; plt
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
Liquid water (\url{http://webbook.nist.gov/cgi/cbook.cgi?ID=C7732185&amp;Units=SI&amp;Mask=2#Thermo-Condensed})
&lt;/p&gt;
&lt;div class="org-src-container"&gt;

&lt;pre class="src src-python"&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;# valid over 298-500&lt;/span&gt;

Hf_liq = -285.830   &lt;span style="color: #ff0000; font-weight: bold;"&gt;# kJ/mol&lt;/span&gt;
S_liq = 0.06995     &lt;span style="color: #ff0000; font-weight: bold;"&gt;# kJ/mol/K&lt;/span&gt;
shomateL = [-203.6060,
            1523.290,
           -3196.413,
            2474.455,
               3.855326,
            -256.5478,
            -488.7163,
            -285.8304]
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
Gas phase water (\url{http://webbook.nist.gov/cgi/cbook.cgi?ID=C7732185&amp;Units=SI&amp;Mask=1&amp;Type=JANAFG&amp;Table=on#JANAFG})
&lt;/p&gt;

&lt;p&gt;
Interestingly, these parameters are listed as valid only above 500K. That means we have to extrapolate the values down to 298K. That is risky for polynomial models, as they can deviate substantially outside the region they were fitted to.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;

&lt;pre class="src src-python"&gt;Hf_gas = -241.826  &lt;span style="color: #ff0000; font-weight: bold;"&gt;# kJ/mol&lt;/span&gt;
S_gas = 0.188835   &lt;span style="color: #ff0000; font-weight: bold;"&gt;# kJ/mol/K&lt;/span&gt;

shomateG = [30.09200,
             6.832514,
             6.793435,
            -2.534480,
             0.082139,
          -250.8810,
           223.3967,
          -241.8264]
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
Now, we wan to compute G for each phase as a function of T
&lt;/p&gt;

&lt;div class="org-src-container"&gt;

&lt;pre class="src src-python"&gt;&lt;span style="color: #8b0000;"&gt;import&lt;/span&gt; numpy &lt;span style="color: #8b0000;"&gt;as&lt;/span&gt; np

T = np.linspace(0, 200) + 273.15
t = T / 1000.0

sTT = np.vstack([np.log(t),
                 t,
                 (t**2) / 2.0,
                 (t**3) / 3.0,
                 -1.0 / (2*t**2),
                 0 * t,
                 t**0,
                 0 * t**0]).T / 1000.0

hTT = np.vstack([t,
                 (t**2)/2.0,
                 (t**3)/3.0,
                 (t**4)/4.0,
                 -1.0 / t,
                 1 * t**0,
                 0 * t**0,
                 -1 * t**0]).T

Gliq = Hf_liq + np.dot(hTT, shomateL) - T*(np.dot(sTT, shomateL))
Ggas = Hf_gas + np.dot(hTT, shomateG) - T*(np.dot(sTT, shomateG))
                 
&lt;span style="color: #8b0000;"&gt;from&lt;/span&gt; scipy.interpolate &lt;span style="color: #8b0000;"&gt;import&lt;/span&gt; interp1d
&lt;span style="color: #8b0000;"&gt;from&lt;/span&gt; scipy.optimize &lt;span style="color: #8b0000;"&gt;import&lt;/span&gt; fsolve

f = interp1d(T, Gliq - Ggas)
bp, = fsolve(f, 373)
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; &lt;span style="color: #228b22;"&gt;'The boiling point is {0} K'&lt;/span&gt;.format(bp)
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
&amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; ... ... ... ... ... ... ... &amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; ... ... ... ... ... ... ... &amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; ... &amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; The boiling point is 373.206081312 K
&lt;/pre&gt;

&lt;div class="org-src-container"&gt;

&lt;pre class="src src-python"&gt;plt.figure(); plt.clf()
plt.plot(T-273.15, Gliq, T-273.15, Ggas)
plt.legend([&lt;span style="color: #228b22;"&gt;'liquid water'&lt;/span&gt;, &lt;span style="color: #228b22;"&gt;'steam'&lt;/span&gt;])

plt.xlabel(&lt;span style="color: #228b22;"&gt;'Temperature $^\circ$C'&lt;/span&gt;)
plt.ylabel(&lt;span style="color: #228b22;"&gt;'$\Delta G$ (kJ/mol)'&lt;/span&gt;)
plt.title(&lt;span style="color: #228b22;"&gt;'The boiling point is approximately {0:1.2f} $^\circ$C'&lt;/span&gt;.format(bp-273.15))
plt.savefig(&lt;span style="color: #228b22;"&gt;'images/boiling-water.png'&lt;/span&gt;)
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
&amp;lt;matplotlib.figure.Figure object at 0x050D2E30&amp;gt;
[&amp;lt;matplotlib.lines.Line2D object at 0x051AB610&amp;gt;, &amp;lt;matplotlib.lines.Line2D object at 0x051B4C90&amp;gt;]
&amp;lt;matplotlib.legend.Legend object at 0x051B9030&amp;gt;
&amp;gt;&amp;gt;&amp;gt; &amp;lt;matplotlib.text.Text object at 0x0519E390&amp;gt;
&amp;lt;matplotlib.text.Text object at 0x050FB390&amp;gt;
&amp;lt;matplotlib.text.Text object at 0x050FBFB0&amp;gt;
&lt;/pre&gt;

&lt;p&gt;&lt;img src="/img/./images/boiling-water.png"&gt;&lt;p&gt;

&lt;div id="outline-container-1" class="outline-2"&gt;
&lt;h2 id="sec-1"&gt;&lt;span class="section-number-2"&gt;1&lt;/span&gt; Summary&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-1"&gt;
&lt;p&gt;
The answer we get us 0.05 K too high, which is not bad considering we estimated it using parameters that were fitted to thermodynamic data and that had finite precision and extrapolated the steam properties below the region the parameters were stated to be valid for.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Copyright (C) 2013 by John Kitchin. See the &lt;a href="/copying.html"&gt;License&lt;/a&gt; for information about copying.&lt;p&gt;&lt;p&gt;&lt;a href="/org/2013/02/04/Estimating-the-boiling-point-of-water.org"&gt;org-mode source&lt;/a&gt;&lt;p&gt;]]></content:encoded>
    </item>
  </channel>
</rss>
