<?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>Solving an eigenvalue differential equation with a neural network</title>
      <link>https://kitchingroup.cheme.cmu.edu/blog/2017/11/29/Solving-an-eigenvalue-differential-equation-with-a-neural-network</link>
      <pubDate>Wed, 29 Nov 2017 21:17:03 EST</pubDate>
      <category><![CDATA[eigenvalue]]></category>
      <category><![CDATA[autograd]]></category>
      <category><![CDATA[bvp]]></category>
      <guid isPermaLink="false">k6Vx3x7cr1E5mihuaxrKNEeJLZs=</guid>
      <description>Solving an eigenvalue differential equation with a neural network</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="#orgdd6adc2"&gt;1. The neural network setup&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#org08ec74a"&gt;2. The objective function&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#org865c7c4"&gt;3. The minimization&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#org31faf55"&gt;4. The first excited state&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#org3e74a47"&gt;5. Summary&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
The 1D harmonic oscillator is described &lt;a href="https://quantummechanics.ucsd.edu/ph130a/130_notes/node153.html"&gt;here&lt;/a&gt;. It is a boundary value differential equation with eigenvalues. If we let let &amp;omega;=1, m=1, and units where &amp;hbar;=1. then, the governing differential equation becomes:
&lt;/p&gt;

&lt;p&gt;
\(-0.5 \frac{d^2\psi(x)}{dx^2} + (0.5 x^2 - E) \psi(x) = 0\)
&lt;/p&gt;

&lt;p&gt;
with boundary conditions: \(\psi(-\infty) = \psi(\infty) = 0\)
&lt;/p&gt;

&lt;p&gt;
We can further stipulate that the probability of finding the particle over this domain is equal to one: \(\int_{-\infty}^{\infty} \psi^2(x) dx = 1\). In this set of equations, \(E\) is an eigenvalue, which means there are only non-trivial solutions for certain values of \(E\).
&lt;/p&gt;

&lt;p&gt;
Our goal is to solve this equation using a neural network to represent the wave function. This is a different problem than the one &lt;a href="http://kitchingroup.cheme.cmu.edu/blog/2017/11/28/Solving-ODEs-with-a-neural-network-and-autograd/"&gt;here&lt;/a&gt; or &lt;a href="http://kitchingroup.cheme.cmu.edu/blog/2017/11/27/Solving-BVPs-with-a-neural-network-and-autograd/"&gt;here&lt;/a&gt; because of the eigenvalue. This is an additional adjustable parameter we have to find. Also, we have the normalization constraint to consider, which we did not consider before.
&lt;/p&gt;

&lt;div id="outline-container-orgdd6adc2" class="outline-2"&gt;
&lt;h2 id="orgdd6adc2"&gt;&lt;span class="section-number-2"&gt;1&lt;/span&gt; The neural network setup&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-1"&gt;
&lt;p&gt;
Here we setup the neural network and its derivatives. This is the same as we did before.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython" id="orge0a59c3"&gt;&lt;span style="color: #0000FF;"&gt;import&lt;/span&gt; autograd.numpy &lt;span style="color: #0000FF;"&gt;as&lt;/span&gt; np
&lt;span style="color: #0000FF;"&gt;from&lt;/span&gt; autograd &lt;span style="color: #0000FF;"&gt;import&lt;/span&gt; grad, elementwise_grad
&lt;span style="color: #0000FF;"&gt;import&lt;/span&gt; autograd.numpy.random &lt;span style="color: #0000FF;"&gt;as&lt;/span&gt; npr
&lt;span style="color: #0000FF;"&gt;from&lt;/span&gt; autograd.misc.optimizers &lt;span style="color: #0000FF;"&gt;import&lt;/span&gt; adam

&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;init_random_params&lt;/span&gt;(scale, layer_sizes, rs=npr.RandomState(42)):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #036A07;"&gt;"""Build a list of (weights, biases) tuples, one for each layer."""&lt;/span&gt;
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; [(rs.randn(insize, outsize) * scale,   &lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;weight matrix&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;rs.randn(outsize) * scale)           &lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;bias vector&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: #0000FF;"&gt;for&lt;/span&gt; insize, outsize &lt;span style="color: #0000FF;"&gt;in&lt;/span&gt; &lt;span style="color: #006FE0;"&gt;zip&lt;/span&gt;(layer_sizes[:-1], layer_sizes[1:])]

&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;swish&lt;/span&gt;(x):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #036A07;"&gt;"see https://arxiv.org/pdf/1710.05941.pdf"&lt;/span&gt;
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; x / (1.0 + np.exp(-x))

&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;psi&lt;/span&gt;(nnparams, inputs):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #036A07;"&gt;"Neural network wavefunction"&lt;/span&gt;
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;for&lt;/span&gt; W, b &lt;span style="color: #0000FF;"&gt;in&lt;/span&gt; nnparams:
&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: #BA36A5;"&gt;outputs&lt;/span&gt; = np.dot(inputs, W) + b
&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: #BA36A5;"&gt;inputs&lt;/span&gt; = swish(outputs)    
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; outputs

&lt;span style="color: #BA36A5;"&gt;psip&lt;/span&gt; = elementwise_grad(psi, 1) &lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;dpsi/dx &lt;/span&gt;
&lt;span style="color: #BA36A5;"&gt;psipp&lt;/span&gt; = elementwise_grad(psip, 1) &lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;d^2psi/dx^2&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;div id="outline-container-org08ec74a" class="outline-2"&gt;
&lt;h2 id="org08ec74a"&gt;&lt;span class="section-number-2"&gt;2&lt;/span&gt; The objective function&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-2"&gt;
&lt;p&gt;
The important function we need is the objective function. This function codes the Schrödinger equation, the boundary conditions, and the normalization as a cost function that we will later seek to minimize. Ideally, at the solution the objective function will be zero. We can't put infinity into our objective function, but it turns out that x = &amp;plusmn; 6 is practically infinity in this case, so we approximate the boundary conditions there. 
&lt;/p&gt;

&lt;p&gt;
Another note is the numerical integration by the trapezoid rule. I use a vectorized version of this because autograd doesn't have a trapz derivative and I didn't feel like figuring one out.
&lt;/p&gt;

&lt;p&gt;
We define the params to vary here as a dictionary containing neural network weights and biases, and the value of the eigenvalue.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython" id="orge9e096c"&gt;&lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;Here is our initial guess of params:&lt;/span&gt;
&lt;span style="color: #BA36A5;"&gt;nnparams&lt;/span&gt; = init_random_params(0.1, layer_sizes=[1, 8, 1])

&lt;span style="color: #BA36A5;"&gt;params&lt;/span&gt; = {&lt;span style="color: #008000;"&gt;'nn'&lt;/span&gt;: nnparams, &lt;span style="color: #008000;"&gt;'E'&lt;/span&gt;: 0.4}

&lt;span style="color: #BA36A5;"&gt;x&lt;/span&gt; = np.linspace(-6, 6, 200)[:, &lt;span style="color: #D0372D;"&gt;None&lt;/span&gt;]

&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;objective&lt;/span&gt;(params, step):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;nnparams&lt;/span&gt; = params[&lt;span style="color: #008000;"&gt;'nn'&lt;/span&gt;]
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;E&lt;/span&gt; = params[&lt;span style="color: #008000;"&gt;'E'&lt;/span&gt;]        
&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;This is Schrodinger's eqn&lt;/span&gt;
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;zeq&lt;/span&gt; = -0.5 * psipp(nnparams, x) + (0.5 * x**2 - E) * psi(nnparams, x) 
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;bc0&lt;/span&gt; = psi(nnparams, -6.0) &lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;This approximates -infinity&lt;/span&gt;
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;bc1&lt;/span&gt; = psi(nnparams, 6.0)  &lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;This approximates +infinity&lt;/span&gt;
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;y2&lt;/span&gt; = psi(nnparams, x)**2
&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;This is a numerical trapezoid integration&lt;/span&gt;
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;prob&lt;/span&gt; = np.&lt;span style="color: #006FE0;"&gt;sum&lt;/span&gt;((y2[1:] + y2[0:-1]) / 2 * (x[1:] - x[0:-1]))
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; np.mean(zeq**2) + bc0**2 + bc1**2 + (1.0 - prob)**2

&lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;This gives us feedback from the optimizer&lt;/span&gt;
&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;callback&lt;/span&gt;(params, step, g):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;if&lt;/span&gt; step % 1000 == 0:
&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: #0000FF;"&gt;print&lt;/span&gt;(&lt;span style="color: #008000;"&gt;"Iteration {0:3d} objective {1}"&lt;/span&gt;.&lt;span style="color: #006FE0;"&gt;format&lt;/span&gt;(step,
&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;   &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;   &lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt; objective(params, step)))
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;div id="outline-container-org865c7c4" class="outline-2"&gt;
&lt;h2 id="org865c7c4"&gt;&lt;span class="section-number-2"&gt;3&lt;/span&gt; The minimization&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-3"&gt;
&lt;p&gt;
Now, we just let an optimizer minimize the objective function for us. Note, I ran this next block more than once, as the objective continued to decrease. I ran this one at least two times, and the loss was still decreasing slowly.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython" id="orgf241c39"&gt;&lt;span style="color: #BA36A5;"&gt;params&lt;/span&gt; = adam(grad(objective), params,
&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; step_size=0.001, num_iters=5001, callback=callback) 

&lt;span style="color: #0000FF;"&gt;print&lt;/span&gt;(params[&lt;span style="color: #008000;"&gt;'E'&lt;/span&gt;])
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
Iteration   0 objective [[ 0.00330204]]
Iteration 1000 objective [[ 0.00246459]]
Iteration 2000 objective [[ 0.00169862]]
Iteration 3000 objective [[ 0.00131453]]
Iteration 4000 objective [[ 0.00113132]]
Iteration 5000 objective [[ 0.00104405]]
0.5029457355415167

&lt;/pre&gt;

&lt;p&gt;
Good news, the lowest energy eigenvalue is known to be 0.5 for our choice of parameters, and that is approximately what we got. Now let's see our solution and compare it to the known solution. Interestingly we got the negative of the solution, which is still a solution. The NN solution is not indistinguishable from the analytical solution, and has some spurious curvature in the tails, but it is approximately correct, and more training might get it closer. A different activation function might also work better.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython" id="orgc343304"&gt;%matplotlib inline
&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: #BA36A5;"&gt;x&lt;/span&gt; = np.linspace(-6, 6)[:, &lt;span style="color: #D0372D;"&gt;None&lt;/span&gt;]
&lt;span style="color: #BA36A5;"&gt;y&lt;/span&gt; = psi(params[&lt;span style="color: #008000;"&gt;'nn'&lt;/span&gt;], x)

plt.plot(x, -y, label=&lt;span style="color: #008000;"&gt;'NN'&lt;/span&gt;)
plt.plot(x, (1/np.pi)**0.25 * np.exp(-x**2 / 2), &lt;span style="color: #008000;"&gt;'r--'&lt;/span&gt;, label=&lt;span style="color: #008000;"&gt;'analytical'&lt;/span&gt;)
plt.legend()
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
&lt;img src="/media/ob-ipython-a0315846d401b5468d391df4b1ee6e84.png"&gt; 
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;div id="outline-container-org31faf55" class="outline-2"&gt;
&lt;h2 id="org31faf55"&gt;&lt;span class="section-number-2"&gt;4&lt;/span&gt; The first excited state&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-4"&gt;
&lt;p&gt;
Now, what about the first excited state? This has an eigenvalue of 1.5, and the solution has odd parity. We can naively change the eigenvalue, and hope that the optimizer will find the right new solution. We do that here, and use the old NN params.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython" id="org78762bc"&gt;&lt;span style="color: #BA36A5;"&gt;params&lt;/span&gt;[&lt;span style="color: #008000;"&gt;'E'&lt;/span&gt;] = 1.6
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
Now, we run a round of optimization:
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython" id="org77ad283"&gt;&lt;span style="color: #BA36A5;"&gt;params&lt;/span&gt; = adam(grad(objective), params,
&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; step_size=0.003, num_iters=5001, callback=callback) 

&lt;span style="color: #0000FF;"&gt;print&lt;/span&gt;(params[&lt;span style="color: #008000;"&gt;'E'&lt;/span&gt;])
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
Iteration   0 objective [[ 0.09918192]]
Iteration 1000 objective [[ 0.00102333]]
Iteration 2000 objective [[ 0.00100269]]
Iteration 3000 objective [[ 0.00098684]]
Iteration 4000 objective [[ 0.00097425]]
Iteration 5000 objective [[ 0.00096347]]
0.502326347406645

&lt;/pre&gt;


&lt;p&gt;
That doesn't work though. The optimizer just pushes the solution back to the known one. Next, we try starting from scratch with the eigenvalue guess.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython" id="org41c431c"&gt;&lt;span style="color: #BA36A5;"&gt;nnparams&lt;/span&gt; = init_random_params(0.1, layer_sizes=[1, 8, 1])

&lt;span style="color: #BA36A5;"&gt;params&lt;/span&gt; = {&lt;span style="color: #008000;"&gt;'nn'&lt;/span&gt;: nnparams, &lt;span style="color: #008000;"&gt;'E'&lt;/span&gt;: 1.6}

&lt;span style="color: #BA36A5;"&gt;params&lt;/span&gt; = adam(grad(objective), params,
&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; step_size=0.003, num_iters=5001, callback=callback) 

&lt;span style="color: #0000FF;"&gt;print&lt;/span&gt;(params[&lt;span style="color: #008000;"&gt;'E'&lt;/span&gt;])
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
Iteration   0 objective [[ 2.08318762]]
Iteration 1000 objective [[ 0.02358685]]
Iteration 2000 objective [[ 0.00726497]]
Iteration 3000 objective [[ 0.00336433]]
Iteration 4000 objective [[ 0.00229851]]
Iteration 5000 objective [[ 0.00190942]]
0.5066213334684926

&lt;/pre&gt;

&lt;p&gt;
That also doesn't work. We are going to have to steer this. The idea is pre-train the neural network to have the basic shape and symmetry we want, and then use that as the input for the objective function. The first excited state has odd parity, and here is a guess of that shape. This is a pretty ugly hacked up version that only roughly has the right shape. I am counting on the NN smoothing out the discontinuities.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython" id="org586b931"&gt;&lt;span style="color: #BA36A5;"&gt;xm&lt;/span&gt; = np.linspace(-6, 6)[:, &lt;span style="color: #D0372D;"&gt;None&lt;/span&gt;]
&lt;span style="color: #BA36A5;"&gt;ym&lt;/span&gt; = -0.5 * ((-1 * (xm + 1.5)**2) + 1.5) * (xm &amp;lt; 0) * (xm &amp;gt; -3)
&lt;span style="color: #BA36A5;"&gt;yp&lt;/span&gt; = -0.5 * ((1 * (xm - 1.5)**2 ) - 1.5) * (xm &amp;gt; 0) * (xm &amp;lt; 3)

plt.plot(xm, (ym + yp))
plt.plot(x, (1/np.pi)**0.25 * np.sqrt(2) * x * np.exp(-x**2 / 2), &lt;span style="color: #008000;"&gt;'r--'&lt;/span&gt;, label=&lt;span style="color: #008000;"&gt;'analytical'&lt;/span&gt;)
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
&lt;img src="/media/ob-ipython-7306bb4c2a75d356dd2246681bec193e.png"&gt; 
&lt;/p&gt;

&lt;p&gt;
Now we pretrain a bit.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython" id="orgea1c301"&gt;&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;pretrain&lt;/span&gt;(params, step):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;nnparams&lt;/span&gt; = params[&lt;span style="color: #008000;"&gt;'nn'&lt;/span&gt;]
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;errs&lt;/span&gt; = psi(nnparams, xm) - (ym + yp)
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; np.mean(errs**2)

&lt;span style="color: #BA36A5;"&gt;params&lt;/span&gt; = adam(grad(pretrain), params,
&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; step_size=0.003, num_iters=501, callback=callback) 
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
Iteration   0 objective [[ 1.09283695]]

&lt;/pre&gt;

&lt;p&gt;
Here is the new initial guess we are going to use. You can see that indeed a lot of smoothing has occurred.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython" id="org9df043e"&gt;plt.plot(xm, ym + yp, xm, psi(params[&lt;span style="color: #008000;"&gt;'nn'&lt;/span&gt;], xm))
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
&lt;img src="/media/ob-ipython-861dc15ae81c1a9d2bcab2aeca1c7b64.png"&gt; 
&lt;/p&gt;

&lt;p&gt;
That has the right shape now. So we go back to the original objective function. 
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython" id="org5298900"&gt;&lt;span style="color: #BA36A5;"&gt;params&lt;/span&gt; = adam(grad(objective), params,
&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; step_size=0.001, num_iters=5001, callback=callback) 

&lt;span style="color: #0000FF;"&gt;print&lt;/span&gt;(params[&lt;span style="color: #008000;"&gt;'E'&lt;/span&gt;])
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
Iteration   0 objective [[ 0.00370029]]
Iteration 1000 objective [[ 0.00358193]]
Iteration 2000 objective [[ 0.00345137]]
Iteration 3000 objective [[ 0.00333]]
Iteration 4000 objective [[ 0.0032198]]
Iteration 5000 objective [[ 0.00311844]]
1.5065724128094344

&lt;/pre&gt;

&lt;p&gt;
I ran that optimization block many times. The loss is still decreasing, but slowly. More importantly, the eigenvalue is converging to 1.5, which is the known analytical value, and the solution is converging to the known solution. 
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython" id="org0d766e2"&gt;&lt;span style="color: #BA36A5;"&gt;x&lt;/span&gt; = np.linspace(-6, 6)[:, &lt;span style="color: #D0372D;"&gt;None&lt;/span&gt;]
&lt;span style="color: #BA36A5;"&gt;y&lt;/span&gt; = psi(params[&lt;span style="color: #008000;"&gt;'nn'&lt;/span&gt;], x)

plt.plot(x, y, label=&lt;span style="color: #008000;"&gt;'NN'&lt;/span&gt;)
plt.plot(x, (1/np.pi)**0.25 * np.sqrt(2) * x * np.exp(-x**2 / 2), &lt;span style="color: #008000;"&gt;'r--'&lt;/span&gt;, label=&lt;span style="color: #008000;"&gt;'analytical'&lt;/span&gt;)
plt.legend()
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
&lt;img src="/media/ob-ipython-e63e275d2112849010d3e28381ccf41b.png"&gt; 
&lt;/p&gt;

&lt;p&gt;
We can confirm the normalization is reasonable:
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython" id="org6eef549"&gt;&lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;check the normalization&lt;/span&gt;
&lt;span style="color: #0000FF;"&gt;print&lt;/span&gt;(np.trapz(y.T * y.T, x.T))
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
[ 0.99781886]

&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;div id="outline-container-org3e74a47" class="outline-2"&gt;
&lt;h2 id="org3e74a47"&gt;&lt;span class="section-number-2"&gt;5&lt;/span&gt; Summary&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-5"&gt;
&lt;p&gt;
This is another example of using autograd to solve an eigenvalue differential equation. Some of these solutions required tens of thousands of iterations of training. The groundstate wavefunction was very easy to get. The first excited state, on the other hand, took some active steering. This is very much like how an initial guess can change which solution a nonlinear optimization (which this is) finds.
&lt;/p&gt;

&lt;p&gt;
There are other ways to solve this particular problem. What I think is interesting about this is the possibility to solve harder problems, e.g. not just a harmonic potential, but a more complex one. You could pretrain a network on the harmonic solution, and then use it as the initial guess for the harder problem (which has no analytical solution). 
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Copyright (C) 2017 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/2017/11/29/Solving-an-eigenvalue-differential-equation-with-a-neural-network.org"&gt;org-mode source&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Org-mode version = 9.1.2&lt;/p&gt;]]></content:encoded>
    </item>
    <item>
      <title>Solving BVPs with a neural network and autograd</title>
      <link>https://kitchingroup.cheme.cmu.edu/blog/2017/11/27/Solving-BVPs-with-a-neural-network-and-autograd</link>
      <pubDate>Mon, 27 Nov 2017 19:59:52 EST</pubDate>
      <category><![CDATA[autograd]]></category>
      <category><![CDATA[bvp]]></category>
      <guid isPermaLink="false">SYBTJvTkUE5Sl8i68AtarqxIA5w=</guid>
      <description>Solving BVPs with a neural network and autograd</description>
      <content:encoded><![CDATA[


&lt;p&gt;
In this &lt;a href="http://kitchingroup.cheme.cmu.edu/blog/2013/03/11/Solving-the-Blasius-equation/"&gt;post&lt;/a&gt; we solved a boundary value problem by discretizing it, and approximating the derivatives by finite differences. Here I explore using a neural network to approximate the unknown function, autograd to get the required derivatives, and using autograd to train the neural network to satisfy the differential equations. We will look at the Blasius equation again.
&lt;/p&gt;

\begin{eqnarray}
f''' + \frac{1}{2} f f'' &amp;=&amp; 0 \\
f(0) &amp;=&amp; 0 \\
f'(0) &amp;=&amp; 0 \\
f'(\infty) &amp;=&amp; 1
\end{eqnarray}

&lt;p&gt;
Here I setup a simple neural network
&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; autograd.numpy &lt;span style="color: #0000FF;"&gt;as&lt;/span&gt; np
&lt;span style="color: #0000FF;"&gt;from&lt;/span&gt; autograd &lt;span style="color: #0000FF;"&gt;import&lt;/span&gt; grad, elementwise_grad
&lt;span style="color: #0000FF;"&gt;import&lt;/span&gt; autograd.numpy.random &lt;span style="color: #0000FF;"&gt;as&lt;/span&gt; npr
&lt;span style="color: #0000FF;"&gt;from&lt;/span&gt; autograd.misc.optimizers &lt;span style="color: #0000FF;"&gt;import&lt;/span&gt; adam

&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;init_random_params&lt;/span&gt;(scale, layer_sizes, rs=npr.RandomState(0)):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #036A07;"&gt;"""Build a list of (weights, biases) tuples, one for each layer."""&lt;/span&gt;
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; [(rs.randn(insize, outsize) * scale,   &lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;weight matrix&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;rs.randn(outsize) * scale)           &lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;bias vector&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: #0000FF;"&gt;for&lt;/span&gt; insize, outsize &lt;span style="color: #0000FF;"&gt;in&lt;/span&gt; &lt;span style="color: #006FE0;"&gt;zip&lt;/span&gt;(layer_sizes[:-1], layer_sizes[1:])]


&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;swish&lt;/span&gt;(x):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #036A07;"&gt;"see https://arxiv.org/pdf/1710.05941.pdf"&lt;/span&gt;
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; x / (1.0 + np.exp(-x))


&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;f&lt;/span&gt;(params, inputs):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #036A07;"&gt;"Neural network functions"&lt;/span&gt;
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;for&lt;/span&gt; W, b &lt;span style="color: #0000FF;"&gt;in&lt;/span&gt; params:
&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: #BA36A5;"&gt;outputs&lt;/span&gt; = np.dot(inputs, W) + b
&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: #BA36A5;"&gt;inputs&lt;/span&gt; = swish(outputs)    
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; outputs

&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;Here is our initial guess of params:&lt;/span&gt;
&lt;span style="color: #BA36A5;"&gt;params&lt;/span&gt; = init_random_params(0.1, layer_sizes=[1, 8, 1])

&lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;Derivatives&lt;/span&gt;
&lt;span style="color: #BA36A5;"&gt;fp&lt;/span&gt; = elementwise_grad(f, 1)
&lt;span style="color: #BA36A5;"&gt;fpp&lt;/span&gt; = elementwise_grad(fp, 1)
&lt;span style="color: #BA36A5;"&gt;fppp&lt;/span&gt; = elementwise_grad(fpp, 1)

&lt;span style="color: #BA36A5;"&gt;eta&lt;/span&gt; = np.linspace(0, 6).reshape((-1, 1))

&lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;This is the function we seek to minimize&lt;/span&gt;
&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;objective&lt;/span&gt;(params, step):
&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;These should all be zero at the solution&lt;/span&gt;
&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;f''' + 0.5 f'' f = 0&lt;/span&gt;
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;zeq&lt;/span&gt; = fppp(params, eta) + 0.5 * f(params, eta) * fpp(params, eta) 
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;bc0&lt;/span&gt; = f(params, 0.0)  &lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;equal to zero at solution&lt;/span&gt;
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;bc1&lt;/span&gt; = fp(params, 0.0)  &lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;equal to zero at solution&lt;/span&gt;
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;bc2&lt;/span&gt; = fp(params, 6.0) - 1.0 &lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;this is the one at "infinity"&lt;/span&gt;
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; np.mean(zeq**2) + bc0**2 + bc1**2 + bc2**2

&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;callback&lt;/span&gt;(params, step, g):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;if&lt;/span&gt; step % 1000 == 0:
&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: #0000FF;"&gt;print&lt;/span&gt;(&lt;span style="color: #008000;"&gt;"Iteration {0:3d} objective {1}"&lt;/span&gt;.&lt;span style="color: #006FE0;"&gt;format&lt;/span&gt;(step,
&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;   &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;   &lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt; objective(params, step)))

&lt;span style="color: #BA36A5;"&gt;params&lt;/span&gt; = adam(grad(objective), params,
&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; step_size=0.001, num_iters=10000, callback=callback) 

&lt;span style="color: #0000FF;"&gt;print&lt;/span&gt;(&lt;span style="color: #008000;"&gt;'f(0) = {}'&lt;/span&gt;.&lt;span style="color: #006FE0;"&gt;format&lt;/span&gt;(f(params, 0.0)))
&lt;span style="color: #0000FF;"&gt;print&lt;/span&gt;(&lt;span style="color: #008000;"&gt;'fp(0) = {}'&lt;/span&gt;.&lt;span style="color: #006FE0;"&gt;format&lt;/span&gt;(fp(params, 0.0)))
&lt;span style="color: #0000FF;"&gt;print&lt;/span&gt;(&lt;span style="color: #008000;"&gt;'fp(6) = {}'&lt;/span&gt;.&lt;span style="color: #006FE0;"&gt;format&lt;/span&gt;(fp(params, 6.0)))
&lt;span style="color: #0000FF;"&gt;print&lt;/span&gt;(&lt;span style="color: #008000;"&gt;'fpp(0) = {}'&lt;/span&gt;.&lt;span style="color: #006FE0;"&gt;format&lt;/span&gt;(fpp(params, 0.0)))

&lt;span style="color: #0000FF;"&gt;import&lt;/span&gt; matplotlib.pyplot &lt;span style="color: #0000FF;"&gt;as&lt;/span&gt; plt
plt.plot(eta, f(params, eta))
plt.xlabel(&lt;span style="color: #008000;"&gt;'$\eta$'&lt;/span&gt;)
plt.ylabel(&lt;span style="color: #008000;"&gt;'$f(\eta)$'&lt;/span&gt;)
plt.xlim([0, 6])
plt.ylim([0, 4.5])
plt.savefig(&lt;span style="color: #008000;"&gt;'nn-blasius.png'&lt;/span&gt;)
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
Iteration   0 objective 1.11472535
Iteration 1000 objective 0.00049768
Iteration 2000 objective 0.0004579
Iteration 3000 objective 0.00041697
Iteration 4000 objective 0.00037408
Iteration 5000 objective 0.00033705
Iteration 6000 objective 0.00031016
Iteration 7000 objective 0.00029197
Iteration 8000 objective 0.00027585
Iteration 9000 objective 0.00024616
f(0) = -0.00014613
fp(0) = 0.0003518041251639459
fp(6) = 0.999518061473252
fpp(0) = 0.3263370503702663
&lt;/p&gt;

&lt;p&gt;
&lt;img src="/media/nn-blasius.png"&gt; 
I think it is worth discussing what we accomplished here. You can see we have arrived at an approximate solution to our differential equation and the boundary conditions. The boundary conditions seem pretty closely met, and the figure is approximately the same as the previous post. Even better, our solution is &lt;i&gt;an actual function&lt;/i&gt; and not a numeric solution that has to be interpolated. We can evaluate it any where we want, including its derivatives!
&lt;/p&gt;

&lt;p&gt;
We did not, however, have to convert the ODE into a system of first-order differential equations, and we did &lt;i&gt;not&lt;/i&gt; have to approximate the derivatives with finite differences.
&lt;/p&gt;

&lt;p&gt;
Note, however, that with finite differences we got &lt;code&gt;f''(0)=0.3325&lt;/code&gt;. This &lt;a href="https://www.calpoly.edu/~kshollen/ME347/Handouts/Blasius.pdf"&gt;site&lt;/a&gt; reports &lt;code&gt;f''(0)=0.3321&lt;/code&gt;. We get close to that here with &lt;code&gt;f''(0) = 0.3263&lt;/code&gt;. We could probably get closer to this with more training to reduce the objective function further, or with a finer grid. It is evident that even after 9000 steps, it is still decreasing. Getting accurate derivatives is a stringent test for this, as they are measures of the function curvature.
&lt;/p&gt;

&lt;p&gt;
It is hard to tell how broadly useful this is; I have not tried it beyond this example. In the past, I have found solving BVPs to be pretty sensitive to the initial guesses of the solution. Here we made almost no guess at all, and still got a solution. I find that pretty remarkable.
&lt;/p&gt;
&lt;p&gt;Copyright (C) 2017 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/2017/11/27/Solving-BVPs-with-a-neural-network-and-autograd.org"&gt;org-mode source&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Org-mode version = 9.1.2&lt;/p&gt;]]></content:encoded>
    </item>
    <item>
      <title>Solving the Blasius equation</title>
      <link>https://kitchingroup.cheme.cmu.edu/blog/2013/03/11/Solving-the-Blasius-equation</link>
      <pubDate>Mon, 11 Mar 2013 10:44:56 EDT</pubDate>
      <category><![CDATA[bvp]]></category>
      <guid isPermaLink="false">ODc-Vmqi0O_YirdSGj_xQAB3ZAM=</guid>
      <description>Solving the Blasius equation</description>
      <content:encoded><![CDATA[


&lt;p&gt;
In fluid mechanics the Blasius equation comes up (&lt;a href="http://en.wikipedia.org/wiki/Blasius_boundary_layer"&gt;http://en.wikipedia.org/wiki/Blasius_boundary_layer&lt;/a&gt;) to describe the boundary layer that forms near a flat plate with fluid moving by it. The nonlinear differential equation is:
&lt;/p&gt;

\begin{eqnarray}
f''' + \frac{1}{2} f f'' &amp;=&amp; 0 \\
f(0) &amp;=&amp; 0 \\
f'(0) &amp;=&amp; 0 \\
f'(\infty) &amp;=&amp; 1
\end{eqnarray}

&lt;p&gt;
This is a nonlinear, boundary value problem. The point of solving this equation is to get the value of \(f''(0)\) to evaluate the shear stress at the plate.
&lt;/p&gt;

&lt;p&gt;
We have to convert this to a system of first-order differential equations. Let \(f_1 = f\), \(f_2 = f_1'\) and \(f_3 = f_2'\). This leads to:
&lt;/p&gt;

\begin{eqnarray}
f_1' = f_2 \\
f_2' = f_3 \\
f_3' = -\frac{1}{2} f_1 f_3 \\
f_1(0) = 0 \\
f_2(0) = 0 \\
f_2(\infty) = 1
\end{eqnarray}

&lt;p&gt;
It is not possible to specify a boundary condition at \(\infty\) numerically, so we will have to use a large number, and verify it is "large enough". From the solution, we evaluate the derivatives at \(\eta=0\), and we have \(f''(0) = f_3(0)\).
&lt;/p&gt;

&lt;p&gt;
We have to provide initial guesses for f_1, f_2 and f_3. This is the hardest part about this problem. We know that f_1 starts at zero, and is flat there (f'(0)=0), but at large eta, it has a constant slope of one. We will guess a simple line of slope = 1 for f_1. That is correct at large eta, and is zero at &amp;eta;=0. If the slope of the function is constant at large \(\eta\), then the values of higher derivatives must tend to zero. We choose an exponential decay as a guess.
&lt;/p&gt;

&lt;p&gt;
Finally, we let a solver iteratively find a solution for us, and find the answer we want. The solver is in the pycse module.
&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; pycse &lt;span style="color: #0000FF;"&gt;import&lt;/span&gt; bvp

&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;odefun&lt;/span&gt;(F, x):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;f1&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;f2&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;f3&lt;/span&gt; = F.T
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; np.column_stack([f2,
&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;   &lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   f3,
&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;   &lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   -0.5 * f1 * f3])

&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;bcfun&lt;/span&gt;(Y):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;fa&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;fb&lt;/span&gt; = Y[0, :], Y[-1, :]
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; [fa[0],        &lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;f1(0) =  0&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;   fa[1],        &lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;f2(0) = 0&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;   1.0 - fb[1]]  &lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;f2(inf) = 1&lt;/span&gt;

&lt;span style="color: #BA36A5;"&gt;eta&lt;/span&gt; = np.linspace(0, 6, 100)
&lt;span style="color: #BA36A5;"&gt;f1init&lt;/span&gt; = eta
&lt;span style="color: #BA36A5;"&gt;f2init&lt;/span&gt; = np.exp(-eta)
&lt;span style="color: #BA36A5;"&gt;f3init&lt;/span&gt; = np.exp(-eta)

&lt;span style="color: #BA36A5;"&gt;Finit&lt;/span&gt; = np.column_stack([f1init, f2init, f3init])

&lt;span style="color: #BA36A5;"&gt;sol&lt;/span&gt; = bvp(odefun, bcfun, eta, Finit)
&lt;span style="color: #BA36A5;"&gt;f1&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;f2&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;f3&lt;/span&gt; = sol.T

&lt;span style="color: #0000FF;"&gt;print&lt;/span&gt;(&lt;span style="color: #008000;"&gt;"f''(0) = f_3(0) = {0}"&lt;/span&gt;.&lt;span style="color: #006FE0;"&gt;format&lt;/span&gt;(f3[0]))

&lt;span style="color: #0000FF;"&gt;import&lt;/span&gt; matplotlib.pyplot &lt;span style="color: #0000FF;"&gt;as&lt;/span&gt; plt
plt.plot(eta, f1)
plt.xlabel(&lt;span style="color: #008000;"&gt;'$\eta$'&lt;/span&gt;)
plt.ylabel(&lt;span style="color: #008000;"&gt;'$f(\eta)$'&lt;/span&gt;)
plt.savefig(&lt;span style="color: #008000;"&gt;'images/blasius.png'&lt;/span&gt;)
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
f''(0) = f_3(0) = 0.3324911095517483

&lt;/pre&gt;


&lt;p&gt;
&lt;img src="/media/blasius.png"&gt; 
&lt;/p&gt;

&lt;p&gt;
&lt;span class="timestamp-wrapper"&gt;&lt;span class="timestamp"&gt;&amp;lt;2017-05-17 Wed&amp;gt; &lt;/span&gt;&lt;/span&gt; You need pycse 1.6.4 for this example.
&lt;/p&gt;
&lt;p&gt;Copyright (C) 2017 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/11/Solving-the-Blasius-equation.org"&gt;org-mode source&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Org-mode version = 9.1.2&lt;/p&gt;]]></content:encoded>
    </item>
    <item>
      <title>Another look at nonlinear BVPs</title>
      <link>https://kitchingroup.cheme.cmu.edu/blog/2013/03/11/Another-look-at-nonlinear-BVPs</link>
      <pubDate>Mon, 11 Mar 2013 10:44:40 EDT</pubDate>
      <category><![CDATA[bvp]]></category>
      <guid isPermaLink="false">f7B-1mUw9xZvl0ANN_I5q6L7BSA=</guid>
      <description>Another look at nonlinear BVPs</description>
      <content:encoded><![CDATA[


&lt;p&gt;
Adapted from &lt;a href="http://www.mathworks.com/help/matlab/ref/bvp4c.html" &gt;http://www.mathworks.com/help/matlab/ref/bvp4c.html&lt;/a&gt;

&lt;/p&gt;

&lt;p&gt;
Boundary value problems may have more than one solution. Let us consider the BVP:
&lt;/p&gt;

\begin{eqnarray}
y'' + |y| &amp;=&amp; 0 \\
y(0) &amp;=&amp; 0 \\
y(4) &amp;=&amp; -2
\end{eqnarray}

&lt;p&gt;
We will see this equation has two answers, depending on your initial guess. We convert this to the following set of coupled equations:
&lt;/p&gt;

\begin{eqnarray}
y_1' &amp;=&amp; y_2 \\
y_2' &amp;=&amp; -|y_1| \\
y_1(0)&amp;=&amp; 0\\
y_1(4) &amp;=&amp; -2
\end{eqnarray}

&lt;p&gt;
This BVP is nonlinear because of the absolute value. We will have to guess solutions to get started. We will guess two different solutions, both of which will be constant values.  We will use pycse.bvp to solve 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
&lt;span style="color: #8b0000;"&gt;from&lt;/span&gt; pycse &lt;span style="color: #8b0000;"&gt;import&lt;/span&gt; bvp
&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;odefun&lt;/span&gt;(Y, x):
    y1, y2 = Y
    dy1dx = y2
    dy2dx = -np.abs(y1)
    &lt;span style="color: #8b0000;"&gt;return&lt;/span&gt; [dy1dx, dy2dx]

&lt;span style="color: #8b0000;"&gt;def&lt;/span&gt; &lt;span style="color: #8b2323;"&gt;bcfun&lt;/span&gt;(Ya, Yb):
    y1a, y2a = Ya
    y1b, y2b = Yb

    &lt;span style="color: #8b0000;"&gt;return&lt;/span&gt; [y1a, -2 - y1b]

x = np.linspace(0, 4, 100)

y1 = 1.0 * np.ones(x.shape)
y2 = 0.0 * np.ones(x.shape)

Yinit = np.vstack([y1, y2])

sol = bvp(odefun, bcfun, x, Yinit)

plt.plot(x, sol[0])

&lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;another initial guess&lt;/span&gt;
y1 = -1.0 * np.ones(x.shape)
y2 = 0.0 * np.ones(x.shape)

Yinit = np.vstack([y1, y2])

sol = bvp(odefun, bcfun, x, Yinit)

plt.plot(x, sol[0])
plt.legend([&lt;span style="color: #228b22;"&gt;'guess 1'&lt;/span&gt;, &lt;span style="color: #228b22;"&gt;'guess 2'&lt;/span&gt;])
plt.savefig(&lt;span style="color: #228b22;"&gt;'images/bvp-another-nonlin-1.png'&lt;/span&gt;)
plt.show()
&lt;/pre&gt;
&lt;/div&gt;


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

&lt;p&gt;
This example shows that a nonlinear BVP may have different solutions, and which one you get depends on the guess you make for the solution. This is analogous to solving nonlinear algebraic equations (which is what is done in solving this problem!).
&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/11/Another-look-at-nonlinear-BVPs.org"&gt;org-mode source&lt;/a&gt;&lt;p&gt;]]></content:encoded>
    </item>
    <item>
      <title>Boundary value problem in heat conduction</title>
      <link>https://kitchingroup.cheme.cmu.edu/blog/2013/03/06/Boundary-value-problem-in-heat-conduction</link>
      <pubDate>Wed, 06 Mar 2013 19:35:39 EST</pubDate>
      <category><![CDATA[bvp]]></category>
      <guid isPermaLink="false">3aI5z4CyumczzU5swqki2i6aDhE=</guid>
      <description>Boundary value problem in heat conduction</description>
      <content:encoded><![CDATA[


&lt;p&gt;
&lt;a href="http://matlab.cheme.cmu.edu/2011/08/11/boundary-value-problem-in-heat-conduction/" &gt;Matlab post&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;
For steady state heat conduction the temperature distribution in one-dimension is governed by the Laplace equation:
&lt;/p&gt;

&lt;p&gt;
$$ \nabla^2 T = 0$$
&lt;/p&gt;

&lt;p&gt;
with boundary conditions that at \(T(x=a) = T_A\) and \(T(x=L) = T_B\).
&lt;/p&gt;

&lt;p&gt;
The analytical solution is not difficult here: \(T = T_A-\frac{T_A-T_B}{L}x\), but we will solve this by finite differences.
&lt;/p&gt;

&lt;p&gt;
For this problem, lets consider a slab that is defined by x=0 to x=L, with \(T(x=0) = 100\), and \(T(x=L) = 200\). We want to find the function T(x) inside the slab.
&lt;/p&gt;

&lt;p&gt;
We approximate the second derivative by finite differences as
&lt;/p&gt;

&lt;p&gt;
\( f''(x) \approx \frac{f(x-h) - 2 f(x) + f(x+h)}{h^2} \)
&lt;/p&gt;

&lt;p&gt;
Since the second derivative in this case is equal to zero, we have at each discretized node \(0 = T_{i-1} - 2 T_i + T_{i+1}\). We know the values of \(T_{x=0} = \alpha\) and \(T_{x=L} = \beta\).
&lt;/p&gt;

&lt;p&gt;
\[A = \left [ \begin{array}{ccccc} %
 -2         &amp; 1 &amp; 0                    &amp; 0 &amp; 0 \\
1           &amp; -2&amp; 1 &amp; 0 &amp; 0 \\
0                    &amp; \ddots               &amp; \ddots               &amp; \ddots &amp; 0 \\
0                    &amp; 0                    &amp; 1 &amp; -2 &amp; 1 \\
0                    &amp; 0                    &amp; 0  &amp; 1  &amp; -2  \end{array} \right ] \]
&lt;/p&gt;

&lt;p&gt;
\[ x = \left [ \begin{array}{c} T_1 \\ \vdots \\ T_N \end{array} \right ] \]
&lt;/p&gt;

&lt;p&gt;
\[ b = \left [ \begin{array}{c} -T(x=0) \\
0 \\
\vdots \\
0 \\
-T(x=L) \end{array} \right] \]
&lt;/p&gt;

&lt;p&gt;
These are linear equations in the unknowns \(x\) that we can easily solve. Here, we evaluate the 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: #ff0000; font-weight: bold;"&gt;#&lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;we use the notation T(x1) = alpha and T(x2) = beta&lt;/span&gt;
x1 = 0; alpha = 100
x2 = 5; beta = 200

npoints = 100

&lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;preallocate and shape the b vector and A-matrix&lt;/span&gt;
b = np.zeros((npoints, 1));
b[0] = -alpha
b[-1] = -beta

A = np.zeros((npoints, npoints));

&lt;span style="color: #ff0000; font-weight: bold;"&gt;#&lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;now we populate the A-matrix and b vector elements&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;(npoints ):
    &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;(npoints):
        &lt;span style="color: #8b0000;"&gt;if&lt;/span&gt; j == i: &lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;the diagonal&lt;/span&gt;
            A[i,j] = -2
        &lt;span style="color: #8b0000;"&gt;elif&lt;/span&gt; j == i - 1: &lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;left of the diagonal&lt;/span&gt;
            A[i,j] = 1
        &lt;span style="color: #8b0000;"&gt;elif&lt;/span&gt; j == i + 1: &lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;right of the diagonal&lt;/span&gt;
            A[i,j] = 1
 
&lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;solve the equations A*y = b for Y&lt;/span&gt;
Y = np.linalg.solve(A,b)

x = np.linspace(x1, x2, npoints + 2)
y = np.hstack([alpha, Y[:,0], beta])

&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(x, y)

plt.plot(x, alpha + (beta - alpha)/(x2 - x1) * x, &lt;span style="color: #228b22;"&gt;'r--'&lt;/span&gt;)

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.legend((&lt;span style="color: #228b22;"&gt;'finite difference'&lt;/span&gt;, &lt;span style="color: #228b22;"&gt;'analytical soln'&lt;/span&gt;), loc=&lt;span style="color: #228b22;"&gt;'best'&lt;/span&gt;)
plt.savefig(&lt;span style="color: #228b22;"&gt;'images/bvp-heat-conduction-1d.png'&lt;/span&gt;)
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;&lt;img src="/img/./images/bvp-heat-conduction-1d.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/03/06/Boundary-value-problem-in-heat-conduction.org"&gt;org-mode source&lt;/a&gt;&lt;p&gt;]]></content:encoded>
    </item>
    <item>
      <title>Plane Poiseuille flow - BVP solve by shooting method</title>
      <link>https://kitchingroup.cheme.cmu.edu/blog/2013/02/15/Plane-Poiseuille-flow-BVP-solve-by-shooting-method</link>
      <pubDate>Fri, 15 Feb 2013 09:00:00 EST</pubDate>
      <category><![CDATA[bvp]]></category>
      <guid isPermaLink="false">bqorDWLjDORIcAN56YRsNlcEif0=</guid>
      <description>Plane Poiseuille flow - BVP solve by shooting method</description>
      <content:encoded><![CDATA[


&lt;p&gt;
&lt;a href="http://matlab.cheme.cmu.edu/2011/09/08/plane-poiseuille-flow-bvp-solve-by-shooting-method/" &gt;Matlab post&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;
One approach to solving BVPs is to use the shooting method. The reason we cannot use an initial value solver for a BVP is that there is not enough information at the initial value to start. In the shooting method, we take the function value at the initial point, and guess what the function derivatives are so that we can do an integration. If our guess was good, then the solution will go through the known second boundary point. If not, we guess again, until we get the answer we need. In this example we repeat the pressure driven flow example, but illustrate the shooting method.
&lt;/p&gt;

&lt;p&gt;
In the pressure driven flow of a fluid with viscosity \(\mu\) between two stationary plates separated by distance \(d\) and driven by a pressure drop \(\Delta P/\Delta x\), the governing equations on the velocity \(u\) of the fluid are (assuming flow in the x-direction with the velocity varying only in the y-direction):
&lt;/p&gt;

&lt;p&gt;
$$\frac{\Delta P}{\Delta x} = \mu \frac{d^2u}{dy^2}$$
&lt;/p&gt;

&lt;p&gt;
with boundary conditions \(u(y=0) = 0\) and \(u(y=d) = 0\), i.e. the no-slip condition at the edges of the plate.
&lt;/p&gt;

&lt;p&gt;
we convert this second order BVP to a system of ODEs by letting \(u_1 = u\), \(u_2 = u_1'\) and then \(u_2' = u_1''\). This leads to:
&lt;/p&gt;

&lt;p&gt;
\(\frac{d u_1}{dy} = u_2\)
&lt;/p&gt;

&lt;p&gt;
\(\frac{d u_2}{dy} = \frac{1}{\mu}\frac{\Delta P}{\Delta x}\)
&lt;/p&gt;

&lt;p&gt;
with boundary conditions \(u_1(y=0) = 0\) and \(u_1(y=d) = 0\).
&lt;/p&gt;

&lt;p&gt;
for this problem we let the plate separation be d=0.1, the viscosity \(\mu = 1\), and \(\frac{\Delta P}{\Delta x} = -100\).
&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; First guess&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-1"&gt;
&lt;p&gt;
We need u_1(0) and u_2(0), but we only have u_1(0). We need to guess a value for u_2(0) and see if the solution goes through the u_2(d)=0 boundary value.
&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

d = 0.1 &lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;plate thickness&lt;/span&gt;

&lt;span style="color: #8b0000;"&gt;def&lt;/span&gt; &lt;span style="color: #8b2323;"&gt;odefun&lt;/span&gt;(U, y):
    u1, u2 = U
    mu = 1
    Pdrop = -100
    du1dy = u2
    du2dy = 1.0 / mu * Pdrop
    &lt;span style="color: #8b0000;"&gt;return&lt;/span&gt; [du1dy, du2dy]

u1_0 = 0 &lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;known&lt;/span&gt;
u2_0 = 1 &lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;guessed&lt;/span&gt;

dspan = np.linspace(0, d)

U = odeint(odefun, [u1_0, u2_0], dspan)

plt.plot(dspan, U[:,0])
plt.plot([d],[0], &lt;span style="color: #228b22;"&gt;'ro'&lt;/span&gt;)
plt.xlabel(&lt;span style="color: #228b22;"&gt;'d'&lt;/span&gt;)
plt.ylabel(&lt;span style="color: #228b22;"&gt;'$u_1$'&lt;/span&gt;)
plt.savefig(&lt;span style="color: #228b22;"&gt;'images/bvp-shooting-1.png'&lt;/span&gt;)
&lt;/pre&gt;
&lt;/div&gt;

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

&lt;p&gt;
Here we have undershot the boundary condition. Let us try a larger guess.
&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; Second guess&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-2"&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

d = 0.1 &lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;plate thickness&lt;/span&gt;

&lt;span style="color: #8b0000;"&gt;def&lt;/span&gt; &lt;span style="color: #8b2323;"&gt;odefun&lt;/span&gt;(U, y):
    u1, u2 = U
    mu = 1
    Pdrop = -100
    du1dy = u2
    du2dy = 1.0 / mu * Pdrop
    &lt;span style="color: #8b0000;"&gt;return&lt;/span&gt; [du1dy, du2dy]

u1_0 = 0 &lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;known&lt;/span&gt;
u2_0 = 10 &lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;guessed&lt;/span&gt;

dspan = np.linspace(0, d)

U = odeint(odefun, [u1_0, u2_0], dspan)

plt.plot(dspan, U[:,0])
plt.plot([d],[0], &lt;span style="color: #228b22;"&gt;'ro'&lt;/span&gt;)
plt.xlabel(&lt;span style="color: #228b22;"&gt;'d'&lt;/span&gt;)
plt.ylabel(&lt;span style="color: #228b22;"&gt;'$u_1$'&lt;/span&gt;)
plt.savefig(&lt;span style="color: #228b22;"&gt;'images/bvp-shooting-2.png'&lt;/span&gt;)
&lt;/pre&gt;
&lt;/div&gt;

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

&lt;p&gt;
Now we have clearly overshot. Let us now make a function that will iterate for us to find the right value.
&lt;/p&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; Let fsolve do the work&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-3"&gt;
&lt;div class="org-src-container"&gt;

&lt;pre class="src src-python"&gt;&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;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

d = 0.1 &lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;plate thickness&lt;/span&gt;
Pdrop = -100
mu = 1

&lt;span style="color: #8b0000;"&gt;def&lt;/span&gt; &lt;span style="color: #8b2323;"&gt;odefun&lt;/span&gt;(U, y):
    u1, u2 = U
    du1dy = u2
    du2dy = 1.0 / mu * Pdrop
    &lt;span style="color: #8b0000;"&gt;return&lt;/span&gt; [du1dy, du2dy]

u1_0 = 0 &lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;known&lt;/span&gt;
dspan = np.linspace(0, d)

&lt;span style="color: #8b0000;"&gt;def&lt;/span&gt; &lt;span style="color: #8b2323;"&gt;objective&lt;/span&gt;(u2_0):
    dspan = np.linspace(0, d)
    U = odeint(odefun, [u1_0, u2_0], dspan)
    u1 = U[:,0]
    &lt;span style="color: #8b0000;"&gt;return&lt;/span&gt; u1[-1]

u2_0, = fsolve(objective, 1.0)

&lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;now solve with optimal u2_0&lt;/span&gt;
U = odeint(odefun, [u1_0, u2_0], dspan)

plt.plot(dspan, U[:,0], label=&lt;span style="color: #228b22;"&gt;'Numerical solution'&lt;/span&gt;)
plt.plot([d],[0], &lt;span style="color: #228b22;"&gt;'ro'&lt;/span&gt;)

&lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;plot an analytical solution&lt;/span&gt;
u = -(Pdrop) * d**2 / 2 / mu * (dspan / d - (dspan / d)**2)
plt.plot(dspan, u, &lt;span style="color: #228b22;"&gt;'r--'&lt;/span&gt;, label=&lt;span style="color: #228b22;"&gt;'Analytical solution'&lt;/span&gt;)


plt.xlabel(&lt;span style="color: #228b22;"&gt;'d'&lt;/span&gt;)
plt.ylabel(&lt;span style="color: #228b22;"&gt;'$u_1$'&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/bvp-shooting-3.png'&lt;/span&gt;)
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;&lt;img src="/img/./images/bvp-shooting-3.png"&gt;&lt;p&gt;

&lt;p&gt;
You can see the agreement is excellent!
&lt;/p&gt;

&lt;p&gt;
This also seems like a useful bit of code to not have to reinvent regularly, so it has been added to pycse as BVP_sh. Here is an example usage.
&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_sh
&lt;span style="color: #8b0000;"&gt;import&lt;/span&gt; matplotlib.pyplot &lt;span style="color: #8b0000;"&gt;as&lt;/span&gt; plt

d = 0.1 &lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;plate thickness&lt;/span&gt;
Pdrop = -100
mu = 1

&lt;span style="color: #8b0000;"&gt;def&lt;/span&gt; &lt;span style="color: #8b2323;"&gt;odefun&lt;/span&gt;(U, y):
    u1, u2 = U
    du1dy = u2
    du2dy = 1.0 / mu * Pdrop
    &lt;span style="color: #8b0000;"&gt;return&lt;/span&gt; [du1dy, du2dy]

x1 = 0.0; alpha = 0.0
x2 = 0.1; beta = 0.0
init = 2.0 &lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;initial guess of slope at x=0&lt;/span&gt;

X,Y = BVP_sh(odefun, x1, x2, alpha, beta, init)
plt.plot(X, Y[:,0])
plt.ylim([0, 0.14])

&lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;plot an analytical solution&lt;/span&gt;
u = -(Pdrop) * d**2 / 2 / mu * (X / d - (X / d)**2)
plt.plot(X, u, &lt;span style="color: #228b22;"&gt;'r--'&lt;/span&gt;, label=&lt;span style="color: #228b22;"&gt;'Analytical solution'&lt;/span&gt;)
plt.savefig(&lt;span style="color: #228b22;"&gt;'images/bvp-shooting-4.png'&lt;/span&gt;)
plt.show()
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;&lt;img src="/img/./images/bvp-shooting-4.png"&gt;&lt;p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Copyright (C) 2013 by John Kitchin. See the &lt;a href="/copying.html"&gt;License&lt;/a&gt; for information about copying.&lt;p&gt;&lt;p&gt;&lt;a href="/org/2013/02/15/Plane-Poiseuille-flow---BVP-solve-by-shooting-method.org"&gt;org-mode source&lt;/a&gt;&lt;p&gt;]]></content:encoded>
    </item>
    <item>
      <title>Plane poiseuelle flow solved by finite difference</title>
      <link>https://kitchingroup.cheme.cmu.edu/blog/2013/02/14/Plane-poiseuelle-flow-solved-by-finite-difference</link>
      <pubDate>Thu, 14 Feb 2013 09:00:00 EST</pubDate>
      <category><![CDATA[bvp]]></category>
      <guid isPermaLink="false">qGeCcu8in1U5XgH9_cNB2-ynl4s=</guid>
      <description>Plane poiseuelle flow solved by finite difference</description>
      <content:encoded><![CDATA[


&lt;p&gt;
&lt;a href="http://matlab.cheme.cmu.edu/2011/09/30/plane-poiseuelle-flow-solved-by-finite-difference/" &gt;Matlab post&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;
Adapted from &lt;a href="http://www.physics.arizona.edu/~restrepo/475B/Notes/sourcehtml/node24.html" &gt;http://www.physics.arizona.edu/~restrepo/475B/Notes/sourcehtml/node24.html&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;
We want to solve a linear boundary value problem of the form: y'' = p(x)y' + q(x)y + r(x) with boundary conditions y(x1) = alpha and y(x2) = beta.
&lt;/p&gt;

&lt;p&gt;
For this example, we solve the plane poiseuille flow problem using a finite difference approach. An advantage of the approach we use here is we do not have to rewrite the second order ODE as a set of coupled first order ODEs, nor do we have to provide guesses for the solution. We do, however, have to discretize the derivatives and formulate a linear algebra problem.
&lt;/p&gt;

&lt;p&gt;
we want to solve u'' = 1/mu*DPDX with u(0)=0 and u(0.1)=0. for this problem we let the plate separation be d=0.1, the viscosity \(\mu = 1\), and \(\frac{\Delta P}{\Delta x} = -100\).
&lt;/p&gt;

&lt;p&gt;
The idea behind the finite difference method is to approximate the derivatives by finite differences on a grid. See here for details. By discretizing the ODE, we arrive at a set of linear algebra equations of the form \(A y = b\), where \(A\) and \(b\) are defined as follows.
&lt;/p&gt;

&lt;p&gt;
\[A = \left [ \begin{array}{ccccc} %
 2 + h^2 q_1         &amp; -1 + \frac{h}{2} p_1 &amp; 0                    &amp; 0 &amp; 0 \\
-1 - \frac{h}{2} p_2 &amp; 2 + h^2 q_2          &amp; -1 + \frac{h}{2} p_2 &amp; 0 &amp; 0 \\
0                    &amp; \ddots               &amp; \ddots               &amp; \ddots &amp; 0 \\
0                    &amp; 0                    &amp; -1 - \frac{h}{2} p_{N-1} &amp; 2 + h^2 q_{N-1} &amp; -1 + \frac{h}{2} p_{N-1} \\
0                    &amp; 0                    &amp; 0  &amp; -1 - \frac{h}{2} p_N &amp; 2 + h^2 q_N \end{array} \right ] \]
&lt;/p&gt;

&lt;p&gt;
\[ y = \left [ \begin{array}{c} y_i \\ \vdots \\ y_N \end{array} \right ] \]
&lt;/p&gt;

&lt;p&gt;
\[ b = \left [ \begin{array}{c} -h^2 r_1 + ( 1 + \frac{h}{2} p_1) \alpha \\
-h^2 r_2 \\
\vdots \\
-h^2 r_{N-1} \\
-h^2 r_N + (1 - \frac{h}{2} p_N) \beta \end{array} \right] \]
&lt;/p&gt;

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

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

&lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;we use the notation for y'' = p(x)y' + q(x)y + r(x)&lt;/span&gt;
&lt;span style="color: #8b0000;"&gt;def&lt;/span&gt; &lt;span style="color: #8b2323;"&gt;p&lt;/span&gt;(x): &lt;span style="color: #8b0000;"&gt;return&lt;/span&gt; 0
&lt;span style="color: #8b0000;"&gt;def&lt;/span&gt; &lt;span style="color: #8b2323;"&gt;q&lt;/span&gt;(x): &lt;span style="color: #8b0000;"&gt;return&lt;/span&gt; 0
&lt;span style="color: #8b0000;"&gt;def&lt;/span&gt; &lt;span style="color: #8b2323;"&gt;r&lt;/span&gt;(x): &lt;span style="color: #8b0000;"&gt;return&lt;/span&gt; -100

&lt;span style="color: #ff0000; font-weight: bold;"&gt;#&lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;we use the notation y(x1) = alpha and y(x2) = beta&lt;/span&gt;

x1 = 0; alpha = 0.0
x2 = 0.1; beta = 0.0

npoints = 100

&lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;compute interval width&lt;/span&gt;
h = (x2-x1)/npoints;

&lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;preallocate and shape the b vector and A-matrix&lt;/span&gt;
b = np.zeros((npoints - 1, 1));
A = np.zeros((npoints - 1, npoints - 1));
X = np.zeros((npoints - 1, 1));

&lt;span style="color: #ff0000; font-weight: bold;"&gt;#&lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;now we populate the A-matrix and b vector elements&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;(npoints - 1):
    X[i,0] = x1 + (i + 1) * h

    &lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;get the value of the BVP Odes at this x&lt;/span&gt;
    pi = p(X[i])
    qi = q(X[i])
    ri = r(X[i])

    &lt;span style="color: #8b0000;"&gt;if&lt;/span&gt; i == 0:
        &lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;first boundary condition&lt;/span&gt;
        b[i] = -h**2 * ri + (1 + h / 2 * pi)*alpha; 
    &lt;span style="color: #8b0000;"&gt;elif&lt;/span&gt; i == npoints - 1:
        &lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;second boundary condition&lt;/span&gt;
        b[i] = -h**2 * ri + (1 - h / 2 * pi)*beta; 
    &lt;span style="color: #8b0000;"&gt;else:&lt;/span&gt;
        b[i] = -h**2 * ri &lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;intermediate points&lt;/span&gt;
    
    &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;(npoints - 1):
        &lt;span style="color: #8b0000;"&gt;if&lt;/span&gt; j == i: &lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;the diagonal&lt;/span&gt;
            A[i,j] = 2 + h**2 * qi
        &lt;span style="color: #8b0000;"&gt;elif&lt;/span&gt; j == i - 1: &lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;left of the diagonal&lt;/span&gt;
            A[i,j] = -1 - h / 2 * pi
        &lt;span style="color: #8b0000;"&gt;elif&lt;/span&gt; j == i + 1: &lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;right of the diagonal&lt;/span&gt;
            A[i,j] = -1 + h / 2 * pi
        &lt;span style="color: #8b0000;"&gt;else:&lt;/span&gt;
            A[i,j] = 0 &lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;off the tri-diagonal&lt;/span&gt;
 
&lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;solve the equations A*y = b for Y&lt;/span&gt;
Y = np.linalg.solve(A,b)

x = np.hstack([x1, X[:,0], x2])
y = np.hstack([alpha, Y[:,0], beta])

&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(x, y)

mu = 1
d = 0.1
x = np.linspace(0,0.1);
Pdrop = -100 &lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;this is DeltaP/Deltax&lt;/span&gt;
u = -(Pdrop) * d**2 / 2.0 / mu * (x / d - (x / d)**2)
plt.plot(x,u,&lt;span style="color: #228b22;"&gt;'r--'&lt;/span&gt;)

plt.xlabel(&lt;span style="color: #228b22;"&gt;'distance between plates'&lt;/span&gt;)
plt.ylabel(&lt;span style="color: #228b22;"&gt;'fluid velocity'&lt;/span&gt;)
plt.legend((&lt;span style="color: #228b22;"&gt;'finite difference'&lt;/span&gt;, &lt;span style="color: #228b22;"&gt;'analytical soln'&lt;/span&gt;))
plt.savefig(&lt;span style="color: #228b22;"&gt;'images/pp-bvp-fd.png'&lt;/span&gt;)
plt.show()
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;&lt;img src="/img/./images/pp-bvp-fd.png"&gt;&lt;p&gt;

&lt;p&gt;
You can see excellent agreement here between the numerical and analytical solution.&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/14/Plane-poiseuelle-flow-solved-by-finite-difference.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>
  </channel>
</rss>
