<?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>Transient diffusion - partial differential equations</title>
      <link>https://kitchingroup.cheme.cmu.edu/blog/2013/04/02/Transient-diffusion-partial-differential-equations</link>
      <pubDate>Tue, 02 Apr 2013 09:27:42 EDT</pubDate>
      <category><![CDATA[pde]]></category>
      <guid isPermaLink="false">VRp1cPaRr2c73g7BnG_yYjVBeiA=</guid>
      <description>Transient diffusion - partial differential equations</description>
      <content:encoded><![CDATA[


&lt;p&gt;

We want to solve for the concentration profile of  component that diffuses into a 1D rod, with an impermeable barrier at the end. The PDE governing this situation is:
&lt;/p&gt;

&lt;p&gt;
\(\frac{\partial C}{\partial t} = D \frac{\partial^2 C}{\partial x^2}\)
&lt;/p&gt;

&lt;p&gt;
at \(t=0\), in this example we have \(C_0(x) = 0\) as an initial condition, with boundary conditions \(C(0,t)=0.1\) and \(\partial C/ \partial x(L,t)=0\).
&lt;/p&gt;

&lt;p&gt;
We are going to discretize this equation in both time and space to arrive at the solution. We will let \(i\) be the index for the spatial discretization, and \(j\) be the index for the temporal discretization. The discretization looks like this.
&lt;/p&gt;

&lt;p&gt;&lt;img src="/img/./images/pde-diffusion-discretization-scheme.png"&gt;&lt;p&gt;

&lt;p&gt;
Note that we cannot use the method of lines as we did before because we have the derivative-based boundary condition at one of the boundaries.
&lt;/p&gt;

&lt;p&gt;
We approximate the time derivative as:
&lt;/p&gt;

&lt;p&gt;
\(\frac{\partial C}{\partial t} \bigg| _{i,j} \approx \frac{C_{i,j+1} - C_{i,j}}{\Delta t} \)
&lt;/p&gt;

&lt;p&gt;
\(\frac{\partial^2 C}{\partial  x^2} \bigg| _{i,j} \approx \frac{C_{i+1,j} - 2 C_{i,j} + C_{i-1,j}}{h^2} \)
&lt;/p&gt;

&lt;p&gt;
We define \(\alpha = \frac{D \Delta t}{h^2}\), and from these two approximations and the PDE, we solve for the unknown solution at a later time step as:
&lt;/p&gt;

&lt;p&gt;
\(C_{i, j+1} = \alpha C_{i+1,j} + (1 - 2 \alpha) C_{i,j}  + \alpha C_{i-1,j} \)
&lt;/p&gt;

&lt;p&gt;
We know \(C_{i,j=0}\) from the initial conditions, so we simply need to iterate to evaluate \(C_{i,j}\), which is the solution at each time step.
&lt;/p&gt;

&lt;p&gt;
See also: &lt;a href="http://www3.nd.edu/~jjwteach/441/PdfNotes/lecture16.pdf" &gt;http://www3.nd.edu/~jjwteach/441/PdfNotes/lecture16.pdf&lt;/a&gt;
&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

N = 20  &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&lt;/span&gt;
L = 1.0
X = np.linspace(0, L, N) &lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;position along the rod&lt;/span&gt;
h = L / (N - 1)          &lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;discretization spacing&lt;/span&gt;

C0t = 0.1  &lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;concentration at x = 0&lt;/span&gt;
D = 0.02

tfinal = 50.0
Ntsteps = 1000
dt = tfinal / (Ntsteps - 1)
t = np.linspace(0, tfinal, Ntsteps)

alpha = D * dt / h**2
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; alpha

C_xt = [] &lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;container for all the time steps&lt;/span&gt;

&lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;initial condition at t = 0&lt;/span&gt;
C = np.zeros(X.shape)
C[0] = C0t

C_xt += [C]

&lt;span style="color: #8b0000;"&gt;for&lt;/span&gt; j &lt;span style="color: #8b0000;"&gt;in&lt;/span&gt; &lt;span style="color: #8b0000;"&gt;range&lt;/span&gt;(1, Ntsteps):
    N = np.zeros(C.shape)
    N[0] =  C0t
    N[1:-1] = alpha*C[2:] + (1 - 2 * alpha) * C[1:-1] + alpha * C[0:-2]
    N[-1] = N[-2]  &lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;derivative boundary condition flux = 0&lt;/span&gt;
    C[:] = N
    C_xt += [N]

    &lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;plot selective solutions&lt;/span&gt;
    &lt;span style="color: #8b0000;"&gt;if&lt;/span&gt; j &lt;span style="color: #8b0000;"&gt;in&lt;/span&gt; [1,2,5,10,20,50,100,200,500]:
        plt.plot(X, N, label=&lt;span style="color: #228b22;"&gt;'t={0:1.2f}'&lt;/span&gt;.format(t[j]))

plt.xlabel(&lt;span style="color: #228b22;"&gt;'Position in rod'&lt;/span&gt;)
plt.ylabel(&lt;span style="color: #228b22;"&gt;'Concentration'&lt;/span&gt;)
plt.title(&lt;span style="color: #228b22;"&gt;'Concentration at different times'&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-diffusion-temporal-dependence.png'&lt;/span&gt;)

C_xt = np.array(C_xt)
plt.figure()
plt.plot(t, C_xt[:,5], label=&lt;span style="color: #228b22;"&gt;'x={0:1.2f}'&lt;/span&gt;.format(X[5]))
plt.plot(t, C_xt[:,10], label=&lt;span style="color: #228b22;"&gt;'x={0:1.2f}'&lt;/span&gt;.format(X[10]))
plt.plot(t, C_xt[:,15], label=&lt;span style="color: #228b22;"&gt;'x={0:1.2f}'&lt;/span&gt;.format(X[15]))
plt.plot(t, C_xt[:,19], label=&lt;span style="color: #228b22;"&gt;'x={0:1.2f}'&lt;/span&gt;.format(X[19]))
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;'Concentration'&lt;/span&gt;)
plt.savefig(&lt;span style="color: #228b22;"&gt;'images/transient-diffusion-position-dependence.png'&lt;/span&gt;)

plt.show()
&lt;/pre&gt;
&lt;/div&gt;

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

&lt;p&gt;&lt;img src="/img/./images/transient-diffusion-temporal-dependence.png"&gt;&lt;p&gt;

&lt;p&gt;&lt;img src="/img/./images/transient-diffusion-position-dependence.png"&gt;&lt;p&gt;

&lt;p&gt;
The solution is somewhat sensitive to the choices of time step and spatial discretization. If you make the time step too big, the method is not stable, and large oscillations may occur.
&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/04/02/Transient-diffusion---partial-differential-equations.org"&gt;org-mode source&lt;/a&gt;&lt;p&gt;]]></content:encoded>
    </item>
    <item>
      <title>A nonlinear BVP</title>
      <link>https://kitchingroup.cheme.cmu.edu/blog/2013/03/08/A-nonlinear-BVP</link>
      <pubDate>Fri, 08 Mar 2013 09:19:43 EST</pubDate>
      <category><![CDATA[pde]]></category>
      <guid isPermaLink="false">W7l83vQuog0wfRlQCSy-zG4dgmw=</guid>
      <description>A nonlinear BVP</description>
      <content:encoded><![CDATA[


&lt;p&gt;

Adapted from Example 8.7 in _Numerical Methods in Engineering with Python_ by Jaan Kiusalaas.
&lt;/p&gt;

&lt;p&gt;
We want to solve \(y''(x) = -3 y(x) y'(x)\) with $y(0) = 0 and \(y(2) = 1\) using a finite difference method. We discretize the region and approximate the derivatives as:
&lt;/p&gt;

&lt;p&gt;
\(y''(x) \approx \frac{y_{i-1} - 2 y_i + y_{i+1}}{h^2} \)
&lt;/p&gt;

&lt;p&gt;
\(y'(x) \approx \frac{y_{i+1} - y_{i-1}}{2 h} \)
&lt;/p&gt;

&lt;p&gt;
We define a function \(y''(x) = F(x, y, y')\). At each node in our discretized region, we will have an equation that looks like \(y''(x) - F(x, y, y') = 0\), which will be nonlinear in the unknown solution \(y\). The set of equations to solve is:
&lt;/p&gt;

\begin{eqnarray}
y_0 - \alpha &amp;=&amp; 0 \\
\frac{y_{i-1} - 2 y_i + y_{i+1}}{h^2} + (3 y_i) (\frac{y_{i+1} - y_{i-1}}{2 h}) &amp;=&amp; 0 \\
y_L - \beta &amp;=&amp;0
\end{eqnarray}

&lt;p&gt;
Since we use a nonlinear solver, we will have to provide an initial guess to the solution. We will in this case assume a line. In other cases, a bad initial guess may lead to no 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.optimize &lt;span style="color: #8b0000;"&gt;import&lt;/span&gt; fsolve
&lt;span style="color: #8b0000;"&gt;import&lt;/span&gt; matplotlib.pyplot &lt;span style="color: #8b0000;"&gt;as&lt;/span&gt; plt

x1 = 0.0
x2 = 2.0

alpha = 0.0
beta = 1.0

N = 11
X = np.linspace(x1, x2, N)
h = (x2 - x1) / (N - 1)

&lt;span style="color: #8b0000;"&gt;def&lt;/span&gt; &lt;span style="color: #8b2323;"&gt;Ypp&lt;/span&gt;(x, y, yprime):
    &lt;span style="color: #228b22;"&gt;'''define y'' = 3*y*y' '''&lt;/span&gt;
    &lt;span style="color: #8b0000;"&gt;return&lt;/span&gt; -3.0 * y * yprime

&lt;span style="color: #8b0000;"&gt;def&lt;/span&gt; &lt;span style="color: #8b2323;"&gt;residuals&lt;/span&gt;(y):
    &lt;span style="color: #228b22;"&gt;'''When we have the right values of y, this function will be zero.'''&lt;/span&gt;

    res = np.zeros(y.shape)

    res[0] = y[0] - alpha
    
    &lt;span style="color: #8b0000;"&gt;for&lt;/span&gt; i &lt;span style="color: #8b0000;"&gt;in&lt;/span&gt; &lt;span style="color: #8b0000;"&gt;range&lt;/span&gt;(1, N - 1):
        x = X[i]
        YPP = (y[i - 1] - 2 * y[i] + y[i + 1]) / h**2
        YP = (y[i + 1] - y[i - 1]) / (2 * h)
        res[i] = YPP - Ypp(x, y[i], YP)

    res[-1] = y[-1] - beta
    &lt;span style="color: #8b0000;"&gt;return&lt;/span&gt; res

&lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;we need an initial guess&lt;/span&gt;
init = alpha + (beta - alpha) / (x2 - x1) * X

Y = fsolve(residuals, init)

plt.plot(X, Y)
plt.savefig(&lt;span style="color: #228b22;"&gt;'images/bvp-nonlinear-1.png'&lt;/span&gt;)
&lt;/pre&gt;
&lt;/div&gt;

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

&lt;p&gt;
That code looks useful, so I put it in the pycse module in the function BVP_nl. Here is an example usage. We have to create two functions, one for the differential equation, and one for the initial guess.
&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; pycse &lt;span style="color: #8b0000;"&gt;import&lt;/span&gt;  BVP_nl
&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;Ypp&lt;/span&gt;(x, y, yprime):
    &lt;span style="color: #228b22;"&gt;'''define y'' = 3*y*y' '''&lt;/span&gt;
    &lt;span style="color: #8b0000;"&gt;return&lt;/span&gt; -3.0 * y * yprime

&lt;span style="color: #8b0000;"&gt;def&lt;/span&gt; &lt;span style="color: #8b2323;"&gt;init&lt;/span&gt;(x):
    &lt;span style="color: #8b0000;"&gt;return&lt;/span&gt; alpha + (beta - alpha) / (x2 - x1) * x

x1 = 0.0
x2 = 2.0

alpha = 0.0
beta = 1.0

N = 11
x, y = BVP_nl(Ypp, x1, x2, alpha, beta, init, N)

plt.plot(x, y)
plt.savefig(&lt;span style="color: #228b22;"&gt;'images/bvp-nonlinear-2.png'&lt;/span&gt;)
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;&lt;img src="/img/./images/bvp-nonlinear-2.png"&gt;&lt;p&gt;

&lt;p&gt;
The results are the same.
&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/08/A-nonlinear-BVP.org"&gt;org-mode source&lt;/a&gt;&lt;p&gt;]]></content:encoded>
    </item>
    <item>
      <title>Transient heat conduction - partial differential equations</title>
      <link>https://kitchingroup.cheme.cmu.edu/blog/2013/03/07/Transient-heat-conduction-partial-differential-equations</link>
      <pubDate>Thu, 07 Mar 2013 15:54:08 EST</pubDate>
      <category><![CDATA[pde]]></category>
      <guid isPermaLink="false">vYwQTXiPOjKla95AZ51ynjv57Cg=</guid>
      <description>Transient heat conduction - partial differential equations</description>
      <content:encoded><![CDATA[


&lt;p&gt;
&lt;a href="http://matlab.cheme.cmu.edu/2011/08/21/transient-heat-conduction-partial-differential-equations/" &gt;Matlab post&lt;/a&gt;
adapated from &lt;a href="http://msemac.redwoods.edu/~darnold/math55/DEproj/sp02/AbeRichards/slideshowdefinal.pdf" &gt;http://msemac.redwoods.edu/~darnold/math55/DEproj/sp02/AbeRichards/slideshowdefinal.pdf&lt;/a&gt;

&lt;/p&gt;

&lt;p&gt;
We solved a steady state BVP modeling heat conduction. Today we examine the transient behavior of a rod at constant T put between two heat reservoirs at different temperatures, again T1 = 100, and T2 = 200. The rod will start at 150. Over time, we should expect a solution that approaches the steady state solution: a linear temperature profile from one side of the rod to the other.
&lt;/p&gt;

&lt;p&gt;
\(\frac{\partial u}{\partial t} = k \frac{\partial^2 u}{\partial x^2}\)
&lt;/p&gt;

&lt;p&gt;
at \(t=0\), in this example we have \(u_0(x) = 150\) as an initial condition. with boundary conditions \(u(0,t)=100\) and \(u(L,t)=200\).
&lt;/p&gt;

&lt;p&gt;
In Matlab there is the pdepe command. There is not yet a PDE solver in scipy. Instead, we will utilze the method of lines to solve this problem. We discretize the rod into segments, and approximate the second derivative in the spatial dimension as \(\frac{\partial^2 u}{\partial x^2} = (u(x + h) - 2 u(x) + u(x-h))/ h^2\) at each node. This leads to a set of coupled ordinary differential equations that is easy to solve.
&lt;/p&gt;

&lt;p&gt;
Let us say the rod has a length of 1, \(k=0.02\), and solve for the time-dependent temperature profiles.
&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

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&lt;/span&gt;
L = 1.0
X = np.linspace(0, L, N) &lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;position along the rod&lt;/span&gt;
h = L / (N - 1)

k = 0.02

&lt;span style="color: #8b0000;"&gt;def&lt;/span&gt; &lt;span style="color: #8b2323;"&gt;odefunc&lt;/span&gt;(u, t):
    dudt = np.zeros(X.shape)

    dudt[0] = 0 &lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;constant at boundary condition&lt;/span&gt;
    dudt[-1] = 0

    &lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;now for the internal nodes&lt;/span&gt;
    &lt;span style="color: #8b0000;"&gt;for&lt;/span&gt; i &lt;span style="color: #8b0000;"&gt;in&lt;/span&gt; &lt;span style="color: #8b0000;"&gt;range&lt;/span&gt;(1, N-1):
        dudt[i] = k * (u[i + 1] - 2*u[i] + u[i - 1]) / h**2

    &lt;span style="color: #8b0000;"&gt;return&lt;/span&gt; dudt

init = 150.0 * np.ones(X.shape) &lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;initial temperature&lt;/span&gt;
init[0] = 100.0  &lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;one boundary condition&lt;/span&gt;
init[-1] = 200.0 &lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;the other boundary condition&lt;/span&gt;

tspan = np.linspace(0.0, 5.0, 100)
sol = odeint(odefunc, init, tspan)


&lt;span style="color: #8b0000;"&gt;for&lt;/span&gt; i &lt;span style="color: #8b0000;"&gt;in&lt;/span&gt; &lt;span style="color: #8b0000;"&gt;range&lt;/span&gt;(0, &lt;span style="color: #8b0000;"&gt;len&lt;/span&gt;(tspan), 5):
    plt.plot(X, sol[i], label=&lt;span style="color: #228b22;"&gt;'t={0:1.2f}'&lt;/span&gt;.format(tspan[i]))

&lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;put legend outside the figure&lt;/span&gt;
plt.legend(loc=&lt;span style="color: #228b22;"&gt;'center left'&lt;/span&gt;, bbox_to_anchor=(1, 0.5))
plt.xlabel(&lt;span style="color: #228b22;"&gt;'X position'&lt;/span&gt;)
plt.ylabel(&lt;span style="color: #228b22;"&gt;'Temperature'&lt;/span&gt;)

&lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;adjust figure edges so the legend is in the figure&lt;/span&gt;
plt.subplots_adjust(top=0.89, right=0.77)
plt.savefig(&lt;span style="color: #228b22;"&gt;'images/pde-transient-heat-1.png'&lt;/span&gt;)


&lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;Make a 3d figure&lt;/span&gt;
&lt;span style="color: #8b0000;"&gt;from&lt;/span&gt; mpl_toolkits.mplot3d &lt;span style="color: #8b0000;"&gt;import&lt;/span&gt; Axes3D
fig = plt.figure()
ax = fig.add_subplot(111, projection=&lt;span style="color: #228b22;"&gt;'3d'&lt;/span&gt;)

SX, ST = np.meshgrid(X, tspan)
ax.plot_surface(SX, ST, sol, cmap=&lt;span style="color: #228b22;"&gt;'jet'&lt;/span&gt;)
ax.set_xlabel(&lt;span style="color: #228b22;"&gt;'X'&lt;/span&gt;)
ax.set_ylabel(&lt;span style="color: #228b22;"&gt;'time'&lt;/span&gt;)
ax.set_zlabel(&lt;span style="color: #228b22;"&gt;'T'&lt;/span&gt;)
ax.view_init(elev=15, azim=-124) &lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;adjust view so it is easy to see&lt;/span&gt;
plt.savefig(&lt;span style="color: #228b22;"&gt;'images/pde-transient-heat-3d.png'&lt;/span&gt;)

&lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;animated solution. We will use imagemagick for this&lt;/span&gt;

&lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;we save each frame as an image, and use the imagemagick convert command to &lt;/span&gt;
&lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;make an animated gif&lt;/span&gt;
&lt;span style="color: #8b0000;"&gt;for&lt;/span&gt; i &lt;span style="color: #8b0000;"&gt;in&lt;/span&gt; &lt;span style="color: #8b0000;"&gt;range&lt;/span&gt;(len(tspan)):
    plt.clf()
    plt.plot(X, sol[i])
    plt.xlabel(&lt;span style="color: #228b22;"&gt;'X'&lt;/span&gt;)
    plt.ylabel(&lt;span style="color: #228b22;"&gt;'T(X)'&lt;/span&gt;)
    plt.title(&lt;span style="color: #228b22;"&gt;'t = {0}'&lt;/span&gt;.format(tspan[i]))
    plt.savefig(&lt;span style="color: #228b22;"&gt;'___t{0:03d}.png'&lt;/span&gt;.format(i))

&lt;span style="color: #8b0000;"&gt;import&lt;/span&gt; commands
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; commands.getoutput(&lt;span style="color: #228b22;"&gt;'convert -quality 100 ___t*.png images/transient_heat.gif'&lt;/span&gt;)
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; commands.getoutput(&lt;span style="color: #228b22;"&gt;'rm ___t*.png'&lt;/span&gt;) &lt;span style="color: #ff0000; font-weight: bold;"&gt;#&lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;remove temp files&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;

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

&lt;p&gt;
This version of the graphical solution is not that easy to read, although with some study you can see the solution evolves from the initial condition which is flat, to the steady state solution which is a linear temperature ramp.
&lt;p&gt;&lt;img src="/img/./images/pde-transient-heat-1.png"&gt;&lt;p&gt;
&lt;/p&gt;

&lt;p&gt;
The 3d version may be easier to interpret. The temperature profile starts out flat, and gradually changes to the linear ramp.
&lt;p&gt;&lt;img src="/img/./images/pde-transient-heat-3d.png"&gt;&lt;p&gt;
&lt;/p&gt;

&lt;p&gt;
Finally, the animated solution.
&lt;/p&gt;

&lt;p&gt;&lt;img src="/img/./images/transient_heat.gif"&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/03/07/Transient-heat-conduction---partial-differential-equations.org"&gt;org-mode source&lt;/a&gt;&lt;p&gt;]]></content:encoded>
    </item>
    <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>
  </channel>
</rss>
