<?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>Modeling a transient plug flow reactor</title>
      <link>https://kitchingroup.cheme.cmu.edu/blog/2013/03/06/Modeling-a-transient-plug-flow-reactor</link>
      <pubDate>Wed, 06 Mar 2013 15:51:44 EST</pubDate>
      <category><![CDATA[animation]]></category>
      <category><![CDATA[pde]]></category>
      <guid isPermaLink="false">oErIFo4T0c1Hz6pZAjHjrMITWks=</guid>
      <description>Modeling a transient plug flow reactor</description>
      <content:encoded><![CDATA[


&lt;p&gt;
&lt;a href="http://matlab.cheme.cmu.edu/2011/11/17/modeling-a-transient-plug-flow-reactor" &gt;Matlab post&lt;/a&gt;



&lt;/p&gt;

&lt;p&gt;
The PDE that describes the transient behavior of a plug flow reactor with constant volumetric flow rate is:
&lt;/p&gt;

&lt;p&gt;
\( \frac{\partial C_A}{\partial dt} = -\nu_0 \frac{\partial C_A}{\partial dV} + r_A \).
&lt;/p&gt;

&lt;p&gt;
To solve this numerically in python, we will utilize the method of lines. The idea is to discretize the reactor in volume, and approximate the spatial derivatives by finite differences. Then we will have a set of coupled ordinary differential equations that can be solved in the usual way. Let us simplify the notation with \(C = C_A\), and let \(r_A = -k C^2\). Graphically this looks like this:
&lt;/p&gt;

&lt;p&gt;&lt;img src="/img/./images/pde-method-of-lines.png"&gt;&lt;p&gt;

&lt;p&gt;
This leads to the following set of equations:
&lt;/p&gt;

\begin{eqnarray}
\frac{dC_0}{dt} &amp;=&amp; 0 \text{ (entrance concentration never changes)} \\
\frac{dC_1}{dt} &amp;=&amp; -\nu_0 \frac{C_1 - C_0}{V_1 - V_0} - k C_1^2 \\
\frac{dC_2}{dt} &amp;=&amp; -\nu_0 \frac{C_2 - C_1}{V_2 - V_1} - k C_2^2 \\
\vdots \\
\frac{dC_4}{dt} &amp;=&amp; -\nu_0 \frac{C_4 - C_3}{V_4 - V_3} - k C_4^2 
\end{eqnarray}

&lt;p&gt;
Last, we need initial conditions for all the nodes in the discretization. Let us assume the reactor was full of empty solvent, so that \(C_i = 0\) at \(t=0\). In the next block of code, we get the transient solutions, and the steady state solution.
&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.integrate &lt;span style="color: #8b0000;"&gt;import&lt;/span&gt; odeint

Ca0 = 2     &lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;Entering concentration&lt;/span&gt;
vo = 2      &lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;volumetric flow rate&lt;/span&gt;
volume = 20 &lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;total volume of reactor, spacetime = 10&lt;/span&gt;
k = 1       &lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;reaction rate constant&lt;/span&gt;

N = 100     &lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;number of points to discretize the reactor volume on&lt;/span&gt;

init = np.zeros(N)    &lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;Concentration in reactor at t = 0&lt;/span&gt;
init[0] = Ca0         &lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;concentration at entrance&lt;/span&gt;

V = np.linspace(0, volume, N) &lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;discretized volume elements&lt;/span&gt;
tspan = np.linspace(0, 25)    &lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;time span to integrate over&lt;/span&gt;

&lt;span style="color: #8b0000;"&gt;def&lt;/span&gt; &lt;span style="color: #8b2323;"&gt;method_of_lines&lt;/span&gt;(C, t):
    &lt;span style="color: #228b22;"&gt;'coupled ODES at each node point'&lt;/span&gt;
    D = -vo * np.diff(C) / np.diff(V) - k * C[1:]**2
    &lt;span style="color: #8b0000;"&gt;return&lt;/span&gt; np.concatenate([[0], &lt;span style="color: #ff0000; font-weight: bold;"&gt;#&lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;C0 is constant at entrance&lt;/span&gt;
                            D])


sol = odeint(method_of_lines, init, tspan)

&lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;steady state solution&lt;/span&gt;
&lt;span style="color: #8b0000;"&gt;def&lt;/span&gt; &lt;span style="color: #8b2323;"&gt;pfr&lt;/span&gt;(C, V):
    &lt;span style="color: #8b0000;"&gt;return&lt;/span&gt; 1.0 / vo * (-k * C**2)

ssol = odeint(pfr, Ca0, V)
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
The transient solution contains the time dependent behavior of each node in the discretized reactor. Each row contains the concentration as a function of volume at a specific time point. For example, we can plot the concentration of A at the exit vs. time (that is, the last entry of each row) as:
&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
plt.plot(tspan, sol[:, -1])
plt.xlabel(&lt;span style="color: #228b22;"&gt;'time'&lt;/span&gt;)
plt.ylabel(&lt;span style="color: #228b22;"&gt;'$C_A$ at exit'&lt;/span&gt;)
plt.savefig(&lt;span style="color: #228b22;"&gt;'images/transient-pfr-1.png'&lt;/span&gt;)
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
[&amp;lt;matplotlib.lines.Line2D object at 0x05A18830&amp;gt;]
&amp;lt;matplotlib.text.Text object at 0x059FE1D0&amp;gt;
&amp;lt;matplotlib.text.Text object at 0x05A05270&amp;gt;
&lt;/pre&gt;


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

&lt;p&gt;
After approximately one space time, the steady state solution is reached at the exit. For completeness, we also examine the steady state solution.
&lt;/p&gt;
&lt;div class="org-src-container"&gt;

&lt;pre class="src src-python"&gt;plt.figure()
plt.plot(V, ssol, label=&lt;span style="color: #228b22;"&gt;'Steady state'&lt;/span&gt;)
plt.plot(V, sol[-1], label=&lt;span style="color: #228b22;"&gt;'t = {}'&lt;/span&gt;.format(tspan[-1]))
plt.xlabel(&lt;span style="color: #228b22;"&gt;'Volume'&lt;/span&gt;)
plt.ylabel(&lt;span style="color: #228b22;"&gt;'$C_A$'&lt;/span&gt;)
plt.legend(loc=&lt;span style="color: #228b22;"&gt;'best'&lt;/span&gt;)
plt.savefig(&lt;span style="color: #228b22;"&gt;'images/transient-pfr-2.png'&lt;/span&gt;)
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;&lt;img src="/img/./images/transient-pfr-2.png"&gt;&lt;p&gt;

&lt;p&gt;
There is some minor disagreement between the final transient solution and the steady state solution. That is due to the approximation in discretizing the reactor volume. In this example we used 100 nodes. You get better agreement with a larger number of nodes, say 200 or more. Of course, it takes slightly longer to compute then, since the number of coupled odes is equal to the number of nodes.
&lt;/p&gt;

&lt;p&gt;
We can also create an animated gif to show how the concentration of A throughout the reactor varies with time. Note, I had to install ffmpeg (&lt;a href="http://ffmpeg.org/" &gt;http://ffmpeg.org/&lt;/a&gt;) to save the animation.
&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; matplotlib &lt;span style="color: #8b0000;"&gt;import&lt;/span&gt; animation

&lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;make empty figure&lt;/span&gt;
fig = plt.figure()
ax = plt.axes(xlim=(0, 20), ylim=(0, 2))
line, = ax.plot(V, init, lw=2)

&lt;span style="color: #8b0000;"&gt;def&lt;/span&gt; &lt;span style="color: #8b2323;"&gt;animate&lt;/span&gt;(i):
    line.set_xdata(V)
    line.set_ydata(sol[i])
    ax.set_title(&lt;span style="color: #228b22;"&gt;'t = {0}'&lt;/span&gt;.format(tspan[i]))
    ax.figure.canvas.draw() 
    &lt;span style="color: #8b0000;"&gt;return&lt;/span&gt; line,
    

anim = animation.FuncAnimation(fig, animate, frames=50,  blit=&lt;span style="color: #8b0000;"&gt;True&lt;/span&gt;)

anim.save(&lt;span style="color: #228b22;"&gt;'images/transient_pfr.mp4'&lt;/span&gt;, fps=10)
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
&lt;a href="http://jkitchin.github.com/media/transient_pfr.mp4" &gt;http://jkitchin.github.com/media/transient_pfr.mp4&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;
You can see from the animation that after about 10 time units, the solution is not changing further, suggesting steady state has been reached.
&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/03/06/Modeling-a-transient-plug-flow-reactor.org"&gt;org-mode source&lt;/a&gt;&lt;p&gt;]]></content:encoded>
    </item>
    <item>
      <title>Determining linear independence of a set of vectors</title>
      <link>https://kitchingroup.cheme.cmu.edu/blog/2013/03/01/Determining-linear-independence-of-a-set-of-vectors</link>
      <pubDate>Fri, 01 Mar 2013 16:44:46 EST</pubDate>
      <category><![CDATA[linear algebra]]></category>
      <guid isPermaLink="false">4sPRgpQwos2UdI1hgVqlmfRMzHc=</guid>
      <description>Determining linear independence of a set of vectors</description>
      <content:encoded><![CDATA[


&lt;p&gt;
 &lt;a href="http://matlab.cheme.cmu.edu/2011/08/02/determining-linear-independence-of-a-set-of-vectors/" &gt;Matlab post&lt;/a&gt;
Occasionally we have a set of vectors and we need to determine whether the vectors are linearly independent of each other. This may be necessary to determine if the vectors form a basis, or to determine how many independent equations there are, or to determine how many independent reactions there are.
&lt;/p&gt;

&lt;p&gt;
Reference: Kreysig, Advanced Engineering Mathematics, sec. 7.4
&lt;/p&gt;

&lt;p&gt;
Matlab provides a rank command which gives you the number of singular values greater than some tolerance. The numpy.rank function, unfortunately, does not do that. It returns the number of dimensions in the array. We will just compute the rank from singular value decomposition.
&lt;/p&gt;

&lt;p&gt;
The default tolerance used in Matlab is max(size(A))*eps(norm(A)). Let us break that down. eps(norm(A)) is the positive distance from abs(X) to the next larger in magnitude floating point number of the same precision as X. Basically, the smallest significant number. We multiply that by the size of A, and take the largest number. We have to use some judgment in what the tolerance is, and what &amp;ldquo;zero&amp;rdquo; means.
&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
v1 = [6, 0, 3, 1, 4, 2];
v2 = [0, -1, 2, 7, 0, 5];
v3 = [12, 3, 0, -19, 8, -11];

A = np.row_stack([v1, v2, v3])

&lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;matlab definition&lt;/span&gt;
eps = np.finfo(np.linalg.norm(A).dtype).eps
TOLERANCE = &lt;span style="color: #8b0000;"&gt;max&lt;/span&gt;(eps * np.array(A.shape))

U, s, V = np.linalg.svd(A)
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; s
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; np.sum(s &amp;gt; TOLERANCE)

TOLERANCE = 1e-14
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; np.sum(s &amp;gt; TOLERANCE)
&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; [  2.75209239e+01   9.30584482e+00   1.42425400e-15]
3
&amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; 2
&lt;/pre&gt;

&lt;p&gt;
You can see if you choose too small a TOLERANCE, nothing looks like zero. the result with TOLERANCE=1e-14 suggests the rows are not linearly independent. Let us show that one row can be expressed as a linear combination of the other rows. 
&lt;/p&gt;

&lt;p&gt;
The number of rows is greater than the rank, so these vectors are not
independent. Let's demonstrate that one vector can be defined as a linear
combination of the other two vectors. Mathematically we represent this
as:
&lt;/p&gt;

&lt;p&gt;
\(x_1 \mathit{v1} + x_2 \mathit{v2} = v3\)
&lt;/p&gt;

&lt;p&gt;
or
&lt;/p&gt;

&lt;p&gt;
\([x_1 x_2][v1; v2] = v3\)
&lt;/p&gt;

&lt;p&gt;
This is not the usual linear algebra form of Ax = b. To get there, we
transpose each side of the equation to get:
&lt;/p&gt;

&lt;p&gt;
[v1.T v2.T][x_1; x_2] = v3.T
&lt;/p&gt;

&lt;p&gt;
which is the form Ax = b. We solve it in a least-squares sense.
&lt;/p&gt;

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

&lt;pre class="src src-python"&gt;A = np.column_stack([v1, v2])
x = np.linalg.lstsq(A, v3)
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; x[0]
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
&amp;gt;&amp;gt;&amp;gt; [ 2. -3.]
&lt;/pre&gt;

&lt;p&gt;
This shows that v3 = 2*v1 - 3*v2
&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; another example&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-1"&gt;
&lt;div class="org-src-container"&gt;

&lt;pre class="src src-python"&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;#&lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;Problem set 7.4 #17&lt;/span&gt;
&lt;span style="color: #8b0000;"&gt;import&lt;/span&gt; numpy &lt;span style="color: #8b0000;"&gt;as&lt;/span&gt; np

v1 = [0.2, 1.2, 5.3, 2.8, 1.6]
v2 = [4.3, 3.4, 0.9, 2.0, -4.3]

A = np.row_stack([v1, v2])
U, s, V = np.linalg.svd(A)
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; s
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
[ 7.57773162  5.99149259]
&lt;/pre&gt;

&lt;p&gt;
You can tell by inspection the rank is 2 because there are no near-zero singular values. 
&lt;/p&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; Near deficient rank&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-2"&gt;
&lt;p&gt;
the rank command roughly works in the following way: the matrix is converted to a reduced row echelon form, and then the number of rows that are not all equal to zero are counted. Matlab uses a tolerance to determine what is equal to zero. If there is uncertainty in the numbers, you may have to define what zero is, e.g. if the absolute value of a number is less than 1e-5, you may consider that close enough to be zero. The default tolerance is usually very small, of order 1e-15. If we believe that any number less than 1e-5 is practically equivalent to zero, we can use that information to compute the rank like this.
&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

A = [[1, 2, 3],
     [0, 2, 3],
     [0, 0, 1e-6]]

U, s, V = np.linalg.svd(A)
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; s
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; np.sum(np.abs(s) &amp;gt; 1e-15)
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; np.sum(np.abs(s) &amp;gt; 1e-5)
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
[  5.14874857e+00   7.00277208e-01   5.54700196e-07]
3
2
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id="outline-container-3" class="outline-2"&gt;
&lt;h2 id="sec-3"&gt;&lt;span class="section-number-2"&gt;3&lt;/span&gt; Application to independent chemical reactions.&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-3"&gt;
&lt;p&gt;
reference: Exercise 2.4 in Chemical Reactor Analysis and Design Fundamentals by Rawlings and Ekerdt.
&lt;/p&gt;

&lt;p&gt;
The following reactions are proposed in the hydrogenation of bromine:
&lt;/p&gt;

&lt;p&gt;
Let this be our species vector: v = [H2 H Br2 Br HBr].T
&lt;/p&gt;

&lt;p&gt;
the reactions are then defined by M*v where M is a stoichometric matrix in which each row represents a reaction with negative stoichiometric coefficients for reactants, and positive stoichiometric coefficients for products. A stoichiometric coefficient of 0 is used for species not participating in the reaction.
&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: #ff0000; font-weight: bold;"&gt;#    &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;[H2  H Br2 Br HBr]&lt;/span&gt;
M = [[-1,  0, -1,  0,  2],  &lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;H2 + Br2 == 2HBR&lt;/span&gt;
     [ 0,  0, -1,  2,  0],  &lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;Br2 == 2Br&lt;/span&gt;
     [-1,  1,  0, -1,  1],  &lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;Br + H2 == HBr + H&lt;/span&gt;
     [ 0, -1, -1,  1,  1],  &lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;H + Br2 == HBr + Br&lt;/span&gt;
     [ 1, -1,  0,  1,  -1], &lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;H + HBr == H2 + Br&lt;/span&gt;
     [ 0,  0,  1, -2,  0]]  &lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;2Br == Br2&lt;/span&gt;

U, s, V = np.linalg.svd(M)
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; s
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; np.sum(np.abs(s) &amp;gt; 1e-15)

&lt;span style="color: #8b0000;"&gt;import&lt;/span&gt; sympy 
M = sympy.Matrix(M)
reduced_form, inds = M.rref()

&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; reduced_form

labels = [&lt;span style="color: #228b22;"&gt;'H2'&lt;/span&gt;,  &lt;span style="color: #228b22;"&gt;'H'&lt;/span&gt;, &lt;span style="color: #228b22;"&gt;'Br2'&lt;/span&gt;, &lt;span style="color: #228b22;"&gt;'Br'&lt;/span&gt;, &lt;span style="color: #228b22;"&gt;'HBr'&lt;/span&gt;]
&lt;span style="color: #8b0000;"&gt;for&lt;/span&gt; row &lt;span style="color: #8b0000;"&gt;in&lt;/span&gt; reduced_form.tolist():
    s = &lt;span style="color: #228b22;"&gt;'0 = '&lt;/span&gt;
    &lt;span style="color: #8b0000;"&gt;for&lt;/span&gt; nu,species &lt;span style="color: #8b0000;"&gt;in&lt;/span&gt; &lt;span style="color: #8b0000;"&gt;zip&lt;/span&gt;(row,labels):
        &lt;span style="color: #8b0000;"&gt;if&lt;/span&gt; nu != 0:
            
            s += &lt;span style="color: #228b22;"&gt;' {0:+d}{1}'&lt;/span&gt;.format(&lt;span style="color: #8b0000;"&gt;int&lt;/span&gt;(nu), species)
    &lt;span style="color: #8b0000;"&gt;if&lt;/span&gt; s != &lt;span style="color: #228b22;"&gt;'0 = '&lt;/span&gt;: &lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; s
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
[  3.84742803e+00   3.32555975e+00   1.46217301e+00   1.73313660e-16
   8.57422679e-17]
3
[1, 0, 0,  2, -2]
[0, 1, 0,  1, -1]
[0, 0, 1, -2,  0]
[0, 0, 0,  0,  0]
[0, 0, 0,  0,  0]
[0, 0, 0,  0,  0]
0 =  +1H2 +2Br -2HBr
0 =  +1H +1Br -1HBr
0 =  +1Br2 -2Br
&lt;/pre&gt;

&lt;p&gt;
6 reactions are given, but the rank of the matrix is only 3. so there
are only three independent reactions. You can see that reaction 6 is just
the opposite of reaction 2, so it is clearly not independent. Also,
reactions 3 and 5 are just the reverse of each other, so one of them can
also be eliminated. finally, reaction 4 is equal to reaction 1 minus
reaction 3.
&lt;/p&gt;

&lt;p&gt;
There are many possible independent reactions. In the code above, we use sympy to put the matrix into reduced row echelon form, which enables us to identify three independent reactions, and shows that three rows are all zero, i.e. they are not independent of the other three reactions. The choice of independent reactions is not unique.
&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/03/01/Determining-linear-independence-of-a-set-of-vectors.org"&gt;org-mode source&lt;/a&gt;&lt;p&gt;]]></content:encoded>
    </item>
    <item>
      <title>Conservation of mass in chemical reactions</title>
      <link>https://kitchingroup.cheme.cmu.edu/blog/2013/02/27/Conservation-of-mass-in-chemical-reactions</link>
      <pubDate>Wed, 27 Feb 2013 10:54:08 EST</pubDate>
      <category><![CDATA[linear algebra]]></category>
      <guid isPermaLink="false">2ODnefiZYh22emOG_OKqU6d3-ns=</guid>
      <description>Conservation of mass in chemical reactions</description>
      <content:encoded><![CDATA[


&lt;p&gt;
&lt;a href="http://matlab.cheme.cmu.edu/2011/12/18/conservation-of-mass-in-chemical-reactions/" &gt;Matlab post&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;
Atoms cannot be destroyed in non-nuclear chemical reactions, hence it follows that the same number of atoms entering a reactor must also leave the reactor. The atoms may leave the reactor in a different molecular configuration due to the reaction, but the total mass leaving the reactor must be the same. Here we look at a few ways to show this.
&lt;/p&gt;

&lt;p&gt;
We consider the water gas shift reaction : \(CO + H_2O \rightleftharpoons H_2 + CO_2\). We can illustrate the conservation of mass with the following equation: \(\bf{\nu}\bf{M}=\bf{0}\). Where \(\bf{\nu}\) is the stoichiometric coefficient vector and \(\bf{M}\) is a column vector of molecular weights. For simplicity, we use pure isotope molecular weights, and not the isotope-weighted molecular weights. This equation simply examines the mass on the right side of the equation and the mass on left side of the equation. 
&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
nu = [-1, -1, 1, 1];
M = [28, 18, 2, 44];
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; np.dot(nu, M)
&lt;/pre&gt;
&lt;/div&gt;

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

&lt;p&gt;
You can see that sum of the stoichiometric coefficients times molecular weights is zero. In other words a CO and H_2O have the same mass as H_2 and CO_2.
&lt;/p&gt;

&lt;p&gt;
For any balanced chemical equation, there are the same number of each kind of atom on each side of the equation. Since the mass of each atom is unchanged with reaction, that means the mass of all the species that are reactants must equal the mass of all the species that are products! Here we look at the number of C, O, and H on each side of the reaction. Now if we add the mass of atoms in the reactants and products, it should sum to zero (since we used the negative sign for stoichiometric coefficients of reactants).
&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: #ff0000; font-weight: bold;"&gt;# C   O   H&lt;/span&gt;
reactants = [-1, -2, -2]
products  = [ 1,  2,  2]

atomic_masses = [12.011, 15.999, 1.0079]  &lt;span style="color: #ff0000; font-weight: bold;"&gt;# atomic masses&lt;/span&gt;

&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; np.dot(reactants, atomic_masses) + np.dot(products, atomic_masses)
&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; 0.0
&lt;/pre&gt;

&lt;p&gt;
That is all there is to mass conservation with reactions. Nothing changes if there are lots of reactions, as long as each reaction is properly balanced, and none of them are nuclear reactions!
&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/Conservation-of-mass-in-chemical-reactions.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>Plug flow reactor with a pressure drop</title>
      <link>https://kitchingroup.cheme.cmu.edu/blog/2013/02/18/Plug-flow-reactor-with-a-pressure-drop</link>
      <pubDate>Mon, 18 Feb 2013 09:00:00 EST</pubDate>
      <category><![CDATA[ode]]></category>
      <guid isPermaLink="false">64GviaCGZ1MfVbLqmhN8NSume9U=</guid>
      <description>Plug flow reactor with a pressure drop</description>
      <content:encoded><![CDATA[



&lt;p&gt;
If there is a pressure drop in a plug flow reactor, &lt;sup&gt;&lt;a id="fnr.1" name="fnr.1" class="footref" href="#fn.1"&gt;1&lt;/a&gt;&lt;/sup&gt; there are two equations needed to determine the exit conversion: one for the conversion, and one from the pressure drop.
&lt;/p&gt;

\begin{eqnarray}
\frac{dX}{dW} &amp;=&amp; \frac{k'}{F_A0} \left ( \frac{1-X}{1 + \epsilon X} \right) y\\
\frac{dX}{dy} &amp;=&amp; -\frac{\alpha (1 + \epsilon X)}{2y}
\end{eqnarray}

&lt;p&gt;
Here is how to integrate these equations numerically in python.
&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.integrate &lt;span style="color: #8b0000;"&gt;import&lt;/span&gt; odeint
&lt;span style="color: #8b0000;"&gt;import&lt;/span&gt; matplotlib.pyplot &lt;span style="color: #8b0000;"&gt;as&lt;/span&gt; plt

kprime = 0.0266
Fa0 = 1.08
alpha = 0.0166
epsilon = -0.15

&lt;span style="color: #8b0000;"&gt;def&lt;/span&gt; &lt;span style="color: #8b2323;"&gt;dFdW&lt;/span&gt;(F, W):
    &lt;span style="color: #228b22;"&gt;'set of ODEs to integrate'&lt;/span&gt;
    X = F[0]
    y = F[1]
    dXdW = kprime / Fa0 * (1-X) / (1 + epsilon*X) * y
    dydW = -alpha * (1 + epsilon * X) / (2 * y)
    &lt;span style="color: #8b0000;"&gt;return&lt;/span&gt; [dXdW, dydW]

Wspan = np.linspace(0,60)
X0 = 0.0
y0 = 1.0
F0 = [X0, y0]
sol = odeint(dFdW, F0, Wspan)

&lt;span style="color: #ff0000; font-weight: bold;"&gt;# now plot the results&lt;/span&gt;
plt.plot(Wspan, sol[:,0], label=&lt;span style="color: #228b22;"&gt;'Conversion'&lt;/span&gt;)
plt.plot(Wspan, sol[:,1], &lt;span style="color: #228b22;"&gt;'g--'&lt;/span&gt;, label=&lt;span style="color: #228b22;"&gt;'y=$P/P_0$'&lt;/span&gt;)
plt.legend(loc=&lt;span style="color: #228b22;"&gt;'best'&lt;/span&gt;)
plt.xlabel(&lt;span style="color: #228b22;"&gt;'Catalyst weight (lb_m)'&lt;/span&gt;)
plt.savefig(&lt;span style="color: #228b22;"&gt;'images/2013-01-08-pdrop.png'&lt;/span&gt;)
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
Here is the resulting figure.
&lt;/p&gt;

&lt;p&gt;&lt;img src="/img/./images/2013-01-08-pdrop.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/18/Plug-flow-reactor-with-a-pressure-drop.org"&gt;org-mode source&lt;/a&gt;&lt;p&gt;]]></content:encoded>
    </item>
    <item>
      <title>Solving CSTR design equations</title>
      <link>https://kitchingroup.cheme.cmu.edu/blog/2013/02/18/Solving-CSTR-design-equations</link>
      <pubDate>Mon, 18 Feb 2013 09:00:00 EST</pubDate>
      <category><![CDATA[nonlinear algebra]]></category>
      <guid isPermaLink="false">BW3kShDPxaW0cPOTB-sgqJTkv8M=</guid>
      <description>Solving CSTR design equations</description>
      <content:encoded><![CDATA[



&lt;p&gt;
Given a continuously stirred tank reactor with a volume of 66,000 dm^3 where the reaction \(A \rightarrow B\) occurs, at a rate of \(-r_A = k C_A^2\) (\(k=3\) L/mol/h), with an entering molar flow of F_{A0} = 5 mol/h and a volumetric flowrate of 10 L/h, what is the exit concentration of A?
&lt;/p&gt;

&lt;p&gt;
From a mole balance we know that at steady state \(0 = F_{A0} - F_A + V r_A\). That equation simply states the sum of the molar flow of A in in minus the molar flow of A out  plus the molar rate A is generated is equal to zero at steady state. This is directly the equation we need to solve. We need the following relationship:
&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;\(F_A = v0 C_A\)
&lt;/li&gt;
&lt;/ol&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

Fa0 = 5.0
v0 = 10.

V = 66000.0  &lt;span style="color: #ff0000; font-weight: bold;"&gt;# reactor volume L^3&lt;/span&gt;
k = 3.0      &lt;span style="color: #ff0000; font-weight: bold;"&gt;# rate constant L/mol/h&lt;/span&gt;

&lt;span style="color: #8b0000;"&gt;def&lt;/span&gt; &lt;span style="color: #8b2323;"&gt;func&lt;/span&gt;(Ca):
    &lt;span style="color: #228b22;"&gt;"Mole balance for a CSTR. Solve this equation for func(Ca)=0"&lt;/span&gt;
    Fa = v0 * Ca     &lt;span style="color: #ff0000; font-weight: bold;"&gt;# exit molar flow of A&lt;/span&gt;
    ra = -k * Ca**2  &lt;span style="color: #ff0000; font-weight: bold;"&gt;# rate of reaction of A L/mol/h&lt;/span&gt;
    &lt;span style="color: #8b0000;"&gt;return&lt;/span&gt; Fa0 - Fa + V * ra

&lt;span style="color: #ff0000; font-weight: bold;"&gt;# CA guess that that 90 % is reacted away&lt;/span&gt;
CA_guess = 0.1 * Fa0 / v0
CA_sol, = fsolve(func, CA_guess)

&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; &lt;span style="color: #228b22;"&gt;'The exit concentration is {0} mol/L'&lt;/span&gt;.format(CA_sol)
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
The exit concentration is 0.005 mol/L
&lt;/pre&gt;

&lt;p&gt;
It is a little confusing why it is necessary to put a comma after the CA_sol in the fsolve command. If you do not put it there, you get brackets around the answer.
&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/18/Solving-CSTR-design-equations.org"&gt;org-mode source&lt;/a&gt;&lt;p&gt;]]></content:encoded>
    </item>
    <item>
      <title>Integrating the batch reactor mole balance</title>
      <link>https://kitchingroup.cheme.cmu.edu/blog/2013/02/18/Integrating-the-batch-reactor-mole-balance</link>
      <pubDate>Mon, 18 Feb 2013 09:00:00 EST</pubDate>
      <category><![CDATA[ode]]></category>
      <guid isPermaLink="false">REjhsFgQ-T7H7xFdpSog1o6_MDk=</guid>
      <description>Integrating the batch reactor mole balance</description>
      <content:encoded><![CDATA[



&lt;p&gt;
An alternative approach of evaluating an integral is to integrate a differential equation. For the batch reactor, the differential equation that describes conversion as a function of time is:
&lt;/p&gt;

&lt;p&gt;
\(\frac{dX}{dt} = -r_A V/N_{A0}\).
&lt;/p&gt;

&lt;p&gt;
Given a value of initial concentration, or volume and initial number of moles of A, we can integrate this ODE to find the conversion at some later time. We assume that \(X(t=0)=0\). We will integrate the ODE over a time span of 0 to 10,000 seconds.
&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; odeint
&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

k = 1.0e-3
Ca0 = 1.0  &lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;mol/L&lt;/span&gt;

&lt;span style="color: #8b0000;"&gt;def&lt;/span&gt; &lt;span style="color: #8b2323;"&gt;func&lt;/span&gt;(X, t):
    ra = -k * (Ca0 * (1 - X))**2
    &lt;span style="color: #8b0000;"&gt;return&lt;/span&gt; -ra / Ca0

X0 = 0
tspan = np.linspace(0,10000)

sol = odeint(func, X0, tspan)
plt.plot(tspan,sol)
plt.xlabel(&lt;span style="color: #228b22;"&gt;'Time (sec)'&lt;/span&gt;)
plt.ylabel(&lt;span style="color: #228b22;"&gt;'Conversion'&lt;/span&gt;)
plt.savefig(&lt;span style="color: #228b22;"&gt;'images/2013-01-06-batch-conversion.png'&lt;/span&gt;)
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;&lt;img src="/img/./images/2013-01-06-batch-conversion.png"&gt;&lt;p&gt;

&lt;p&gt;
You can read off of this figure to find the time required to achieve a particular conversion.
&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/18/Integrating-the-batch-reactor-mole-balance.org"&gt;org-mode source&lt;/a&gt;&lt;p&gt;
]]></content:encoded>
    </item>
    <item>
      <title>Numerically calculating an effectiveness factor for a porous catalyst bead</title>
      <link>https://kitchingroup.cheme.cmu.edu/blog/2013/02/13/Numerically-calculating-an-effectiveness-factor-for-a-porous-catalyst-bead</link>
      <pubDate>Wed, 13 Feb 2013 09:00:00 EST</pubDate>
      <category><![CDATA[bvp]]></category>
      <guid isPermaLink="false">PdbWZ12FzwgkK-OS8ykGwpzMUbk=</guid>
      <description>Numerically calculating an effectiveness factor for a porous catalyst bead</description>
      <content:encoded><![CDATA[


&lt;p&gt;
&lt;a href="http://matlab.cheme.cmu.edu/2011/11/18/numerically-calculating-an-effectiveness-factor-for-a-porous-catalyst-bead/"&gt;Matlab post&lt;/a&gt; 
&lt;/p&gt;

&lt;p&gt;
If reaction rates are fast compared to diffusion in a porous catalyst pellet, then the observed kinetics will appear to be slower than they really are because not all of the catalyst surface area will be effectively used. For example, the reactants may all be consumed in the near surface area of a catalyst bead, and the inside of the bead will be unutilized because no reactants can get in due to the high reaction rates.
&lt;/p&gt;

&lt;p&gt;
References: Ch 12. Elements of Chemical Reaction Engineering, Fogler, 4th edition.
&lt;/p&gt;

&lt;p&gt;
A mole balance on the particle volume in spherical coordinates with a first order reaction leads to: &lt;img src="ltxpng/pycse_583fc34e02e0dfd7da742eac8e08f28fed0361fb.png" alt="$\frac{d^2Ca}{dr^2} + \frac{2}{r}\frac{dCa}{dr}-\frac{k}{D_e}C_A=0$" /&gt; with boundary conditions &lt;img src="ltxpng/pycse_4866f4778622517d6c40909af5ddee8dd88e4e4b.png" alt="$C_A(R) = C_{As}$" /&gt; and &lt;img src="ltxpng/pycse_c6b9758ef2d8da94f97a02ad3267e4546ed89545.png" alt="$\frac{dCa}{dr}=0$" /&gt; at &lt;img src="ltxpng/pycse_bf76d476f1b6de028534649c02962bb23035ae13.png" alt="$r=0$" /&gt;. We convert this equation to a system of first order ODEs by letting &lt;img src="ltxpng/pycse_cfb3bc1cb2bda6ef332c07d9817aeac44dea4a8b.png" alt="$W_A=\frac{dCa}{dr}$" /&gt;. Then, our two equations become:
&lt;/p&gt;

&lt;p&gt;
&lt;img src="ltxpng/pycse_2a2b847add5fa85ab01423d270fe3702f9eafe38.png" alt="\(\frac{dCa}{dr} = W_A\)" /&gt;
&lt;/p&gt;

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

&lt;p&gt;
&lt;img src="ltxpng/pycse_d57c97774a37a7ef56fbf47b93b687c9f5d6725e.png" alt="\(\frac{dW_A}{dr} = -\frac{2}{r} W_A + \frac{k}{D_E} C_A\)" /&gt;
&lt;/p&gt;

&lt;p&gt;
We have a condition of no flux (&lt;img src="ltxpng/pycse_f8e519b8e68d9b04fdd5ceee6a4c5b1ce76f1a7c.png" alt="$W_A=0$" /&gt;) at r=0 and Ca(R) = CAs, which makes this a boundary value problem. We use the shooting method here, and guess what Ca(0) is and iterate the guess to get Ca(R) = CAs.
&lt;/p&gt;

&lt;p&gt;
The value of the second differential equation at r=0 is tricky because at this place we have a 0/0 term. We use L'Hopital's rule to evaluate it. The derivative of the top is &lt;img src="ltxpng/pycse_092ea3e2d18ee85c9af99247db455c8cccce06d5.png" alt="$\frac{dW_A}{dr}$" /&gt; and the derivative of the bottom is 1. So, we have 
&lt;img src="ltxpng/pycse_5460cdb1bbc54e713c3a2f9bb18a2126a0575198.png" alt="\(\frac{dW_A}{dr} = -2\frac{dW_A}{dr} + \frac{k}{D_E} C_A\)" /&gt;
&lt;/p&gt;

&lt;p&gt;
Which leads to:
&lt;/p&gt;

&lt;p&gt;
&lt;img src="ltxpng/pycse_195ccf4fd71c1883fbc31726cb636c64719b6ddf.png" alt="\(3 \frac{dW_A}{dr} =  \frac{k}{D_E} C_A\)" /&gt;
&lt;/p&gt;

&lt;p&gt;
or &lt;img src="ltxpng/pycse_2d1800a78e5c0ef322ba22f10481b52c3b6bc358.png" alt="\(\frac{dW_A}{dr} =  \frac{3k}{D_E} C_A\)" /&gt; at &lt;img src="ltxpng/pycse_bf76d476f1b6de028534649c02962bb23035ae13.png" alt="$r=0$" /&gt;.
&lt;/p&gt;

&lt;p&gt;
Finally, we implement the equations in Python and solve.
&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;from&lt;/span&gt; scipy.integrate &lt;span style="color: #0000FF;"&gt;import&lt;/span&gt; odeint
&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: #333333; background-color: #FFFFFF; font-weight: bold;"&gt;De&lt;/span&gt; = &lt;span style="color: #333333; background-color: #FFFFFF; font-weight: bold;"&gt;0&lt;/span&gt;.&lt;span style="color: #333333; background-color: #FFFFFF; font-weight: bold;"&gt;1&lt;/span&gt;    &lt;span style="color: #8D8D84; font-weight: bold; font-style: italic;"&gt;# diffusivity cm^2/s&lt;/span&gt;
&lt;span style="color: #333333; background-color: #FFFFFF; font-weight: bold;"&gt;R&lt;/span&gt; = &lt;span style="color: #333333; background-color: #FFFFFF; font-weight: bold;"&gt;0&lt;/span&gt;.&lt;span style="color: #333333; background-color: #FFFFFF; font-weight: bold;"&gt;5&lt;/span&gt;    &lt;span style="color: #8D8D84; font-weight: bold; font-style: italic;"&gt;# particle radius, cm&lt;/span&gt;
&lt;span style="color: #333333; background-color: #FFFFFF; font-weight: bold;"&gt;k&lt;/span&gt; = &lt;span style="color: #333333; background-color: #FFFFFF; font-weight: bold;"&gt;6&lt;/span&gt;.&lt;span style="color: #333333; background-color: #FFFFFF; font-weight: bold;"&gt;4&lt;/span&gt;    &lt;span style="color: #8D8D84; font-weight: bold; font-style: italic;"&gt;# rate constant (1/s)&lt;/span&gt;
&lt;span style="color: #333333; background-color: #FFFFFF; font-weight: bold;"&gt;CAs&lt;/span&gt; = &lt;span style="color: #333333; background-color: #FFFFFF; font-weight: bold;"&gt;0&lt;/span&gt;.&lt;span style="color: #333333; background-color: #FFFFFF; font-weight: bold;"&gt;2&lt;/span&gt;   &lt;span style="color: #8D8D84; font-weight: bold; font-style: italic;"&gt;# concentration of A at outer radius of particle (mol/L)&lt;/span&gt;


&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;ode&lt;/span&gt;(Y, r):
    &lt;span style="color: #333333; background-color: #FFFFFF; font-weight: bold;"&gt;Wa&lt;/span&gt; = Y[&lt;span style="color: #333333; background-color: #FFFFFF; font-weight: bold;"&gt;0&lt;/span&gt;]  &lt;span style="color: #8D8D84; font-weight: bold; font-style: italic;"&gt;# molar rate of delivery of A to surface of particle&lt;/span&gt;
    &lt;span style="color: #333333; background-color: #FFFFFF; font-weight: bold;"&gt;Ca&lt;/span&gt; = Y[&lt;span style="color: #333333; background-color: #FFFFFF; font-weight: bold;"&gt;1&lt;/span&gt;]  &lt;span style="color: #8D8D84; font-weight: bold; font-style: italic;"&gt;# concentration of A in the particle at r&lt;/span&gt;
    &lt;span style="color: #8D8D84; font-weight: bold; font-style: italic;"&gt;# this solves the singularity at r = 0&lt;/span&gt;
    &lt;span style="color: #0000FF;"&gt;if&lt;/span&gt; &lt;span style="color: #333333; background-color: #FFFFFF; font-weight: bold;"&gt;r&lt;/span&gt; == &lt;span style="color: #333333; background-color: #FFFFFF; font-weight: bold;"&gt;0&lt;/span&gt;:
        &lt;span style="color: #333333; background-color: #FFFFFF; font-weight: bold;"&gt;dWadr&lt;/span&gt; = k / &lt;span style="color: #333333; background-color: #FFFFFF; font-weight: bold;"&gt;3&lt;/span&gt;.&lt;span style="color: #333333; background-color: #FFFFFF; font-weight: bold;"&gt;0&lt;/span&gt; * De * Ca
    &lt;span style="color: #0000FF;"&gt;else&lt;/span&gt;:
        &lt;span style="color: #333333; background-color: #FFFFFF; font-weight: bold;"&gt;dWadr&lt;/span&gt; = -&lt;span style="color: #333333; background-color: #FFFFFF; font-weight: bold;"&gt;2&lt;/span&gt; * Wa / r + k / De * Ca
    &lt;span style="color: #333333; background-color: #FFFFFF; font-weight: bold;"&gt;dCadr&lt;/span&gt; = Wa
    &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; [dWadr, dCadr]

&lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-weight: bold; font-style: italic;"&gt;Initial conditions&lt;/span&gt;
&lt;span style="color: #333333; background-color: #FFFFFF; font-weight: bold;"&gt;Ca0&lt;/span&gt; = &lt;span style="color: #333333; background-color: #FFFFFF; font-weight: bold;"&gt;0&lt;/span&gt;.&lt;span style="color: #333333; background-color: #FFFFFF; font-weight: bold;"&gt;029315&lt;/span&gt;  &lt;span style="color: #8D8D84; font-weight: bold; font-style: italic;"&gt;# Ca(0) (mol/L) guessed to satisfy Ca(R) = CAs&lt;/span&gt;
&lt;span style="color: #333333; background-color: #FFFFFF; font-weight: bold;"&gt;Wa0&lt;/span&gt; = &lt;span style="color: #333333; background-color: #FFFFFF; font-weight: bold;"&gt;0&lt;/span&gt;         &lt;span style="color: #8D8D84; font-weight: bold; font-style: italic;"&gt;# no flux at r=0 (mol/m^2/s)&lt;/span&gt;

&lt;span style="color: #333333; background-color: #FFFFFF; font-weight: bold;"&gt;rspan&lt;/span&gt; = np.linspace(&lt;span style="color: #333333; background-color: #FFFFFF; font-weight: bold;"&gt;0&lt;/span&gt;, R, &lt;span style="color: #333333; background-color: #FFFFFF; font-weight: bold;"&gt;500&lt;/span&gt;)

&lt;span style="color: #333333; background-color: #FFFFFF; font-weight: bold;"&gt;Y&lt;/span&gt; = odeint(ode, [Wa0, Ca0], rspan)

&lt;span style="color: #333333; background-color: #FFFFFF; font-weight: bold;"&gt;Ca&lt;/span&gt; = Y[:, &lt;span style="color: #333333; background-color: #FFFFFF; font-weight: bold;"&gt;1&lt;/span&gt;]

&lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-weight: bold; font-style: italic;"&gt;here we check that Ca(R) = Cas&lt;/span&gt;
&lt;span style="color: #0000FF;"&gt;print&lt;/span&gt; &lt;span style="color: #036A07;"&gt;'At r={0} Ca={1}'&lt;/span&gt;.format(rspan[-&lt;span style="color: #333333; background-color: #FFFFFF; font-weight: bold;"&gt;1&lt;/span&gt;], Ca[-&lt;span style="color: #333333; background-color: #FFFFFF; font-weight: bold;"&gt;1&lt;/span&gt;])

plt.plot(rspan, Ca)
plt.xlabel(&lt;span style="color: #036A07;"&gt;'Particle radius'&lt;/span&gt;)
plt.ylabel(&lt;span style="color: #036A07;"&gt;'$C_A$'&lt;/span&gt;)
plt.savefig(&lt;span style="color: #036A07;"&gt;'images/effectiveness-factor.png'&lt;/span&gt;)

&lt;span style="color: #333333; background-color: #FFFFFF; font-weight: bold;"&gt;r&lt;/span&gt; = rspan
&lt;span style="color: #333333; background-color: #FFFFFF; font-weight: bold;"&gt;eta_numerical&lt;/span&gt; = (np.trapz(k * Ca * &lt;span style="color: #333333; background-color: #FFFFFF; font-weight: bold;"&gt;4&lt;/span&gt; * np.pi * (r**&lt;span style="color: #333333; background-color: #FFFFFF; font-weight: bold;"&gt;2&lt;/span&gt;), r)
                 / np.trapz(k * CAs * &lt;span style="color: #333333; background-color: #FFFFFF; font-weight: bold;"&gt;4&lt;/span&gt; * np.pi * (r**&lt;span style="color: #333333; background-color: #FFFFFF; font-weight: bold;"&gt;2&lt;/span&gt;), r))

&lt;span style="color: #0000FF;"&gt;print&lt;/span&gt;(eta_numerical)

&lt;span style="color: #333333; background-color: #FFFFFF; font-weight: bold;"&gt;phi&lt;/span&gt; = R * np.sqrt(k / De)
&lt;span style="color: #333333; background-color: #FFFFFF; font-weight: bold;"&gt;eta_analytical&lt;/span&gt; = (&lt;span style="color: #333333; background-color: #FFFFFF; font-weight: bold;"&gt;3&lt;/span&gt; / phi**&lt;span style="color: #333333; background-color: #FFFFFF; font-weight: bold;"&gt;2&lt;/span&gt;) * (phi * (&lt;span style="color: #333333; background-color: #FFFFFF; font-weight: bold;"&gt;1&lt;/span&gt;.&lt;span style="color: #333333; background-color: #FFFFFF; font-weight: bold;"&gt;0&lt;/span&gt; / np.tanh(phi)) - &lt;span style="color: #333333; background-color: #FFFFFF; font-weight: bold;"&gt;1&lt;/span&gt;)
&lt;span style="color: #0000FF;"&gt;print&lt;/span&gt;(eta_analytical)
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
At r=0.5 Ca=0.200001488652
[&amp;lt;matplotlib.lines.Line2D object at 0x114275550&amp;gt;]
&amp;lt;matplotlib.text.Text object at 0x10d5fe890&amp;gt;
&amp;lt;matplotlib.text.Text object at 0x10d5ff890&amp;gt;
0.563011348314

0.563003362801
&lt;/pre&gt;


&lt;div class="figure"&gt;
&lt;p&gt;&lt;img src="/media/2013-02-13-Numerically-calculating-an-effectiveness-factor-for-a-porous-catalyst-bead/effectiveness-factor.png"&gt; 
&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;
You can see the concentration of A inside the particle is significantly lower than outside the particle. That is because it is reacting away faster than it can diffuse into the particle. Hence, the overall reaction rate in the particle is lower than it would be without the diffusion limit.
&lt;/p&gt;

&lt;p&gt;
The effectiveness factor is the ratio of the actual reaction rate in the particle with diffusion limitation to the ideal rate in the particle if there was no concentration gradient:
&lt;/p&gt;

&lt;p&gt;
&lt;img src="ltxpng/pycse_e4d91f15d6b5776f2048ac79e7be69f850f309e0.png" alt="$$\eta = \frac{\int_0^R k'' a C_A(r) 4 \pi r^2 dr}{\int_0^R k'' a C_{As} 4 \pi r^2 dr}$$" /&gt;
&lt;/p&gt;

&lt;p&gt;
We will evaluate this numerically from our solution and compare it to the analytical solution. The results are in good agreement, and you can make the numerical estimate better by increasing the number of points in the solution so that the numerical integration is more accurate.
&lt;/p&gt;

&lt;p&gt;
Why go through the numerical solution when an analytical solution exists? The analytical solution here is only good for 1st order kinetics in a sphere. What would you do for a complicated rate law? You might be able to find some limiting conditions where the analytical equation above is relevant, and if you are lucky, they are appropriate for your problem. If not, it is a good thing you can figure this out numerically!
&lt;/p&gt;

&lt;p&gt;
Thanks to Radovan Omorjan for helping me figure out the ODE at r=0!
&lt;/p&gt;
&lt;p&gt;Copyright (C) 2015 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/13/Numerically-calculating-an-effectiveness-factor-for-a-porous-catalyst-bead.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>Solving parameterized ODEs over and over conveniently</title>
      <link>https://kitchingroup.cheme.cmu.edu/blog/2013/02/07/Solving-parameterized-ODEs-over-and-over-conveniently</link>
      <pubDate>Thu, 07 Feb 2013 09:00:00 EST</pubDate>
      <category><![CDATA[ode]]></category>
      <guid isPermaLink="false">C_w7GrPNGTX4JB_9iSgel696JBw=</guid>
      <description>Solving parameterized ODEs over and over conveniently</description>
      <content:encoded><![CDATA[


&lt;p&gt;
&lt;a href="http://matlab.cheme.cmu.edu/2011/09/16/parameterized-odes/" &gt;Matlab post&lt;/a&gt;

Sometimes we have an ODE that depends on a parameter, and we want to solve the ODE for several parameter values. It is inconvenient to write an ode function for each parameter case. Here we examine a convenient way to solve this problem; we pass the parameter to the ODE at runtime. We consider the following ODE:
&lt;/p&gt;

&lt;p&gt;
$$\frac{dCa}{dt} = -k Ca(t)$$
&lt;/p&gt;

&lt;p&gt;
where \(k\) is a parameter, and we want to solve the equation for a couple of values of \(k\) to test the sensitivity of the solution on the parameter. Our question is, given \(Ca(t=0)=2\), how long does it take to get \(Ca = 1\), and how sensitive is the answer to small variations in \(k\)?
&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.integrate &lt;span style="color: #8b0000;"&gt;import&lt;/span&gt; odeint
&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: #8b0000;"&gt;def&lt;/span&gt; &lt;span style="color: #8b2323;"&gt;myode&lt;/span&gt;(Ca, t, k):
    &lt;span style="color: #228b22;"&gt;'ODE definition'&lt;/span&gt;
    dCadt = -k * Ca
    &lt;span style="color: #8b0000;"&gt;return&lt;/span&gt; dCadt

tspan = np.linspace(0, 0.5)
k0 = 2
Ca0 = 2

plt.figure(); plt.clf()

&lt;span style="color: #8b0000;"&gt;for&lt;/span&gt; k &lt;span style="color: #8b0000;"&gt;in&lt;/span&gt; [0.95 * k0, k0, 1.05 * k0]:
    sol = odeint(myode, Ca0, tspan, args=(k,))
    plt.plot(tspan, sol, label=&lt;span style="color: #228b22;"&gt;'k={0:1.2f}'&lt;/span&gt;.format(k))
    &lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; &lt;span style="color: #228b22;"&gt;'At t=0.5 Ca = {0:1.2f} mol/L'&lt;/span&gt;.format(sol[-1][0])

plt.legend(loc=&lt;span style="color: #228b22;"&gt;'best'&lt;/span&gt;)
plt.xlabel(&lt;span style="color: #228b22;"&gt;'Time'&lt;/span&gt;)
plt.ylabel(&lt;span style="color: #228b22;"&gt;'$C_A$ (mol/L)'&lt;/span&gt;)
plt.savefig(&lt;span style="color: #228b22;"&gt;'images/parameterized-ode1.png'&lt;/span&gt;)
&lt;/pre&gt;
&lt;/div&gt;
&lt;pre class="example"&gt;
At t=0.5 Ca = 0.77 mol/L
At t=0.5 Ca = 0.74 mol/L
At t=0.5 Ca = 0.70 mol/L
&lt;/pre&gt;

&lt;p&gt;&lt;img src="/img/./images/parameterized-ode1.png"&gt;&lt;p&gt;

&lt;p&gt;
You can see there are some variations in the concentration at t = 0.5. You could over or underestimate the concentration if you have the wrong estimate of $k$! You have to use some judgement here to decide how long to run the reaction to ensure a target goal is met.   &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/07/Solving-parameterized-ODEs-over-and-over-conveniently.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>
  </channel>
</rss>
