<?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>Using autograd to plot implicit functions</title>
      <link>https://kitchingroup.cheme.cmu.edu/blog/2019/10/02/Using-autograd-to-plot-implicit-functions</link>
      <pubDate>Wed, 02 Oct 2019 21:30:46 EDT</pubDate>
      <category><![CDATA[implicit-function]]></category>
      <category><![CDATA[autograd]]></category>
      <category><![CDATA[nonlinear-algebra]]></category>
      <guid isPermaLink="false">wHgJ4s626IZnowZ4a9K6z-zL8p0=</guid>
      <description>Using autograd to plot implicit functions</description>
      <content:encoded><![CDATA[


&lt;p&gt;
Consider the solution to these equations (adapted from &lt;a href="https://www.mathworks.com/help/optim/ug/fsolve.html"&gt;https://www.mathworks.com/help/optim/ug/fsolve.html&lt;/a&gt;):
&lt;/p&gt;

&lt;p&gt;
\(e^{-e^{-(x_1 + x_2)}} = x_2 (1 + x_1^2)\)
&lt;/p&gt;

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

&lt;p&gt;
\(x_1 \cos(x_2) + x_2 \sin(x_1) = 1/2\)
&lt;/p&gt;

&lt;p&gt;
It is not clear how many solutions there are to this set of equations, or what you should guess for the initial guess. Usually, the best way to see where a solution might be is to plot the equations and see where they intersect. These equations are implicit though, and it is not easy to plot them because we cannot solve for \(x_2\) in terms of \(x_1\) in either case. Here we explore a strategy to get plots so we can see where solutions could be.
&lt;/p&gt;

&lt;p&gt;
The idea is that we find one solution to each equation independently. Then, we derive a differential equation for each equation so we can integrate it to find the curve that is defined by the implicit function.  First, we find a solution for each equation. We guess a value for \(x_2\) and then find the value of \(x_1\) that solves each equation independently.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython"&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; scipy.optimize &lt;span style="color: #0000FF;"&gt;import&lt;/span&gt; fsolve

&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;f1&lt;/span&gt;(x1, x2):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; np.exp(-np.exp(-(x1 + x2))) - x2 * (1 + x1**2)

&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;f2&lt;/span&gt;(x1, x2):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; x1 * np.cos(x2) + x2 * np.sin(x1) - 0.5

&lt;span style="color: #BA36A5;"&gt;x2_1&lt;/span&gt; = 0.6
x1_1, = fsolve(f1, 0, args=(x2_1,))
&lt;span style="color: #0000FF;"&gt;print&lt;/span&gt;(&lt;span style="color: #008000;"&gt;'f1: '&lt;/span&gt;, x1_1, x2_1)

&lt;span style="color: #BA36A5;"&gt;x2_2&lt;/span&gt; = 1.0
x1_2, = fsolve(f2, 0 ,args=(x2_2,))
&lt;span style="color: #0000FF;"&gt;print&lt;/span&gt;(&lt;span style="color: #008000;"&gt;'f2: '&lt;/span&gt;, x1_2, x2_2)
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
f1:  0.08638978040861575 0.6
f2:  0.32842406163614396 1.0
&lt;/p&gt;

&lt;p&gt;
Next, we need a differential equation that is \(dx_2/dx_1\). If we had that, we could just integrate it from one of the starting points above, and get the curve we want. The functions are implicit, so we have to use the implicit derivative, which for the first equation is \(dx_2/dx_1 = -df1/dx_1 / df1/dx_2\). We will get these gradients from autograd. Then, we just integrate the solution. Here we do this for the first equation.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython"&gt;&lt;span style="color: #0000FF;"&gt;from&lt;/span&gt; scipy.integrate &lt;span style="color: #0000FF;"&gt;import&lt;/span&gt; solve_ivp
&lt;span style="color: #0000FF;"&gt;from&lt;/span&gt; autograd &lt;span style="color: #0000FF;"&gt;import&lt;/span&gt; grad

&lt;span style="color: #BA36A5;"&gt;df1dx1&lt;/span&gt; = grad(f1, 0)
&lt;span style="color: #BA36A5;"&gt;df1dx2&lt;/span&gt; = grad(f1, 1)

&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;dx2dx1_1&lt;/span&gt;(x1, x2):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; -df1dx1(x1, x2) / df1dx2(x1, x2)

&lt;span style="color: #BA36A5;"&gt;x1_span&lt;/span&gt; = (x1_1, 1)
&lt;span style="color: #BA36A5;"&gt;x2_0&lt;/span&gt; = (x2_1, )
&lt;span style="color: #BA36A5;"&gt;sol1&lt;/span&gt; = solve_ivp(dx2dx1_1, x1_span, x2_0, max_step=0.1)
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
And then, we do it for the second equation.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython"&gt;&lt;span style="color: #BA36A5;"&gt;df2dx1&lt;/span&gt; = grad(f2, 0)
&lt;span style="color: #BA36A5;"&gt;df2dx2&lt;/span&gt; = grad(f2, 1)

&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;dx2dx1_2&lt;/span&gt;(x1, x2):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; -df2dx1(x1, x2) / df2dx2(x1, x2)

&lt;span style="color: #BA36A5;"&gt;x1_span&lt;/span&gt; = (x1_2, 1)
&lt;span style="color: #BA36A5;"&gt;x2_0&lt;/span&gt; = (x2_2, )
&lt;span style="color: #BA36A5;"&gt;sol2&lt;/span&gt; = solve_ivp(dx2dx1_2, x1_span, x2_0, max_step=0.1)
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
Finally, we plot the two solutions.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython"&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
plt.plot(sol1.t, sol1.y.T)
plt.plot(sol2.t, sol2.y.T)
plt.xlabel(&lt;span style="color: #008000;"&gt;'$x_1$'&lt;/span&gt;)
plt.ylabel(&lt;span style="color: #008000;"&gt;'$x_2$'&lt;/span&gt;)
plt.legend([&lt;span style="color: #008000;"&gt;'f1'&lt;/span&gt;, &lt;span style="color: #008000;"&gt;'f2'&lt;/span&gt;])
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
&amp;lt;Figure size 432x288 with 1 Axes&amp;gt;
&lt;/pre&gt;


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

&lt;p&gt;
You can see now that in this range, there is only one intersection, i.e. one solution, and it is near \(x_1=0.4, x_2=0.6\). We can finally use that as an initial guess to find the only solution in this region, with confidence we are not missing any solutions.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython"&gt;&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;objective&lt;/span&gt;(X):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;x1&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;x2&lt;/span&gt; = X
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; [f1(x1, x2), f2(x1, x2)]

fsolve(objective, [0.4, 0.6])
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
array([0.35324662, 0.60608174])
&lt;/pre&gt;

&lt;p&gt;
That is the same solution as reported at the Matlab site. Another use of autograd for the win here.
&lt;/p&gt;
&lt;p&gt;Copyright (C) 2019 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/2019/10/02/Using-autograd-to-plot-implicit-functions.org"&gt;org-mode source&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Org-mode version = 9.2.3&lt;/p&gt;]]></content:encoded>
    </item>
    <item>
      <title>Solving differential algebraic equations with help from autograd</title>
      <link>https://kitchingroup.cheme.cmu.edu/blog/2019/09/22/Solving-differential-algebraic-equations-with-help-from-autograd</link>
      <pubDate>Sun, 22 Sep 2019 12:59:25 EDT</pubDate>
      <category><![CDATA[dae]]></category>
      <category><![CDATA[autograd]]></category>
      <category><![CDATA[ode]]></category>
      <guid isPermaLink="false">eATX_e4VOuQRCAt5B_CujmZtj4w=</guid>
      <description>Solving differential algebraic equations with help from autograd</description>
      <content:encoded><![CDATA[


&lt;p&gt;
This problem is adapted from one in "Problem Solving in Chemical Engineering with Numerical Methods, Michael B. Cutlip, Mordechai Shacham".
&lt;/p&gt;

&lt;p&gt;
In the binary, batch distillation of benzene (1) and toluene (2), the moles of liquid \(L\) remaining as a function of the mole fraction of toluene (\(x_2\)) is expressed by:
&lt;/p&gt;

&lt;p&gt;
\(\frac{dL}{dx_2} = \frac{L}{x_2 (k_2 - 1)}\)
&lt;/p&gt;

&lt;p&gt;
where \(k_2\) is the vapor liquid equilibrium ratio for toluene. This can be computed as:
&lt;/p&gt;

&lt;p&gt;
\(k_i = P_i / P\) where \(P_i = 10^{A_i + \frac{B_i}{T +C_i}}\) and that pressure is in mmHg, and the temperature is in degrees Celsius.
&lt;/p&gt;

&lt;p&gt;
One difficulty in solving this problem is that the temperature is not constant; it changes with the composition. We know that the temperature changes to satisfy this constraint  \(k_1(T) x_1 + k_2(T) x_2 = 1\).
&lt;/p&gt;

&lt;p&gt;
Sometimes, one can solve for T directly, and substitute it into the first ODE, but this is not a possibility here. One way you might solve this is to use the constraint to find \(T\) inside an ODE function, but that is tricky; nonlinear algebra solvers need a guess and don't always converge, or may converge to non-physical solutions. They also require iterative solutions, so they will be slower than an approach where we just have to integrate the solution.  A better way is to derive a second ODE \(dT/dx_2\) from the constraint.  The constraint is implicit in \(T\), so We  compute it as \(dT/dx_2 = -df/dx_2 / df/dT\) where \(f(x_2, T) = k_1(T) x_1 + k_2(T) x_2  - 1 = 0\). This equation is used to compute the bubble point temperature. Note, it is possible to derive these analytically, but who wants to?  We can use autograd to get those derivatives for us instead.
&lt;/p&gt;

&lt;p&gt;
The following information is given:
&lt;/p&gt;

&lt;p&gt;
The total pressure is fixed at 1.2 atm, and the distillation starts at \(x_2=0.4\). There are initially 100 moles in the distillation.
&lt;/p&gt;

&lt;table border="2" cellspacing="0" cellpadding="6" rules="groups" frame="hsides"&gt;


&lt;colgroup&gt;
&lt;col  class="org-left" /&gt;

&lt;col  class="org-right" /&gt;

&lt;col  class="org-right" /&gt;

&lt;col  class="org-right" /&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th scope="col" class="org-left"&gt;species&lt;/th&gt;
&lt;th scope="col" class="org-right"&gt;A&lt;/th&gt;
&lt;th scope="col" class="org-right"&gt;B&lt;/th&gt;
&lt;th scope="col" class="org-right"&gt;C&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class="org-left"&gt;benzene&lt;/td&gt;
&lt;td class="org-right"&gt;6.90565&lt;/td&gt;
&lt;td class="org-right"&gt;-1211.033&lt;/td&gt;
&lt;td class="org-right"&gt;220.79&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class="org-left"&gt;toluene&lt;/td&gt;
&lt;td class="org-right"&gt;6.95464&lt;/td&gt;
&lt;td class="org-right"&gt;-1344.8&lt;/td&gt;
&lt;td class="org-right"&gt;219.482&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;
We have to start by finding the initial temperature from the constraint.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython"&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
&lt;span style="color: #0000FF;"&gt;from&lt;/span&gt; scipy.integrate &lt;span style="color: #0000FF;"&gt;import&lt;/span&gt; solve_ivp
&lt;span style="color: #0000FF;"&gt;from&lt;/span&gt; scipy.optimize &lt;span style="color: #0000FF;"&gt;import&lt;/span&gt; fsolve
%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;P&lt;/span&gt; = 760 * 1.2 &lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;mmHg&lt;/span&gt;
&lt;span style="color: #BA36A5;"&gt;A1&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;B1&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;C1&lt;/span&gt; = 6.90565, -1211.033,  220.79
&lt;span style="color: #BA36A5;"&gt;A2&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;B2&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;C2&lt;/span&gt; = 6.95464, -1344.8, 219.482

&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;k1&lt;/span&gt;(T):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; 10**(A1 + B1 / (C1 + T)) / P

&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;k2&lt;/span&gt;(T):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; 10**(A2 + B2 / (C2 + T)) / P

&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;f&lt;/span&gt;(x2, T):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;x1&lt;/span&gt; = 1 - x2
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; k1(T) * x1 + k2(T) * x2 - 1

T0, = fsolve(&lt;span style="color: #0000FF;"&gt;lambda&lt;/span&gt; T: f(0.4, T), 96)
&lt;span style="color: #0000FF;"&gt;print&lt;/span&gt;(f&lt;span style="color: #008000;"&gt;'The initial temperature is {T0:1.2f} degC.'&lt;/span&gt;)
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
The initial temperature is 95.59 degC.
&lt;/p&gt;

&lt;p&gt;
Next, we compute the derivative we need. This derivative is derived from the constraint, which should ensure that the temperature changes as required to maintain the constraint.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython"&gt;&lt;span style="color: #BA36A5;"&gt;dfdx2&lt;/span&gt; = grad(f, 0)
&lt;span style="color: #BA36A5;"&gt;dfdT&lt;/span&gt; = grad(f, 1)

&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;dTdx2&lt;/span&gt;(x2, T):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; -dfdx2(x2, T) / dfdT(x2, T)

&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;ode&lt;/span&gt;(x2, X):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;L&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;T&lt;/span&gt; = X
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;dLdx2&lt;/span&gt; = L / (x2 * (k2(T) - 1))
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; [dLdx2, dTdx2(x2, T)]
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
Next we solve and plot the ODE.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython"&gt;&lt;span style="color: #BA36A5;"&gt;x2span&lt;/span&gt; = (0.4, 0.8)
&lt;span style="color: #BA36A5;"&gt;X0&lt;/span&gt; = (100, T0)
&lt;span style="color: #BA36A5;"&gt;sol&lt;/span&gt; = solve_ivp(ode, x2span, X0, max_step=0.01)

plt.plot(sol.t, sol.y.T)
plt.legend([&lt;span style="color: #008000;"&gt;'L'&lt;/span&gt;, &lt;span style="color: #008000;"&gt;'T'&lt;/span&gt;]);
plt.xlabel(&lt;span style="color: #008000;"&gt;'$x_2$'&lt;/span&gt;)
plt.ylabel(&lt;span style="color: #008000;"&gt;'L, T'&lt;/span&gt;)
&lt;span style="color: #BA36A5;"&gt;x2&lt;/span&gt; = sol.t
&lt;span style="color: #BA36A5;"&gt;L&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;T&lt;/span&gt; = sol.y
&lt;span style="color: #0000FF;"&gt;print&lt;/span&gt;(f&lt;span style="color: #008000;"&gt;'At x2={x2[-1]:1.2f} there are {L[-1]:1.2f} moles of liquid left at {T[-1]:1.2f} degC'&lt;/span&gt;)
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
At x2=0.80 there are 14.04 moles of liquid left at 108.57 degC
&lt;/p&gt;

&lt;pre class="example"&gt;
&amp;lt;Figure size 432x288 with 1 Axes&amp;gt;
&lt;/pre&gt;


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

&lt;p&gt;
You can see that the liquid level drops, and the temperature rises.
&lt;/p&gt;

&lt;p&gt;
Let's double check that the constraint is actually met. We do that qualitatively here by plotting it, and quantitatively by showing all values are close to 0.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython"&gt;&lt;span style="color: #BA36A5;"&gt;constraint&lt;/span&gt; = k1(T) * (1 - x2) + k2(T) * x2 - 1
plt.plot(x2, constraint)
plt.ylim([-1, 1])
plt.xlabel(&lt;span style="color: #008000;"&gt;'$x_2$'&lt;/span&gt;)
plt.ylabel(&lt;span style="color: #008000;"&gt;'constraint value'&lt;/span&gt;)
&lt;span style="color: #0000FF;"&gt;print&lt;/span&gt;(np.allclose(constraint, np.zeros_like(constraint)))
constraint
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
True
&lt;/p&gt;

&lt;pre class="example"&gt;
array([ 2.22044605e-16,  4.44089210e-16,  2.22044605e-16,  0.00000000e+00,
        1.11022302e-15,  0.00000000e+00,  6.66133815e-16,  0.00000000e+00,
       -2.22044605e-16,  1.33226763e-15,  8.88178420e-16, -4.44089210e-16,
        4.44089210e-16,  1.11022302e-15, -2.22044605e-16,  0.00000000e+00,
       -2.22044605e-16, -1.11022302e-15,  4.44089210e-16,  0.00000000e+00,
       -4.44089210e-16,  4.44089210e-16, -6.66133815e-16, -4.44089210e-16,
        4.44089210e-16, -1.11022302e-16, -8.88178420e-16, -8.88178420e-16,
       -9.99200722e-16, -3.33066907e-16, -7.77156117e-16, -2.22044605e-16,
       -9.99200722e-16, -1.11022302e-15, -3.33066907e-16, -1.99840144e-15,
       -1.33226763e-15, -2.44249065e-15, -1.55431223e-15, -6.66133815e-16,
       -2.22044605e-16])
&lt;/pre&gt;


&lt;pre class="example"&gt;
&amp;lt;Figure size 432x288 with 1 Axes&amp;gt;
&lt;/pre&gt;


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


&lt;p&gt;
So indeed, the constraint is met! Once again, autograd comes to the rescue in making a computable derivative from an algebraic constraint so that we can solve a DAE as a set of ODEs using our regular machinery. Nice work autograd!
&lt;/p&gt;
&lt;p&gt;Copyright (C) 2019 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/2019/09/22/Solving-differential-algebraic-equations-with-help-from-autograd.org"&gt;org-mode source&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Org-mode version = 9.2.3&lt;/p&gt;]]></content:encoded>
    </item>
    <item>
      <title>Sensitivity analysis with odeint and autograd</title>
      <link>https://kitchingroup.cheme.cmu.edu/blog/2019/09/13/Sensitivity-analysis-with-odeint-and-autograd</link>
      <pubDate>Fri, 13 Sep 2019 09:56:09 EDT</pubDate>
      <category><![CDATA[autograd]]></category>
      <category><![CDATA[ode]]></category>
      <guid isPermaLink="false">a-lpqe22WfbPZV59JCJkZAVvMR0=</guid>
      <description>Sensitivity analysis with odeint and autograd</description>
      <content:encoded><![CDATA[


&lt;p&gt;
In this &lt;a href="http://kitchingroup.cheme.cmu.edu/blog/2018/10/11/A-differentiable-ODE-integrator-for-sensitivity-analysis/"&gt;previous post&lt;/a&gt; I showed a way to do sensitivity analysis of the solution of a differential equation to parameters in the equation using autograd. The basic approach was to write a differentiable integrator, and then use it in a function so that autograd could take the derivative.
&lt;/p&gt;

&lt;p&gt;
Since that time, autograd has added &lt;a href="https://github.com/HIPS/autograd/blob/master/autograd/scipy/integrate.py"&gt;derivative support&lt;/a&gt; for &lt;code&gt;scipy.integrate.odeint&lt;/code&gt;. In this post we examine that. As usual with autograd, we have to import the autograd version of numpy, and the autograd version of odeint. We will find the derivative of the solution to an ODE (which is an array) so we need to also import the jacobian function. Finally, there is a subtle, and non-obvious requirement that we need to import the autograd tuple. That ensures that the variables are differentiable through the tuple we will use for the arguments.
&lt;/p&gt;

&lt;p&gt;
The differential equation we solve returns the concentration of a species as a function of time, and the solution depends on two parameters, i.e. \(C = f(t; k_1, k_{-1})\), and we are interested in the time-dependent sensitivity of \(C\) with respect to those parameters. The approach we use is to define a function that has those parameters as arguments. The function will solve the ODE and return the time-dependent solution. First we make that solution, mostly to see that the autograd version of odeint works.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython"&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.scipy.integrate &lt;span style="color: #0000FF;"&gt;import&lt;/span&gt; odeint
&lt;span style="color: #0000FF;"&gt;from&lt;/span&gt; autograd &lt;span style="color: #0000FF;"&gt;import&lt;/span&gt; jacobian
&lt;span style="color: #0000FF;"&gt;from&lt;/span&gt; autograd.builtins &lt;span style="color: #0000FF;"&gt;import&lt;/span&gt; &lt;span style="color: #006FE0;"&gt;tuple&lt;/span&gt;

&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;Ca0&lt;/span&gt; = 1.0
&lt;span style="color: #BA36A5;"&gt;k1&lt;/span&gt; = &lt;span style="color: #BA36A5;"&gt;k_1&lt;/span&gt; = 3.0

&lt;span style="color: #BA36A5;"&gt;tspan&lt;/span&gt; = np.linspace(0, 0.5)

&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;C&lt;/span&gt;(K):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;k1&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;k_1&lt;/span&gt; = K
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;dCdt&lt;/span&gt;(Ca, t, k1, k_1):
&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;return&lt;/span&gt; -k1 * Ca + k_1 * (Ca0 - Ca)
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;sol&lt;/span&gt; = odeint(dCdt, Ca0, tspan, &lt;span style="color: #006FE0;"&gt;tuple&lt;/span&gt;((k1, k_1)))
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; sol

plt.plot(tspan, C([k1, k_1]))
plt.xlim([tspan.&lt;span style="color: #006FE0;"&gt;min&lt;/span&gt;(), tspan.&lt;span style="color: #006FE0;"&gt;max&lt;/span&gt;()])
plt.xlabel(&lt;span style="color: #008000;"&gt;'t'&lt;/span&gt;)
plt.ylabel(&lt;span style="color: #008000;"&gt;'C'&lt;/span&gt;);
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
&amp;lt;Figure size 432x288 with 1 Axes&amp;gt;
&lt;/pre&gt;


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


&lt;p&gt;
Now, the solution is an array, and we want the derivative of C with respect to the parameters at each time point. That means we want the jacobian derivative of the output with respect to the input. Here is the autograd approach to doing that. The jacobian function returns a function that we can evaluate to get the derivatives.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython"&gt;&lt;span style="color: #0000FF;"&gt;import&lt;/span&gt; time
&lt;span style="color: #BA36A5;"&gt;t0&lt;/span&gt; = time.time()
&lt;span style="color: #BA36A5;"&gt;dCdk&lt;/span&gt; = jacobian(C, 0)


&lt;span style="color: #BA36A5;"&gt;k_sensitivity&lt;/span&gt; = dCdk(np.array([k1, k_1]))

&lt;span style="color: #BA36A5;"&gt;k1_sensitivity&lt;/span&gt; = k_sensitivity[:, 0, 0]
&lt;span style="color: #BA36A5;"&gt;k_1_sensitivity&lt;/span&gt; = k_sensitivity[:, 0, 1]

plt.plot(tspan, np.&lt;span style="color: #006FE0;"&gt;abs&lt;/span&gt;(k1_sensitivity), label=&lt;span style="color: #008000;"&gt;'dC/dk1'&lt;/span&gt;)
plt.plot(tspan, np.&lt;span style="color: #006FE0;"&gt;abs&lt;/span&gt;(k_1_sensitivity), label=&lt;span style="color: #008000;"&gt;'dC/dk_1'&lt;/span&gt;)
plt.legend(loc=&lt;span style="color: #008000;"&gt;'best'&lt;/span&gt;)
plt.xlabel(&lt;span style="color: #008000;"&gt;'t'&lt;/span&gt;)
plt.ylabel(&lt;span style="color: #008000;"&gt;'sensitivity'&lt;/span&gt;)
&lt;span style="color: #0000FF;"&gt;print&lt;/span&gt;(f&lt;span style="color: #008000;"&gt;'Elapsed time = {time.time() - t0:1.1f} seconds'&lt;/span&gt;)
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
Elapsed time = 38.2 seconds
&lt;/p&gt;

&lt;pre class="example"&gt;
&amp;lt;Figure size 432x288 with 1 Axes&amp;gt;
&lt;/pre&gt;


&lt;p&gt;
&lt;figure&gt;&lt;img src="/media/3a0a58bb6d4b3e1b215c2918d511f3a8a3a2ca3d.png"&gt;&lt;/figure&gt; 
&lt;/p&gt;

&lt;p&gt;
That looks similar to the results from before. It is pretty slow I think, that took more than half a minute to work out. That is still faster and probably more correct than if I had to do it by hand. In contrast, however, the finite difference code below is comparatively very fast! I don't know what is slow in the autograd implementation. I guess it is an implementation detail.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython"&gt;&lt;span style="color: #0000FF;"&gt;import&lt;/span&gt; numdifftools &lt;span style="color: #0000FF;"&gt;as&lt;/span&gt; nd
&lt;span style="color: #BA36A5;"&gt;t0&lt;/span&gt; = time.time()

&lt;span style="color: #BA36A5;"&gt;fdk1&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;fdk_1&lt;/span&gt; = nd.Jacobian(C)([k1, k_1]).T
&lt;span style="color: #0000FF;"&gt;print&lt;/span&gt;(f&lt;span style="color: #008000;"&gt;'Elapsed time = {time.time() - t0:1.1f} seconds'&lt;/span&gt;)

plt.plot(tspan, np.&lt;span style="color: #006FE0;"&gt;abs&lt;/span&gt;(fdk1), label=&lt;span style="color: #008000;"&gt;'fd dC/dk1'&lt;/span&gt;)
plt.plot(tspan, np.&lt;span style="color: #006FE0;"&gt;abs&lt;/span&gt;(fdk_1), label=&lt;span style="color: #008000;"&gt;'fd dC/dk_1'&lt;/span&gt;)
plt.plot(tspan, np.&lt;span style="color: #006FE0;"&gt;abs&lt;/span&gt;(k1_sensitivity), &lt;span style="color: #008000;"&gt;'y--'&lt;/span&gt;, label=&lt;span style="color: #008000;"&gt;'dC/dk1'&lt;/span&gt;)
plt.plot(tspan, np.&lt;span style="color: #006FE0;"&gt;abs&lt;/span&gt;(k_1_sensitivity),&lt;span style="color: #008000;"&gt;'m--'&lt;/span&gt;, label=&lt;span style="color: #008000;"&gt;'dC/dk_1'&lt;/span&gt;)
plt.legend(loc=&lt;span style="color: #008000;"&gt;'best'&lt;/span&gt;);
plt.xlabel(&lt;span style="color: #008000;"&gt;'t'&lt;/span&gt;);
plt.ylabel(&lt;span style="color: #008000;"&gt;'sensitivity'&lt;/span&gt;);
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
Elapsed time = 0.1 seconds
&lt;/p&gt;

&lt;pre class="example"&gt;
&amp;lt;Figure size 432x288 with 1 Axes&amp;gt;
&lt;/pre&gt;


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

&lt;p&gt;
You can see the two results are visually indistinguishable. Even the code is pretty similar. I would tend to prefer the autograd way since it should be less sensitive to finite difference artifacts, but it is nice to have an independent way to test if it is working.
&lt;/p&gt;
&lt;p&gt;Copyright (C) 2019 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/2019/09/13/Sensitivity-analysis-with-odeint-and-autograd.org"&gt;org-mode source&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Org-mode version = 9.2.3&lt;/p&gt;]]></content:encoded>
    </item>
    <item>
      <title>Line integrals in Python with autograd</title>
      <link>https://kitchingroup.cheme.cmu.edu/blog/2018/11/16/Line-integrals-in-Python-with-autograd</link>
      <pubDate>Fri, 16 Nov 2018 08:39:44 EST</pubDate>
      <category><![CDATA[integration]]></category>
      <category><![CDATA[autograd]]></category>
      <category><![CDATA[python]]></category>
      <guid isPermaLink="false">8pnl2uSxymXf56S8frpp6psrwys=</guid>
      <description>Line integrals in Python with autograd</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="#org107f13d"&gt;1. Summary&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
A line integral is an integral of a function along a curve in space. We usually represent the curve by a parametric equation, e.g. \(\mathbf{r}(t) = [x(t), y(t), z(t)] = x(t)\mathbf{i} + y(t)\mathbf{j} + z(t)\mathbf{k}\).  So, in general the curve will be a vector function, and the function we want to integrate will also be a vector function.
&lt;/p&gt;

&lt;p&gt;
Then, we can write the line integral definition as:
&lt;/p&gt;

&lt;p&gt;
\(\int_C \mathbf{F(r)}\cdot d\mathbf{r} = \int_a^b \mathbf{F}({\mathbf{r}(t)) \cdot \mathbf{r'}(t) dt\) where \(\mathbf{r'}(t) = \frac{d\mathbf{r}}{dt}\). This integrand is a scalar function, because of the dot product.
&lt;/p&gt;

&lt;p&gt;
The following examples are adapted from Chapter 10 in Advanced Engineering Mathematics by Kreysig.
&lt;/p&gt;

&lt;p&gt;
The first example is the evaluation of  a line integral in the plane. We want to evaluate the integral of \(\mathbf{F(r)}=[-y, -xy]\) on the curve \(\mathbf{r(t)}=[-sin(t), cos(t)]\) from t=0 to t = &amp;pi;/2. The answer in the book is given as 0.4521. Here we evaluate this numerically, using autograd for the relevant derivative. Since the curve has multiple outputs, we have to use the jacobian function to get the derivatives. After that, it is a simple bit of matrix multiplication, and a call to the quad function.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython"&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; jacobian
&lt;span style="color: #0000FF;"&gt;from&lt;/span&gt; scipy.integrate &lt;span style="color: #0000FF;"&gt;import&lt;/span&gt; quad

&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;F&lt;/span&gt;(X):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;x&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;y&lt;/span&gt; = X
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; -y, -x * y

&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;r&lt;/span&gt;(t):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; np.array([-np.sin(t), np.cos(t)])

&lt;span style="color: #BA36A5;"&gt;drdt&lt;/span&gt; = jacobian(r)

&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;integrand&lt;/span&gt;(t):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; F(r(t)) @ drdt(t)

&lt;span style="color: #BA36A5;"&gt;I&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;e&lt;/span&gt; = quad(integrand, 0.0, np.pi / 2)
&lt;span style="color: #0000FF;"&gt;print&lt;/span&gt;(f&lt;span style="color: #008000;"&gt;'The integral is {I:1.4f}.'&lt;/span&gt;)
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
The integral is 0.4521.


&lt;/pre&gt;

&lt;p&gt;
We get the same result as the analytical solution.
&lt;/p&gt;


&lt;p&gt;
The next example is in three dimensions. Find the line integral along \(\mathbf{r}(t)=[cos(t), sin(t), 3t]\) of the function \(\mathbf{F(r)}=[z, x, y]\) from t=0 to t=2 &amp;pi;. The solution is given as 21.99.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython"&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; elementwise_grad, grad, jacobian

&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;F&lt;/span&gt;(X):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;x&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;y&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;z&lt;/span&gt; = X
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; [z, x, y]

&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;C&lt;/span&gt;(t):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; np.array([np.cos(t), np.sin(t), 3 * t])

&lt;span style="color: #BA36A5;"&gt;dCdt&lt;/span&gt; = jacobian(C, 0)

&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;integrand&lt;/span&gt;(t):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; F(C(t)) @ dCdt(t)

&lt;span style="color: #BA36A5;"&gt;I&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;e&lt;/span&gt; = quad(integrand, 0, 2 * np.pi)
&lt;span style="color: #0000FF;"&gt;print&lt;/span&gt;(f&lt;span style="color: #008000;"&gt;'The integral is {I:1.2f}.'&lt;/span&gt;)
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
The integral is 21.99.


&lt;/pre&gt;

&lt;p&gt;
That is also the same as the analytical solution. Note the real analytical solution was 7 &amp;pi;, which is nearly equivalent to our answer.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython"&gt;7 * np.pi - I
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
3.552713678800501e-15

&lt;/pre&gt;


&lt;p&gt;
As a final example, we consider an alternate form of the line integral. In this form we do not use a dot product, so the integral results in a vector. This doesn't require anything from autograd, but does require us to be somewhat clever in how to do the integrals since quad can only integrate scalar functions. We need to integrate each component of the integrand independently. Here is one approach where we use lambda functions for each component. You could also manually separate the components.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython"&gt;&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;F&lt;/span&gt;(r):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;x&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;y&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;z&lt;/span&gt; = r
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; x * y, y * z, z

&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;r&lt;/span&gt;(t):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; np.array([np.cos(t), np.sin(t), 3 * t])

&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;integrand&lt;/span&gt;(t):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; F(r(t))

[quad(&lt;span style="color: #0000FF;"&gt;lambda&lt;/span&gt; t: integrand(t)[i], 0, 2 * np.pi)[0] &lt;span style="color: #0000FF;"&gt;for&lt;/span&gt; i &lt;span style="color: #0000FF;"&gt;in&lt;/span&gt; [0, 1, 2]]
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
[-6.9054847581172525e-18, -18.849555921538755, 59.21762640653615]

&lt;/pre&gt;

&lt;p&gt;
The analytical solution in this case was given as:
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython"&gt;[0, -6 * np.pi, 6 * np.pi**2]
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
[0, -18.84955592153876, 59.21762640653615]

&lt;/pre&gt;

&lt;p&gt;
which is evidently the same as our numerical solution.
&lt;/p&gt;

&lt;p&gt;
Maybe an alternative, but more verbose is this vectorized integrate function. We still make temporary functions for integrating, and the vectorization is essentially like the list comprehension above, but we avoid the lambda functions.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython"&gt;&lt;span style="color: #6434A3;"&gt;@np.vectorize&lt;/span&gt;
&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;integrate&lt;/span&gt;(i):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;integrand&lt;/span&gt;(t):
&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;return&lt;/span&gt; F(r(t))[i]
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;I&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;e&lt;/span&gt; = quad(integrand, 0, 2 * np.pi)
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; I

integrate([0, 1, 2])
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
array([ -6.90548476e-18,  -1.88495559e+01,   5.92176264e+01])

&lt;/pre&gt;

&lt;div id="outline-container-org107f13d" class="outline-2"&gt;
&lt;h2 id="org107f13d"&gt;&lt;span class="section-number-2"&gt;1&lt;/span&gt; Summary&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-1"&gt;
&lt;p&gt;
Once again, autograd provides a convenient way to compute function jacobians which make it easy to evaluate line integrals in Python.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Copyright (C) 2018 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/2018/11/16/Line-integrals-in-Python-with-autograd.org"&gt;org-mode source&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Org-mode version = 9.1.14&lt;/p&gt;]]></content:encoded>
    </item>
    <item>
      <title>Using autograd for error propagation</title>
      <link>https://kitchingroup.cheme.cmu.edu/blog/2018/11/05/Using-autograd-for-error-propagation</link>
      <pubDate>Mon, 05 Nov 2018 21:04:21 EST</pubDate>
      <category><![CDATA[autograd]]></category>
      <category><![CDATA[uncertainty]]></category>
      <guid isPermaLink="false">VOnvqoFwCueTJTkZl1jY2hDGjqY=</guid>
      <description>Using autograd for error propagation</description>
      <content:encoded><![CDATA[


&lt;p&gt;
Back in &lt;a href="http://kitchingroup.cheme.cmu.edu/blog/2013/03/07/Another-approach-to-error-propagation/"&gt;2013&lt;/a&gt; I wrote about using the uncertainties package to propagate uncertainties. The problem setup was for finding the uncertainty in the exit concentration from a CSTR when there are uncertainties in the other parameters. In this problem we were given this information about the parameters and their uncertainties.
&lt;/p&gt;

&lt;table border="2" cellspacing="0" cellpadding="6" rules="groups" frame="hsides"&gt;


&lt;colgroup&gt;
&lt;col  class="org-left" /&gt;

&lt;col  class="org-right" /&gt;

&lt;col  class="org-right" /&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th scope="col" class="org-left"&gt;Parameter&lt;/th&gt;
&lt;th scope="col" class="org-right"&gt;value&lt;/th&gt;
&lt;th scope="col" class="org-right"&gt;&amp;sigma;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class="org-left"&gt;Fa0&lt;/td&gt;
&lt;td class="org-right"&gt;5&lt;/td&gt;
&lt;td class="org-right"&gt;0.05&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class="org-left"&gt;v0&lt;/td&gt;
&lt;td class="org-right"&gt;10&lt;/td&gt;
&lt;td class="org-right"&gt;0.1&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class="org-left"&gt;V&lt;/td&gt;
&lt;td class="org-right"&gt;66000&lt;/td&gt;
&lt;td class="org-right"&gt;100&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td class="org-left"&gt;k&lt;/td&gt;
&lt;td class="org-right"&gt;3&lt;/td&gt;
&lt;td class="org-right"&gt;0.2&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;
The exit concentration is found by solving this equation:
&lt;/p&gt;

&lt;p&gt;
\(0 = Fa0 - v0 * Ca - k * Ca**2 * V\)
&lt;/p&gt;

&lt;p&gt;
So the question was what is Ca, and what is the uncertainty on it? Finding Ca is easy with fsolve.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython"&gt;&lt;span style="color: #0000FF;"&gt;from&lt;/span&gt; scipy.optimize &lt;span style="color: #0000FF;"&gt;import&lt;/span&gt; fsolve

&lt;span style="color: #BA36A5;"&gt;Fa0&lt;/span&gt; = 5.0
&lt;span style="color: #BA36A5;"&gt;v0&lt;/span&gt; = 10.0

&lt;span style="color: #BA36A5;"&gt;V&lt;/span&gt; = 66000.0
&lt;span style="color: #BA36A5;"&gt;k&lt;/span&gt; = 3.0

&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;func&lt;/span&gt;(Ca, v0, k, Fa0, V):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #036A07;"&gt;"Mole balance for a CSTR. Solve this equation for func(Ca)=0"&lt;/span&gt;
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;Fa&lt;/span&gt; = v0 * Ca     &lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;exit molar flow of A&lt;/span&gt;
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;ra&lt;/span&gt; = -k * Ca**2  &lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;rate of reaction of A L/mol/h&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; Fa0 - Fa + V * ra

Ca, = fsolve(func, 0.1 * Fa0 / v0, args=(v0, k, Fa0, V))
Ca
&lt;/pre&gt;
&lt;/div&gt;

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

&lt;/pre&gt;

&lt;p&gt;
The uncertainty on Ca is a little trickier. A &lt;a href="https://en.wikipedia.org/wiki/Propagation_of_uncertainty#Simplification"&gt;simplified&lt;/a&gt; way to estimate it is:
&lt;/p&gt;

&lt;p&gt;
\(\sigma_{Ca} = \sqrt{(dCa/dv0)^2 \sigma_{v0}^2 + (dCa/dv0)^2 \sigma_{v0}^2 + (dCa/dFa0)^2 \sigma_{Fa0}^2 + (dCa/dV)^2 \sigma_{V}^2}\)
&lt;/p&gt;

&lt;p&gt;
We know the &amp;sigma;_i for each input, we just need those partial derivatives. However, we only have the implicit function we used to solve for Ca, and I do not want to do the algebra to solve for Ca. Luckily, we &lt;a href="http://kitchingroup.cheme.cmu.edu/blog/2018/10/08/Getting-derivatives-from-implicit-functions-with-autograd/"&gt;previously worked out&lt;/a&gt; how to get these derivatives from an implicit function using autograd. We just need to loop through the arguments, get the relevant derivatives, and accumulate the product of the squared derivatives and errors. Finally, take the square root of that sum.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython"&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

&lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;these are the uncertainties on the inputs&lt;/span&gt;
&lt;span style="color: #BA36A5;"&gt;s&lt;/span&gt; = [&lt;span style="color: #D0372D;"&gt;None&lt;/span&gt;, 0.1, 0.2, 0.05, 100]

&lt;span style="color: #BA36A5;"&gt;S2&lt;/span&gt; = 0.0

&lt;span style="color: #BA36A5;"&gt;dfdCa&lt;/span&gt; = grad(func, 0)
&lt;span style="color: #0000FF;"&gt;for&lt;/span&gt; i &lt;span style="color: #0000FF;"&gt;in&lt;/span&gt; &lt;span style="color: #006FE0;"&gt;range&lt;/span&gt;(1, 5):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;dfdarg2&lt;/span&gt; = grad(func, i)
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;dCadarg2&lt;/span&gt; = -dfdarg2(Ca, v0, k, Fa0, V) / dfdCa(Ca, v0, k, Fa0, V)
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;S2&lt;/span&gt; += dCadarg2**2 * s[i]**2

&lt;span style="color: #BA36A5;"&gt;Ca_s&lt;/span&gt; = np.sqrt(S2)
&lt;span style="color: #0000FF;"&gt;print&lt;/span&gt;(f&lt;span style="color: #008000;"&gt;'Ca = {Ca:1.5f} +\- {Ca_s}'&lt;/span&gt;)
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
Ca = 0.00500 +\- 0.00016776432898276802


&lt;/pre&gt;

&lt;p&gt;
That is the same uncertainty estimate the quantities package provided. One benefit here is I did not have to do the somewhat complicated wrapping procedure around fsolve that was required with uncertainties to get this. On the other hand, I did have to derive the formula and implement them. It worked fine here, since we have an implicit function and a way to get the required derivatives. It could take some work to do this with the exit concentration of a PFR, which requires an integrator. Maybe that &lt;a href="http://kitchingroup.cheme.cmu.edu/blog/2018/10/11/A-differentiable-ODE-integrator-for-sensitivity-analysis/"&gt;differentiable integrator&lt;/a&gt; will come in handy again!
&lt;/p&gt;
&lt;p&gt;Copyright (C) 2018 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/2018/11/05/Using-autograd-for-error-propagation.org"&gt;org-mode source&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Org-mode version = 9.1.14&lt;/p&gt;]]></content:encoded>
    </item>
    <item>
      <title>Constrained optimization with Lagrange multipliers and autograd</title>
      <link>https://kitchingroup.cheme.cmu.edu/blog/2018/11/03/Constrained-optimization-with-Lagrange-multipliers-and-autograd</link>
      <pubDate>Sat, 03 Nov 2018 09:39:20 EDT</pubDate>
      <category><![CDATA[optimization]]></category>
      <category><![CDATA[autograd]]></category>
      <guid isPermaLink="false">Iy0PvHLUa3-zADMW_uQnOCfZ6Z4=</guid>
      <description>Constrained optimization with Lagrange multipliers and autograd</description>
      <content:encoded><![CDATA[


&lt;p&gt;
Constrained optimization is common in engineering problems solving. A prototypical example (from Greenberg, Advanced Engineering Mathematics, Ch 13.7) is to find the point on a plane that is closest to the origin. The plane is defined by the equation \(2x - y + z = 3\), and we seek to minimize \(x^2 + y^2 + z^2\) subject to the equality constraint defined by the plane. &lt;code&gt;scipy.optimize.minimize&lt;/code&gt; provides a pretty convenient interface to solve a problem like this, ans shown here.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython"&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.optimize &lt;span style="color: #0000FF;"&gt;import&lt;/span&gt; minimize

&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;objective&lt;/span&gt;(X):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;x&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;y&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;z&lt;/span&gt; = X
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; x**2 + y**2 + z**2

&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;eq&lt;/span&gt;(X):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;x&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;y&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;z&lt;/span&gt; = X
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; 2 * x - y + z - 3

&lt;span style="color: #BA36A5;"&gt;sol&lt;/span&gt; = minimize(objective, [1, -0.5, 0.5], constraints={&lt;span style="color: #008000;"&gt;'type'&lt;/span&gt;: &lt;span style="color: #008000;"&gt;'eq'&lt;/span&gt;, &lt;span style="color: #008000;"&gt;'fun'&lt;/span&gt;: eq})
sol
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
    fun: 1.5
    jac: array([ 2.00000001, -0.99999999,  1.00000001])
message: 'Optimization terminated successfully.'
   nfev: 5
    nit: 1
   njev: 1
 status: 0
success: True
      x: array([ 1. , -0.5,  0.5])

&lt;/pre&gt;

&lt;p&gt;
I like the minimize function a lot, although I am not crazy for how the constraints are provided. The alternative used to be that there was an argument for equality constraints and another for inequality constraints. Analogous to &lt;code&gt;scipy.integrate.solve_ivp&lt;/code&gt; event functions, they could have also used function attributes.
&lt;/p&gt;

&lt;p&gt;
Sometimes, it might be desirable to go back to basics though, especially if you are unaware of the &lt;code&gt;minimize&lt;/code&gt; function or perhaps suspect it is not working right and want an independent answer. Next we look at how to construct this constrained optimization problem using Lagrange multipliers. This converts the problem into an augmented unconstrained optimization problem we can use &lt;code&gt;fsolve&lt;/code&gt; on. The gist of this method is we formulate a new problem:
&lt;/p&gt;

&lt;p&gt;
\(F(X) = f(X) - \lambda g(X)\)
&lt;/p&gt;

&lt;p&gt;
and then solve the simultaneous resulting equations:
&lt;/p&gt;

&lt;p&gt;
\(F_x(X) = F_y(X) = F_z(X) = g(X) = 0\) where \(F_x\) is the derivative of \(f*\) with respect to \(x\), and \(g(X)\) is the equality constraint written so it is equal to zero. Since we end up with four equations that equal zero, we can simply use fsolve to get the solution. Many &lt;a href="http://kitchingroup.cheme.cmu.edu/blog/2013/02/03/Using-Lagrange-multipliers-in-optimization/"&gt;years ago&lt;/a&gt; I used a finite difference approximation to the derivatives. Today we use autograd to get the desired derivatives. Here it is.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython"&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

&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;F&lt;/span&gt;(L):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #036A07;"&gt;'Augmented Lagrange function'&lt;/span&gt;
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;x&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;y&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;z&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;_lambda&lt;/span&gt; = L
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; objective([x, y, z]) - _lambda * eq([x, y, z])

&lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;Gradients of the Lagrange function&lt;/span&gt;
&lt;span style="color: #BA36A5;"&gt;dfdL&lt;/span&gt; = grad(F, 0)

&lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;Find L that returns all zeros in this function.&lt;/span&gt;
&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;obj&lt;/span&gt;(L):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;x&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;y&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;z&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;_lambda&lt;/span&gt; = L
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;dFdx&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;dFdy&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;dFdz&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;dFdlam&lt;/span&gt; = dfdL(L)
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; [dFdx, dFdy, dFdz, eq([x, y, z])]

&lt;span style="color: #0000FF;"&gt;from&lt;/span&gt; scipy.optimize &lt;span style="color: #0000FF;"&gt;import&lt;/span&gt; fsolve
&lt;span style="color: #BA36A5;"&gt;x&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;y&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;z&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;_lam&lt;/span&gt; = fsolve(obj, [0.0, 0.0, 0.0, 1.0])
&lt;span style="color: #0000FF;"&gt;print&lt;/span&gt;(f&lt;span style="color: #008000;"&gt;'The answer is at {x, y, z}'&lt;/span&gt;)
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
The answer is at (1.0, -0.5, 0.5)


&lt;/pre&gt;

&lt;p&gt;
That is the same answer as before. Note we have still relied on some black box solver inside of fsolve (instead of inside minimize), but it might be more clear what problem we are solving (e.g. finding zeros). It takes a bit more work to set this up, since we have to construct the augmented function, but autograd makes it pretty convenient to set up the final objective function we want to solve.
&lt;/p&gt;

&lt;p&gt;
How do we know we are at a minimum? We can check that the Hessian is positive definite in the original function we wanted to minimize. You can see here the array is positive definite, e.g. all the eigenvalues are positive. autograd makes this easy too.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython"&gt;&lt;span style="color: #0000FF;"&gt;from&lt;/span&gt; autograd &lt;span style="color: #0000FF;"&gt;import&lt;/span&gt; hessian
&lt;span style="color: #BA36A5;"&gt;h&lt;/span&gt; = hessian(objective, 0)
h(np.array([x, y, z]))
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
array([[ 2.,  0.,  0.],
       [ 0.,  2.,  0.],
       [ 0.,  0.,  2.]])

&lt;/pre&gt;

&lt;p&gt;
In case it isn't evident from that structure that the eigenvalues are all positive, here we compute them:
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython"&gt;np.linalg.eig(h(np.array([x, y, z])))[0]
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
array([ 2.,  2.,  2.])

&lt;/pre&gt;

&lt;p&gt;
In summary, autograd continues to enable advanced engineering problems to be solved.
&lt;/p&gt;
&lt;p&gt;Copyright (C) 2018 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/2018/11/03/Constrained-optimization-with-Lagrange-multipliers-and-autograd.org"&gt;org-mode source&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Org-mode version = 9.1.14&lt;/p&gt;]]></content:encoded>
    </item>
    <item>
      <title>Solving coupled ODEs with a neural network and autograd</title>
      <link>https://kitchingroup.cheme.cmu.edu/blog/2018/11/02/Solving-coupled-ODEs-with-a-neural-network-and-autograd</link>
      <pubDate>Fri, 02 Nov 2018 19:53:00 EDT</pubDate>
      <category><![CDATA[autograd]]></category>
      <category><![CDATA[ode]]></category>
      <guid isPermaLink="false">Ry5Ux3UbG7_HZnK_dVp9iHlLtpE=</guid>
      <description>Solving coupled ODEs with a neural network and autograd</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="#orgfefaa95"&gt;1. The standard numerical solution&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#orge5dacb7"&gt;2. Can a neural network learn the solution?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#orga332637"&gt;3. Given a neural network function how do we get the right derivatives?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#orgf85faff"&gt;4. Solving the system of ODEs with a neural network&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#orgbbded67"&gt;5. Summary&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
In a previous &lt;a href="http://kitchingroup.cheme.cmu.edu/blog/2017/11/28/Solving-ODEs-with-a-neural-network-and-autograd/index.html"&gt;post&lt;/a&gt; I wrote about using ideas from machine learning to solve an ordinary differential equation using a neural network for the solution. A friend recently tried to apply that idea to coupled ordinary differential equations, without success. It seems like that should work, so here we diagnose the issue and figure it out. This is a long post, but it works in the end.
&lt;/p&gt;

&lt;p&gt;
In the classic series reaction \(A \rightarrow B \rightarrow C\) in a batch reactor, we get the set of coupled mole balances:
&lt;/p&gt;

&lt;p&gt;
\(dC_A/dt = -k_1 C_A\)
&lt;/p&gt;

&lt;p&gt;
\(dC_B/dt = k_1 C_A - k_2 C_B\)
&lt;/p&gt;

&lt;p&gt;
\(dC_C/dt = k2 C_B\)
&lt;/p&gt;

&lt;div id="outline-container-orgfefaa95" class="outline-2"&gt;
&lt;h2 id="orgfefaa95"&gt;&lt;span class="section-number-2"&gt;1&lt;/span&gt; The standard numerical solution&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-1"&gt;
&lt;p&gt;
Here is the standard numerical solution to this problem. This will give us a reference for what the solution should look like.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython"&gt;&lt;span style="color: #0000FF;"&gt;from&lt;/span&gt; scipy.integrate &lt;span style="color: #0000FF;"&gt;import&lt;/span&gt; solve_ivp

&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;ode&lt;/span&gt;(t, C):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;Ca&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;Cb&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;Cc&lt;/span&gt; = C
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;dCadt&lt;/span&gt; = -k1 * Ca
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;dCbdt&lt;/span&gt; = k1 * Ca - k2 * Cb
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;dCcdt&lt;/span&gt; = k2 * Cb
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; [dCadt, dCbdt, dCcdt]

&lt;span style="color: #BA36A5;"&gt;C0&lt;/span&gt; = [1.0, 0.0, 0.0]
&lt;span style="color: #BA36A5;"&gt;k1&lt;/span&gt; = 1
&lt;span style="color: #BA36A5;"&gt;k2&lt;/span&gt; = 1

&lt;span style="color: #BA36A5;"&gt;sol&lt;/span&gt; = solve_ivp(ode, (0, 10), C0)

%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

plt.plot(sol.t, sol.y.T)
plt.legend([&lt;span style="color: #008000;"&gt;'A'&lt;/span&gt;, &lt;span style="color: #008000;"&gt;'B'&lt;/span&gt;, &lt;span style="color: #008000;"&gt;'C'&lt;/span&gt;])
plt.xlabel(&lt;span style="color: #008000;"&gt;'Time'&lt;/span&gt;)
plt.ylabel(&lt;span style="color: #008000;"&gt;'C'&lt;/span&gt;)
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
&lt;img src="/media/d0abffb7b8615837cad7f2cceb378aac-65837xDK.png"&gt; 
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;div id="outline-container-orge5dacb7" class="outline-2"&gt;
&lt;h2 id="orge5dacb7"&gt;&lt;span class="section-number-2"&gt;2&lt;/span&gt; Can a neural network learn the solution?&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-2"&gt;
&lt;p&gt;
The first thing I want to show is that you can train a neural network to reproduce this solution. That is certainly a prerequisite to the idea working. We use the  same code I used before, but this time our neural network will output three values, one for each concentration.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython"&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, jacobian
&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;C&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: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;initial guess for the weights and biases&lt;/span&gt;
&lt;span style="color: #BA36A5;"&gt;params&lt;/span&gt; = init_random_params(0.1, layer_sizes=[1, 8, 3])
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
Now, we train our network to reproduce the solution. I ran this block manually a bunch of times, but eventually you see that we can train a one layer network with 8 nodes to output all three concentrations pretty accurately. So, there is no issue there, a neural network can represent the solution.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython"&gt;&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;objective_soln&lt;/span&gt;(params, step):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; np.&lt;span style="color: #006FE0;"&gt;sum&lt;/span&gt;((sol.y.T - C(params, sol.t.reshape([-1, 1])))**2)

&lt;span style="color: #BA36A5;"&gt;params&lt;/span&gt; = adam(grad(objective_soln), 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=500)

plt.plot(sol.t.reshape([-1, 1]), C(params, sol.t.reshape([-1, 1])),
&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;sol.t, sol.y.T, &lt;span style="color: #008000;"&gt;'o'&lt;/span&gt;)
plt.legend([&lt;span style="color: #008000;"&gt;'A'&lt;/span&gt;, &lt;span style="color: #008000;"&gt;'B'&lt;/span&gt;, &lt;span style="color: #008000;"&gt;'C'&lt;/span&gt;, &lt;span style="color: #008000;"&gt;'Ann'&lt;/span&gt;, &lt;span style="color: #008000;"&gt;'Bnn'&lt;/span&gt;, &lt;span style="color: #008000;"&gt;'Cnn'&lt;/span&gt;])
plt.xlabel(&lt;span style="color: #008000;"&gt;'Time'&lt;/span&gt;)
plt.ylabel(&lt;span style="color: #008000;"&gt;'C'&lt;/span&gt;)
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
&lt;img src="/media/d0abffb7b8615837cad7f2cceb378aac-65837YpQ.png"&gt; 
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;div id="outline-container-orga332637" class="outline-2"&gt;
&lt;h2 id="orga332637"&gt;&lt;span class="section-number-2"&gt;3&lt;/span&gt; Given a neural network function how do we get the right derivatives?&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-3"&gt;
&lt;p&gt;
The next issue is how do we get the relevant derivatives. The solution method I developed here relies on using optimization to find a set of weights that produces a neural network whose derivatives are consistent with the ODE equations. So, we need to be able to get the derivatives that are relevant in the equations.
&lt;/p&gt;

&lt;p&gt;
The neural network outputs three concentrations, and we need the time derivatives of them. Autograd provides three options: &lt;code&gt;grad&lt;/code&gt;, &lt;code&gt;elementwise_grad&lt;/code&gt; and &lt;code&gt;jacobian&lt;/code&gt;. We cannot use &lt;code&gt;grad&lt;/code&gt; because our function is not scalar. We cannot use &lt;code&gt;elementwise_grad&lt;/code&gt; because that will give the wrong shape (I think it may be the sum of the gradients). That leaves us with the &lt;code&gt;jacobian&lt;/code&gt;. This, however, gives an initially unintuitive (i.e. it isn't what we need out of the box) result. The output is 4-dimensional in this case, consistent with the documentation of that function.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython"&gt;&lt;span style="color: #BA36A5;"&gt;jacC&lt;/span&gt; = jacobian(C, 1)
jacC(params, sol.t.reshape([-1, 1])).shape
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
(17, 3, 17, 1)

&lt;/pre&gt;


&lt;p&gt;
Why does it have this shape? Our time input vector we used has 17 time values, in a column vector. That leads to an output from the NN with a shape of (17, 3), i.e. the concentrations of each species at each time. The jacobian will output an array of shape (17, 3, 17, 1), and we have to extract the pieces we want from that. The first and third dimensions are related to the time steps. The second dimension is the species, and the last dimension is nothing here, but is there because the input is in a column. I use some fancy indexing on the array to get the desired arrays of the derivatives. This is not obvious out of the box. I only figured this out by direct comparison of the data from a numerical solution and the output of the jacobian. Here I show how to do that, and make sure that the derivatives we pull out are comparable to the derivatives defined by the ODEs above. Parity here means they are comparable.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython"&gt;&lt;span style="color: #BA36A5;"&gt;i&lt;/span&gt; = np.arange(&lt;span style="color: #006FE0;"&gt;len&lt;/span&gt;(sol.t))
plt.plot(jacC(params, sol.t.reshape([-1, 1]))[i, 0, i, 0],   -k1 * sol.y[0], &lt;span style="color: #008000;"&gt;'ro'&lt;/span&gt;)
plt.plot(jacC(params, sol.t.reshape([-1, 1]))[i, 1, i, 0],   -k2 * sol.y[1] + k1 * sol.y[0], &lt;span style="color: #008000;"&gt;'bo'&lt;/span&gt;)
plt.plot(jacC(params, sol.t.reshape([-1, 1]))[i, 2, i, 0],   k2 * sol.y[1], &lt;span style="color: #008000;"&gt;'go'&lt;/span&gt;)
&lt;/pre&gt;
&lt;/div&gt;

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

&lt;/pre&gt;



&lt;p&gt;
&lt;img src="/media/d0abffb7b8615837cad7f2cceb378aac-65837yLF.png"&gt; 
&lt;/p&gt;

&lt;p&gt;
Note this is pretty inefficient. It requires a lot of calculations (the jacobian here has &lt;code class="src src-python"&gt;&lt;span style="color: #0000FF;"&gt;print&lt;/span&gt;(17*3*17)&lt;/code&gt; &lt;code&gt;867&lt;/code&gt; elements) to create the jacobian, and we don't need most of them. You could avoid this by creating separate neural networks for each species, and then just use elementwise_grad on each one. Alternatively, one might be able to more efficiently compute some vector-jacobian product. Nevertheless, it looks like we can get the correct derivatives out of the neural network, we just need a convenient function to return them. Here is one such function for this problem, using a fancier slicing and reshaping to get the derivative array.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython"&gt;&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;jac&lt;/span&gt; = jacobian(C, 1)

&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;dCdt&lt;/span&gt;(params, t):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;i&lt;/span&gt; = np.arange(&lt;span style="color: #006FE0;"&gt;len&lt;/span&gt;(t))
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; jac(params, t)[i, :, i].reshape((&lt;span style="color: #006FE0;"&gt;len&lt;/span&gt;(t), 3))
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;


&lt;div id="outline-container-orgf85faff" class="outline-2"&gt;
&lt;h2 id="orgf85faff"&gt;&lt;span class="section-number-2"&gt;4&lt;/span&gt; Solving the system of ODEs with a neural network&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-4"&gt;
&lt;p&gt;
Finally, we are ready to try solving the ODEs solely by the neural network approach. We reinitialize the neural network first, and define a time grid to solve it on.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython"&gt;&lt;span style="color: #BA36A5;"&gt;t&lt;/span&gt; = np.linspace(0, 10, 25).reshape((-1, 1))
&lt;span style="color: #BA36A5;"&gt;params&lt;/span&gt; = init_random_params(0.1, layer_sizes=[1, 8, 3])
&lt;span style="color: #BA36A5;"&gt;i&lt;/span&gt; = 0    &lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;number of training steps&lt;/span&gt;
&lt;span style="color: #BA36A5;"&gt;N&lt;/span&gt; = 501  &lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;epochs for training&lt;/span&gt;
&lt;span style="color: #BA36A5;"&gt;et&lt;/span&gt; = 0.0 &lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;total elapsed time&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
We define our objective function. This function will be zero at the perfect solution, and has contributions for each mole balance and the initial conditions. It could make sense to put additional penalties for things like negative concentrations, or the sum of concentrations is a constant, but we do not do that here, and it does not seem to be necessary.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython"&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;Ca&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;Cb&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;Cc&lt;/span&gt; = C(params, t).T
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;dCadt&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;dCbdt&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;dCcdt&lt;/span&gt; = dCdt(params, t).T

&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;z1&lt;/span&gt; = np.&lt;span style="color: #006FE0;"&gt;sum&lt;/span&gt;((dCadt + k1 * Ca)**2)
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;z2&lt;/span&gt; = np.&lt;span style="color: #006FE0;"&gt;sum&lt;/span&gt;((dCbdt - k1 * Ca + k2 * Cb)**2)
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;z3&lt;/span&gt; = np.&lt;span style="color: #006FE0;"&gt;sum&lt;/span&gt;((dCcdt - k2 * Cb)**2)
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;ic&lt;/span&gt; = np.&lt;span style="color: #006FE0;"&gt;sum&lt;/span&gt;((np.array([Ca[0], Cb[0], Cc[0]]) - C0)**2)  &lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;initial conditions&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; z1 + z2 + z3 + ic

&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 % 100 == 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)))

objective(params, 0)  &lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;make sure the objective is scalar&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;

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

&lt;/pre&gt;

&lt;p&gt;
Finally, we run the optimization. I also manually ran this block several times.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython"&gt;&lt;span style="color: #0000FF;"&gt;import&lt;/span&gt; time
&lt;span style="color: #BA36A5;"&gt;t0&lt;/span&gt; = time.time()

&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=N, callback=callback)

&lt;span style="color: #BA36A5;"&gt;i&lt;/span&gt; += N
&lt;span style="color: #BA36A5;"&gt;t1&lt;/span&gt; = (time.time() - t0) / 60
&lt;span style="color: #BA36A5;"&gt;et&lt;/span&gt; += t1

plt.plot(t, C(params, t), sol.t, sol.y.T, &lt;span style="color: #008000;"&gt;'o'&lt;/span&gt;)
plt.legend([&lt;span style="color: #008000;"&gt;'Ann'&lt;/span&gt;, &lt;span style="color: #008000;"&gt;'Bnn'&lt;/span&gt;, &lt;span style="color: #008000;"&gt;'Cnn'&lt;/span&gt;, &lt;span style="color: #008000;"&gt;'A'&lt;/span&gt;, &lt;span style="color: #008000;"&gt;'B'&lt;/span&gt;, &lt;span style="color: #008000;"&gt;'C'&lt;/span&gt;])
plt.xlabel(&lt;span style="color: #008000;"&gt;'Time'&lt;/span&gt;)
plt.ylabel(&lt;span style="color: #008000;"&gt;'C'&lt;/span&gt;)
&lt;span style="color: #0000FF;"&gt;print&lt;/span&gt;(f&lt;span style="color: #008000;"&gt;'{t1:1.1f} minutes elapsed this time. Total time = {et:1.2f} min. Total epochs = {i}.'&lt;/span&gt;)
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
Iteration   0 objective 0.00047651643957525214
Iteration 100 objective 0.0004473301532609342
Iteration 200 objective 0.00041218410058863227
Iteration 300 objective 0.00037161526137030344
Iteration 400 objective 0.000327567400443358
Iteration 500 objective 0.0002836975879675981
0.6 minutes elapsed this time. Total time = 4.05 min. Total epochs = 3006.


&lt;/pre&gt;


&lt;p&gt;
&lt;img src="/media/d0abffb7b8615837cad7f2cceb378aac-65837AXS.png"&gt; 
&lt;/p&gt;

&lt;p&gt;
The effort seems to have been worth it though, we get a pretty good solution from our neural network.
&lt;/p&gt;

&lt;p&gt;
We can check the accuracy of the derivatives by noting the sum of the derivatives in this case should be zero. Here you can see that the sum is pretty small. It would take additional optimization to a lower error to get this to be smaller.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython"&gt;plt.plot(t, np.&lt;span style="color: #006FE0;"&gt;sum&lt;/span&gt;(dCdt(params, t), axis=1))
plt.xlabel(&lt;span style="color: #008000;"&gt;'Time'&lt;/span&gt;)
plt.ylabel(r&lt;span style="color: #008000;"&gt;'$\Sigma dC/dt$'&lt;/span&gt;)
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
&lt;img src="/media/d0abffb7b8615837cad7f2cceb378aac-65837NhY.png"&gt; 
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;



&lt;div id="outline-container-orgbbded67" class="outline-2"&gt;
&lt;h2 id="orgbbded67"&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;
In the end, this method is illustrated to work for systems of ODEs also. There is some subtlety in how to get the relevant derivatives from the jacobian, but after that, it is essentially the same. I think it would be &lt;i&gt;much&lt;/i&gt; faster to do this with separate neural networks for each function in the solution because then you do not need the jacobian, you can use elementwise_grad.
&lt;/p&gt;

&lt;p&gt;
This is not faster than direct numerical integration. One benefit to this solution over a numerical solution is we get an actual continuous function as the solution, rather than an array of data.  This solution is not reliable at longer times, but then again neither is extrapolation of numeric data. It could be interesting to explore if this has any benefits for stiff equations. Maybe another day. For now, I am declaring victory for autograd on this problem.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Copyright (C) 2018 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/2018/11/02/Solving-coupled-ODEs-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.14&lt;/p&gt;]]></content:encoded>
    </item>
    <item>
      <title>A differentiable ODE integrator for sensitivity analysis</title>
      <link>https://kitchingroup.cheme.cmu.edu/blog/2018/10/11/A-differentiable-ODE-integrator-for-sensitivity-analysis</link>
      <pubDate>Thu, 11 Oct 2018 12:13:01 EDT</pubDate>
      <category><![CDATA[autograd]]></category>
      <category><![CDATA[sensitivity]]></category>
      <category><![CDATA[ode]]></category>
      <guid isPermaLink="false">x1b-cquyAyl6Ic4uE4zs0jj8a6U=</guid>
      <description>A differentiable ODE integrator for sensitivity analysis</description>
      <content:encoded><![CDATA[


&lt;p&gt;
&lt;a href="http://kitchingroup.cheme.cmu.edu/blog/2018/10/10/Autograd-and-the-derivative-of-an-integral-function/"&gt;Last time&lt;/a&gt; I wrote about using automatic differentiation to find the derivative of an integral function. A related topic is finding derivatives of functions that are defined by differential equations. We typically use a numerical integrator to find solutions to these functions. Those leave us with numeric solutions which we then have to use to approximate derivatives. What if the integrator itself was differentiable? It is after all, just a program, and automatic differentiation should be able to tell us the derivatives of functions that use them. This is not a new idea, there is already a differentiable ODE solver in &lt;a href="https://www.tensorflow.org/versions/r1.1/api_docs/python/tf/contrib/integrate/odeint"&gt;Tensorflow&lt;/a&gt;. Here I will implement a simple Runge Kutta integrator and then show how we can use automatic differentiation to do &lt;i&gt;sensitivity analysis&lt;/i&gt; on the numeric solution.
&lt;/p&gt;

&lt;p&gt;
I previously used autograd for sensitivity analysis on &lt;i&gt;analytical&lt;/i&gt; solutions in this &lt;a href="http://kitchingroup.cheme.cmu.edu/blog/2017/11/15/Sensitivity-analysis-using-automatic-differentiation-in-Python/"&gt;post&lt;/a&gt;. Here I will compare those results to the results from sensitivity analysis on the &lt;i&gt;numerical solutions&lt;/i&gt;.
&lt;/p&gt;

&lt;p&gt;
First, we need an autograd compatible ODE integrator. Here is one implementation of a simple, fourth order Runge-Kutta integrator. Usually, I would use indexing to do this, but that was not compatible with autograd, so I just accumulate the solution. This is a limitation of autograd, and it is probably not an issue with Tensorflow, for example, or probably pytorch. Those are more sophisticated, and more difficult to use packages than autograd. Here I am just prototyping an idea, so we stick with autograd.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython"&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
%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: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;rk4&lt;/span&gt;(f, tspan, y0, N=50):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;x&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;h&lt;/span&gt; = np.linspace(*tspan, N, retstep=&lt;span style="color: #D0372D;"&gt;True&lt;/span&gt;)
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;y&lt;/span&gt; = []
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;y&lt;/span&gt; = y + [y0]
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;for&lt;/span&gt; i &lt;span style="color: #0000FF;"&gt;in&lt;/span&gt; &lt;span style="color: #006FE0;"&gt;range&lt;/span&gt;(0, &lt;span style="color: #006FE0;"&gt;len&lt;/span&gt;(x) - 1):
&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;k1&lt;/span&gt; = h * f(x[i], y[i])
&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;k2&lt;/span&gt; = h * f(x[i] + h / 2, y[i] + k1 / 2)
&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;k3&lt;/span&gt; = h * f(x[i] + h / 2, y[i] + k2 / 2)
&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;k4&lt;/span&gt; = h * f(x[i + 1], y[i] + k3)
&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;y&lt;/span&gt; += [y[-1] + (k1 + (2 * k2) + (2 * k3) + k4) / 6]
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; x, y
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
Now, we just check that it works as expected:
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython"&gt;&lt;span style="color: #BA36A5;"&gt;Ca0&lt;/span&gt; = 1.0
&lt;span style="color: #BA36A5;"&gt;k1&lt;/span&gt; = &lt;span style="color: #BA36A5;"&gt;k_1&lt;/span&gt; = 3.0

&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;dCdt&lt;/span&gt;(t, Ca):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; -k1 * Ca + k_1 * (Ca0 - Ca)

&lt;span style="color: #BA36A5;"&gt;t&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;Ca&lt;/span&gt; = rk4(dCdt, (0, 0.5), Ca0)

&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;analytical_A&lt;/span&gt;(t, k1, k_1):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; Ca0 / (k1 + k_1) * (k1 * np.exp(-(k1 + k_1) * t) + k_1)

plt.plot(t, Ca, label=&lt;span style="color: #008000;"&gt;'RK4'&lt;/span&gt;)
plt.plot(t, analytical_A(t, k1, k_1), &lt;span style="color: #008000;"&gt;'r--'&lt;/span&gt;, label=&lt;span style="color: #008000;"&gt;'analytical'&lt;/span&gt;)
plt.xlabel(&lt;span style="color: #008000;"&gt;'t'&lt;/span&gt;)
plt.ylabel(&lt;span style="color: #008000;"&gt;'[A]'&lt;/span&gt;)
plt.xlim([0, 0.5])
plt.ylim([0.5, 1])
plt.legend()
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
&lt;img src="/media/6a1c5e4c896d855655b8da8b54214af3-90490Zdl.png"&gt; 
&lt;/p&gt;

&lt;p&gt;
That looks fine, we cannot visually distinguish the two solutions, and they both look like Figure 1 in this &lt;a href="http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.428.6699&amp;amp;rep=rep1&amp;amp;type=pdf"&gt;paper&lt;/a&gt;. Note the analytical solution is not that complex, but it would not take much variation of the rate law to make this solution difficult to derive.
&lt;/p&gt;

&lt;p&gt;
Next, to do sensitivity analysis, we need to define a function for \(A\) that depends on the rate constants, so we can take a derivative of it with respect to the parameters we want the sensitivity from. We seek the derivatives: \(\frac{dC_A}{dk_1}\) and \(\frac{dC_A}{dk_{-1}}\). Here is a function that does that. It will return the value of [A] at \(t\) given an initial concentration and the rate constants.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython"&gt;&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;A&lt;/span&gt;(Ca0, k1, k_1, t):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;dCdt&lt;/span&gt;(t, Ca):
&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;return&lt;/span&gt; -k1 * Ca + k_1 * (Ca0 - Ca)
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;t&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;Ca_&lt;/span&gt; = rk4(dCdt, (0, t), Ca0)
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; Ca_[-1]

&lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;Here are the two derivatives we seek.&lt;/span&gt;
&lt;span style="color: #BA36A5;"&gt;dCadk1&lt;/span&gt; = grad(A, 1)
&lt;span style="color: #BA36A5;"&gt;dCadk_1&lt;/span&gt; = grad(A, 2)
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
We also use autograd to get the derivatives from the analytical solution for comparison.
&lt;/p&gt;
&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython"&gt;&lt;span style="color: #BA36A5;"&gt;dAdk1&lt;/span&gt; = grad(analytical_A, 1)
&lt;span style="color: #BA36A5;"&gt;dAdk_1&lt;/span&gt; = grad(analytical_A, 2)
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
Now, we can plot the sensitivities over the time range and compare them. I use the list comprehensions here because the AD functions aren't vectorized.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython"&gt;&lt;span style="color: #BA36A5;"&gt;tspan&lt;/span&gt; = np.linspace(0, 0.5)

&lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;From the numerical solutions&lt;/span&gt;
&lt;span style="color: #BA36A5;"&gt;k1_sensitivity&lt;/span&gt; = [dCadk1(1.0, 3.0, 3.0, t) &lt;span style="color: #0000FF;"&gt;for&lt;/span&gt; t &lt;span style="color: #0000FF;"&gt;in&lt;/span&gt; tspan]
&lt;span style="color: #BA36A5;"&gt;k_1_sensitivity&lt;/span&gt; = [dCadk_1(1.0, 3.0, 3.0, t) &lt;span style="color: #0000FF;"&gt;for&lt;/span&gt; t &lt;span style="color: #0000FF;"&gt;in&lt;/span&gt; tspan]

&lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;from the analytical solutions&lt;/span&gt;
&lt;span style="color: #BA36A5;"&gt;ak1_sensitivity&lt;/span&gt; = [dAdk1(t, 3.0, 3.0) &lt;span style="color: #0000FF;"&gt;for&lt;/span&gt; t &lt;span style="color: #0000FF;"&gt;in&lt;/span&gt; tspan]
&lt;span style="color: #BA36A5;"&gt;ak_1_sensitivity&lt;/span&gt; = [dAdk_1(t, 3.0, 3.0) &lt;span style="color: #0000FF;"&gt;for&lt;/span&gt; t &lt;span style="color: #0000FF;"&gt;in&lt;/span&gt; tspan]

plt.plot(tspan, np.&lt;span style="color: #006FE0;"&gt;abs&lt;/span&gt;(ak1_sensitivity), &lt;span style="color: #008000;"&gt;'b-'&lt;/span&gt;, label=&lt;span style="color: #008000;"&gt;'k1 analytical'&lt;/span&gt;)
plt.plot(tspan, np.&lt;span style="color: #006FE0;"&gt;abs&lt;/span&gt;(k1_sensitivity), &lt;span style="color: #008000;"&gt;'y--'&lt;/span&gt;, label=&lt;span style="color: #008000;"&gt;'k1 numerical'&lt;/span&gt;)

plt.plot(tspan, np.&lt;span style="color: #006FE0;"&gt;abs&lt;/span&gt;(ak_1_sensitivity), &lt;span style="color: #008000;"&gt;'r-'&lt;/span&gt;, label=&lt;span style="color: #008000;"&gt;'k_1 analytical'&lt;/span&gt;)
plt.plot(tspan, np.&lt;span style="color: #006FE0;"&gt;abs&lt;/span&gt;(k_1_sensitivity), &lt;span style="color: #008000;"&gt;'k--'&lt;/span&gt;, label=&lt;span style="color: #008000;"&gt;'k_1 numerical'&lt;/span&gt;)

plt.xlim([0, 0.5])
plt.ylim([0, 0.1])
plt.legend()
plt.xlabel(&lt;span style="color: #008000;"&gt;'t'&lt;/span&gt;)
plt.ylabel(&lt;span style="color: #008000;"&gt;'sensitivity'&lt;/span&gt;)
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
&lt;img src="/media/6a1c5e4c896d855655b8da8b54214af3-90490mnr.png"&gt; 
&lt;/p&gt;



&lt;p&gt;
The two approaches are indistinguishable on paper. I will note that it takes a lot longer to make the graph from the numerical solution than from the analytical solution because at each point you have to reintegrate the solution from the beginning, which is certainly not efficient. That is an implementation detail that could probably be solved, at the expense of making the code look different than the way I would normally think about the problem.
&lt;/p&gt;

&lt;p&gt;
On the other hand, it is remarkable we get derivatives from the numerical solution, &lt;i&gt;and they look really good&lt;/i&gt;! That means we could do sensitivity analysis on more complex reactions, and still have a reasonable way to get sensitivity. The work here is a long way from that. My simple Runge-Kutta integrator isn't directly useful for systems of ODEs, it wouldn't work well on stiff problems, the step size isn't adaptive, etc. The Tensorflow implementation might be more suitable for this though, and maybe this post is motivation to learn how to use it!
&lt;/p&gt;
&lt;p&gt;Copyright (C) 2018 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/2018/10/11/A-differentiable-ODE-integrator-for-sensitivity-analysis.org"&gt;org-mode source&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Org-mode version = 9.1.13&lt;/p&gt;]]></content:encoded>
    </item>
    <item>
      <title>Autograd and the derivative of an integral function</title>
      <link>https://kitchingroup.cheme.cmu.edu/blog/2018/10/10/Autograd-and-the-derivative-of-an-integral-function</link>
      <pubDate>Wed, 10 Oct 2018 18:24:12 EDT</pubDate>
      <category><![CDATA[autograd]]></category>
      <category><![CDATA[python]]></category>
      <guid isPermaLink="false">cO6xXl0wefWmv_shWZaPoagKeJs=</guid>
      <description>Autograd and the derivative of an integral function</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="#org4ec4dc0"&gt;1. Example 1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#org079e9a6"&gt;2. Example 2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#org7aa394c"&gt;3. Summary&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
There are many functions that are defined by integrals. The &lt;a href="https://en.wikipedia.org/wiki/Error_function"&gt;error function&lt;/a&gt;, for example is defined by \(erf(x) = \frac{2}{\sqrt{\pi}}\int_0^x e^{-t^2}dt\).
&lt;/p&gt;

&lt;p&gt;
Another &lt;a href="https://en.wikipedia.org/wiki/Leibniz_integral_rule#Example_1"&gt;example&lt;/a&gt; is:
&lt;/p&gt;

&lt;p&gt;
\(\phi(\alpha) = \int_0^1 \frac{\alpha}{x^2 + \alpha^2} dx\).
&lt;/p&gt;

&lt;p&gt;
We have reasonable ways to evaluate these functions numerically, e.g. &lt;code&gt;scipy.integrate.quad&lt;/code&gt;, or &lt;code&gt;numpy.trapz&lt;/code&gt;, but what about the derivatives of these functions? The analytical way to do this is to use the &lt;a href="https://en.wikipedia.org/wiki/Leibniz_integral_rule"&gt;Leibniz rule&lt;/a&gt;, which involves integrating a derivative and evaluating it at the limits. For some functions, this may also lead to new integrals you have to numerically evaluate. Today, we consider the role that automatic differentiation can play in this.
&lt;/p&gt;

&lt;p&gt;
The idea is simple, we define a function in Python as usual, and in the function body calculate the integral in a program. Then we use autograd to get the derivative of the function.
&lt;/p&gt;

&lt;p&gt;
In this case, we have an analytical derivative to compare the answers to:
&lt;/p&gt;

&lt;p&gt;
\(\frac{d\phi}{d\alpha} = -\frac{1}{1 + \alpha^2}\).
&lt;/p&gt;

&lt;div id="outline-container-org4ec4dc0" class="outline-2"&gt;
&lt;h2 id="org4ec4dc0"&gt;&lt;span class="section-number-2"&gt;1&lt;/span&gt; Example 1&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-1"&gt;
&lt;p&gt;
For simplicity, I am going to approximate the integral with the trapezoid method in vectorized form.  Here is our program to define \(\phi(\alpha)\). I found we need a pretty dense grid on the x value so that we have a pretty accurate integral, especially near \(x=0\) where there is a singularity as &amp;alpha; goes to zero. That doesn't worry me too much, there are better integral approximations to use, including Simpson's method, adaptive methods and perhaps quadrature. If you define them so autograd can use them, they should all work. I chose the trapezoidal method because it is simple to implement here. Note, however, the autograd.numpy wrappers don't have a definition for &lt;code&gt;numpy.trapz&lt;/code&gt; to use it directly. You could add one, or just do this.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython"&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

&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;trapz&lt;/span&gt;(y, x):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;d&lt;/span&gt; = np.diff(x)
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; np.&lt;span style="color: #006FE0;"&gt;sum&lt;/span&gt;((y[0:-1] + y[1:]) * d / 2)


&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;phi&lt;/span&gt;(alpha):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;x&lt;/span&gt; = np.linspace(0, 1, 1000)
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;y&lt;/span&gt; = alpha / (x**2 + alpha**2)
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; trapz(y, x)


&lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;This is the derivative here!&lt;/span&gt;
&lt;span style="color: #BA36A5;"&gt;adphi&lt;/span&gt; = grad(phi, 0)
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
Now, we can plot the derivatives. I will plot both the analytical and automatic differentiated results.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython"&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: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;results from AD&lt;/span&gt;
&lt;span style="color: #BA36A5;"&gt;alpha&lt;/span&gt; = np.linspace(0.01, 1)

&lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;The AD derivative function is not vectorized, so we use this list comprehension.&lt;/span&gt;
&lt;span style="color: #BA36A5;"&gt;dphidalpha&lt;/span&gt; = [adphi(a) &lt;span style="color: #0000FF;"&gt;for&lt;/span&gt; a &lt;span style="color: #0000FF;"&gt;in&lt;/span&gt; alpha]

&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;analytical_dphi&lt;/span&gt;(alpha):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; -1 / (1 + alpha**2)

plt.plot(alpha, analytical_dphi(alpha), label=&lt;span style="color: #008000;"&gt;'analytical'&lt;/span&gt;)
plt.plot(alpha, dphidalpha, &lt;span style="color: #008000;"&gt;'r--'&lt;/span&gt;, label=&lt;span style="color: #008000;"&gt;'AD'&lt;/span&gt;)
plt.xlabel(r&lt;span style="color: #008000;"&gt;'$\alpha$'&lt;/span&gt;)
plt.ylabel(r&lt;span style="color: #008000;"&gt;'$frac{d\phi}{d\alpha}$'&lt;/span&gt;)
plt.legend()
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
&lt;img src="/media/7fed41ae1651a1b7a7f52674a2164226-90490jWu.png"&gt; 
&lt;/p&gt;

&lt;p&gt;
Visually, these are indistinguishable from each other. We can look at the errors too, and here we see they are negligible, and probably we can attribute them to the approximation we use for the integral, and not due to automatic differentiation.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython"&gt;&lt;span style="color: #BA36A5;"&gt;perr&lt;/span&gt; = (analytical_dphi(alpha) - dphidalpha) / analytical_dphi(alpha) * 100
plt.plot(alpha, perr, label=&lt;span style="color: #008000;"&gt;'analytical'&lt;/span&gt;)
plt.xlabel(r&lt;span style="color: #008000;"&gt;'$\alpha$'&lt;/span&gt;)
plt.ylabel(&lt;span style="color: #008000;"&gt;'%error'&lt;/span&gt;)
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
&lt;img src="/media/7fed41ae1651a1b7a7f52674a2164226-90490wg0.png"&gt; 
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;


&lt;div id="outline-container-org079e9a6" class="outline-2"&gt;
&lt;h2 id="org079e9a6"&gt;&lt;span class="section-number-2"&gt;2&lt;/span&gt; Example 2&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-2"&gt;
&lt;p&gt;
In &lt;a href="https://en.wikipedia.org/wiki/Leibniz_integral_rule#Example_2"&gt;example 2&lt;/a&gt; there is this function, which has variable limits:
&lt;/p&gt;

&lt;p&gt;
\(f(x) = \int_{\sin x}^{\cos x} \cosh t^2 dt\)
&lt;/p&gt;

&lt;p&gt;
What is \(f'(x)\) here? It can be derived with some effort and it is:
&lt;/p&gt;

&lt;p&gt;
\(f'(x) = -\cosh(\cos^2 x) \sin x - \cosh(\sin^2 x) \cos x\)
&lt;/p&gt;

&lt;p&gt;
This function was kind of fun to code up, I hadn't thought about how to represent variable limits, but here it is.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython"&gt;&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;f&lt;/span&gt;(x):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;a&lt;/span&gt; = np.sin(x)
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;b&lt;/span&gt; = np.cos(x)
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;t&lt;/span&gt; = np.linspace(a, b, 1000)
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;y&lt;/span&gt; = np.cosh(t**2)
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; trapz(y, t)

&lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;Here is our derivative!&lt;/span&gt;
&lt;span style="color: #BA36A5;"&gt;dfdx&lt;/span&gt; = grad(f, 0)
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
Here is a graphical comparison of the two:
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython"&gt;&lt;span style="color: #BA36A5;"&gt;x&lt;/span&gt; = np.linspace(0, 2 * np.pi)

&lt;span style="color: #BA36A5;"&gt;analytical&lt;/span&gt; = -np.cosh(np.cos(x)**2) * np.sin(x) - \
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   np.cosh(np.sin(x)**2) * np.cos(x)
&lt;span style="color: #BA36A5;"&gt;ad&lt;/span&gt; = [dfdx(_x) &lt;span style="color: #0000FF;"&gt;for&lt;/span&gt; _x &lt;span style="color: #0000FF;"&gt;in&lt;/span&gt; x]

plt.plot(x, analytical, label=&lt;span style="color: #008000;"&gt;'analytical'&lt;/span&gt;)
plt.plot(x, ad, &lt;span style="color: #008000;"&gt;'r--'&lt;/span&gt;, label=&lt;span style="color: #008000;"&gt;'AD'&lt;/span&gt;)
plt.xlabel(&lt;span style="color: #008000;"&gt;'x'&lt;/span&gt;)
plt.ylabel(&lt;span style="color: #008000;"&gt;'df/dx'&lt;/span&gt;)
plt.legend()
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
&lt;img src="/media/7fed41ae1651a1b7a7f52674a2164226-90490iqD.png"&gt; 
&lt;/p&gt;


&lt;p&gt;
These are once again indistinguishable.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;div id="outline-container-org7aa394c" class="outline-2"&gt;
&lt;h2 id="org7aa394c"&gt;&lt;span class="section-number-2"&gt;3&lt;/span&gt; Summary&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-3"&gt;
&lt;p&gt;
These are amazing results to me. Before trying it, I would not have thought it would be so easy to evaluate the derivative of these functions. These work of course because all the operations involved in computing the integral are differentiable and defined in autograd. It certainly opens the door to all kinds of new approaches to solving engineering problems that need the derivatives for various purposes like optimization, sensitivity analysis, etc.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Copyright (C) 2018 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/2018/10/10/Autograd-and-the-derivative-of-an-integral-function.org"&gt;org-mode source&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Org-mode version = 9.1.13&lt;/p&gt;]]></content:encoded>
    </item>
    <item>
      <title>Compressibility variation from an implicit equation of state</title>
      <link>https://kitchingroup.cheme.cmu.edu/blog/2018/10/09/Compressibility-variation-from-an-implicit-equation-of-state</link>
      <pubDate>Tue, 09 Oct 2018 09:21:06 EDT</pubDate>
      <category><![CDATA[autograd]]></category>
      <category><![CDATA[python]]></category>
      <guid isPermaLink="false">5_ZW_tD3wR0tLozwLCnpfsH_myk=</guid>
      <description>Compressibility variation from an implicit equation of state</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="#org56ce300"&gt;1. Summary thoughts&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
In this &lt;a href="http://kitchingroup.cheme.cmu.edu/blog/2018/10/07/Compressibility-factor-variation-from-the-van-der-Waals-equation-by-three-different-approaches/"&gt;post&lt;/a&gt; I explored using automatic differentiation to compute how the compressibility of a gas defined by the van der Waal equation varies with the reduced pressure. In that example we had an explicit function of the pressure as a function of the volume and temperature, and we could derive a differential equation that defines the variation we were interested in.
&lt;/p&gt;

&lt;p&gt;
I thought we should be able to derive the differential equation more directly, still using automatic differentiation and we explore that idea here. The general strategy to compute the compressibility as a function of pressure is to integrate \(dV / dP_r\) over a range of \(P_r\) to get the molar volume as a function of \(P_r\), and then to directly compute the compressibility from \(Z = PV/(RT)\).
&lt;/p&gt;

&lt;p&gt;
To use this approach we need to get \(dV / dP_r\) from the van der Waal equation. Previously, we derived this in a round about way from the explicit form of the van der Waal equation. Here, we follow the work in this &lt;a href="http://kitchingroup.cheme.cmu.edu/blog/2018/10/08/Getting-derivatives-from-implicit-functions-with-autograd/"&gt;post&lt;/a&gt; to get the derivative from the implicit form of the van der Waal equation:
&lt;/p&gt;

&lt;p&gt;
\(f(V, P_r, T_r) = \frac{R Tr * Tc}{V - b} - \frac{a}{V^2} - P_r Pc = 0\)
&lt;/p&gt;

&lt;p&gt;
Based on the work in this &lt;a href="http://kitchingroup.cheme.cmu.edu/blog/2018/10/08/Getting-derivatives-from-implicit-functions-with-autograd/"&gt;post&lt;/a&gt;, we can get
&lt;/p&gt;

&lt;p&gt;
\(dV/dP_r = (-df/dP_r) / (df/dV)\)
&lt;/p&gt;

&lt;p&gt;
and the two derivatives on the right can be found easily by automatic differentiation. First, we express the van der Waal equation in implicit form, with the variables as \(V, P_r, T_r\). Only two of those variables are independent; if you define two of them you can compute the third one using a tool like fsolve.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython"&gt;&lt;span style="color: #BA36A5;"&gt;R&lt;/span&gt; = 0.08206
&lt;span style="color: #BA36A5;"&gt;Pc&lt;/span&gt; = 72.9
&lt;span style="color: #BA36A5;"&gt;Tc&lt;/span&gt; = 304.2

&lt;span style="color: #BA36A5;"&gt;a&lt;/span&gt; = 27 * R**2 * Tc**2 / (Pc * 64)
&lt;span style="color: #BA36A5;"&gt;b&lt;/span&gt; = R * Tc / (8 * Pc)

&lt;span style="color: #BA36A5;"&gt;Tr&lt;/span&gt; = 1.1  &lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;Constant for this example&lt;/span&gt;

&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;f&lt;/span&gt;(V, Pr, Tr):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; R * Tr * Tc / (V - b) - a / V**2 - Pr * Pc
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
Now, if we want to know how does the volume vary with \(P_r\), we need to derive the derivative \(dV/dP_r\), and then integrate it. Here we use autograd to define the derivatives, and then we define a function that uses them. Note the arguments in the function dVdPr are in an order that anticipates we want to integrate it in solve_ivp, to get a function \(V(P_r)\).
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython"&gt;&lt;span style="color: #0000FF;"&gt;from&lt;/span&gt; autograd &lt;span style="color: #0000FF;"&gt;import&lt;/span&gt; grad

&lt;span style="color: #BA36A5;"&gt;dfdPr&lt;/span&gt; = grad(f, 1)  &lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;derivative of f with respect to arg at index=1: Pr&lt;/span&gt;
&lt;span style="color: #BA36A5;"&gt;dfdV&lt;/span&gt; = grad(f, 0)  &lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;derivative of f with respect to arg at index=0: V&lt;/span&gt;

&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;dVdPr&lt;/span&gt;(Pr, V):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; -dfdPr(V, Pr, Tr) / dfdV(V, Pr, Tr)  &lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;Tr is a constant in here&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
Now, we need an initial condition to start the integration from. We want the volume at \(P_r=0.1\). We have to use fsolve for this, or some other method that tells you want is the volume at \(P_r=0.1\). We can pass the values of \(P_r\) and \(T_R\) as arguments to our implicit function. Since \(V\) is the first argument, we can directly solve our implicit function. Otherwise you would have to define a helper objective function to use with fsolve.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython"&gt;&lt;span style="color: #0000FF;"&gt;from&lt;/span&gt; scipy.optimize &lt;span style="color: #0000FF;"&gt;import&lt;/span&gt; fsolve

V0, = fsolve(f, 3.5, args=(0.1, 1.1))
V0
&lt;/pre&gt;
&lt;/div&gt;

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

&lt;/pre&gt;

&lt;p&gt;
Finally, we are ready to integrate the ODE, and plot the solution.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython"&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; solve_ivp

&lt;span style="color: #BA36A5;"&gt;Pr_span&lt;/span&gt; = (0.1, 10)
&lt;span style="color: #BA36A5;"&gt;Pr_eval&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;h&lt;/span&gt; = np.linspace(*Pr_span, retstep=&lt;span style="color: #D0372D;"&gt;True&lt;/span&gt;)

&lt;span style="color: #BA36A5;"&gt;sol&lt;/span&gt; = solve_ivp(dVdPr, Pr_span, (V0,), max_step=h)
&lt;span style="color: #0000FF;"&gt;print&lt;/span&gt;(sol.message)

%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;Pr&lt;/span&gt; = sol.t  &lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;the P_r steps used in the solution&lt;/span&gt;
&lt;span style="color: #BA36A5;"&gt;V&lt;/span&gt; = sol.y[0]  &lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;V(P_r) from the solution&lt;/span&gt;

&lt;span style="color: #BA36A5;"&gt;Z&lt;/span&gt; = Pr * Pc * V / (R * Tr * Tc)  &lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;Compressibility Z(P_r)&lt;/span&gt;

plt.plot(Pr, Z)
plt.xlabel(&lt;span style="color: #008000;"&gt;'$P_r$'&lt;/span&gt;)
plt.ylabel(&lt;span style="color: #008000;"&gt;'Z'&lt;/span&gt;)
plt.xlim([0, 10])
plt.ylim([0, 2])
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
The solver successfully reached the end of the integration interval.


&lt;/pre&gt;

&lt;pre class="example"&gt;
(0, 2)

&lt;/pre&gt;



&lt;p&gt;
&lt;img src="/media/0a06a9507e7d4f809f61d49b8988e2d1-90490gTZ.png"&gt; 
&lt;/p&gt;

&lt;p&gt;
That is the same result as we got before.
&lt;/p&gt;

&lt;div id="outline-container-org56ce300" class="outline-2"&gt;
&lt;h2 id="org56ce300"&gt;&lt;span class="section-number-2"&gt;1&lt;/span&gt; Summary thoughts&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-1"&gt;
&lt;p&gt;
This method also worked successfully to solve this problem. In most ways, this method has less algebraic manipulations required to get to the solution. In &lt;a href="http://kitchingroup.cheme.cmu.edu/blog/2018/10/07/Compressibility-factor-variation-from-the-van-der-Waals-equation-by-three-different-approaches/#orge63b16e"&gt;method 3&lt;/a&gt;, we had to do some calculus that relied on a particular explicit form of the van der Waal equation. While those manipulations were not particularly difficulty, the leave opportunities for mistakes, and they will be more difficult for an implicit equation of state (e.g. if there was a \(P\) on the right hand side).
&lt;/p&gt;

&lt;p&gt;
This approach also required some manipulation, but it is a standard one and that is how do you get a derivative from an implicit function. After that, it is straightforward to define the desired derivative as a function and then integrate it to get the solution. So, we still don't get a free pass on calculus, but we do reduce the number of manipulations required to get to the solution. I consider that a plus.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Copyright (C) 2018 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/2018/10/09/Compressibility-variation-from-an-implicit-equation-of-state.org"&gt;org-mode source&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Org-mode version = 9.1.13&lt;/p&gt;]]></content:encoded>
    </item>
  </channel>
</rss>
