<?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>Caching expensive function calls so you don't have to rerun them</title>
      <link>https://kitchingroup.cheme.cmu.edu/blog/2023/02/01/Caching-expensive-function-calls-so-you-don-t-have-to-rerun-them</link>
      <pubDate>Wed, 01 Feb 2023 20:09:44 EST</pubDate>
      <category><![CDATA[python]]></category>
      <guid isPermaLink="false">TuAI-nnf5JY8R8O-DrQ6Ra7u8hI=</guid>
      <description>Caching expensive function calls so you don't have to rerun them</description>
      <content:encoded><![CDATA[


&lt;div id="table-of-contents" role="doc-toc"&gt;
&lt;h2&gt;Table of Contents&lt;/h2&gt;
&lt;div id="text-table-of-contents" role="doc-toc"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#org07769a2"&gt;1. where things start to go wrong&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#org45a5a5a"&gt;1.1. Global variables&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#org81a44de"&gt;1.2. running functions with mutable arguments&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#orgeecc951"&gt;1.3. If you run the same function different ways, the cache is not reused&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#orgb8d7f30"&gt;1.4. Fragile cache invalidation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#orgd5fbbc2"&gt;2. Some partial solutions with pycse.hashcache&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#orge7c0587"&gt;2.1. No known problem with global variables&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#org857113f"&gt;2.2. hashcache and mutable arguments&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#org4925e4a"&gt;2.3. Reuse the cache when you run different ways&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#org8461f88"&gt;2.4. Insensitivity to unimportant changes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#orgfb0f2ed"&gt;3. Is it the answer?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
Check out the video at:
&lt;/p&gt;
&lt;iframe width="560" height="315" src="https://www.youtube.com/embed/Sp0qebuYsZU" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen&gt;&lt;/iframe&gt;

&lt;p&gt;
Nobody likes to run expensive jobs more than necessary, so cache solutions are often used where you save the results, and look them up later. There is functools.cache in Python, but it is memory only, and not persistent, so you start over in a new session.
&lt;/p&gt;

&lt;p&gt;
For persistent cache, joblib (&lt;a href="https://joblib.readthedocs.io/en/latest/"&gt;https://joblib.readthedocs.io/en/latest/&lt;/a&gt;) is a standard tool for this. Here is a simple example: 
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-jupyter-python"&gt;&lt;span style="color: #0000FF;"&gt;from&lt;/span&gt; joblib &lt;span style="color: #0000FF;"&gt;import&lt;/span&gt; Memory
&lt;span style="color: #BA36A5;"&gt;location&lt;/span&gt; = &lt;span style="color: #008000;"&gt;'/Users/jkitchin/Dropbox/emacs/journal/2023/02/01/joblib_cache/joblib_cache'&lt;/span&gt;
&lt;span style="color: #BA36A5;"&gt;memory&lt;/span&gt; = Memory(location, verbose=0)

&lt;span style="color: #6434A3;"&gt;@memory.cache&lt;/span&gt;
&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;fun&lt;/span&gt;(x=1.0):
    &lt;span style="color: #006FE0;"&gt;print&lt;/span&gt;(&lt;span style="color: #008000;"&gt;'If you see this, go get a coffee while it runs &amp;#128012;.'&lt;/span&gt;)
    &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; x**2

&lt;span style="color: #006FE0;"&gt;print&lt;/span&gt;(fun(2)) &lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;Runs the function&lt;/span&gt;
&lt;span style="color: #006FE0;"&gt;print&lt;/span&gt;(fun(2)) &lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;Looks up the cached value&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
If you see this, go get a coffee while it runs 🐌.
4
4
&lt;/p&gt;

&lt;p&gt;
That works because joblib saves the results in a file in the location you specify.
&lt;/p&gt;

&lt;p&gt;
Here is another example with another arg.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-jupyter-python"&gt;&lt;span style="color: #6434A3;"&gt;@memory.cache&lt;/span&gt;
&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;f2&lt;/span&gt;(x=1.0, a=3):
    &lt;span style="color: #006FE0;"&gt;print&lt;/span&gt;(f&lt;span style="color: #008000;"&gt;'If you see this, go get a coffee while it runs. a=&lt;/span&gt;{"&amp;#128012;"*a}&lt;span style="color: #008000;"&gt;'&lt;/span&gt;)
    &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; a*x**2

(f2(2),       &lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;Runs function&lt;/span&gt;
 f2(2, a=3),  &lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;calls cache&lt;/span&gt;
 f2(2, 4))    &lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;Runs another function because a changed&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
If you see this, go get a coffee while it runs. a=🐌🐌🐌
If you see this, go get a coffee while it runs. a=🐌🐌🐌🐌
&lt;/p&gt;
&lt;table border="2" cellspacing="0" cellpadding="6" rules="groups" frame="hsides"&gt;


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

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

&lt;col  class="org-right" /&gt;
&lt;/colgroup&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class="org-right"&gt;12&lt;/td&gt;
&lt;td class="org-right"&gt;12&lt;/td&gt;
&lt;td class="org-right"&gt;16&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;
Here, we look up from the cache each time.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-jupyter-python"&gt;(f2(2), f2(2, a=3), f2(2, 4))
&lt;/pre&gt;
&lt;/div&gt;

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


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

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

&lt;col  class="org-right" /&gt;
&lt;/colgroup&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class="org-right"&gt;12&lt;/td&gt;
&lt;td class="org-right"&gt;12&lt;/td&gt;
&lt;td class="org-right"&gt;16&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;


&lt;div id="outline-container-org07769a2" class="outline-2"&gt;
&lt;h2 id="org07769a2"&gt;&lt;span class="section-number-2"&gt;1.&lt;/span&gt; where things start to go wrong&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-1"&gt;
&lt;/div&gt;
&lt;div id="outline-container-org45a5a5a" class="outline-3"&gt;
&lt;h3 id="org45a5a5a"&gt;&lt;span class="section-number-3"&gt;1.1.&lt;/span&gt; Global variables&lt;/h3&gt;
&lt;div class="outline-text-3" id="text-1-1"&gt;
&lt;p&gt;
First, we look at an uncached version of a function that uses a global variable.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-jupyter-python"&gt;&lt;span style="color: #BA36A5;"&gt;a&lt;/span&gt; = 3

&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;f3&lt;/span&gt;(x=1.0):
    &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; a*x**2

f3(2)
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
12
&lt;/p&gt;

&lt;p&gt;
We can change &lt;code&gt;a&lt;/code&gt; and see the change.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-jupyter-python"&gt;&lt;span style="color: #BA36A5;"&gt;a&lt;/span&gt;=0
f3(2)
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
0
&lt;/p&gt;

&lt;p&gt;
Now we look at a cached version.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-jupyter-python"&gt;&lt;span style="color: #BA36A5;"&gt;a&lt;/span&gt; = 3
&lt;span style="color: #6434A3;"&gt;@memory.cache&lt;/span&gt;
&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;f4&lt;/span&gt;(x=1.0):
    &lt;span style="color: #006FE0;"&gt;print&lt;/span&gt;(&lt;span style="color: #008000;"&gt;'If you see this, go get a coffee while it runs &amp;#128012;.'&lt;/span&gt;)
    &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; a*x**2

(f4(2), f4(2), f4(2))
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
If you see this, go get a coffee while it runs 🐌.
&lt;/p&gt;
&lt;table border="2" cellspacing="0" cellpadding="6" rules="groups" frame="hsides"&gt;


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

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

&lt;col  class="org-right" /&gt;
&lt;/colgroup&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class="org-right"&gt;12&lt;/td&gt;
&lt;td class="org-right"&gt;12&lt;/td&gt;
&lt;td class="org-right"&gt;12&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;
Changing the global variable does not change the cache though. uh oh. This is just wrong. The answers should clearly be 0. Incorrect cache invalidation strikes.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-jupyter-python"&gt;&lt;span style="color: #BA36A5;"&gt;a&lt;/span&gt; = 0
(f4(2), f4(2), f4(2))
&lt;/pre&gt;
&lt;/div&gt;

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


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

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

&lt;col  class="org-right" /&gt;
&lt;/colgroup&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class="org-right"&gt;12&lt;/td&gt;
&lt;td class="org-right"&gt;12&lt;/td&gt;
&lt;td class="org-right"&gt;12&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;div id="outline-container-org81a44de" class="outline-3"&gt;
&lt;h3 id="org81a44de"&gt;&lt;span class="section-number-3"&gt;1.2.&lt;/span&gt; running functions with mutable arguments&lt;/h3&gt;
&lt;div class="outline-text-3" id="text-1-2"&gt;
&lt;p&gt;
Using mutable arguments is a recipe for trouble and unanticipated problems, but it is easy to unintentionally do, and not always obvious, as I show here.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-jupyter-python"&gt;&lt;span style="color: #0000FF;"&gt;from&lt;/span&gt; ase.build &lt;span style="color: #0000FF;"&gt;import&lt;/span&gt; bulk
&lt;span style="color: #0000FF;"&gt;from&lt;/span&gt; ase.calculators.emt &lt;span style="color: #0000FF;"&gt;import&lt;/span&gt; EMT

&lt;span style="color: #BA36A5;"&gt;atoms&lt;/span&gt; = bulk(&lt;span style="color: #008000;"&gt;'Pd'&lt;/span&gt;)
atoms.set_calculator(EMT())

&lt;span style="color: #6434A3;"&gt;@memory.cache&lt;/span&gt;
&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;f&lt;/span&gt;(atoms):
    &lt;span style="color: #006FE0;"&gt;print&lt;/span&gt;(&lt;span style="color: #008000;"&gt;'If you see this, go get a coffee.'&lt;/span&gt;)
    &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; atoms.get_potential_energy()

(f(atoms), f(atoms))

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

&lt;p&gt;
If you see this, go get a coffee.
If you see this, go get a coffee.
&lt;/p&gt;
&lt;table border="2" cellspacing="0" cellpadding="6" rules="groups" frame="hsides"&gt;


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

&lt;col  class="org-right" /&gt;
&lt;/colgroup&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class="org-right"&gt;0.0003422625372841992&lt;/td&gt;
&lt;td class="org-right"&gt;0.0003422625372841992&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;
You can see this ran twice. The reason is that the atoms object was mutated by adding data onto it. Here is how I know:
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-jupyter-python"&gt;&lt;span style="color: #0000FF;"&gt;import&lt;/span&gt; joblib
&lt;span style="color: #BA36A5;"&gt;atoms&lt;/span&gt; = bulk(&lt;span style="color: #008000;"&gt;'Pd'&lt;/span&gt;)
atoms.set_calculator(EMT())
joblib.&lt;span style="color: #006FE0;"&gt;hash&lt;/span&gt;(atoms), atoms._calc.results
&lt;/pre&gt;
&lt;/div&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-left" /&gt;
&lt;/colgroup&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class="org-left"&gt;ee2ed2eb9fdb4b3d6416803a33f43a22&lt;/td&gt;
&lt;td class="org-left"&gt;nil&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;
Here you can see that simply running the get energy function the hash changes because the results dictionary on the calculator changes. That means subsequent uses of the atoms object will have a different hash, and you cannot rely on that to look up the results. In this case the results should not change the output of the function, but since they are included in the hash, it incorrectly invalidates the hash.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-jupyter-python"&gt;atoms.get_potential_energy()
joblib.&lt;span style="color: #006FE0;"&gt;hash&lt;/span&gt;(atoms), atoms._calc.results
&lt;/pre&gt;
&lt;/div&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-left" /&gt;
&lt;/colgroup&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class="org-left"&gt;d37ef0a5761f499060b4f55bdf644814&lt;/td&gt;
&lt;td class="org-left"&gt;(energy : 0.0003422625372841992 energies : array ((0.00034226)) free_energy : 0.0003422625372841992 forces : array (((0 0 0))))&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;
Suffice to say, this is non-obvious, but having seen it, not a surprise; mutable arguments are frequently a source of problems.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;div id="outline-container-orgeecc951" class="outline-3"&gt;
&lt;h3 id="orgeecc951"&gt;&lt;span class="section-number-3"&gt;1.3.&lt;/span&gt; If you run the same function different ways, the cache is not reused&lt;/h3&gt;
&lt;div class="outline-text-3" id="text-1-3"&gt;
&lt;p&gt;
Some aspects of this are specific to org-mode and how scripts are run in it. Here we have to use an absolute path to make sure we use the right cache. That still doesn't solve the problem though as we will see.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-python"&gt;&lt;span style="color: #0000FF;"&gt;from&lt;/span&gt; joblib &lt;span style="color: #0000FF;"&gt;import&lt;/span&gt; Memory
&lt;span style="color: #BA36A5;"&gt;location&lt;/span&gt; = &lt;span style="color: #008000;"&gt;'/Users/jkitchin/Dropbox/emacs/journal/2023/02/01/joblib_cache/joblib_cache'&lt;/span&gt;
&lt;span style="color: #BA36A5;"&gt;memory&lt;/span&gt; = Memory(location, verbose=0)

a = 3
&lt;span style="color: #6434A3;"&gt;@memory.cache&lt;/span&gt;
&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;f4&lt;/span&gt;(x=1.0):
    &lt;span style="color: #006FE0;"&gt;print&lt;/span&gt;(&lt;span style="color: #008000;"&gt;'If you see this, go get a coffee while it runs'&lt;/span&gt;)
    &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; a*x**2

&lt;span style="color: #006FE0;"&gt;print&lt;/span&gt;((f4(2), f4(2), f4(2)))
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
The issue is that joblib uses the file name it thinks the function is from in the path it saves the results. The filename is different
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;div id="outline-container-orgb8d7f30" class="outline-3"&gt;
&lt;h3 id="orgb8d7f30"&gt;&lt;span class="section-number-3"&gt;1.4.&lt;/span&gt; Fragile cache invalidation&lt;/h3&gt;
&lt;div class="outline-text-3" id="text-1-4"&gt;
&lt;p&gt;
joblib uses the function source in its hash. That means &lt;i&gt;any&lt;/i&gt; change to the source, including the function name, renaming variables, whitespace, comments or docstring changes invalidates the hash even though they may have no change in the output. That is an overabundance of caution, but simple to implement.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-jupyter-python"&gt;&lt;span style="color: #6434A3;"&gt;@memory.cache&lt;/span&gt;
&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;f4&lt;/span&gt;(x=1.0):
    &lt;span style="color: #036A07;"&gt;'add a ds.'&lt;/span&gt;
    &lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;comment&lt;/span&gt;
    &lt;span style="color: #006FE0;"&gt;print&lt;/span&gt;(&lt;span style="color: #008000;"&gt;'If you see this, go get a coffee while it runs'&lt;/span&gt;)
    &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; a*x**2

&lt;span style="color: #006FE0;"&gt;print&lt;/span&gt;((f4(2), f4(2), f4(2)))
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
If you see this, go get a coffee while it runs
(0, 0, 0)
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;


&lt;div id="outline-container-orgd5fbbc2" class="outline-2"&gt;
&lt;h2 id="orgd5fbbc2"&gt;&lt;span class="section-number-2"&gt;2.&lt;/span&gt; Some partial solutions with pycse.hashcache&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-2"&gt;
&lt;p&gt;
I wrote &lt;code&gt;hashcache&lt;/code&gt; to solve some of these problems. It is actually built on top of joblib.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-jupyter-python"&gt;&lt;span style="color: #0000FF;"&gt;import&lt;/span&gt; pycse
pycse.__version__, pycse.__file__
&lt;/pre&gt;
&lt;/div&gt;

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


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

&lt;col  class="org-left" /&gt;
&lt;/colgroup&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class="org-right"&gt;2.2.1&lt;/td&gt;
&lt;td class="org-left"&gt;/Users/jkitchin/Dropbox/python/pycse/pycse/__init__.py&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-jupyter-python"&gt;&lt;span style="color: #0000FF;"&gt;from&lt;/span&gt; pycse.hashcache &lt;span style="color: #0000FF;"&gt;import&lt;/span&gt; hashcache
  
hashcache.&lt;span style="color: #BA36A5;"&gt;location&lt;/span&gt; = &lt;span style="color: #008000;"&gt;"/Users/jkitchin/Dropbox/emacs/journal/2023/02/01/cache"&lt;/span&gt;
hashcache.&lt;span style="color: #BA36A5;"&gt;verbose&lt;/span&gt; = &lt;span style="color: #D0372D;"&gt;False&lt;/span&gt;

&lt;span style="color: #6434A3;"&gt;@hashcache&lt;/span&gt;
&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;h1&lt;/span&gt;(x):
    &lt;span style="color: #006FE0;"&gt;print&lt;/span&gt;(&lt;span style="color: #008000;"&gt;'This runs soo slow... Go get a coffee'&lt;/span&gt;)
    &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; x**2

h1(2), h1(2)
&lt;/pre&gt;
&lt;/div&gt;

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


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

&lt;col  class="org-right" /&gt;
&lt;/colgroup&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class="org-right"&gt;4&lt;/td&gt;
&lt;td class="org-right"&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;

&lt;div id="outline-container-orge7c0587" class="outline-3"&gt;
&lt;h3 id="orge7c0587"&gt;&lt;span class="section-number-3"&gt;2.1.&lt;/span&gt; No known problem with global variables&lt;/h3&gt;
&lt;div class="outline-text-3" id="text-2-1"&gt;
&lt;div class="org-src-container"&gt;
&lt;pre class="src src-jupyter-python"&gt;&lt;span style="color: #BA36A5;"&gt;a&lt;/span&gt; = 3
&lt;span style="color: #6434A3;"&gt;@hashcache&lt;/span&gt;
&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;h4&lt;/span&gt;(x=1.0):
    &lt;span style="color: #006FE0;"&gt;print&lt;/span&gt;(&lt;span style="color: #008000;"&gt;'If you see this, go get a coffee while it runs'&lt;/span&gt;)
    &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; a*x**2

(h4(2), h4(2), h4(2))

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

&lt;p&gt;
If you see this, go get a coffee while it runs
&lt;/p&gt;
&lt;table border="2" cellspacing="0" cellpadding="6" rules="groups" frame="hsides"&gt;


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

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

&lt;col  class="org-right" /&gt;
&lt;/colgroup&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class="org-right"&gt;12&lt;/td&gt;
&lt;td class="org-right"&gt;12&lt;/td&gt;
&lt;td class="org-right"&gt;12&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-jupyter-python"&gt;&lt;span style="color: #BA36A5;"&gt;a&lt;/span&gt;=0
(h4(2), h4(2), h4(2))
&lt;/pre&gt;
&lt;/div&gt;

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


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

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

&lt;col  class="org-right" /&gt;
&lt;/colgroup&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class="org-right"&gt;0&lt;/td&gt;
&lt;td class="org-right"&gt;0&lt;/td&gt;
&lt;td class="org-right"&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;
Whew!!! we got the right answers. hashcache does a better job detecting the external change.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;div id="outline-container-org857113f" class="outline-3"&gt;
&lt;h3 id="org857113f"&gt;&lt;span class="section-number-3"&gt;2.2.&lt;/span&gt; hashcache and mutable arguments&lt;/h3&gt;
&lt;div class="outline-text-3" id="text-2-2"&gt;
&lt;p&gt;
hashcache does not solve the mutable argument problem, but, it does warn you it detected it.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-jupyter-python"&gt;&lt;span style="color: #0000FF;"&gt;from&lt;/span&gt; ase.build &lt;span style="color: #0000FF;"&gt;import&lt;/span&gt; bulk
&lt;span style="color: #0000FF;"&gt;from&lt;/span&gt; ase.calculators.emt &lt;span style="color: #0000FF;"&gt;import&lt;/span&gt; EMT

&lt;span style="color: #BA36A5;"&gt;atoms&lt;/span&gt; = bulk(&lt;span style="color: #008000;"&gt;'Pd'&lt;/span&gt;)
atoms.set_calculator(EMT())

&lt;span style="color: #6434A3;"&gt;@hashcache&lt;/span&gt;
&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;h&lt;/span&gt;(atoms):
    &lt;span style="color: #006FE0;"&gt;print&lt;/span&gt;(&lt;span style="color: #008000;"&gt;'If you see this, go get a coffee.'&lt;/span&gt;)
    &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; atoms.get_potential_energy()

(h(atoms), h(atoms), h(atoms))
&lt;/pre&gt;
&lt;/div&gt;

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


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

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

&lt;col  class="org-right" /&gt;
&lt;/colgroup&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class="org-right"&gt;0.0003422625372841992&lt;/td&gt;
&lt;td class="org-right"&gt;0.0003422625372841992&lt;/td&gt;
&lt;td class="org-right"&gt;0.0003422625372841992&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;div id="outline-container-org4925e4a" class="outline-3"&gt;
&lt;h3 id="org4925e4a"&gt;&lt;span class="section-number-3"&gt;2.3.&lt;/span&gt; Reuse the cache when you run different ways&lt;/h3&gt;
&lt;div class="outline-text-3" id="text-2-3"&gt;
&lt;p&gt;
hashcache uses the same cache at the function and function environment level, so it avoids reruns even from different places. It is a judgement call by you to say if this is the right thing to do.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-jupyter-python"&gt;&lt;span style="color: #006FE0;"&gt;print&lt;/span&gt;(h1(2), h1(2))
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
4 4
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-python"&gt;&lt;span style="color: #0000FF;"&gt;from&lt;/span&gt; pycse.hashcache &lt;span style="color: #0000FF;"&gt;import&lt;/span&gt; hashcache
hashcache.&lt;span style="color: #BA36A5;"&gt;location&lt;/span&gt; = &lt;span style="color: #008000;"&gt;"/Users/jkitchin/Dropbox/emacs/journal/2023/02/01/cache"&lt;/span&gt;

&lt;span style="color: #6434A3;"&gt;@hashcache&lt;/span&gt;
&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;h1&lt;/span&gt;(x):
    &lt;span style="color: #006FE0;"&gt;print&lt;/span&gt;(&lt;span style="color: #008000;"&gt;'This runs soo slow... Go get a coffee'&lt;/span&gt;)
    &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; x**2

&lt;span style="color: #006FE0;"&gt;print&lt;/span&gt;(h1(2), h1(2))
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;div id="outline-container-org8461f88" class="outline-3"&gt;
&lt;h3 id="org8461f88"&gt;&lt;span class="section-number-3"&gt;2.4.&lt;/span&gt; Insensitivity to unimportant changes&lt;/h3&gt;
&lt;div class="outline-text-3" id="text-2-4"&gt;
&lt;p&gt;
Instead of hashing the source of the function, in hashcache I hash the bytecode instead. This is certainly less sensitive to unimportant changes like docstrings, comments or whitespace. I do use the function name in the hash, so even though that does not affect the output, I thought it might be confusing in the future.
&lt;/p&gt;

&lt;p&gt;
Here, small changes like comments, docstrings, etc, don't affect the hash.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-jupyter-python"&gt;&lt;span style="color: #BA36A5;"&gt;a&lt;/span&gt; = 3
&lt;span style="color: #6434A3;"&gt;@hashcache&lt;/span&gt;
&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;h4&lt;/span&gt;(x=1.0):
    &lt;span style="color: #036A07;"&gt;'doc string'&lt;/span&gt;
    &lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;comments&lt;/span&gt;
    &lt;span style="color: #006FE0;"&gt;print&lt;/span&gt;(&lt;span style="color: #008000;"&gt;'If you see this, go get a coffee while it runs'&lt;/span&gt;)
    &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; a*x**2    

(h4(2), h4(2), h4(2))
&lt;/pre&gt;
&lt;/div&gt;

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


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

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

&lt;col  class="org-right" /&gt;
&lt;/colgroup&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class="org-right"&gt;12&lt;/td&gt;
&lt;td class="org-right"&gt;12&lt;/td&gt;
&lt;td class="org-right"&gt;12&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;div id="outline-container-orgfb0f2ed" class="outline-2"&gt;
&lt;h2 id="orgfb0f2ed"&gt;&lt;span class="section-number-2"&gt;3.&lt;/span&gt; Is it the answer?&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-3"&gt;
&lt;p&gt;
Probably not completely. It is almost certain I have not captured all the ways the cache should be invalidated, or when a new cache should be used. hashcache is for now, a proof of concept in understanding why this is a hard problem to solve. I prefer its behavior over the defaults in joblib so far though.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Copyright (C) 2023 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/2023/02/01/Caching-expensive-function-calls-so-you-don't-have-to-rerun-them.org"&gt;org-mode source&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Org-mode version = 9.5.5&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>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>
    <item>
      <title>Getting derivatives from implicit functions with autograd</title>
      <link>https://kitchingroup.cheme.cmu.edu/blog/2018/10/08/Getting-derivatives-from-implicit-functions-with-autograd</link>
      <pubDate>Mon, 08 Oct 2018 19:53:21 EDT</pubDate>
      <category><![CDATA[autograd]]></category>
      <category><![CDATA[python]]></category>
      <guid isPermaLink="false">vzdJUvksnYiFe3TCZNWRgZOmbqc=</guid>
      <description>Getting derivatives from implicit functions 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="#org1e7bbe1"&gt;1. Example 1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#org30374e2"&gt;2. Example 2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#orgc49e13d"&gt;3. Example 3&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#org452c660"&gt;4. Example 4&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#org67383a5"&gt;5. Example 5&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#org0081ece"&gt;6. Example 6&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#orgedd1f4b"&gt;7. Example 7&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#orgb2b9394"&gt;8. Conclusions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
If we have an implicit function: \(f(x, y(x)) = 0\), but we want to compute the derivative \(dy/dx\) we can use the chain rule to derive:
&lt;/p&gt;

&lt;p&gt;
\(df/dx + df/dy dy/dx = 0\)
&lt;/p&gt;

&lt;p&gt;
We can then solve for \(dy/dx\):
&lt;/p&gt;

&lt;p&gt;
\(dy/dx = -df/dx / df/dy\) to get the desired derivative. The interesting point of this blog post is that we can get the two derivatives on the right hand side of this equation using automatic differentiation of the function \(f(x, y)\)! There are a few examples of analytical approaches to derivatives from implicit functions &lt;a href="https://www.math.ucdavis.edu/~kouba/CalcOneDIRECTORY/implicitdiffdirectory/ImplicitDiff.html"&gt;here&lt;/a&gt;, and I wanted to explore them with autograd in this post.
&lt;/p&gt;

&lt;p&gt;
In the following examples, we will assume that \(y\) is a function of \(x\) and that \(x\) is independent. We will consider a series of implicit equations, compute \(dy/dx\) using autograd from the formula above, and compare them to the analytical results in the web page referenced above.
&lt;/p&gt;

&lt;p&gt;
The \(dy/dx\) functions generally depend on both \(x\), and \(y\). Technically, these are the derivatives along the curve \(y(x)\), but since we can evaluate them at any points, we will use some random points for \(x\) and \(y\) to test for equality between the analytical derivatives and the autograd derivatives. This isn't a rigorous proof of equality, but it is the only thing that makes sense to do for now. It is assumed that if these points are ok, all the others are too. That might be a broad claim, since we only sample \(x\) and \(y\) from 0 to 1 here but the approach is general. Here are the imports and the random test points for all the examples that follow.
&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: #BA36A5;"&gt;xr&lt;/span&gt; = np.random.random(50)
&lt;span style="color: #BA36A5;"&gt;yr&lt;/span&gt; = np.random.random(50)
&lt;/pre&gt;
&lt;/div&gt;


&lt;div id="outline-container-org1e7bbe1" class="outline-2"&gt;
&lt;h2 id="org1e7bbe1"&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;
\(x^3 + y^3 = 4\)
&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;f1&lt;/span&gt;(x, y):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; x**3 + y**3 - 4

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

&lt;span style="color: #BA36A5;"&gt;dydx_1&lt;/span&gt; = &lt;span style="color: #0000FF;"&gt;lambda&lt;/span&gt; x, y: -D1x(x, y) / D1y(x, y)
&lt;span style="color: #BA36A5;"&gt;dydx_1a&lt;/span&gt; = &lt;span style="color: #0000FF;"&gt;lambda&lt;/span&gt; x, y: -x**2 / y**2

np.allclose(dydx_1a(xr, yr),
&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;[dydx_1(_xr, _yr) &lt;span style="color: #0000FF;"&gt;for&lt;/span&gt; _xr, _yr &lt;span style="color: #0000FF;"&gt;in&lt;/span&gt; &lt;span style="color: #006FE0;"&gt;zip&lt;/span&gt;(xr, yr)])
&lt;/pre&gt;
&lt;/div&gt;

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

&lt;/pre&gt;

&lt;p&gt;
The output of True means the autograd results and the analytical results are "all close", i.e. within a tolerance the results are the same. The required derivatives of this are not that difficult to derive, but it is nice to see a simple example that "just works". A subtle point of the dydx function is that it is not &lt;i&gt;vectorized&lt;/i&gt; which is why I used a list comprehension to evaluate all the points. It might be possible to make a pseudo-vectorized version with the np.vectorize decorator, but that is not of interest here.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;div id="outline-container-org30374e2" class="outline-2"&gt;
&lt;h2 id="org30374e2"&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;
\((x - y)^2 = x + y - 1\)
&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;f2&lt;/span&gt;(x, y):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; (x - y)**2 - x - y + 1

&lt;span style="color: #BA36A5;"&gt;D2x&lt;/span&gt; = grad(f2, 0)
&lt;span style="color: #BA36A5;"&gt;D2y&lt;/span&gt; = grad(f2, 1)

&lt;span style="color: #BA36A5;"&gt;dydx_2&lt;/span&gt; = &lt;span style="color: #0000FF;"&gt;lambda&lt;/span&gt; x, y: -D2x(x, y) / D2y(x, y)
&lt;span style="color: #BA36A5;"&gt;dydx2_a&lt;/span&gt; = &lt;span style="color: #0000FF;"&gt;lambda&lt;/span&gt; x, y: (2 * y - 2 * x + 1) / (2 * y - 2 * x - 1)

np.allclose(dydx2_a(xr, yr),
&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;   [dydx_2(_xr, _yr) &lt;span style="color: #0000FF;"&gt;for&lt;/span&gt; _xr, _yr &lt;span style="color: #0000FF;"&gt;in&lt;/span&gt; &lt;span style="color: #006FE0;"&gt;zip&lt;/span&gt;(xr, yr)])
&lt;/pre&gt;
&lt;/div&gt;

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

&lt;/pre&gt;

&lt;p&gt;
This also works.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;div id="outline-container-orgc49e13d" class="outline-2"&gt;
&lt;h2 id="orgc49e13d"&gt;&lt;span class="section-number-2"&gt;3&lt;/span&gt; Example 3&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-3"&gt;
&lt;p&gt;
\(y = sin(3x + 4y)\)
&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;f3&lt;/span&gt;(x, y):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; y - np.sin(3 * x + 4 * y)


&lt;span style="color: #BA36A5;"&gt;D3x&lt;/span&gt; = grad(f3, 0)
&lt;span style="color: #BA36A5;"&gt;D3y&lt;/span&gt; = grad(f3, 1)

&lt;span style="color: #BA36A5;"&gt;dydx_3&lt;/span&gt; = &lt;span style="color: #0000FF;"&gt;lambda&lt;/span&gt; x, y: -D3x(x, y) / D3y(x, y)
&lt;span style="color: #BA36A5;"&gt;dydx3_a&lt;/span&gt; = &lt;span style="color: #0000FF;"&gt;lambda&lt;/span&gt; x, y: (3 * np.cos(3 * x + 4 * y)) / (1 - 4 * np.cos(3 * x + 4 * y))

np.allclose(dydx3_a(xr, yr),
&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;   [dydx_3(_xr, _yr) &lt;span style="color: #0000FF;"&gt;for&lt;/span&gt; _xr, _yr &lt;span style="color: #0000FF;"&gt;in&lt;/span&gt; &lt;span style="color: #006FE0;"&gt;zip&lt;/span&gt;(xr, yr)])
&lt;/pre&gt;
&lt;/div&gt;

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

&lt;/pre&gt;

&lt;p&gt;
Check.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;div id="outline-container-org452c660" class="outline-2"&gt;
&lt;h2 id="org452c660"&gt;&lt;span class="section-number-2"&gt;4&lt;/span&gt; Example 4&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-4"&gt;
&lt;p&gt;
\(y = x^2 y^3 + x^3 y^2\)
&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;f4&lt;/span&gt;(x, y):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; y - x**2 * y**3 - x**3 * y**2


&lt;span style="color: #BA36A5;"&gt;D4x&lt;/span&gt; = grad(f4, 0)
&lt;span style="color: #BA36A5;"&gt;D4y&lt;/span&gt; = grad(f4, 1)

&lt;span style="color: #BA36A5;"&gt;dydx_4&lt;/span&gt; = &lt;span style="color: #0000FF;"&gt;lambda&lt;/span&gt; x, y: -D4x(x, y) / D4y(x, y)
&lt;span style="color: #BA36A5;"&gt;dydx4_a&lt;/span&gt; = &lt;span style="color: #0000FF;"&gt;lambda&lt;/span&gt; x, y: (2 * x * y**3 + 3 * x**2 * y**2) / (1 - 3 * x**2 * y**2 - 2 * x**3 * y)

np.allclose(dydx4_a(xr, yr),
&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;   [dydx_4(_xr, _yr) &lt;span style="color: #0000FF;"&gt;for&lt;/span&gt; _xr, _yr &lt;span style="color: #0000FF;"&gt;in&lt;/span&gt; &lt;span style="color: #006FE0;"&gt;zip&lt;/span&gt;(xr, yr)])
&lt;/pre&gt;
&lt;/div&gt;

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

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

&lt;div id="outline-container-org67383a5" class="outline-2"&gt;
&lt;h2 id="org67383a5"&gt;&lt;span class="section-number-2"&gt;5&lt;/span&gt; Example 5&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-5"&gt;
&lt;p&gt;
\(e^{xy} = e^{4x} - e^{5y}\)
&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;f5&lt;/span&gt;(x, y):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; np.exp(4 * x) - np.exp(5 * y) - np.exp(x * y)

&lt;span style="color: #BA36A5;"&gt;D5x&lt;/span&gt; = grad(f5, 0)
&lt;span style="color: #BA36A5;"&gt;D5y&lt;/span&gt; = grad(f5, 1)

&lt;span style="color: #BA36A5;"&gt;dydx_5&lt;/span&gt; = &lt;span style="color: #0000FF;"&gt;lambda&lt;/span&gt; x, y: -D5x(x, y) / D5y(x, y)
&lt;span style="color: #BA36A5;"&gt;dydx5_a&lt;/span&gt; = &lt;span style="color: #0000FF;"&gt;lambda&lt;/span&gt; x, y: (4 * np.exp(4 * x) - y * np.exp(x * y)) / (x * np.exp(x * y) + 5 * np.exp(5 * y))

np.allclose(dydx5_a(xr, yr),
&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;   [dydx_5(_xr, _yr) &lt;span style="color: #0000FF;"&gt;for&lt;/span&gt; _xr, _yr &lt;span style="color: #0000FF;"&gt;in&lt;/span&gt; &lt;span style="color: #006FE0;"&gt;zip&lt;/span&gt;(xr, yr)])
&lt;/pre&gt;
&lt;/div&gt;

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

&lt;/pre&gt;

&lt;p&gt;
Also check.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;div id="outline-container-org0081ece" class="outline-2"&gt;
&lt;h2 id="org0081ece"&gt;&lt;span class="section-number-2"&gt;6&lt;/span&gt; Example 6&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-6"&gt;
&lt;p&gt;
\(\cos^2 x + cos^2 y = cos(2x + 2y)\)
&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;f6&lt;/span&gt;(x, y):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; np.cos(x)**2 + np.cos(y)**2 - np.cos(2 * x + 2 * y)

&lt;span style="color: #BA36A5;"&gt;D6x&lt;/span&gt; = grad(f6, 0)
&lt;span style="color: #BA36A5;"&gt;D6y&lt;/span&gt; = grad(f6, 1)

&lt;span style="color: #BA36A5;"&gt;dydx_6&lt;/span&gt; = &lt;span style="color: #0000FF;"&gt;lambda&lt;/span&gt; x, y: -D6x(x, y) / D6y(x, y)
&lt;span style="color: #BA36A5;"&gt;dydx6_a&lt;/span&gt; = &lt;span style="color: #0000FF;"&gt;lambda&lt;/span&gt; x, y: (np.cos(x) * np.sin(x) - np.sin(2 * x + 2 * y)) / (np.sin(2 * x + 2 * y) - np.cos(y) * np.sin(y))

np.allclose(dydx6_a(xr, yr),
&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;   [dydx_6(_xr, _yr) &lt;span style="color: #0000FF;"&gt;for&lt;/span&gt; _xr, _yr &lt;span style="color: #0000FF;"&gt;in&lt;/span&gt; &lt;span style="color: #006FE0;"&gt;zip&lt;/span&gt;(xr, yr)])
&lt;/pre&gt;
&lt;/div&gt;

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

&lt;/pre&gt;

&lt;p&gt;
Check.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;div id="outline-container-orgedd1f4b" class="outline-2"&gt;
&lt;h2 id="orgedd1f4b"&gt;&lt;span class="section-number-2"&gt;7&lt;/span&gt; Example 7&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-7"&gt;
&lt;p&gt;
\(x = 3 + \sqrt{x^2 + y^2}\)
&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;f7&lt;/span&gt;(x, y):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; 3 + np.sqrt(x**2 + y**2) - x

&lt;span style="color: #BA36A5;"&gt;D7x&lt;/span&gt; = grad(f7, 0)
&lt;span style="color: #BA36A5;"&gt;D7y&lt;/span&gt; = grad(f7, 1)

&lt;span style="color: #BA36A5;"&gt;dydx_7&lt;/span&gt; = &lt;span style="color: #0000FF;"&gt;lambda&lt;/span&gt; x, y: -D7x(x, y) / D7y(x, y)
&lt;span style="color: #BA36A5;"&gt;dydx7_a&lt;/span&gt; = &lt;span style="color: #0000FF;"&gt;lambda&lt;/span&gt; x, y: (np.sqrt(x**2 + y**2) - x) / y

np.allclose(dydx7_a(xr, yr),
&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;   [dydx_7(_xr, _yr) &lt;span style="color: #0000FF;"&gt;for&lt;/span&gt; _xr, _yr &lt;span style="color: #0000FF;"&gt;in&lt;/span&gt; &lt;span style="color: #006FE0;"&gt;zip&lt;/span&gt;(xr, yr)])
&lt;/pre&gt;
&lt;/div&gt;

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

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


&lt;div id="outline-container-orgb2b9394" class="outline-2"&gt;
&lt;h2 id="orgb2b9394"&gt;&lt;span class="section-number-2"&gt;8&lt;/span&gt; Conclusions&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-8"&gt;
&lt;p&gt;
There are a handful of other examples at the website referenced in the beginning, but I am stopping here. After seven examples of quantitative agreement between the easy to derive autograd derivatives, and the easy to moderately difficult analytical derivatives, it seems like it is autograd for the win here. This technique has some important implications for engineering calculations that I will explore in a future post. Until then, this is yet another interesting thing we can do with automatic differentiation!
&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/08/Getting-derivatives-from-implicit-functions-with-autograd.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 factor variation from the van der Waals equation by three different approaches</title>
      <link>https://kitchingroup.cheme.cmu.edu/blog/2018/10/07/Compressibility-factor-variation-from-the-van-der-Waals-equation-by-three-different-approaches</link>
      <pubDate>Sun, 07 Oct 2018 13:08:11 EDT</pubDate>
      <category><![CDATA[autograd]]></category>
      <category><![CDATA[ode]]></category>
      <category><![CDATA[nonlinear algebra]]></category>
      <category><![CDATA[python]]></category>
      <guid isPermaLink="false">nzuQ2552fiegwa_OqTuF-vQ3pMs=</guid>
      <description>Compressibility factor variation from the van der Waals equation by three different approaches</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="#org2fd7cfa"&gt;1. Method 1 - fsolve&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#org7ade82a"&gt;2. Method 2 - solve_ivp&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#orge63b16e"&gt;3. Method 3 - autograd + solve_ivp&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
In the book &lt;span class="underline"&gt;Problem solving in chemical and biochemical engineering with POLYMATH, Excel and Matlab&lt;/span&gt; by Cutlip and Shacham there is a problem (7.1) where you want to plot the compressibility factor for CO&lt;sub&gt;2&lt;/sub&gt; over a range of \(0.1 \le P_r &lt;= 10\) for a constant \(T_r=1.1\) using the van der Waal equation of state. There are a two standard ways to do this:
&lt;/p&gt;

&lt;ol class="org-ol"&gt;
&lt;li&gt;Solve a nonlinear equation for different values of \(P_r\).&lt;/li&gt;
&lt;li&gt;Solve a nonlinear equation for one value of \(P_r\), then derive an ODE for how the compressibility varies with \(P_r\) and integrate it over the relevant range.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;
In this post, we compare and contrast the two methods, and consider a variation of the second method that uses automatic differentiation.
&lt;/p&gt;

&lt;div id="outline-container-org2fd7cfa" class="outline-2"&gt;
&lt;h2 id="org2fd7cfa"&gt;&lt;span class="section-number-2"&gt;1&lt;/span&gt; Method 1 - fsolve&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-1"&gt;
&lt;p&gt;
The van der Waal equation of state is:
&lt;/p&gt;

&lt;p&gt;
\(P = \frac{R T}{V - b} - \frac{a}{V^2}\).
&lt;/p&gt;

&lt;p&gt;
We define the reduced pressure as \(P_r = P / P_c\), and the reduced temperature as \(T_r = T / T_c\).
&lt;/p&gt;

&lt;p&gt;
So, we simply solve for V at a given \(P_r\), and then compute \(Z\). There is a subtle trick needed to make this easy to solve, and that is to multiply each side of the equation by \((V - b)\) to avoid a singularity when \(V = b\), which happens in this case near \(P_r \approx 7.5\).
&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: #0000FF;"&gt;import&lt;/span&gt; numpy &lt;span style="color: #0000FF;"&gt;as&lt;/span&gt; np
%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;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: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;objective&lt;/span&gt;(V, Pr):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;P&lt;/span&gt; = Pr * Pc
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;T&lt;/span&gt; = Tr * Tc
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; P * (V - b) - (R * T)  +  a / V**2 * (V - b)


&lt;span style="color: #BA36A5;"&gt;Pr_range&lt;/span&gt; = np.linspace(0.1, 10)
&lt;span style="color: #BA36A5;"&gt;V&lt;/span&gt; = [fsolve(objective, 3, args=(Pr,))[0] &lt;span style="color: #0000FF;"&gt;for&lt;/span&gt; Pr &lt;span style="color: #0000FF;"&gt;in&lt;/span&gt; Pr_range]

&lt;span style="color: #BA36A5;"&gt;T&lt;/span&gt; = Tr * Tc
&lt;span style="color: #BA36A5;"&gt;P_range&lt;/span&gt; = Pr_range * Pc
&lt;span style="color: #BA36A5;"&gt;Z&lt;/span&gt; = P_range * V / (R * T)

plt.plot(Pr_range, 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;
(0, 2)

&lt;/pre&gt;



&lt;p&gt;
&lt;img src="/media/13bc1d996aa4bd032faad00425793120-90490byl.png"&gt; 
&lt;/p&gt;

&lt;p&gt;
That looks like Figure 7-1 in the book. This approach is fine, but the equation did require a little algebraic finesse to solve, and you have to use some iteration to get the solution.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;div id="outline-container-org7ade82a" class="outline-2"&gt;
&lt;h2 id="org7ade82a"&gt;&lt;span class="section-number-2"&gt;2&lt;/span&gt; Method 2 - solve_ivp&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-2"&gt;
&lt;p&gt;
In this method, you have to derive an expression for \(\frac{dV}{dP_r}\). That derivation goes like this:
&lt;/p&gt;

&lt;p&gt;
\(\frac{dV}{dP_r} = \frac{dV}{dP} \frac{dP}{dP_r}\)
&lt;/p&gt;

&lt;p&gt;
The first term \(\frac{dV}{dP}\) is \((\frac{dP}{dV})^{-1}\), which we can derive directly from the van der Waal equation, and the second term is just a constant: \(P_c\) from the definition of \(P_r\).
&lt;/p&gt;

&lt;p&gt;
They derived:
&lt;/p&gt;

&lt;p&gt;
\(\frac{dP}{dV} = -\frac{R T}{(V - b)^2} + \frac{2 a}{V^3}\)
&lt;/p&gt;

&lt;p&gt;
We need to solve for one V, at the beginning of the range of \(P_r\) we are interested in.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython"&gt;V0, = fsolve(objective, 3, args=(0.1,))
V0
&lt;/pre&gt;
&lt;/div&gt;

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

&lt;/pre&gt;

&lt;p&gt;
Now, we can define the functions, and integrate them to get the same solution. I defined these pretty verbosely, just for readability.
&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;dPdV&lt;/span&gt;(V):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; -R * T / (V - b)**2 + 2 * a / V**3

&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;dVdP&lt;/span&gt;(V):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; 1 / dPdV(V)

&lt;span style="color: #BA36A5;"&gt;dPdPr&lt;/span&gt; = Pc

&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; dVdP(V) * dPdPr

&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,), dense_output=&lt;span style="color: #D0372D;"&gt;True&lt;/span&gt;, max_step=h)

&lt;span style="color: #BA36A5;"&gt;V&lt;/span&gt; = sol.y[0]
&lt;span style="color: #BA36A5;"&gt;P&lt;/span&gt; = sol.t * Pc
&lt;span style="color: #BA36A5;"&gt;Z&lt;/span&gt; = P * V / (R * T)
plt.plot(sol.t, 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;
(0, 2)

&lt;/pre&gt;



&lt;p&gt;
&lt;img src="/media/13bc1d996aa4bd032faad00425793120-90490o8r.png"&gt; 
&lt;/p&gt;

&lt;p&gt;
This also looks like Figure 7-1. It is arguably a better approach since we only need an initial condition, and after that have a reliable integration (rather than many iterative solutions from an initial guess of the solution in fsolve).
&lt;/p&gt;

&lt;p&gt;
The only downside to this approach (in my opinion) is the need to derive and implement derivatives. As equations of state get more complex, this gets more tedious and complicated.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;div id="outline-container-orge63b16e" class="outline-2"&gt;
&lt;h2 id="orge63b16e"&gt;&lt;span class="section-number-2"&gt;3&lt;/span&gt; Method 3 - autograd + solve_ivp&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-3"&gt;
&lt;p&gt;
The whole point of automatic differentiation is to get derivatives of functions that are written as programs. We explore here the possibility of using this to solve this problem. The idea is to use autograd to define the derivative \(dP/dV\), and then solve the ODE like we did before.
&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: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;P&lt;/span&gt;(V):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; R * T / (V - b) - a / V**2

&lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;autograd.grad returns a callable that acts like a function&lt;/span&gt;
&lt;span style="color: #BA36A5;"&gt;dPdV&lt;/span&gt; = grad(P, 0)

&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; 1 / dPdV(V) * Pc

&lt;span style="color: #BA36A5;"&gt;sol&lt;/span&gt; = solve_ivp(dVdPr,  Pr_span, (V0,), dense_output=&lt;span style="color: #D0372D;"&gt;True&lt;/span&gt;, max_step=h)

V, = sol.y
&lt;span style="color: #BA36A5;"&gt;P&lt;/span&gt; = sol.t * Pc
&lt;span style="color: #BA36A5;"&gt;Z&lt;/span&gt; = P * V / (R * T)
plt.plot(sol.t, 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;
(0, 2)

&lt;/pre&gt;



&lt;p&gt;
&lt;img src="/media/13bc1d996aa4bd032faad00425793120-90490O2H.png"&gt; 
&lt;/p&gt;

&lt;p&gt;
Not surprisingly, this answer looks the same as the previous ones. I think this solution is pretty awesome. We only had to implement the van der Waal equation, and then let autograd do its job to get the relevant derivative. We don't get a free pass on calculus here; we still have to know which derivatives are important. We also need some knowledge about how to use autograd, but with that, this problem becomes pretty easy to solve.
&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/07/Compressibility-factor-variation-from-the-van-der-Waals-equation-by-three-different-approaches.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>Solving nonlinear algebra problems with internal state information</title>
      <link>https://kitchingroup.cheme.cmu.edu/blog/2018/09/24/Solving-nonlinear-algebra-problems-with-internal-state-information</link>
      <pubDate>Mon, 24 Sep 2018 15:25:06 EDT</pubDate>
      <category><![CDATA[nonlinear algebra]]></category>
      <category><![CDATA[python]]></category>
      <guid isPermaLink="false">BlqHNVHd84spZMeCrwRb4yewh38=</guid>
      <description>Solving nonlinear algebra problems with internal state information</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="#org532d5ce"&gt;1. First approach&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#orga01a4af"&gt;2. Second approach - use a state dictionary as an arg in the objective function&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#orgc590864"&gt;3. third approach - a callable object&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#orgaef298d"&gt;4. Summary&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
In engineering, we often need to solve an equation in one variable, and then use the solution to compute other variables. For example, we might want the bubble point temperature of a mixture, and then to determine the composition of the vapor phase that has formed. In other words, we compute the temperature, and then have to use it in a subsequent step to get the composition. Here is a bubble point computation adapted from example 10.2 in Smith and van Ness, Introduction to Chemical Engineering Thermodynamics.
&lt;/p&gt;

&lt;p&gt;
Given a solution of acetone (x&lt;sub&gt;1&lt;/sub&gt;=0.3), acetonitrile (x&lt;sub&gt;2&lt;/sub&gt;=0.45) and nitromethane (x&lt;sub&gt;3&lt;/sub&gt;=0.25) at a total pressure of 80 kPa, compute the bubble point temperature and gas phase composition.
&lt;/p&gt;

&lt;p&gt;
The key here is to find a temperature where the gas-phase mole fractions sum to one. The gas phase mole fractions are defined by:
&lt;/p&gt;

&lt;p&gt;
\(y_i = x_i Pvap_i(T) / P\)
&lt;/p&gt;

&lt;p&gt;
The typical way I would teach students how solve this looks like this. It uses the Antoine equation coded below to estimate the vapor pressure of each component as a function of temperature, and then uses fsolve to find a temperature where the gas-phase mole fractions sum to one.
&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; fsolve

&lt;span style="color: #BA36A5;"&gt;acetone&lt;/span&gt; = (14.5463, 2940.46, 237.22)
&lt;span style="color: #BA36A5;"&gt;acetonitrile&lt;/span&gt; = (14.2724, 2945.47,224)
&lt;span style="color: #BA36A5;"&gt;nitromethane&lt;/span&gt; = (14.2043, 2972.64, 209)

&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;antoine&lt;/span&gt;(T, A, B, C):
&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: #006FE0;"&gt;float&lt;/span&gt;(T) &lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;there is some subtle issue that comes up when T is an array,&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: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;as passed in from fsolve. It needs to be a float, or you get&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: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;the wrong answer.&lt;/span&gt;
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; np.exp(A - B / (T + C))

&lt;span style="color: #BA36A5;"&gt;x&lt;/span&gt; = np.array([0.3, 0.45, 0.25])
&lt;span style="color: #BA36A5;"&gt;P&lt;/span&gt; = 80

&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;objective&lt;/span&gt;(T):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;Pvap&lt;/span&gt; = np.array([antoine(T, *pars) &lt;span style="color: #0000FF;"&gt;for&lt;/span&gt; pars &lt;span style="color: #0000FF;"&gt;in&lt;/span&gt; [acetone, acetonitrile, nitromethane]])
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;y&lt;/span&gt; = x * Pvap / P
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; 1 - y.&lt;span style="color: #006FE0;"&gt;sum&lt;/span&gt;()

Tans, = fsolve(objective, 70)

&lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;This is where we end up repeating code&lt;/span&gt;
&lt;span style="color: #BA36A5;"&gt;Pvap&lt;/span&gt; = np.array([antoine(Tans, *pars) &lt;span style="color: #0000FF;"&gt;for&lt;/span&gt; pars &lt;span style="color: #0000FF;"&gt;in&lt;/span&gt; [acetone, acetonitrile, nitromethane]])
&lt;span style="color: #BA36A5;"&gt;y&lt;/span&gt; = x * Pvap / P

&lt;span style="color: #0000FF;"&gt;print&lt;/span&gt;(f&lt;span style="color: #008000;"&gt;'The bubble point temperature is {Tans:1.2f} degC, and the gas phase compositions are {np.round(y, 4)}.'&lt;/span&gt;)
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
The bubble point temperature is 68.60 degC, and the gas phase compositions are [ 0.5196  0.3773  0.1031].


&lt;/pre&gt;

&lt;p&gt;
This solution works fine, but there is in my opinion, an issue with the small amount of repeated code at the end that is required to get the composition of the gas-phase. This is a small problem here, but as the problems get bigger it is more and more tedious to correctly repeat all the code to see what the state of a system is at the solution, and it seems wasteful to have to repeat the computations; they were known in the objective function. In the following subsections, I explore some alternative approaches to reduce the repetition.
&lt;/p&gt;

&lt;div id="outline-container-org532d5ce" class="outline-2"&gt;
&lt;h2 id="org532d5ce"&gt;&lt;span class="section-number-2"&gt;1&lt;/span&gt; First approach&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-1"&gt;
&lt;p&gt;
There are two small chunks of repeated code in the example above. One way to minimize the amount of repeated code is to pull these out into reusable functions. Here, we do that, and only have to repeat one function call at the end to get the system composition out.
&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; fsolve

&lt;span style="color: #BA36A5;"&gt;acetone&lt;/span&gt; = (14.5463, 2940.46, 237.22)
&lt;span style="color: #BA36A5;"&gt;acetonitrile&lt;/span&gt; = (14.2724, 2945.47,224)
&lt;span style="color: #BA36A5;"&gt;nitromethane&lt;/span&gt; = (14.2043, 2972.64, 209)

&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;antoine&lt;/span&gt;(T, A, B, C):
&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: #006FE0;"&gt;float&lt;/span&gt;(T) &lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;there is some subtle issue that comes up when T is an array,&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: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;as passed in from fsolve. It needs to be a float, or you get&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: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;the wrong answer.&lt;/span&gt;
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; np.exp(A - B / (T + C))

&lt;span style="color: #BA36A5;"&gt;x&lt;/span&gt; = np.array([0.3, 0.45, 0.25])
&lt;span style="color: #BA36A5;"&gt;P&lt;/span&gt; = 80

&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;Pvap&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([antoine(T, *pars) &lt;span style="color: #0000FF;"&gt;for&lt;/span&gt; pars &lt;span style="color: #0000FF;"&gt;in&lt;/span&gt; [acetone, acetonitrile, nitromethane]])

&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;y&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; x * Pvap(T) / P

&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;objective&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; 1 - y(T).&lt;span style="color: #006FE0;"&gt;sum&lt;/span&gt;()

Tans, = fsolve(objective, 70)

&lt;span style="color: #BA36A5;"&gt;yans&lt;/span&gt; = y(Tans) &lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;minimal repetition of a calculation to get the composition state.&lt;/span&gt;

&lt;span style="color: #0000FF;"&gt;print&lt;/span&gt;(f&lt;span style="color: #008000;"&gt;'The bubble point temperature is {Tans:1.2f} degC, and the gas phase compositions are {np.round(yans, 4)}.'&lt;/span&gt;)
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
The bubble point temperature is 68.60 degC, and the gas phase compositions are [ 0.5196  0.3773  0.1031].


&lt;/pre&gt;

&lt;p&gt;
That is a small improvement. The code is not much shorter, just reorganized for easier reuse. It would be easy in this case to also get the vapor pressures of each species at this temperature, just by calling the &lt;code&gt;Pvap&lt;/code&gt; function. Still, it feels annoying we have to recalculate the answer to something you know must have already been known when the objective function was evaluated.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;div id="outline-container-orga01a4af" class="outline-2"&gt;
&lt;h2 id="orga01a4af"&gt;&lt;span class="section-number-2"&gt;2&lt;/span&gt; Second approach - use a state dictionary as an arg in the objective function&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-2"&gt;
&lt;p&gt;
In this approach, we will use a dictionary to store the state of the objective function. The dictionary will be in the global namespace, and we will just update it each time the objective function is called.
&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; fsolve

&lt;span style="color: #BA36A5;"&gt;acetone&lt;/span&gt; = (14.5463, 2940.46, 237.22)
&lt;span style="color: #BA36A5;"&gt;acetonitrile&lt;/span&gt; = (14.2724, 2945.47,224)
&lt;span style="color: #BA36A5;"&gt;nitromethane&lt;/span&gt; = (14.2043, 2972.64, 209)

&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;antoine&lt;/span&gt;(T, A, B, C):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; np.exp(A - B / (T + C))

&lt;span style="color: #BA36A5;"&gt;x&lt;/span&gt; = np.array([0.3, 0.45, 0.25])

&lt;span style="color: #BA36A5;"&gt;state&lt;/span&gt; = {}

&lt;span style="color: #BA36A5;"&gt;P&lt;/span&gt; = 80


&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;objective&lt;/span&gt;(T, state):
&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: #006FE0;"&gt;float&lt;/span&gt;(T)
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;Pvap&lt;/span&gt; = np.array([antoine(T, *pars) &lt;span style="color: #0000FF;"&gt;for&lt;/span&gt; pars &lt;span style="color: #0000FF;"&gt;in&lt;/span&gt; [acetone, acetonitrile, nitromethane]])
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;y&lt;/span&gt; = x * Pvap / P
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   state.update({&lt;span style="color: #008000;"&gt;'y'&lt;/span&gt;: y,
&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: #008000;"&gt;'T'&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: #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: #008000;"&gt;'Pvap'&lt;/span&gt;: Pvap,
&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: #008000;"&gt;'z'&lt;/span&gt;: 1 - y.&lt;span style="color: #006FE0;"&gt;sum&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; state[&lt;span style="color: #008000;"&gt;'z'&lt;/span&gt;]

Tans, = fsolve(objective, 70, args=(state,))

&lt;span style="color: #0000FF;"&gt;print&lt;/span&gt;(f&lt;span style="color: #008000;"&gt;'The bubble point temperature is {Tans:1.2f} degC, and the gas phase compositions are {np.round(state["y"], 4)}.'&lt;/span&gt;)
&lt;span style="color: #0000FF;"&gt;print&lt;/span&gt;(Tans- state[&lt;span style="color: #008000;"&gt;'T'&lt;/span&gt;]) &lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;check to make sure last value from objective is the same as the solution&lt;/span&gt;
state
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
The bubble point temperature is 68.60 degC, and the gas phase compositions are [ 0.5196  0.3773  0.1031].
0.0


&lt;/pre&gt;

&lt;pre class="example"&gt;
{'Pvap': array([ 138.5620209 ,   67.07966082,   32.98218545]),
 'T': 68.60064626680659,
 'y': array([ 0.51960758,  0.37732309,  0.10306933]),
 'z': -3.4194869158454821e-14}

&lt;/pre&gt;

&lt;p&gt;
What we see in the &lt;code&gt;state&lt;/code&gt; dictionary is the result from the last time that the objective function was called. It appears that the list time it was called is also where the solution comes from, so the other variables stored here should be consistent. Now you can see we have access to both the Pvap and y composition data from the solution without needing any further computations. This approach could be easily extended to store any derived quantities that represent internal states you want. We don't store any history in this, but you could by appending to lists in the dictionary.
&lt;/p&gt;

&lt;p&gt;
One &lt;i&gt;feature&lt;/i&gt; of this is the state dictionary is updated by side effect, and you have to use the state dictionary as an argument parameter to the function.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;


&lt;div id="outline-container-orgc590864" class="outline-2"&gt;
&lt;h2 id="orgc590864"&gt;&lt;span class="section-number-2"&gt;3&lt;/span&gt; third approach - a callable object&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-3"&gt;
&lt;p&gt;
A standard approach to tracking state data is to encapsulate it in a class. fsolve requires a callable function that returns zero at the solution. It is possible to make an object &lt;i&gt;act like a callable function&lt;/i&gt; if we define a &lt;code&gt;__call__&lt;/code&gt; method on it. Then, in this method, we can set attributes on the object to keep track of the state, similar to what we did with the dictionary. Since we have a class, we can define some other special dunder methods, e.g. to print the solution. Here is one implementation.
&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; fsolve

&lt;span style="color: #0000FF;"&gt;class&lt;/span&gt; &lt;span style="color: #6434A3;"&gt;Objective&lt;/span&gt;(&lt;span style="color: #006FE0;"&gt;object&lt;/span&gt;):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;acetone&lt;/span&gt; = (14.5463, 2940.46, 237.22)
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;acetonitrile&lt;/span&gt; = (14.2724, 2945.47,224)
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;nitromethane&lt;/span&gt; = (14.2043, 2972.64, 209)

&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;__init__&lt;/span&gt;(&lt;span style="color: #0000FF;"&gt;self&lt;/span&gt;, x, P):
&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;self&lt;/span&gt;.x = np.array(x)
&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;self&lt;/span&gt;.P = P

&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;antoine&lt;/span&gt;(&lt;span style="color: #0000FF;"&gt;self&lt;/span&gt;, T, A, B, C):
&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; np.exp(A - B / (T + C))

&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;__str__&lt;/span&gt;(&lt;span style="color: #0000FF;"&gt;self&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: #BA36A5;"&gt;s&lt;/span&gt; = f&lt;span style="color: #008000;"&gt;'The bubble point temperature is {self.T:1.2f} degC, and the gas phase compositions are {np.round(self.y, 4)}.'&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;return&lt;/span&gt; s

&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;__call__&lt;/span&gt;(&lt;span style="color: #0000FF;"&gt;self&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: #BA36A5;"&gt;T&lt;/span&gt; = &lt;span style="color: #006FE0;"&gt;float&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;self&lt;/span&gt;.T = 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;self&lt;/span&gt;.Pvap = np.array([&lt;span style="color: #0000FF;"&gt;self&lt;/span&gt;.antoine(T, *pars) &lt;span style="color: #0000FF;"&gt;for&lt;/span&gt; pars &lt;span style="color: #0000FF;"&gt;in&lt;/span&gt; [&lt;span style="color: #0000FF;"&gt;self&lt;/span&gt;.acetone, &lt;span style="color: #0000FF;"&gt;self&lt;/span&gt;.acetonitrile, &lt;span style="color: #0000FF;"&gt;self&lt;/span&gt;.nitromethane]])
&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;self&lt;/span&gt;.y = &lt;span style="color: #0000FF;"&gt;self&lt;/span&gt;.x * &lt;span style="color: #0000FF;"&gt;self&lt;/span&gt;.Pvap / &lt;span style="color: #0000FF;"&gt;self&lt;/span&gt;.P
&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; 1 - &lt;span style="color: #0000FF;"&gt;self&lt;/span&gt;.y.&lt;span style="color: #006FE0;"&gt;sum&lt;/span&gt;()

&lt;span style="color: #BA36A5;"&gt;obj&lt;/span&gt; = Objective(x=np.array([0.3, 0.45, 0.25]), P=80)
ans, = fsolve(obj, 60)

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

&lt;pre class="example"&gt;
The bubble point temperature is 68.60 degC, and the gas phase compositions are [ 0.5196  0.3773  0.1031].


&lt;/pre&gt;


&lt;p&gt;
Similar to the state dictionary approach, there is no repeated code here, and no repeated evaluations to get to the state after the solution. This is a bit more advanced Python than the state dictionary. Note, this implementation doesn't have any checking in it, so if you try to print the object before calling fsolve, you will get an error because the attributes don't exist until &lt;i&gt;after&lt;/i&gt; the object has been called. That is also an issue with the state dictionary above.
&lt;/p&gt;

&lt;p&gt;
There are many choices you could make in constructing this example. Maybe this one has gone too far in encapsulating the antoine function as a method. That limits its reusability for another problem. On the other hand, you can reuse it for any other pressure or liquid composition of acetone, acetonitrile and nitromethane very readily.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;div id="outline-container-orgaef298d" class="outline-2"&gt;
&lt;h2 id="orgaef298d"&gt;&lt;span class="section-number-2"&gt;4&lt;/span&gt; Summary&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-4"&gt;
&lt;p&gt;
We looked at three ways to reduce having redundant code in the solution to problems involving nonlinear algebra. The first approach is conceptually simple; you break out as much as you can into reusable functions, and then at most have repeated function calls. These computations are usually not expensive, so repeating them is mostly tedious and provides opportunities for mistakes. This is probably what I will stick to for teaching students that are just seeing this for the first time.
&lt;/p&gt;

&lt;p&gt;
The second approach used a dictionary (other data structures could work too) as an argument to the objective function, and internal states were kept in the dictionary so that after the problem was solved, you have immediate access to them. This is more advanced than the first approach because it requires understanding that the dictionary is modified as a side effect of solving the problem.
&lt;/p&gt;

&lt;p&gt;
Finally,  we considered an object-oriented class encapsulation of the information we wanted. I consider this the most advanced Python solution, since it requires some understanding of classes, dunder methods and attributes, and how to make an instance of a class.
&lt;/p&gt;

&lt;p&gt;
The last two methods seem like candidates for an advanced class in problem solving. Thoughts?
&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/09/24/Solving-nonlinear-algebra-problems-with-internal-state-information.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>Literate programming with python doctests</title>
      <link>https://kitchingroup.cheme.cmu.edu/blog/2018/05/17/Literate-programming-with-python-doctests</link>
      <pubDate>Thu, 17 May 2018 16:41:19 EDT</pubDate>
      <category><![CDATA[orgmode]]></category>
      <category><![CDATA[noweb]]></category>
      <category><![CDATA[python]]></category>
      <guid isPermaLink="false">LOCS3-CpseSjnbubGeO3Zmhf9Rc=</guid>
      <description>Literate programming with python doctests</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="#org91f890f"&gt;1. Add a way to run the tests&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#org4759a84"&gt;2. Tangle the file&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#org8727607"&gt;3. Run the tests&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#org5a82a2f"&gt;4. The noweb doctest block&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
On the org-mode mailing list we had a nice discussion about using noweb and org-mode in literate programming. The results of that discussion were blogged about &lt;a href="http://kdr2.com/tech/emacs/1805-approach-org-ref-code-to-text.html"&gt;here&lt;/a&gt;. I thought of a different application of this for making &lt;a href="https://pymotw.com/3/doctest/"&gt;doctests&lt;/a&gt; in Python functions. I have to confess I have never liked these because I have always thought they were a pain to write since you basically have to put code and results into a docstring. The ideas developed in the discussion above led me to think of a new way to write these that seems totally reasonable.
&lt;/p&gt;

&lt;p&gt;
The idea is just to put noweb placeholders in the function docstring for the doctests. The placeholders will be expanded when you tangle the file, and they will get their contents from other src-blocks where you have written and run examples to test them.
&lt;/p&gt;

&lt;p&gt;
This video might make the rest of this post easier to follow:
&lt;/p&gt;
&lt;iframe width="560" height="315" src="https://www.youtube.com/embed/u8CWbedVV9Y" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen&gt;&lt;/iframe&gt;

&lt;p&gt;
I will illustrate the idea using org-mode and the ob-ipython I have in scimax. The defaults of my ob-ipython setup are not useful for this example because it puts the execution count and mime types of output in the output. These are not observed in a REPL, and so we turn this off by setting these variables.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-emacs-lisp"&gt;(&lt;span style="color: #0000FF;"&gt;setq&lt;/span&gt; ob-ipython-suppress-execution-count t
      ob-ipython-show-mime-types nil)
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
Now, we make an example function that takes a single argument and returns one divided by that argument. This block is runnable, and the function is then defined in the jupyter kernel. The docstring contains several noweb references to doctest blocks we define later. For now, they don't do anything. See &lt;a href="#org5a82a2f"&gt;The noweb doctest block&lt;/a&gt; section for the block that is used to expand these. This block also has a tangle header which indicates the file to tangle the results to. When I run this block, it is sent to a Jupyter kernel and saved in memory for use in subsequent blocks.
&lt;/p&gt;

&lt;p&gt;
Here is the block with no noweb expansion. Note that this is easier to read in the original org source than it is to read in the published blog format.
&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;func&lt;/span&gt;(a):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #036A07;"&gt;"""A function to divide one by a.&lt;/span&gt;

&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;&lt;span style="color: #036A07;"&gt;   &amp;lt;&amp;lt;doctest("doctest-1")&amp;gt;&amp;gt;&lt;/span&gt;

&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;&lt;span style="color: #036A07;"&gt;   &amp;lt;&amp;lt;doctest("doctest-2")&amp;gt;&amp;gt;&lt;/span&gt;

&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;&lt;span style="color: #036A07;"&gt;   &amp;lt;&amp;lt;doctest("doctest-3")&amp;gt;&amp;gt;&lt;/span&gt;

&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;&lt;span style="color: #036A07;"&gt;   Returns: 1 / a.&lt;/span&gt;
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;&lt;span style="color: #036A07;"&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; 1 / a

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

&lt;p&gt;
Now, we can write a series of named blocks that define various tests we might want to use as doctests. You can run these blocks here, and verify they are correct. Later, when we tangle the document, these will be incorporated into the tangled file in the docstring we defined above.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython" id="org4eaeeaf"&gt;func(5) == 0.2
&lt;/pre&gt;
&lt;/div&gt;

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

&lt;/pre&gt;



&lt;p&gt;
This next test will raise an Exception, and we just run it to make sure it does.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython" id="org560fcaf"&gt;func(0)
&lt;/pre&gt;
&lt;/div&gt;

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

ZeroDivisionErrorTraceback (most recent call last)
&amp;lt;ipython-input-6-ba0cd5a88f0a&amp;gt; in &amp;lt;module&amp;gt;()
----&amp;gt; 1 func(0)

&amp;lt;ipython-input-1-eafd354a3163&amp;gt; in func(a)
     18     Returns: 1 / a.
     19     """
---&amp;gt; 20     return 1 / a

ZeroDivisionError: division by zero

&lt;/pre&gt;



&lt;p&gt;
This is just a doctest with indentation to show how it is used.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython" id="org968debf"&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;(1, 4):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;print&lt;/span&gt;(func(i))
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
1.0
0.5
0.3333333333333333


&lt;/pre&gt;



&lt;p&gt;
That concludes the examples I want incorporated into the doctests. Each one of these blocks has a name, which is used as an argument to the noweb references in the function docstring.
&lt;/p&gt;

&lt;div id="outline-container-org91f890f" class="outline-2"&gt;
&lt;h2 id="org91f890f"&gt;&lt;span class="section-number-2"&gt;1&lt;/span&gt; Add a way to run the tests&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-1"&gt;
&lt;p&gt;
This is a common idiom to enable easy running of the doctests. This will get tangled out to the file.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython"&gt;&lt;span style="color: #0000FF;"&gt;if&lt;/span&gt; &lt;span style="color: #006FE0;"&gt;__name__&lt;/span&gt; == &lt;span style="color: #008000;"&gt;"__main__"&lt;/span&gt;:
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;import&lt;/span&gt; doctest
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   doctest.testmod()
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;


&lt;div id="outline-container-org4759a84" class="outline-2"&gt;
&lt;h2 id="org4759a84"&gt;&lt;span class="section-number-2"&gt;2&lt;/span&gt; Tangle the file&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-2"&gt;
&lt;p&gt;
So far, the Python code we have written only exists in the org-file, and in memory. Tangling is the extraction of the code into a code file.
&lt;/p&gt;

&lt;p&gt;
We run this command, which extracts the code blocks marked for tangling, and expands the noweb references in them.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-emacs-lisp"&gt;(org-babel-tangle)
&lt;/pre&gt;
&lt;/div&gt;

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


&lt;colgroup&gt;
&lt;col  class="org-left" /&gt;
&lt;/colgroup&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class="org-left"&gt;test.py&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;
Here is what we get:
&lt;/p&gt;

&lt;pre class="example"&gt;
def func(a):
    """A function to divide one by a.

    &amp;gt;&amp;gt;&amp;gt; func(5) == 0.2
    True

    &amp;gt;&amp;gt;&amp;gt; func(0)
    Traceback (most recent call last):
    ZeroDivisionError: division by zero

    &amp;gt;&amp;gt;&amp;gt; for i in range(1, 4):
    ...     print(func(i))
    1.0
    0.5
    0.3333333333333333


    Returns: 1 / a.
    """
    return 1 / a

if __name__ == "__main__":
    import doctest
    doctest.testmod()

&lt;/pre&gt;

&lt;p&gt;
That looks like a reasonable python file. You can see the doctest blocks have been inserted into the docstring, as desired. The proof of course is that we can run these doctests, and use the python module. We show that next.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;


&lt;div id="outline-container-org8727607" class="outline-2"&gt;
&lt;h2 id="org8727607"&gt;&lt;span class="section-number-2"&gt;3&lt;/span&gt; Run the tests&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-3"&gt;
&lt;p&gt;
Now, we can check if the tests pass in a fresh run (i.e. not using the version stored in the jupyter kernel.) The standard way to run the doctests is like this:
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-sh"&gt;python test.py -v
&lt;/pre&gt;
&lt;/div&gt;




&lt;p&gt;
Well, that's it! It worked fine. Now we have a python file we can import and reuse, with some doctests that show how it works. For example, here it is in a small Python script.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-python"&gt;&lt;span style="color: #0000FF;"&gt;from&lt;/span&gt; test &lt;span style="color: #0000FF;"&gt;import&lt;/span&gt; func
&lt;span style="color: #0000FF;"&gt;print&lt;/span&gt;(func(3))
&lt;/pre&gt;
&lt;/div&gt;

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

&lt;/pre&gt;



&lt;p&gt;
There are surely some caveats to keep in mind here. This was just a simple proof of concept idea that isn't tested beyond this example. I don't know how many complexities would arise from more complex doctests. But, it seems like a good idea to continue pursuing if you like using doctests, and like using org-mode and interactive/literate programming techniques.
&lt;/p&gt;

&lt;p&gt;
It is definitely an interesting way to use noweb to build up better code files in my opinion.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;div id="outline-container-org5a82a2f" class="outline-2"&gt;
&lt;h2 id="org5a82a2f"&gt;&lt;a id="ID-D4437A03-A9D0-4B6D-B254-5F03CFB25F95"&gt;&lt;/a&gt;&lt;span class="section-number-2"&gt;4&lt;/span&gt; The noweb doctest block&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-4"&gt;
&lt;p&gt;
These blocks are used in the noweb expansions. Each block takes a variable which is the name of a block. This block grabs the body of the named src block and formats it as if it was in a REPL.
&lt;/p&gt;

&lt;p&gt;
We also grab the results of the named block and format it for the doctest. We use a heuristic to detect Tracebacks and modify the output to be consistent with it. In that case we assume the relevant Traceback is on the last line.
&lt;/p&gt;

&lt;p&gt;
Admittedly, this does some fragile feeling things, like trimming whitespace here and there to remove blank lines, and quoting quotes (which was not actually used in this example), and removing the ": " pieces of ob-ipython results. Probably other ways of running the src-blocks would not be that suitable for this.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-emacs-lisp" id="org52486c2"&gt;(org-babel-goto-named-src-block name)
(&lt;span style="color: #0000FF;"&gt;let*&lt;/span&gt; ((src (s-trim-right (org-element-property &lt;span style="color: #006FE0;"&gt;:value&lt;/span&gt; (org-element-context))))
       (src-lines (split-string src &lt;span style="color: #008000;"&gt;"\n"&lt;/span&gt;))
       body result)
  (&lt;span style="color: #0000FF;"&gt;setq&lt;/span&gt; body
        (s-trim-right
         (s-concat &lt;span style="color: #008000;"&gt;"&amp;gt;&amp;gt;&amp;gt; "&lt;/span&gt; (car src-lines) &lt;span style="color: #008000;"&gt;"\n"&lt;/span&gt;
                   (s-join &lt;span style="color: #008000;"&gt;"\n"&lt;/span&gt; (mapcar (&lt;span style="color: #0000FF;"&gt;lambda&lt;/span&gt; (s)
                                          (concat &lt;span style="color: #008000;"&gt;"... "&lt;/span&gt; s))
                                        (cdr src-lines))))))
  &lt;span style="color: #8D8D84;"&gt;;; &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;now the results&lt;/span&gt;
  (org-babel-goto-named-result name)
  (&lt;span style="color: #0000FF;"&gt;let&lt;/span&gt; ((result (org-element-context)))
    (&lt;span style="color: #0000FF;"&gt;setq&lt;/span&gt; result
          (&lt;span style="color: #0000FF;"&gt;thread-last&lt;/span&gt;
              (buffer-substring (org-element-property &lt;span style="color: #006FE0;"&gt;:contents-begin&lt;/span&gt; result)
                                (org-element-property &lt;span style="color: #006FE0;"&gt;:contents-end&lt;/span&gt; result))
            (s-trim)
            &lt;span style="color: #8D8D84;"&gt;;; &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;remove ": " from beginning of lines&lt;/span&gt;
            (replace-regexp-in-string &lt;span style="color: #008000;"&gt;"^: *"&lt;/span&gt; &lt;span style="color: #008000;"&gt;""&lt;/span&gt;)
            &lt;span style="color: #8D8D84;"&gt;;; &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;quote quotes&lt;/span&gt;
            (replace-regexp-in-string &lt;span style="color: #008000;"&gt;"\\\""&lt;/span&gt; &lt;span style="color: #008000;"&gt;"\\\\\""&lt;/span&gt;)))
    (&lt;span style="color: #0000FF;"&gt;when&lt;/span&gt; (string-match &lt;span style="color: #008000;"&gt;"Traceback"&lt;/span&gt; result)
      (&lt;span style="color: #0000FF;"&gt;setq&lt;/span&gt; result (format
                    &lt;span style="color: #008000;"&gt;"Traceback (most recent call last):\n%s"&lt;/span&gt;
                    (car (last (split-string result &lt;span style="color: #008000;"&gt;"\n"&lt;/span&gt;))))))
    (concat body &lt;span style="color: #008000;"&gt;"\n"&lt;/span&gt; result)))
&lt;/pre&gt;
&lt;/div&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/05/17/Literate-programming-with-python-doctests.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>More auto-differentiation goodness for science and engineering</title>
      <link>https://kitchingroup.cheme.cmu.edu/blog/2017/11/22/More-auto-differentiation-goodness-for-science-and-engineering</link>
      <pubDate>Wed, 22 Nov 2017 15:52:26 EST</pubDate>
      <category><![CDATA[autograd]]></category>
      <category><![CDATA[python]]></category>
      <guid isPermaLink="false">40tEiid--9b6BhN59nVkbejz494=</guid>
      <description>More auto-differentiation goodness for science and engineering</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="#org8469481"&gt;1. Showing mixed partial derivatives are equal&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#org04c0323"&gt;2. Root finding with jacobians&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#org579e18e"&gt;3. Getting the pressure from a solid equation of state&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#org43b361c"&gt;4. Deriving activity coefficients and demonstration of the Gibbs-Duhem relation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#org1626a54"&gt;5. Summary&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
In this post I continue my investigations in the use of auto-differentiation via autograd in scientific and mathematical programming. The main focus of today is using autograd to get derivatives that either have mathematical value, eg. accelerating root finding, or demonstrating mathematical rules, or scientific value, e.g. the derivative is related to a property, or illustrates some constraint.
&lt;/p&gt;

&lt;p&gt;
All the code in this post relies on these imports:
&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, jacobian
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;

&lt;/p&gt;

&lt;p&gt;
In the following sections I explore some applications in calculus, root-finding, materials and thermodynamics.
&lt;/p&gt;

&lt;div id="outline-container-org8469481" class="outline-2"&gt;
&lt;h2 id="org8469481"&gt;&lt;span class="section-number-2"&gt;1&lt;/span&gt; Showing mixed partial derivatives are equal&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-1"&gt;
&lt;p&gt;
In calculus, we know that if we have a well-behaved function \(f(x, y)\), then it should be true that \(\frac{\partial^2f}{\partial x \partial y} = \frac{\partial^2f}{\partial y \partial y}\).
&lt;/p&gt;

&lt;p&gt;
Here we use autograd to compute the mixed partial derivatives and show for 10 random points that this statement is true. This doesnt' prove it for all points, of course, but it is easy to prove for any point of interest.
&lt;/p&gt;

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

&lt;span style="color: #BA36A5;"&gt;dfdx&lt;/span&gt; = grad(f)
&lt;span style="color: #BA36A5;"&gt;d2fdxdy&lt;/span&gt; = grad(dfdx, 1)

&lt;span style="color: #BA36A5;"&gt;dfdy&lt;/span&gt; = grad(f, 1)
&lt;span style="color: #BA36A5;"&gt;d2fdydx&lt;/span&gt; = grad(dfdy)

&lt;span style="color: #BA36A5;"&gt;x&lt;/span&gt; = np.random.rand(10)
&lt;span style="color: #BA36A5;"&gt;y&lt;/span&gt; = np.random.rand(10)

&lt;span style="color: #BA36A5;"&gt;T&lt;/span&gt; = [d2fdxdy(x1, y1) == d2fdydx(x1, y1) &lt;span style="color: #0000FF;"&gt;for&lt;/span&gt; x1, y1 &lt;span style="color: #0000FF;"&gt;in&lt;/span&gt; &lt;span style="color: #006FE0;"&gt;zip&lt;/span&gt;(x, y)]

&lt;span style="color: #0000FF;"&gt;print&lt;/span&gt;(np.&lt;span style="color: #006FE0;"&gt;all&lt;/span&gt;(T))
&lt;/pre&gt;
&lt;/div&gt;

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

&lt;div id="outline-container-org04c0323" class="outline-2"&gt;
&lt;h2 id="org04c0323"&gt;&lt;span class="section-number-2"&gt;2&lt;/span&gt; Root finding with jacobians&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-2"&gt;
&lt;p&gt;
fsolve often works fine without access to derivatives. In this example from &lt;a href="http://people.duke.edu/~ccc14/sta-663-2016/13_Optimization.html"&gt;here&lt;/a&gt;, we solve a set of equations with two variables, and it takes 21 iterations to reach the solution.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython" id="org37121e8"&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: #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: #0000FF;"&gt;return&lt;/span&gt; np.array([x[1] - 3*x[0]*(x[0]+1)*(x[0]-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;   &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;.25*x[0]**2 + x[1]**2 - 1])


&lt;span style="color: #BA36A5;"&gt;ans&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;info&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;flag&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;msg&lt;/span&gt; = fsolve(f, (0.5, 0.5), full_output=1)
&lt;span style="color: #0000FF;"&gt;print&lt;/span&gt;(ans)
&lt;span style="color: #0000FF;"&gt;print&lt;/span&gt;(info[&lt;span style="color: #008000;"&gt;'nfev'&lt;/span&gt;])
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
[ 1.117  0.83 ]
21
&lt;/p&gt;

&lt;p&gt;
If we add the jacobian, we get the same result with only 15 iterations, about 1/3 fewer iterations. If the iterations are expensive, this might save a lot of time. 
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython" id="orgaa52a5d"&gt;&lt;span style="color: #BA36A5;"&gt;df&lt;/span&gt; = jacobian(f)
&lt;span style="color: #BA36A5;"&gt;x0&lt;/span&gt; = np.array([0.5, 0.5])

&lt;span style="color: #BA36A5;"&gt;ans&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;info&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;flag&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;msg&lt;/span&gt;  = fsolve(f, x0, fprime=df, full_output=1)
&lt;span style="color: #0000FF;"&gt;print&lt;/span&gt;(ans)
&lt;span style="color: #0000FF;"&gt;print&lt;/span&gt;(info[&lt;span style="color: #008000;"&gt;'nfev'&lt;/span&gt;])
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
[ 1.117  0.83 ]
15
&lt;/p&gt;

&lt;p&gt;
There is a similar &lt;a href="https://github.com/HIPS/autograd/blob/master/examples/rosenbrock.py"&gt;example&lt;/a&gt; provided by autograd.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;div id="outline-container-org579e18e" class="outline-2"&gt;
&lt;h2 id="org579e18e"&gt;&lt;span class="section-number-2"&gt;3&lt;/span&gt; Getting the pressure from a solid equation of state&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-3"&gt;
&lt;p&gt;
In this &lt;a href="http://kitchingroup.cheme.cmu.edu/blog/2013/02/18/Nonlinear-curve-fitting/"&gt;post&lt;/a&gt; we described how to fit a solid equation of state to describe the energy of a solid under isotropic strain. Now, we can readily compute the pressure at a particular volume from the equation:
&lt;/p&gt;

&lt;p&gt;
\(P = -\frac{dE}{dV}\)
&lt;/p&gt;

&lt;p&gt;
We just need the derivative of this equation:
&lt;/p&gt;

&lt;p&gt;
\(E = E_0+\frac{B_0 V}{B'_0}\left[\frac{(V_0/V)^{B'_0}}{B'_0-1}+1\right]-\frac{V_0 B_0}{B'_0-1}\)
&lt;/p&gt;

&lt;p&gt;
or we use autograd to get it for us.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython" id="org3308535"&gt;&lt;span style="color: #BA36A5;"&gt;E0&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;B0&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;BP&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;V0&lt;/span&gt; = -56.466,   0.49,    4.753,  16.573

&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;Murnaghan&lt;/span&gt;(vol):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;E&lt;/span&gt; = E0 + B0 * vol / BP * (((V0 / vol)**BP) / (BP - 1.0) + 1.0) - V0 * B0 / (BP - 1.)
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; E

&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;P&lt;/span&gt;(vol):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;dEdV&lt;/span&gt; = grad(Murnaghan)
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; -dEdV(vol) * 160.21773  &lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;in Gpa&lt;/span&gt;

&lt;span style="color: #0000FF;"&gt;print&lt;/span&gt;(P(V0)) &lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;Pressure at the minimum&lt;/span&gt;
&lt;span style="color: #0000FF;"&gt;print&lt;/span&gt;(P(0.99 * V0))  &lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;Compressed&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;

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

&lt;/pre&gt;

&lt;p&gt;
So it takes positive pressure to compress the system, as expected, and at the minimum the pressure is equal to zero. Seems pretty clear autograd is better than deriving the required pressure derivative.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;div id="outline-container-org43b361c" class="outline-2"&gt;
&lt;h2 id="org43b361c"&gt;&lt;span class="section-number-2"&gt;4&lt;/span&gt; Deriving activity coefficients and demonstration of the Gibbs-Duhem relation&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-4"&gt;
&lt;p&gt;
Thermodynamics tells us that in a binary mixture the following is true:
&lt;/p&gt;

&lt;p&gt;
\(0 = x_1 \frac{d \ln \gamma_1}{dx_1} + (1 - x_1) \frac{d \ln \gamma_2}{dx_1}\)
&lt;/p&gt;

&lt;p&gt;
In other words, the activity coefficients of the two species can't be independent. 
&lt;/p&gt;

&lt;p&gt;
Suppose we have the &lt;a href="https://en.wikipedia.org/wiki/Margules_activity_model"&gt;Margules model&lt;/a&gt; for the excess free energy:
&lt;/p&gt;

&lt;p&gt;
\(G^{ex}/RT = n x_1 (1 - x_1) (A_{21} x_1 + A_{12} (1 - x_1))\)
&lt;/p&gt;

&lt;p&gt;
where \(n = n_1 + n_2\), and \(x_1 = n1 / n\), and \(x_2 = n_2 / n\). 
&lt;/p&gt;

&lt;p&gt;
From this expression, we know:
&lt;/p&gt;

&lt;p&gt;
\(\ln \gamma_1 = \frac{\partial G_{ex}/RT}{\partial n_1}\)
&lt;/p&gt;

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

&lt;p&gt;
\(\ln \gamma_2 = \frac{\partial G_{ex}/RT}{\partial n_2}\)
&lt;/p&gt;

&lt;p&gt;
It is also true that (the Gibbs-Duhem equation):
&lt;/p&gt;

&lt;p&gt;
\(0 = x_1 \frac{d \ln \gamma_1}{d n_1} + x_2 \frac{d \ln \gamma_2}{d n_1}\)
&lt;/p&gt;

&lt;p&gt;
Here we will use autograd to get these derivatives, and demonstrate the Gibbs-Duhem eqn holds for this excess Gibbs energy model.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython" id="orgd986da2"&gt;&lt;span style="color: #BA36A5;"&gt;A12&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;A21&lt;/span&gt; = 2.04, 1.5461  &lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;Acetone/water https://en.wikipedia.org/wiki/Margules_activity_model&lt;/span&gt;

&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;GexRT&lt;/span&gt;(n1, n2):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;n&lt;/span&gt; = n1 + n2
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;x1&lt;/span&gt; = n1 / n
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;x2&lt;/span&gt; = n2 / n
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; n * x1 * x2 * (A21 * x1 + A12 * x2)

&lt;span style="color: #BA36A5;"&gt;lngamma1&lt;/span&gt; = grad(GexRT)     &lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;dGex/dn1&lt;/span&gt;
&lt;span style="color: #BA36A5;"&gt;lngamma2&lt;/span&gt; = grad(GexRT, 1)  &lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;dGex/dn2&lt;/span&gt;

&lt;span style="color: #BA36A5;"&gt;n1&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;n2&lt;/span&gt; = 1.0, 2.0
&lt;span style="color: #BA36A5;"&gt;n&lt;/span&gt; = n1 + n2
&lt;span style="color: #BA36A5;"&gt;x1&lt;/span&gt; = n1 / n
&lt;span style="color: #BA36A5;"&gt;x2&lt;/span&gt; = n2 / n

&lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;Evaluate the activity coefficients&lt;/span&gt;
&lt;span style="color: #0000FF;"&gt;print&lt;/span&gt;(&lt;span style="color: #008000;"&gt;'AD:         '&lt;/span&gt;,lngamma1(n1, n2), lngamma2(n1, n2))

&lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;Compare that to these analytically derived activity coefficients&lt;/span&gt;
&lt;span style="color: #0000FF;"&gt;print&lt;/span&gt;(&lt;span style="color: #008000;"&gt;'Analytical: '&lt;/span&gt;, (A12 + 2 * (A21 - A12) * x1) * x2**2, (A21 + 2 * (A12 - A21) * x2) * x1**2)

&lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;Demonstration of the Gibbs-Duhem rule&lt;/span&gt;
&lt;span style="color: #BA36A5;"&gt;dg1&lt;/span&gt; = grad(lngamma1)
&lt;span style="color: #BA36A5;"&gt;dg2&lt;/span&gt; = grad(lngamma2)

&lt;span style="color: #BA36A5;"&gt;n&lt;/span&gt; = 1.0 &lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;Choose a basis number of moles&lt;/span&gt;
&lt;span style="color: #BA36A5;"&gt;x1&lt;/span&gt; = np.linspace(0, 1)
&lt;span style="color: #BA36A5;"&gt;x2&lt;/span&gt; = 1 - x1
&lt;span style="color: #BA36A5;"&gt;n1&lt;/span&gt; = n * x1
&lt;span style="color: #BA36A5;"&gt;n2&lt;/span&gt; = n - n1

&lt;span style="color: #BA36A5;"&gt;GD&lt;/span&gt; = [_x1 * dg1(_n1, _n2) + _x2 * dg2(_n1, _n2)
&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; _x1, _x2, _n1, _n2 &lt;span style="color: #0000FF;"&gt;in&lt;/span&gt; &lt;span style="color: #006FE0;"&gt;zip&lt;/span&gt;(x1, x2, n1, n2)]

&lt;span style="color: #0000FF;"&gt;print&lt;/span&gt;(np.allclose(GD, np.zeros(&lt;span style="color: #006FE0;"&gt;len&lt;/span&gt;(GD))))
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
('AD:         ', 0.76032592592592585, 0.24495925925925932)
('Analytical: ', 0.760325925925926, 0.24495925925925924)
True

&lt;/pre&gt;

&lt;p&gt;
That is pretty compelling. The autograd derivatives are much easier to code than the analytical solutions (which also had to be derived). You can also see that the Gibbs-Duhem equation is satisfied for this model, at least with a reasonable tolerance for the points we evaluated it at. 
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;div id="outline-container-org1626a54" class="outline-2"&gt;
&lt;h2 id="org1626a54"&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;
Today we examined four ways to use autograd in scientific or mathematical programs to replace the need to derive derivatives by hand. The main requirements for this to work are that you use the autograd.numpy module, and only the functions in it that are supported. It is possible to add your own functions (described in the &lt;a href="https://github.com/HIPS/autograd/blob/master/docs/tutorial.md#extend-autograd-by-defining-your-own-primitives"&gt;tutorial&lt;/a&gt;) if needed. It seems like there are a lot of opportunities for scientific programming for autograd.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Copyright (C) 2017 by John Kitchin. See the &lt;a href="/copying.html"&gt;License&lt;/a&gt; for information about copying.&lt;p&gt;
&lt;p&gt;&lt;a href="/org/2017/11/22/More-auto-differentiation-goodness-for-science-and-engineering.org"&gt;org-mode source&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Org-mode version = 9.1.2&lt;/p&gt;]]></content:encoded>
    </item>
    <item>
      <title>Timing Lennard-Jones implementations - ASE vs autograd</title>
      <link>https://kitchingroup.cheme.cmu.edu/blog/2017/11/20/Timing-Lennard-Jones-implementations-ASE-vs-autograd</link>
      <pubDate>Mon, 20 Nov 2017 21:19:17 EST</pubDate>
      <category><![CDATA[lennardjones]]></category>
      <category><![CDATA[autograd]]></category>
      <category><![CDATA[python]]></category>
      <guid isPermaLink="false">nSG47QhTDNSZivajfbKAXC-gvIU=</guid>
      <description>Timing Lennard-Jones implementations - ASE vs 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="#orgb9c2faf"&gt;1. Timing on the forces&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
In a comment on this &lt;a href="http://kitchingroup.cheme.cmu.edu/blog/2017/11/14/Forces-by-automatic-differentiation-in-molecular-simulation/"&gt;post&lt;/a&gt; Konrad Hinsen asked if the autograd forces on a Lennard-Jones potential would be useable in production. I wasn't sure, and was suspicious that analytical force functions would be faster. It turns out to not be so simple. In this post, I attempt to do some timing experiments for comparison. These are tricky to do right, and in a meaningful way, so I will start by explaining what is tricky and why I think the results are meaningful. 
&lt;/p&gt;

&lt;p&gt;
The ASE calculators cache their results, and return the cached results after the first run. We will do these on a 13-atom icosahedron cluster.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython" id="orgb9b1952"&gt;&lt;span style="color: #0000FF;"&gt;from&lt;/span&gt; ase.calculators.lj &lt;span style="color: #0000FF;"&gt;import&lt;/span&gt; LennardJones
&lt;span style="color: #0000FF;"&gt;from&lt;/span&gt; ase.cluster.icosahedron &lt;span style="color: #0000FF;"&gt;import&lt;/span&gt; Icosahedron

&lt;span style="color: #BA36A5;"&gt;atoms&lt;/span&gt; = Icosahedron(&lt;span style="color: #008000;"&gt;'Ar'&lt;/span&gt;, noshells=2, latticeconstant=3)
atoms.set_calculator(LennardJones())

&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: #0000FF;"&gt;print&lt;/span&gt;(&lt;span style="color: #008000;"&gt;'energy: '&lt;/span&gt;, atoms.get_potential_energy())
&lt;span style="color: #0000FF;"&gt;print&lt;/span&gt;(&lt;span style="color: #008000;"&gt;' time: '&lt;/span&gt;, time.time() - t0)
&lt;span style="color: #0000FF;"&gt;print&lt;/span&gt;()

&lt;span style="color: #BA36A5;"&gt;t0&lt;/span&gt; = time.time()
&lt;span style="color: #0000FF;"&gt;print&lt;/span&gt;(&lt;span style="color: #008000;"&gt;'energy: '&lt;/span&gt;, atoms.get_potential_energy())
&lt;span style="color: #0000FF;"&gt;print&lt;/span&gt;(&lt;span style="color: #008000;"&gt;' time: '&lt;/span&gt;, time.time() - t0)
&lt;span style="color: #0000FF;"&gt;print&lt;/span&gt;()

&lt;span style="color: #BA36A5;"&gt;atoms.calc.results&lt;/span&gt; = {}
&lt;span style="color: #BA36A5;"&gt;t0&lt;/span&gt; = time.time()
&lt;span style="color: #0000FF;"&gt;print&lt;/span&gt;(&lt;span style="color: #008000;"&gt;'energy: '&lt;/span&gt;, atoms.get_potential_energy())
&lt;span style="color: #0000FF;"&gt;print&lt;/span&gt;(&lt;span style="color: #008000;"&gt;'time: '&lt;/span&gt;, time.time() - t0)
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
energy:  -1.25741774649
 time:  0.0028629302978515625

energy:  -1.25741774649
 time:  0.00078582763671875

energy:  -1.25741774649
time:  0.0031850337982177734

&lt;/pre&gt;

&lt;p&gt;
Note the approximate order of magnitude reduction in elapsed time for the second call. After that, we reset the calculator results, and the time goes back up. So, we have to incorporate that into our timing. Also, in the ASE calculator, the forces are simultaneously calculated. There isn't a way to separate the calls. I am going to use the timeit feature in Ipython for the timing. I don't have a lot of control over what else is running on my machine, so I have observed some variability in the timing results. Finally, I am running these on a MacBook Air.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython" id="org23d2e9b"&gt;%%timeit
atoms.get_potential_energy()
&lt;span style="color: #BA36A5;"&gt;atoms.calc.results&lt;/span&gt; = {} &lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;this resets it so it runs each time. Otherwise, we get cached results&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
1.46 ms ± 107 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
&lt;/p&gt;

&lt;p&gt;
That seems like a surprisingly long time. If you neglect the calculator reset, it looks about 10 times faster because the cache lookup is fast!
&lt;/p&gt;

&lt;p&gt;
Let's compare that to an implementation of the Lennard-Jones potential similar to the last time. This implementation differs from &lt;a href="http://kitchingroup.cheme.cmu.edu/blog/2017/11/14/Forces-by-automatic-differentiation-in-molecular-simulation/"&gt;the first one I blogged about&lt;/a&gt;. This one is fully vectorized. It still does not support periodic boundary conditions though. This version may be up to 10 times faster than the previous version. I haven't tested this very well, I only assured it gives the same energy as ASE for the example in this post.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython" id="org2d00a75"&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;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;energy&lt;/span&gt;(positions):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #036A07;"&gt;"Compute the energy of a Lennard-Jones system."&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: #BA36A5;"&gt;sigma&lt;/span&gt; = 1.0
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;epsilon&lt;/span&gt; = 1.0
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;rc&lt;/span&gt; = 3 * sigma

&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;e0&lt;/span&gt; = 4 * epsilon * ((sigma / rc)**12 - (sigma / rc)**6)
&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;natoms&lt;/span&gt; = &lt;span style="color: #006FE0;"&gt;len&lt;/span&gt;(positions)
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;These are the pairs of indices we need to compute distances for.&lt;/span&gt;
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;a&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;b&lt;/span&gt; = np.triu_indices(natoms, 1)

&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;d&lt;/span&gt; = positions[a] - positions[b]
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;r2&lt;/span&gt; = np.&lt;span style="color: #006FE0;"&gt;sum&lt;/span&gt;(d**2, axis=1)
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;c6&lt;/span&gt; = np.where(r2 &amp;lt;= rc**2, (sigma**2 / r2)**3, np.zeros_like(r2))
&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;energy&lt;/span&gt; = -e0 * (c6 != 0.0).&lt;span style="color: #006FE0;"&gt;sum&lt;/span&gt;()
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;c12&lt;/span&gt; = c6**2
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;energy&lt;/span&gt; += np.&lt;span style="color: #006FE0;"&gt;sum&lt;/span&gt;(4 * epsilon * (c12 - c6))
&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; energy

&lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;Just to check we get the same answer&lt;/span&gt;
&lt;span style="color: #0000FF;"&gt;print&lt;/span&gt;(energy(atoms.positions))
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
-1.25741774649
&lt;/p&gt;

&lt;p&gt;
The energy looks good. For timing, we store the positions in a variable, in case there is any lookup time, since this function only needs an array.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython" id="org7312f34"&gt;&lt;span style="color: #BA36A5;"&gt;pos&lt;/span&gt; = atoms.positions
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;

&lt;/p&gt;

&lt;p&gt;
There is no caching to worry about here, we can just get the timing.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython" id="org38e167b"&gt;%%timeit
energy(pos)
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
82.2 µs ± 2.85 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
&lt;/p&gt;

&lt;p&gt;
Wow, that is a lot faster than the ASE implementation. Score one for vectorization.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython" id="org9087076"&gt;&lt;span style="color: #0000FF;"&gt;print&lt;/span&gt;(&lt;span style="color: #008000;"&gt;'Vectorized is {0:1.1f} times faster than ASE at energy.'&lt;/span&gt;.&lt;span style="color: #006FE0;"&gt;format&lt;/span&gt;(1.46e-3 / 82.5e-6))
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
Vectorized is 17.7 times faster than ASE at energy.

&lt;/pre&gt;

&lt;p&gt;
Yep, a fully vectorized implementation is a lot faster than the ASE version which uses loops. So far the difference has nothing to do with autograd.
&lt;/p&gt;

&lt;div id="outline-container-orgb9c2faf" class="outline-2"&gt;
&lt;h2 id="orgb9c2faf"&gt;&lt;span class="section-number-2"&gt;1&lt;/span&gt; Timing on the forces&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-1"&gt;
&lt;p&gt;
The forces are where derivatives are important, and it is a reasonable question of whether hand-coded derivatives are faster or slower than autograd derivatives. We first look at the forces from ASE. The analytical forces take about the same time as the energy, which is not surprising. The same work is done for both of them.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython" id="org689833d"&gt;np.set_printoptions(precision=3, suppress=&lt;span style="color: #D0372D;"&gt;True&lt;/span&gt;)
&lt;span style="color: #0000FF;"&gt;print&lt;/span&gt;(atoms.get_forces())
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
[[-0.    -0.    -0.   ]
 [-0.296 -0.     0.183]
 [-0.296 -0.    -0.183]
 [ 0.296 -0.     0.183]
 [ 0.296 -0.    -0.183]
 [ 0.183 -0.296 -0.   ]
 [-0.183 -0.296  0.   ]
 [ 0.183  0.296 -0.   ]
 [-0.183  0.296  0.   ]
 [-0.     0.183 -0.296]
 [ 0.    -0.183 -0.296]
 [-0.     0.183  0.296]
 [ 0.    -0.183  0.296]]

&lt;/pre&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython" id="orgcaaa884"&gt;%%timeit
atoms.get_forces()
&lt;span style="color: #BA36A5;"&gt;atoms.calc.results&lt;/span&gt; = {}
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
1.22 ms ± 38.7 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

&lt;/pre&gt;

&lt;p&gt;
Here is our auto-differentiated force function.
&lt;/p&gt;

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

&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;forces&lt;/span&gt;(pos):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #BA36A5;"&gt;dEdR&lt;/span&gt; = elementwise_grad(energy)
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; -dEdR(pos)
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
Let's just check the forces for consistency.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython" id="org777df8d"&gt;&lt;span style="color: #0000FF;"&gt;print&lt;/span&gt;(forces(atoms.positions))

&lt;span style="color: #0000FF;"&gt;print&lt;/span&gt;(np.allclose(forces(atoms.positions), atoms.get_forces()))
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
[[-0.    -0.    -0.   ]
 [-0.296 -0.     0.183]
 [-0.296 -0.    -0.183]
 [ 0.296 -0.     0.183]
 [ 0.296 -0.    -0.183]
 [ 0.183 -0.296 -0.   ]
 [-0.183 -0.296  0.   ]
 [ 0.183  0.296 -0.   ]
 [-0.183  0.296  0.   ]
 [-0.     0.183 -0.296]
 [ 0.    -0.183 -0.296]
 [-0.     0.183  0.296]
 [ 0.    -0.183  0.296]]
True

&lt;/pre&gt;

&lt;p&gt;
Those all look the same, so now performance for that:
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython" id="org90b4b07"&gt;%%timeit 

forces(pos)
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
727 µs ± 47.1 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

&lt;/pre&gt;

&lt;p&gt;
This is faster than the ASE version. I suspect that it is largely because of the faster, vectorized algorithm overall. 
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-ipython" id="org7de1cf3"&gt;&lt;span style="color: #0000FF;"&gt;print&lt;/span&gt;(&lt;span style="color: #008000;"&gt;'autograd is {0:1.1f} times faster than ASE on forces.'&lt;/span&gt;.&lt;span style="color: #006FE0;"&gt;format&lt;/span&gt;(1.22e-3 / 727e-6))
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
autograd is 1.7 times faster than ASE on forces.

&lt;/pre&gt;

&lt;p&gt;
autograd forces are consistently 2-6 times faster than the ASE implementation. It could be possible to hand-code a faster function for the forces, if it was fully vectorized. I spent a while seeing what would be required for that, and it is not obvious how to do that. Any solution that uses loops will be slower I think.
&lt;/p&gt;

&lt;p&gt;
This doesn't directly answer the question of whether this can work in production. Everything is still written in Python here, which might limit the size and length of calculations you can practically do. With the right implementation though, it looks promising.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Copyright (C) 2017 by John Kitchin. See the &lt;a href="/copying.html"&gt;License&lt;/a&gt; for information about copying.&lt;p&gt;
&lt;p&gt;&lt;a href="/org/2017/11/20/Timing-Lennard-Jones-implementations---ASE-vs-autograd.org"&gt;org-mode source&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Org-mode version = 9.1.2&lt;/p&gt;]]></content:encoded>
    </item>
  </channel>
</rss>
