<?xml version="1.0" encoding="UTF-8"?>

<rss version="2.0"
     xmlns:content="http://purl.org/rss/1.0/modules/content/"
     xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
     xmlns:atom="http://www.w3.org/2005/Atom"
     xmlns:dc="http://purl.org/dc/elements/1.1/"
     xmlns:wfw="http://wellformedweb.org/CommentAPI/"
     >
  <channel>
    <atom:link href="http://kitchingroup.cheme.cmu.edu/blog/feed/index.xml" rel="self" type="application/rss+xml" />
    <title>The Kitchin Research Group</title>
    <link>https://kitchingroup.cheme.cmu.edu/blog</link>
    <description>Chemical Engineering at Carnegie Mellon University</description>
    <pubDate>Sat, 01 Nov 2025 13:47:46 GMT</pubDate>
    <generator>Blogofile</generator>
    <sy:updatePeriod>hourly</sy:updatePeriod>
    <sy:updateFrequency>1</sy:updateFrequency>
    
    <item>
      <title>Using Custodian to help converge an optimization problem</title>
      <link>https://kitchingroup.cheme.cmu.edu/blog/2023/09/19/Using-Custodian-to-help-converge-an-optimization-problem</link>
      <pubDate>Tue, 19 Sep 2023 15:34:21 EDT</pubDate>
      <category><![CDATA[optimization]]></category>
      <category><![CDATA[programming]]></category>
      <guid isPermaLink="false">Dx1efJ79cKOKFx-BnlZVFB1wGjA=</guid>
      <description>Using Custodian to help converge an optimization problem</description>
      <content:encoded><![CDATA[


&lt;p&gt;
In high-throughput calculations, some fraction of them usually fail for some reason. Sometimes it is easy to fix these calculations and re-run them successfully, for example, you might just need a different initialization, or to increase memory or the number of allowed steps, etc.  &lt;a href="http://materialsproject.github.io/custodian/"&gt;custodian&lt;/a&gt; is a tool that is designed for this purpose. 
&lt;/p&gt;

&lt;p&gt;
The idea is we make a function to do what we want that has arguments that control that. We need a function that can examine the output of the function and determine if it succeeded, and if it didn't succeed to say what new arguments to try next. Then we run the function in custodian and let it take care of rerunning with new arguments until it either succeeds, or tries too many times.
&lt;/p&gt;

&lt;p&gt;
The goal here is to use &lt;a href="http://materialsproject.github.io/custodian/"&gt;custodian&lt;/a&gt; to fix a problem optimization. The example is a little contrived, we set a number of iterations artificially low so that the minimization fails by reaching the maximum number of iterations. Custodian will catch this, and increase the number of iterations until it succeeds. Here is the objective function:
&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; matplotlib.pyplot &lt;span style="color: #0000FF;"&gt;as&lt;/span&gt; plt
&lt;span style="color: #0000FF;"&gt;import&lt;/span&gt; numpy &lt;span style="color: #0000FF;"&gt;as&lt;/span&gt; np

&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;objective&lt;/span&gt;(x):
    &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; np.exp(x**2) - 10*np.exp(x)

&lt;span style="color: #BA36A5;"&gt;x&lt;/span&gt; = np.linspace(0, 2)
plt.plot(x, objective(x))
plt.xlabel(&lt;span style="color: #008000;"&gt;'x'&lt;/span&gt;)
plt.ylabel(&lt;span style="color: #008000;"&gt;'y'&lt;/span&gt;);
&lt;/pre&gt;
&lt;/div&gt;

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

&lt;p&gt;
Clearly there is a minimum near 1.75, but with a bad initial guess, and not enough iterations, an optimizer fails here. We can tell it fails from the message here, and the solution is run it again with more iterations.
&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; scipy.optimize &lt;span style="color: #0000FF;"&gt;import&lt;/span&gt; minimize

minimize(objective, 0.0, options={&lt;span style="color: #008000;"&gt;'maxiter'&lt;/span&gt;: 2})
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example" id="org27fcdb3"&gt;
:RESULTS:
  message: Maximum number of iterations has been exceeded.
  success: False
   status: 1
      fun: -36.86289091418059
        x: [ 1.661e+00]
      nit: 2
      jac: [-2.374e-01]
 hess_inv: [[ 6.889e-03]]
     nfev: 20
     njev: 10
:END:
&lt;/pre&gt;

&lt;p&gt;
With Custodian you define a "Job". This is a class with  &lt;code&gt;params&lt;/code&gt; that contain the adjustable arguments in a dictionary, and a &lt;code&gt;run&lt;/code&gt; method that stores the results in the params attribute. This is an important step, because the error handlers only get the params, so you need the results in there to inspect them.
&lt;/p&gt;

&lt;p&gt;
The error handlers are another class with a &lt;code&gt;check&lt;/code&gt; method that returns True if you should rerun, and a &lt;code&gt;correct&lt;/code&gt; method that sets the params to new values to try next. It seems to return some information about what happened. In the &lt;code&gt;correct&lt;/code&gt; method, we double the maximum number of iterations allowed, and use the last solution point that failed as the initial guess for the next run.
&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; custodian.custodian &lt;span style="color: #0000FF;"&gt;import&lt;/span&gt; Custodian, Job, ErrorHandler

&lt;span style="color: #0000FF;"&gt;class&lt;/span&gt; &lt;span style="color: #6434A3;"&gt;Minimizer&lt;/span&gt;(Job):
    &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;, params=&lt;span style="color: #D0372D;"&gt;None&lt;/span&gt;):
        &lt;span style="color: #0000FF;"&gt;self&lt;/span&gt;.params = params &lt;span style="color: #0000FF;"&gt;if&lt;/span&gt; params &lt;span style="color: #0000FF;"&gt;else&lt;/span&gt; {&lt;span style="color: #008000;"&gt;'maxiter'&lt;/span&gt;: 2, &lt;span style="color: #008000;"&gt;'x0'&lt;/span&gt;: 0}
        
    &lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;run&lt;/span&gt;(&lt;span style="color: #0000FF;"&gt;self&lt;/span&gt;):
        sol = minimize(objective,
                       &lt;span style="color: #0000FF;"&gt;self&lt;/span&gt;.params[&lt;span style="color: #008000;"&gt;'x0'&lt;/span&gt;],
                       options={&lt;span style="color: #008000;"&gt;'maxiter'&lt;/span&gt;: &lt;span style="color: #0000FF;"&gt;self&lt;/span&gt;.params[&lt;span style="color: #008000;"&gt;'maxiter'&lt;/span&gt;]})
        &lt;span style="color: #0000FF;"&gt;self&lt;/span&gt;.params[&lt;span style="color: #008000;"&gt;'sol'&lt;/span&gt;] = sol

&lt;span style="color: #0000FF;"&gt;class&lt;/span&gt; &lt;span style="color: #6434A3;"&gt;MaximumIterationsExceeded&lt;/span&gt;(ErrorHandler):
    &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;, params):
        &lt;span style="color: #0000FF;"&gt;self&lt;/span&gt;.params = params

    &lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;check&lt;/span&gt;(&lt;span style="color: #0000FF;"&gt;self&lt;/span&gt;):
        &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; &lt;span style="color: #0000FF;"&gt;self&lt;/span&gt;.params[&lt;span style="color: #008000;"&gt;'sol'&lt;/span&gt;].message == &lt;span style="color: #008000;"&gt;'Maximum number of iterations has been exceeded.'&lt;/span&gt;

    &lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;correct&lt;/span&gt;(&lt;span style="color: #0000FF;"&gt;self&lt;/span&gt;):
        &lt;span style="color: #0000FF;"&gt;self&lt;/span&gt;.params[&lt;span style="color: #008000;"&gt;'maxiter'&lt;/span&gt;] *= 2
        &lt;span style="color: #0000FF;"&gt;self&lt;/span&gt;.params[&lt;span style="color: #008000;"&gt;'x0'&lt;/span&gt;] = &lt;span style="color: #0000FF;"&gt;self&lt;/span&gt;.params[&lt;span style="color: #008000;"&gt;'sol'&lt;/span&gt;].x        
        &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; {&lt;span style="color: #008000;"&gt;'errors'&lt;/span&gt;: &lt;span style="color: #008000;"&gt;'MaximumIterations Exceeded'&lt;/span&gt;,
                &lt;span style="color: #008000;"&gt;'actions'&lt;/span&gt;: &lt;span style="color: #008000;"&gt;'maxiter = {self.params["maxiter"]}, x0 = {self.params["x0"]}'&lt;/span&gt;}
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
Now we setup the initial params to try, create a Custodian object with the handler and job, and then run it. The results and final params are stored in the params object.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;
&lt;pre class="src src-jupyter-python"&gt;&lt;span style="color: #BA36A5;"&gt;params&lt;/span&gt; = {&lt;span style="color: #008000;"&gt;'maxiter'&lt;/span&gt;: 1, &lt;span style="color: #008000;"&gt;'x0'&lt;/span&gt;: 0}

&lt;span style="color: #BA36A5;"&gt;c&lt;/span&gt; = Custodian([MaximumIterationsExceeded(params)],
              [Minimizer(params)],
               max_errors=5)

c.run()
&lt;span style="color: #0000FF;"&gt;for&lt;/span&gt; key &lt;span style="color: #0000FF;"&gt;in&lt;/span&gt; params:
    &lt;span style="color: #006FE0;"&gt;print&lt;/span&gt;(key, params[key])
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example" id="orgca61279"&gt;
MaximumIterationsExceeded
MaximumIterationsExceeded
maxiter 4
x0 [1.66250127]
sol   message: Optimization terminated successfully.
  success: True
   status: 0
      fun: -36.86307468296398
        x: [ 1.662e+00]
      nit: 1
      jac: [-9.060e-06]
 hess_inv: [[1]]
     nfev: 6
     njev: 3
&lt;/pre&gt;



&lt;p&gt;
Note that params is modified, and finally has the maxiter value that worked, and the solution in it. You can see we had to rerun this problem  twice before it succeeded, but this happened automatically after the setup. This example is easy because we can simply increase the maxiter value, and no serious logic is needed. Other use cases might include try it again with another solver, try again with a different initial guess, etc. 
&lt;/p&gt;

&lt;p&gt;
It feels a little heavyweight to define the classes, and to store the results in params here, but this was overall under an hour of work to put it all together, starting from scratch with the &lt;a href="http://materialsproject.github.io/custodian/"&gt;Custodian documentation&lt;/a&gt; from the example on the front page. You can do more sophisticated things, including having multiple error handlers. Overall, for a package designed for molecular simulations, this worked well for a different kind of problem.
&lt;/p&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/09/19/Using-Custodian-to-help-converge-an-optimization-problem.org"&gt;org-mode source&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Org-mode version = 9.7-pre&lt;/p&gt;]]></content:encoded>
    </item>
    <item>
      <title>Update on finding the minimum distance from a point to a curve</title>
      <link>https://kitchingroup.cheme.cmu.edu/blog/2023/02/17/Update-on-finding-the-minimum-distance-from-a-point-to-a-curve</link>
      <pubDate>Fri, 17 Feb 2023 18:41:43 EST</pubDate>
      <category><![CDATA[optimization]]></category>
      <guid isPermaLink="false">rnWiAoFmft_vKbcPjh7HXe08a8c=</guid>
      <description>Update on finding the minimum distance from a point to a curve</description>
      <content:encoded><![CDATA[


&lt;p&gt;
Almost 10 years ago I &lt;a href="https://kitchingroup.cheme.cmu.edu/blog/2013/02/14/Find-the-minimum-distance-from-a-point-to-a-curve/"&gt;wrote&lt;/a&gt; about finding the minimum distance from a point to a curve using a constrained optimization. At that time, the way to do this used &lt;code&gt;scipy.optimize.fmin_coblya&lt;/code&gt;. I learned today from a student, that sometimes this method fails! I reproduce the code here, updated for Python 3, some style updates, and to show it does indeed fail sometimes, notably when the point is "outside" the parabola.
&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; numpy &lt;span style="color: #0000FF;"&gt;as&lt;/span&gt; np
&lt;span style="color: #0000FF;"&gt;import&lt;/span&gt; matplotlib.pyplot &lt;span style="color: #0000FF;"&gt;as&lt;/span&gt; plt
&lt;span style="color: #0000FF;"&gt;from&lt;/span&gt; scipy.optimize &lt;span style="color: #0000FF;"&gt;import&lt;/span&gt; fmin_cobyla

&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: #0000FF;"&gt;return&lt;/span&gt; x**2

&lt;span style="color: #0000FF;"&gt;for&lt;/span&gt; P &lt;span style="color: #0000FF;"&gt;in&lt;/span&gt; np.array([[0.5, 2],
                   [2, 2],
                   [-1, 2],
                   [-2, 2],
                   [0, 0.5],
                   [0, -0.5]]):
    
    &lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;objective&lt;/span&gt;(X):
        &lt;span style="color: #BA36A5;"&gt;X&lt;/span&gt; = np.array(X)
        &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; np.linalg.norm(X - P)

    &lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;c1&lt;/span&gt;(X):
        &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: #0000FF;"&gt;return&lt;/span&gt; f(x) - y

    &lt;span style="color: #BA36A5;"&gt;X&lt;/span&gt; = fmin_cobyla(objective, x0=[P[0], f(P[0])], cons=[c1])

    &lt;span style="color: #006FE0;"&gt;print&lt;/span&gt;(f&lt;span style="color: #008000;"&gt;'The minimum distance is &lt;/span&gt;{objective(X):1.2f}&lt;span style="color: #008000;"&gt;. Constraint satisfied = &lt;/span&gt;{c1(X) &amp;lt; 1e-6}&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;Verify the vector to this point is normal to the tangent of the curve&lt;/span&gt;
    &lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;position vector from curve to point&lt;/span&gt;
    v1 = np.array(P) - np.array(X)
    &lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;position vector&lt;/span&gt;
    v2 = np.array([1, 2.0 * X[0]])
    &lt;span style="color: #006FE0;"&gt;print&lt;/span&gt;(&lt;span style="color: #008000;"&gt;'dot(v1, v2) = '&lt;/span&gt;, np.dot(v1, v2))

    x = np.linspace(-2, 2, 100)

    plt.plot(x, f(x), &lt;span style="color: #008000;"&gt;'r-'&lt;/span&gt;, label=&lt;span style="color: #008000;"&gt;'f(x)'&lt;/span&gt;)
    plt.plot(P[0], P[1], &lt;span style="color: #008000;"&gt;'bo'&lt;/span&gt;, label=&lt;span style="color: #008000;"&gt;'point'&lt;/span&gt;)
    plt.plot([P[0], X[0]], [P[1], X[1]], &lt;span style="color: #008000;"&gt;'b-'&lt;/span&gt;, label=&lt;span style="color: #008000;"&gt;'shortest distance'&lt;/span&gt;)
    plt.plot([X[0], X[0] + 1], [X[1], X[1] + 2.0 * X[0]], &lt;span style="color: #008000;"&gt;'g-'&lt;/span&gt;, label=&lt;span style="color: #008000;"&gt;'tangent'&lt;/span&gt;)
    plt.axis(&lt;span style="color: #008000;"&gt;'equal'&lt;/span&gt;)
    plt.xlabel(&lt;span style="color: #008000;"&gt;'x'&lt;/span&gt;)
    plt.ylabel(&lt;span style="color: #008000;"&gt;'y'&lt;/span&gt;)    
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
The minimum distance is 0.86. Constraint satisfied = True
dot(v1, v2) =  0.0002913487659186309
The minimum distance is 0.00. Constraint satisfied = False
dot(v1, v2) =  0.00021460906432962284
The minimum distance is 0.39. Constraint satisfied = True
dot(v1, v2) =  0.00014271520451364372
The minimum distance is 0.00. Constraint satisfied = False
dot(v1, v2) =  -0.0004089466778209598
The minimum distance is 0.50. Constraint satisfied = True
dot(v1, v2) =  1.9999998429305957e-12
The minimum distance is 0.00. Constraint satisfied = False
dot(v1, v2) =  8.588744170160093e-06
&lt;figure&gt;&lt;img src="/media/f66cff16ba65526d5877bd894142fa021c51f434.png"&gt;&lt;/figure&gt; 
&lt;/p&gt;

&lt;p&gt;
So, sure enough, the optimizer is failing to find a solution that meets the constraint. It is strange it does not work on the outside. That is almost certainly an algorithm problem. Here we solve it nearly identically with the more modern &lt;code&gt;scipy.optimize.minimize&lt;/code&gt; function, and it converges every time. 
&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; scipy.optimize &lt;span style="color: #0000FF;"&gt;import&lt;/span&gt; minimize

&lt;span style="color: #0000FF;"&gt;for&lt;/span&gt; P &lt;span style="color: #0000FF;"&gt;in&lt;/span&gt; np.array([[0.5, 2],
                   [2, 2],
                   [-1, 2],
                   [-2, 2],
                   [0, 0.5],
                   [0, -0.5]]):
    
    &lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;objective&lt;/span&gt;(X):
        &lt;span style="color: #BA36A5;"&gt;X&lt;/span&gt; = np.array(X)
        &lt;span style="color: #0000FF;"&gt;return&lt;/span&gt; np.linalg.norm(X - P)

    &lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;c1&lt;/span&gt;(X):
        &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: #0000FF;"&gt;return&lt;/span&gt; f(x) - y

    &lt;span style="color: #BA36A5;"&gt;sol&lt;/span&gt; = minimize(objective, x0=[P[0], f(P[0])], constraints={&lt;span style="color: #008000;"&gt;'type'&lt;/span&gt;: &lt;span style="color: #008000;"&gt;'eq'&lt;/span&gt;, &lt;span style="color: #008000;"&gt;'fun'&lt;/span&gt;: c1})
    X = sol.x

    &lt;span style="color: #006FE0;"&gt;print&lt;/span&gt;(f&lt;span style="color: #008000;"&gt;'The minimum distance is &lt;/span&gt;{objective(X):1.2f}&lt;span style="color: #008000;"&gt;. Constraint satisfied = &lt;/span&gt;{sol.status &amp;lt; 1e-6}&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;Verify the vector to this point is normal to the tangent of the curve&lt;/span&gt;
    &lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;position vector from curve to point&lt;/span&gt;
    v1 = np.array(P) - np.array(X)
    &lt;span style="color: #8D8D84;"&gt;# &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;position vector&lt;/span&gt;
    v2 = np.array([1, 2.0 * X[0]])
    &lt;span style="color: #006FE0;"&gt;print&lt;/span&gt;(&lt;span style="color: #008000;"&gt;'dot(v1, v2) = '&lt;/span&gt;, np.dot(v1, v2))

    x = np.linspace(-2, 2, 100)

    plt.plot(x, f(x), &lt;span style="color: #008000;"&gt;'r-'&lt;/span&gt;, label=&lt;span style="color: #008000;"&gt;'f(x)'&lt;/span&gt;)
    plt.plot(P[0], P[1], &lt;span style="color: #008000;"&gt;'bo'&lt;/span&gt;, label=&lt;span style="color: #008000;"&gt;'point'&lt;/span&gt;)
    plt.plot([P[0], X[0]], [P[1], X[1]], &lt;span style="color: #008000;"&gt;'b-'&lt;/span&gt;, label=&lt;span style="color: #008000;"&gt;'shortest distance'&lt;/span&gt;)
    plt.plot([X[0], X[0] + 1], [X[1], X[1] + 2.0 * X[0]], &lt;span style="color: #008000;"&gt;'g-'&lt;/span&gt;, label=&lt;span style="color: #008000;"&gt;'tangent'&lt;/span&gt;)
    plt.axis(&lt;span style="color: #008000;"&gt;'equal'&lt;/span&gt;)
    plt.xlabel(&lt;span style="color: #008000;"&gt;'x'&lt;/span&gt;)
    plt.ylabel(&lt;span style="color: #008000;"&gt;'y'&lt;/span&gt;)
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
The minimum distance is 0.86. Constraint satisfied = True
dot(v1, v2) =  1.0701251773603815e-08
The minimum distance is 0.55. Constraint satisfied = True
dot(v1, v2) =  -0.0005793028003104883
The minimum distance is 0.39. Constraint satisfied = True
dot(v1, v2) =  -1.869272921939391e-05
The minimum distance is 0.55. Constraint satisfied = True
dot(v1, v2) =  0.0005792953298950909
The minimum distance is 0.50. Constraint satisfied = True
dot(v1, v2) =  0.0
The minimum distance is 0.50. Constraint satisfied = True
dot(v1, v2) =  0.0
&lt;figure&gt;&lt;img src="/media/4776aa1f11411aa8cf0c3ea47f96e2a8973e314e.png"&gt;&lt;/figure&gt; 
&lt;/p&gt;

&lt;p&gt;
There is no wisdom in fixing the first problem, here I just tried a newer optimization method. Out of the box with default settings it just worked. I did learn the answer is sensitive to the initial guess, so it could make sense to sample the function and find the point that is closest as the initial guess, but here the simple heuristic guess I used worked fine.
&lt;/p&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/17/Update-on-finding-the-minimum-distance-from-a-point-to-a-curve.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>Constrained optimization with Lagrange multipliers and autograd</title>
      <link>https://kitchingroup.cheme.cmu.edu/blog/2018/11/03/Constrained-optimization-with-Lagrange-multipliers-and-autograd</link>
      <pubDate>Sat, 03 Nov 2018 09:39:20 EDT</pubDate>
      <category><![CDATA[optimization]]></category>
      <category><![CDATA[autograd]]></category>
      <guid isPermaLink="false">Iy0PvHLUa3-zADMW_uQnOCfZ6Z4=</guid>
      <description>Constrained optimization with Lagrange multipliers and autograd</description>
      <content:encoded><![CDATA[


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

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

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

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

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

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

&lt;/pre&gt;

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

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

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

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

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

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

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

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

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

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

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


&lt;/pre&gt;

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

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

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

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

&lt;/pre&gt;

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

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

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

&lt;/pre&gt;

&lt;p&gt;
In summary, autograd continues to enable advanced engineering problems to be solved.
&lt;/p&gt;
&lt;p&gt;Copyright (C) 2018 by John Kitchin. See the &lt;a href="/copying.html"&gt;License&lt;/a&gt; for information about copying.&lt;p&gt;
&lt;p&gt;&lt;a href="/org/2018/11/03/Constrained-optimization-with-Lagrange-multipliers-and-autograd.org"&gt;org-mode source&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Org-mode version = 9.1.14&lt;/p&gt;]]></content:encoded>
    </item>
    <item>
      <title>Finding the maximum power of a photovoltaic device.</title>
      <link>https://kitchingroup.cheme.cmu.edu/blog/2014/04/15/Finding-the-maximum-power-of-a-photovoltaic-device</link>
      <pubDate>Tue, 15 Apr 2014 20:38:10 EDT</pubDate>
      <category><![CDATA[optimization]]></category>
      <category><![CDATA[python]]></category>
      <guid isPermaLink="false">N6khYFE0CgFhCKOJcujrM9MZ7WA=</guid>
      <description>Finding the maximum power of a photovoltaic device.</description>
      <content:encoded><![CDATA[



&lt;p&gt;
A photovoltaic device is characterized by a current-voltage relationship. Let us say, for argument's sake, that the relationship is known and defined by
&lt;/p&gt;

&lt;p&gt;
\(i = 0.5 - 0.5 * V^2\)
&lt;/p&gt;

&lt;p&gt;
The voltage is highest when the current is equal to zero, but of course then you get no power. The current is highest when the voltage is zero, i.e. short-circuited, but there is again no power. We seek the highest power condition, which is to find the maximum of \(i V\). This is a constrained optimization. We solve it by creating an objective function that returns the negative of (\i V\), and then find the minimum.
&lt;/p&gt;

&lt;p&gt;
First, let us examine the i-V relationship.
&lt;/p&gt;
&lt;div class="org-src-container"&gt;

&lt;pre class="src src-python"&gt;&lt;span style="color: #0000FF;"&gt;import&lt;/span&gt; matplotlib.pyplot &lt;span style="color: #0000FF;"&gt;as&lt;/span&gt; plt
&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: #BA36A5;"&gt;V&lt;/span&gt; = np.linspace(0, 1)

&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;i&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; 0.5 - 0.5 * V**2

plt.figure()
plt.plot(V, i(V))
plt.savefig(&lt;span style="color: #008000;"&gt;'images/iV.png'&lt;/span&gt;)
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
&amp;lt;matplotlib.figure.Figure object at 0x11193ec18&amp;gt;
[&amp;lt;matplotlib.lines.Line2D object at 0x111d43668&amp;gt;]
&lt;/pre&gt;


&lt;div class="figure"&gt;
&lt;p&gt;&lt;img src="/media/2014-04-15-Finding-the-maximum-power-of-a-photovoltaic-device./iV.png"&gt; 
&lt;/p&gt;
&lt;/div&gt;


&lt;p&gt;
Now, let us be sure there is a maximum in power.
&lt;/p&gt;
&lt;div class="org-src-container"&gt;

&lt;pre class="src src-python"&gt;&lt;span style="color: #0000FF;"&gt;import&lt;/span&gt; matplotlib.pyplot &lt;span style="color: #0000FF;"&gt;as&lt;/span&gt; plt
&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: #BA36A5;"&gt;V&lt;/span&gt; = np.linspace(0, 1)

&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;i&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; 0.5 - 0.5 * V**2

plt.plot(V, i(V) * V)
plt.savefig(&lt;span style="color: #008000;"&gt;'images/P1.png'&lt;/span&gt;)
&lt;/pre&gt;
&lt;/div&gt;

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


&lt;div class="figure"&gt;
&lt;p&gt;&lt;img src="/media/2014-04-15-Finding-the-maximum-power-of-a-photovoltaic-device./P1.png"&gt; 
&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;
You can see in fact there is a maximum, near V=0.6. We could solve this problem analytically by taking the appropriate derivative and solving it for zero. That still might require solving a nonlinear problem though. We will directly setup and solve the constrained optimization.
&lt;/p&gt;

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

&lt;pre class="src src-python"&gt;&lt;span style="color: #0000FF;"&gt;from&lt;/span&gt; scipy.optimize &lt;span style="color: #0000FF;"&gt;import&lt;/span&gt; fmin_slsqp
&lt;span style="color: #0000FF;"&gt;import&lt;/span&gt; numpy &lt;span style="color: #0000FF;"&gt;as&lt;/span&gt; np
&lt;span style="color: #0000FF;"&gt;import&lt;/span&gt; matplotlib.pyplot &lt;span style="color: #0000FF;"&gt;as&lt;/span&gt; plt

&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;objective&lt;/span&gt;(X):
&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;V&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; - i * V

&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;eqc&lt;/span&gt;(X):
&lt;span style="color: #9B9B9B; background-color: #EDEDED;"&gt; &lt;/span&gt;   &lt;span style="color: #036A07;"&gt;'equality constraint'&lt;/span&gt;
&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;V&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; (0.5 - 0.5 * V**2) - i

&lt;span style="color: #BA36A5;"&gt;X0&lt;/span&gt; = [0.2, 0.6]
&lt;span style="color: #BA36A5;"&gt;X&lt;/span&gt; = fmin_slsqp(objective, X0, eqcons=[eqc])

&lt;span style="color: #BA36A5;"&gt;imax&lt;/span&gt;, &lt;span style="color: #BA36A5;"&gt;Vmax&lt;/span&gt; = X


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

&lt;span style="color: #0000FF;"&gt;def&lt;/span&gt; &lt;span style="color: #006699;"&gt;i&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; 0.5 - 0.5 * V**2

plt.plot(V, i(V), Vmax, imax, &lt;span style="color: #008000;"&gt;'ro'&lt;/span&gt;)
plt.savefig(&lt;span style="color: #008000;"&gt;'images/P2.png'&lt;/span&gt;)
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
Optimization terminated successfully.    (Exit mode 0)
            Current function value: -0.192450127337
            Iterations: 5
            Function evaluations: 20
            Gradient evaluations: 5
[&amp;lt;matplotlib.lines.Line2D object at 0x111946470&amp;gt;, &amp;lt;matplotlib.lines.Line2D object at 0x11192c518&amp;gt;]
&lt;/pre&gt;


&lt;div class="figure"&gt;
&lt;p&gt;&lt;img src="/media/2014-04-15-Finding-the-maximum-power-of-a-photovoltaic-device./P2.png"&gt; 
&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;
You can see the maximum power is approximately 0.2 (unspecified units), at the conditions indicated by the red dot in the figure above.
&lt;/p&gt;
&lt;p&gt;Copyright (C) 2016 by John Kitchin. See the &lt;a href="/copying.html"&gt;License&lt;/a&gt; for information about copying.&lt;p&gt;
&lt;p&gt;&lt;a href="/org/2014/04/15/Finding-the-maximum-power-of-a-photovoltaic-device..org"&gt;org-mode source&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Org-mode version = 8.2.10&lt;/p&gt;]]></content:encoded>
    </item>
    <item>
      <title>Constrained fits to data</title>
      <link>https://kitchingroup.cheme.cmu.edu/blog/2013/06/11/Constrained-fits-to-data</link>
      <pubDate>Tue, 11 Jun 2013 19:39:59 EDT</pubDate>
      <category><![CDATA[data analysis]]></category>
      <category><![CDATA[optimization]]></category>
      <guid isPermaLink="false">tkPJI7DfYBi--ElDtj1PvwvMk08=</guid>
      <description>Constrained fits to data</description>
      <content:encoded><![CDATA[


&lt;p&gt;
Our objective here is to fit a quadratic function in the least squares sense to some data, but we want to constrain the fit so that the function has specific values at the end-points. The application is to fit a function to the lattice constant of an alloy at different compositions. We constrain the fit because we know the lattice constant of the pure metals, which are at the end-points of the fit and we want these to be correct. 
&lt;/p&gt;

&lt;p&gt;
We define the alloy composition in terms of the mole fraction of one species, e.g. \(A_xB_{1-x}\). For \(x=0\), the alloy is pure B, whereas for \(x=1\) the alloy is pure A. According to Vegard's law the lattice constant is a linear composition weighted average of the pure component lattice constants, but sometimes small deviations are observed. Here we will fit a quadratic function that is constrained to give the pure metal component lattice constants at the end points. 
&lt;/p&gt;

&lt;p&gt;
The quadratic function is \(y = a x^2 + b x + c\). One constraint is at \(x=0\) where \(y = c\), or \(c\) is the lattice constant of pure B. The second constraint is at \(x=1\), where \(a + b + c\) is equal to the lattice constant of pure A. Thus, there is only one degree of freedom. \(c = LC_B\), and \(b = LC_A - c - a\), so \(a\) is our only variable.
&lt;/p&gt;

&lt;p&gt;
We will solve this problem by minimizing the summed squared error between the fit and the data. We use the &lt;code&gt;fmin&lt;/code&gt; function in &lt;code&gt;scipy.optimize&lt;/code&gt;. First we create a fit function that encodes the constraints. Then we create an objective function that will be minimized. We have to make a guess about the value of \(a\) that minimizes the summed squared error. A line fits the data moderately well, so we guess a small value, i.e. near zero, for \(a\). Here is the solution.
&lt;/p&gt;

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

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

&lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;Data to fit to&lt;/span&gt;
&lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;x=0 is pure B&lt;/span&gt;
&lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;x=1 is pure A&lt;/span&gt;
X = np.array([0.0, 0.1,  0.25, 0.5,  0.6,  0.8,  1.0])
Y = np.array([3.9, 3.89, 3.87, 3.78, 3.75, 3.69, 3.6])

&lt;span style="color: #8b0000;"&gt;def&lt;/span&gt; &lt;span style="color: #8b2323;"&gt;func&lt;/span&gt;(a, XX):
    LC_A = 3.6
    LC_B = 3.9

    c = LC_B
    b = LC_A - c - a

    yfit = a * XX**2 + b * XX + c
    &lt;span style="color: #8b0000;"&gt;return&lt;/span&gt; yfit

&lt;span style="color: #8b0000;"&gt;def&lt;/span&gt; &lt;span style="color: #8b2323;"&gt;objective&lt;/span&gt;(a):
    &lt;span style="color: #228b22;"&gt;'function to minimize'&lt;/span&gt;
    SSE = np.sum((Y - func(a, X))**2)
    &lt;span style="color: #8b0000;"&gt;return&lt;/span&gt; SSE


&lt;span style="color: #8b0000;"&gt;from&lt;/span&gt; scipy.optimize &lt;span style="color: #8b0000;"&gt;import&lt;/span&gt; fmin

a_fit = fmin(objective, 0)
plt.plot(X, Y, &lt;span style="color: #228b22;"&gt;'bo '&lt;/span&gt;)

x = np.linspace(0, 1)
plt.plot(x, func(a_fit, x))
plt.savefig(&lt;span style="color: #228b22;"&gt;'images/constrained-quadratic-fit.png'&lt;/span&gt;)
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
Optimization terminated successfully.
         Current function value: 0.000445
         Iterations: 19
         Function evaluations: 38
&lt;/pre&gt;

&lt;p&gt;
Here is the result:
&lt;p&gt;&lt;img src="/img/./images/constrained-quadratic-fit.png"&gt;&lt;p&gt;
&lt;/p&gt;

&lt;p&gt;
You can see that the end points go through the end-points as prescribed. 
&lt;/p&gt;
&lt;p&gt;Copyright (C) 2013 by John Kitchin. See the &lt;a href="/copying.html"&gt;License&lt;/a&gt; for information about copying.&lt;p&gt;&lt;p&gt;&lt;a href="/org/2013/06/11/Constrained-fits-to-data.org"&gt;org-mode source&lt;/a&gt;&lt;p&gt;]]></content:encoded>
    </item>
    <item>
      <title>Gibbs energy minimization and the NIST webbook</title>
      <link>https://kitchingroup.cheme.cmu.edu/blog/2013/03/01/Gibbs-energy-minimization-and-the-NIST-webbook</link>
      <pubDate>Fri, 01 Mar 2013 13:11:58 EST</pubDate>
      <category><![CDATA[optimization]]></category>
      <guid isPermaLink="false">2cPyJ1dhosp_-EMfiMiuxmC2KMM=</guid>
      <description>Gibbs energy minimization and the NIST webbook</description>
      <content:encoded><![CDATA[


&lt;p&gt;
&lt;a href="http://matlab.cheme.cmu.edu/2011/12/25/gibbs-energy-minimization-and-the-nist-webbook/" &gt;Matlab post&lt;/a&gt;
In Post 1536 we used the NIST webbook to compute a temperature dependent Gibbs energy of reaction, and then used a reaction extent variable to compute the equilibrium concentrations of each species for the water gas shift reaction.
&lt;/p&gt;

&lt;p&gt;
Today, we look at the direct minimization of the Gibbs free energy of the species, with no assumptions about stoichiometry of reactions. We only apply the constraint of conservation of atoms. We use the NIST Webbook to provide the data for the Gibbs energy of each species.
&lt;/p&gt;

&lt;p&gt;
As a reminder we consider equilibrium between the species \(CO\), \(H_2O\), \(CO_2\) and \(H_2\), at 1000K, and 10 atm total pressure with an initial equimolar molar flow rate of \(CO\) and \(H_2O\).
&lt;/p&gt;

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

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

T = 1000  &lt;span style="color: #ff0000; font-weight: bold;"&gt;# K&lt;/span&gt;
R = 8.314e-3 &lt;span style="color: #ff0000; font-weight: bold;"&gt;# kJ/mol/K&lt;/span&gt;

P = 10.0 &lt;span style="color: #ff0000; font-weight: bold;"&gt;# atm, this is the total pressure in the reactor&lt;/span&gt;
Po = 1.0 &lt;span style="color: #ff0000; font-weight: bold;"&gt;# atm, this is the standard state pressure&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
We are going to store all the data and calculations in vectors, so we need to assign each position in the vector to a species. Here are the definitions we use in this work.
&lt;/p&gt;

&lt;pre class="example"&gt;
1  CO
2  H2O
3  CO2
4  H2
&lt;/pre&gt;

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

&lt;pre class="src src-python"&gt;species = [&lt;span style="color: #228b22;"&gt;'CO'&lt;/span&gt;, &lt;span style="color: #228b22;"&gt;'H2O'&lt;/span&gt;, &lt;span style="color: #228b22;"&gt;'CO2'&lt;/span&gt;, &lt;span style="color: #228b22;"&gt;'H2'&lt;/span&gt;]

&lt;span style="color: #ff0000; font-weight: bold;"&gt;# Heats of formation at 298.15 K&lt;/span&gt;

Hf298 = [
    -110.53,  &lt;span style="color: #ff0000; font-weight: bold;"&gt;# CO&lt;/span&gt;
    -241.826, &lt;span style="color: #ff0000; font-weight: bold;"&gt;# H2O&lt;/span&gt;
    -393.51,  &lt;span style="color: #ff0000; font-weight: bold;"&gt;# CO2&lt;/span&gt;
       0.0]   &lt;span style="color: #ff0000; font-weight: bold;"&gt;# H2&lt;/span&gt;

&lt;span style="color: #ff0000; font-weight: bold;"&gt;# Shomate parameters for each species&lt;/span&gt;
&lt;span style="color: #ff0000; font-weight: bold;"&gt;#           A          B           C          D          E            F          G       H&lt;/span&gt;
WB = [[25.56759,  6.096130,     4.054656,  -2.671301,  0.131021, -118.0089, 227.3665,   -110.5271],  &lt;span style="color: #ff0000; font-weight: bold;"&gt;# CO&lt;/span&gt;
      [30.09200,  6.832514,     6.793435,  -2.534480,  0.082139, -250.8810, 223.3967,   -241.8264],  &lt;span style="color: #ff0000; font-weight: bold;"&gt;# H2O&lt;/span&gt;
      [24.99735,  55.18696,   -33.69137,    7.948387, -0.136638, -403.6075, 228.2431,   -393.5224],  &lt;span style="color: #ff0000; font-weight: bold;"&gt;# CO2&lt;/span&gt;
      [33.066178, -11.363417,  11.432816,  -2.772874, -0.158558, -9.980797, 172.707974,    0.0]]     &lt;span style="color: #ff0000; font-weight: bold;"&gt;# H2&lt;/span&gt;

WB = np.array(WB)

&lt;span style="color: #ff0000; font-weight: bold;"&gt;# Shomate equations&lt;/span&gt;
t = T/1000
T_H = np.array([t,  t**2 / 2.0, t**3 / 3.0, t**4 / 4.0, -1.0 / t, 1.0, 0.0, -1.0])
T_S = np.array([np.log(t), t,  t**2 / 2.0,  t**3 / 3.0, -1.0 / (2.0 * t**2), 0.0, 1.0, 0.0])

H = np.dot(WB, T_H)        &lt;span style="color: #ff0000; font-weight: bold;"&gt;# (H - H_298.15) kJ/mol&lt;/span&gt;
S = np.dot(WB, T_S/1000.0) &lt;span style="color: #ff0000; font-weight: bold;"&gt;# absolute entropy kJ/mol/K&lt;/span&gt;

Gjo = Hf298 + H - T*S      &lt;span style="color: #ff0000; font-weight: bold;"&gt;# Gibbs energy of each component at 1000 K&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
Now, construct the Gibbs free energy function, accounting for the change in activity due to concentration changes (ideal mixing).
&lt;/p&gt;
&lt;div class="org-src-container"&gt;

&lt;pre class="src src-python"&gt;&lt;span style="color: #8b0000;"&gt;def&lt;/span&gt; &lt;span style="color: #8b2323;"&gt;func&lt;/span&gt;(nj):
    nj = np.array(nj)
    Enj = np.sum(nj);
    Gj =  Gjo / (R * T) + np.log(nj / Enj * P / Po)
    &lt;span style="color: #8b0000;"&gt;return&lt;/span&gt; np.dot(nj, Gj)
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
We impose the constraint that all atoms are conserved from the initial conditions to the equilibrium distribution of species. These constraints are in the form of \(A_{eq} n = b_{eq}\), where \(n\) is the vector of mole numbers for each species.
&lt;/p&gt;

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

&lt;pre class="src src-python"&gt;Aeq = np.array([[ 1,    0,    1,    0],  &lt;span style="color: #ff0000; font-weight: bold;"&gt;# C balance&lt;/span&gt;
                [ 1,    1,    2,    0],  &lt;span style="color: #ff0000; font-weight: bold;"&gt;# O balance&lt;/span&gt;
                [ 0,    2,    0,    2]]) &lt;span style="color: #ff0000; font-weight: bold;"&gt;# H balance&lt;/span&gt;

&lt;span style="color: #ff0000; font-weight: bold;"&gt;# equimolar feed of 1 mol H2O and 1 mol CO&lt;/span&gt;
beq = np.array([1,  &lt;span style="color: #ff0000; font-weight: bold;"&gt;# mol C fed&lt;/span&gt;
                2,  &lt;span style="color: #ff0000; font-weight: bold;"&gt;# mol O fed&lt;/span&gt;
                2]) &lt;span style="color: #ff0000; font-weight: bold;"&gt;# mol H fed&lt;/span&gt;

&lt;span style="color: #8b0000;"&gt;def&lt;/span&gt; &lt;span style="color: #8b2323;"&gt;ec1&lt;/span&gt;(nj):
    &lt;span style="color: #228b22;"&gt;'conservation of atoms constraint'&lt;/span&gt;
    &lt;span style="color: #8b0000;"&gt;return&lt;/span&gt; np.dot(Aeq, nj) - beq
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
Now we are ready to solve the problem. 
&lt;/p&gt;

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

&lt;pre class="src src-python"&gt;&lt;span style="color: #8b0000;"&gt;from&lt;/span&gt; scipy.optimize &lt;span style="color: #8b0000;"&gt;import&lt;/span&gt; fmin_slsqp

n0 = [0.5, 0.5, 0.5, 0.5]  &lt;span style="color: #ff0000; font-weight: bold;"&gt;# initial guesses&lt;/span&gt;
N = fmin_slsqp(func, n0, f_eqcons=ec1)
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; N
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
&amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; Optimization terminated successfully.    (Exit mode 0)
            Current function value: -91.204832308
            Iterations: 2
            Function evaluations: 13
            Gradient evaluations: 2
[ 0.45502309  0.45502309  0.54497691  0.54497691]
&lt;/pre&gt;

&lt;div id="outline-container-1" class="outline-2"&gt;
&lt;h2 id="sec-1"&gt;&lt;span class="section-number-2"&gt;1&lt;/span&gt; Compute mole fractions and partial pressures&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-1"&gt;
&lt;p&gt;
The pressures here are in good agreement with the pressures found by other methods. The minor disagreement (in the third or fourth decimal place) is likely due to convergence tolerances in the different algorithms used.
&lt;/p&gt;

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

&lt;pre class="src src-python"&gt;yj = N / np.sum(N)
Pj = yj * P

&lt;span style="color: #8b0000;"&gt;for&lt;/span&gt; s, y, p &lt;span style="color: #8b0000;"&gt;in&lt;/span&gt; &lt;span style="color: #8b0000;"&gt;zip&lt;/span&gt;(species, yj, Pj):
    &lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; &lt;span style="color: #228b22;"&gt;'{0:10s}: {1:1.2f} {2:1.2f}'&lt;/span&gt;.format(s, y, p)
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
&amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; ... ... CO        : 0.23 2.28
H2O       : 0.23 2.28
CO2       : 0.27 2.72
H2        : 0.27 2.72
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id="outline-container-2" class="outline-2"&gt;
&lt;h2 id="sec-2"&gt;&lt;span class="section-number-2"&gt;2&lt;/span&gt; Computing equilibrium constants&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-2"&gt;
&lt;p&gt;
We can compute the equilibrium constant for the reaction \(CO + H_2O \rightleftharpoons CO_2 + H_2\). Compared to the value of K = 1.44 we found at the end of Post 1536 , the agreement is excellent. Note, that to define an equilibrium constant it is necessary to specify a reaction, even though it is not necessary to even consider a reaction to obtain the equilibrium distribution of species!
&lt;/p&gt;

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

&lt;pre class="src src-python"&gt;nuj = np.array([-1, -1, 1, 1])  &lt;span style="color: #ff0000; font-weight: bold;"&gt;# stoichiometric coefficients of the reaction&lt;/span&gt;
K = np.prod(yj**nuj)
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; K
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
&amp;gt;&amp;gt;&amp;gt; 1.43446295961
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Copyright (C) 2013 by John Kitchin. See the &lt;a href="/copying.html"&gt;License&lt;/a&gt; for information about copying.&lt;p&gt;&lt;p&gt;&lt;a href="/org/2013/03/01/Gibbs-energy-minimization-and-the-NIST-webbook.org"&gt;org-mode source&lt;/a&gt;&lt;p&gt;]]></content:encoded>
    </item>
    <item>
      <title>Finding equilibrium composition by direct minimization of Gibbs free energy on mole numbers</title>
      <link>https://kitchingroup.cheme.cmu.edu/blog/2013/03/01/Finding-equilibrium-composition-by-direct-minimization-of-Gibbs-free-energy-on-mole-numbers</link>
      <pubDate>Fri, 01 Mar 2013 12:27:48 EST</pubDate>
      <category><![CDATA[optimization]]></category>
      <guid isPermaLink="false">j0YUTyQ_GaQ-VsNlprCKzxr8qH8=</guid>
      <description>Finding equilibrium composition by direct minimization of Gibbs free energy on mole numbers</description>
      <content:encoded><![CDATA[


&lt;div id="table-of-contents"&gt;
&lt;h2&gt;Table of Contents&lt;/h2&gt;
&lt;div id="text-table-of-contents"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#sec-1"&gt;1. The Gibbs energy of a mixture&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#sec-2"&gt;2. Linear equality constraints for atomic mass conservation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#sec-3"&gt;3. Equilibrium constant based on mole numbers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#sec-4"&gt;4. Summary&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
&lt;a href="http://matlab.cheme.cmu.edu/2011/12/25/finding-equilibrium-composition-by-direct-minimization-of-gibbs-free-energy-on-mole-numbers/"&gt;Matlab post&lt;/a&gt; 
Adapted from problem 4.5 in Cutlip and Shacham
Ethane and steam are fed to a steam cracker at a total pressure of 1 atm and at 1000K at a ratio of 4 mol H2O to 1 mol ethane. Estimate the equilibrium distribution of products (CH4, C2H4, C2H2, CO2, CO, O2, H2, H2O, and C2H6).
&lt;/p&gt;

&lt;p&gt;
Solution method: We will construct a Gibbs energy function for the mixture, and obtain the equilibrium composition by minimization of the function subject to elemental mass balance constraints.
&lt;/p&gt;

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

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

&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;R&lt;/span&gt; = &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;00198588&lt;/span&gt; &lt;span style="color: #ff0000; font-weight: bold;"&gt;# kcal/mol/K&lt;/span&gt;
&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;T&lt;/span&gt; = &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;1000&lt;/span&gt; &lt;span style="color: #ff0000; font-weight: bold;"&gt;# K&lt;/span&gt;

&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;species&lt;/span&gt; = [&lt;span style="color: #228b22;"&gt;'CH4'&lt;/span&gt;, &lt;span style="color: #228b22;"&gt;'C2H4'&lt;/span&gt;, &lt;span style="color: #228b22;"&gt;'C2H2'&lt;/span&gt;, &lt;span style="color: #228b22;"&gt;'CO2'&lt;/span&gt;, &lt;span style="color: #228b22;"&gt;'CO'&lt;/span&gt;, &lt;span style="color: #228b22;"&gt;'O2'&lt;/span&gt;, &lt;span style="color: #228b22;"&gt;'H2'&lt;/span&gt;, &lt;span style="color: #228b22;"&gt;'H2O'&lt;/span&gt;, &lt;span style="color: #228b22;"&gt;'C2H6'&lt;/span&gt;]

# &lt;span style="color: #ff0000; font-weight: bold;"&gt;$G_^\circ for each species. These are the heats of formation for each&lt;/span&gt;
# &lt;span style="color: #ff0000; font-weight: bold;"&gt;species.&lt;/span&gt;
&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;Gjo&lt;/span&gt; = np.array([&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;4&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;61&lt;/span&gt;, &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;28&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;249&lt;/span&gt;, &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;40&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;604&lt;/span&gt;, -&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;94&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;61&lt;/span&gt;, -&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;47&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;942&lt;/span&gt;, &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;, &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;, -&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;46&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;03&lt;/span&gt;, &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;26&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;13&lt;/span&gt;]) &lt;span style="color: #ff0000; font-weight: bold;"&gt;# kcal/mol&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;

&lt;div id="outline-container-sec-1" class="outline-2"&gt;
&lt;h2 id="sec-1"&gt;&lt;span class="section-number-2"&gt;1&lt;/span&gt; The Gibbs energy of a mixture&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-1"&gt;
&lt;p&gt;
We start with \(G=\sum\limits_j n_j \mu_j\). Recalling that we define \(\mu_j = G_j^\circ + RT \ln a_j\), and in the ideal gas limit, \(a_j = y_j P/P^\circ\), and that \(y_j = \frac{n_j}{\sum n_j}\). Since in this problem, P = 1 atm, this leads to the function \(\frac{G}{RT} = \sum\limits_{j=1}^n n_j\left(\frac{G_j^\circ}{RT} + \ln \frac{n_j}{\sum n_j}\right)\).
&lt;/p&gt;

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

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

&lt;span style="color: #8b0000;"&gt;def&lt;/span&gt; &lt;span style="color: #8b2323;"&gt;func&lt;/span&gt;(nj):
    &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;nj&lt;/span&gt; = np.array(nj)
    &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;Enj&lt;/span&gt; = np.sum(nj);
    &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;G&lt;/span&gt; = np.sum(nj * (Gjo / R / T + np.log(nj / Enj)))
    &lt;span style="color: #8b0000;"&gt;return&lt;/span&gt; G
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;div id="outline-container-sec-2" class="outline-2"&gt;
&lt;h2 id="sec-2"&gt;&lt;span class="section-number-2"&gt;2&lt;/span&gt; Linear equality constraints for atomic mass conservation&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-2"&gt;
&lt;p&gt;
The total number of each type of atom must be the same as what entered the reactor. These form equality constraints on the equilibrium composition. We express these constraints as: \(A_{eq} n = b\) where \(n\) is a vector of the moles of each species present in the mixture. CH4 C2H4 C2H2 CO2 CO O2 H2 H2O C2H6
&lt;/p&gt;

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

&lt;pre class="src src-python"&gt;&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;Aeq&lt;/span&gt; = np.array([[&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;,   &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;,    &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;,   &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;2&lt;/span&gt;,   &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;1&lt;/span&gt;,  &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;2&lt;/span&gt;,  &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;,  &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;1&lt;/span&gt;,   &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;],      &lt;span style="color: #ff0000; font-weight: bold;"&gt;# oxygen balance&lt;/span&gt;
                [&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;4&lt;/span&gt;,   &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;4&lt;/span&gt;,    &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;2&lt;/span&gt;,   &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;,   &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;,  &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;,  &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;2&lt;/span&gt;,  &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;2&lt;/span&gt;,   &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;6&lt;/span&gt;],      &lt;span style="color: #ff0000; font-weight: bold;"&gt;# hydrogen balance&lt;/span&gt;
                [&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;1&lt;/span&gt;,   &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;2&lt;/span&gt;,    &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;2&lt;/span&gt;,   &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;1&lt;/span&gt;,   &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;1&lt;/span&gt;,  &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;,  &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;,  &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;,   &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;2&lt;/span&gt;]])     &lt;span style="color: #ff0000; font-weight: bold;"&gt;# carbon balance&lt;/span&gt;

# &lt;span style="color: #ff0000; font-weight: bold;"&gt;the incoming feed was 4 mol H2O and 1 mol ethane&lt;/span&gt;
&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;beq&lt;/span&gt; = np.array([&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;4&lt;/span&gt;,  &lt;span style="color: #ff0000; font-weight: bold;"&gt;# moles of oxygen atoms coming in&lt;/span&gt;
                &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;14&lt;/span&gt;, &lt;span style="color: #ff0000; font-weight: bold;"&gt;# moles of hydrogen atoms coming in&lt;/span&gt;
                &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;2&lt;/span&gt;]) &lt;span style="color: #ff0000; font-weight: bold;"&gt;# moles of carbon atoms coming in&lt;/span&gt;

&lt;span style="color: #8b0000;"&gt;def&lt;/span&gt; &lt;span style="color: #8b2323;"&gt;ec1&lt;/span&gt;(n):
    &lt;span style="color: #228b22;"&gt;'equality constraint'&lt;/span&gt;
    &lt;span style="color: #8b0000;"&gt;return&lt;/span&gt; np.dot(Aeq, n) - beq

&lt;span style="color: #8b0000;"&gt;def&lt;/span&gt; &lt;span style="color: #8b2323;"&gt;ic1&lt;/span&gt;(n):
    &lt;span style="color: #228b22;"&gt;'''inequality constraint&lt;/span&gt;
&lt;span style="color: #228b22;"&gt;       all n&amp;gt;=0&lt;/span&gt;
&lt;span style="color: #228b22;"&gt;    '''&lt;/span&gt;   
    &lt;span style="color: #8b0000;"&gt;return&lt;/span&gt; n
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
Now we solve the problem.
&lt;/p&gt;

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

&lt;pre class="src src-python"&gt;# &lt;span style="color: #ff0000; font-weight: bold;"&gt;initial guess suggested in the example&lt;/span&gt;
&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;n0&lt;/span&gt; = [1e-&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;3&lt;/span&gt;, 1e-&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;3&lt;/span&gt;, 1e-&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;3&lt;/span&gt;, &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;993&lt;/span&gt;, &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;1&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;, 1e-&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;4&lt;/span&gt;, &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;5&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;992&lt;/span&gt;, &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;1&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;, 1e-&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;3&lt;/span&gt;] 

&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;n0&lt;/span&gt; = [&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;066&lt;/span&gt;, &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;8&lt;/span&gt;.7e-&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;08&lt;/span&gt;, &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;2&lt;/span&gt;.1e-&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;14&lt;/span&gt;, &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;545&lt;/span&gt;, &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;1&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;39&lt;/span&gt;, &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;5&lt;/span&gt;.7e-&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;14&lt;/span&gt;, &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;5&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;346&lt;/span&gt;, &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;1&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;521&lt;/span&gt;, &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;1&lt;/span&gt;.58e-&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;7&lt;/span&gt;]

&lt;span style="color: #8b0000;"&gt;from&lt;/span&gt; scipy.optimize &lt;span style="color: #8b0000;"&gt;import&lt;/span&gt; fmin_slsqp

&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;X&lt;/span&gt; = fmin_slsqp(func, n0, &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;f_eqcons&lt;/span&gt;=ec1,&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;f_ieqcons&lt;/span&gt;=ic1,&lt;span style="color: #cd0000;"&gt; iter&lt;/span&gt;=&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;300&lt;/span&gt;, &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;acc&lt;/span&gt;=1e-&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;12&lt;/span&gt;)

&lt;span style="color: #8b0000;"&gt;for&lt;/span&gt; s,x &lt;span style="color: #8b0000;"&gt;in&lt;/span&gt;&lt;span style="color: #cd0000;"&gt; zip&lt;/span&gt;(species, X):
    &lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; &lt;span style="color: #228b22;"&gt;'{0:10s} {1:1.4g}'&lt;/span&gt;.format(s, x)

# &lt;span style="color: #ff0000; font-weight: bold;"&gt;check that constraints were met&lt;/span&gt;
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; np.dot(Aeq, X) - beq
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; np.all( np.abs( np.dot(Aeq, X) - beq) &amp;lt; 1e-&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;12&lt;/span&gt;)
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
&amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; Optimization terminated successfully.    (Exit mode 0)
            Current function value: -104.403951524
            Iterations: 16
            Function evaluations: 193
            Gradient evaluations: 15
&amp;gt;&amp;gt;&amp;gt; ... ... CH4        0.06644
C2H4       9.48e-08
C2H2       1.487e-13
CO2        0.545
CO         1.389
O2         3.096e-13
H2         5.346
H2O        1.521
C2H6       1.581e-07
... [  0.00000000e+00   0.00000000e+00   4.44089210e-16]
True
&lt;/pre&gt;

&lt;p&gt;
I found it necessary to tighten the accuracy parameter to get pretty good matches to the solutions found in Matlab. It was also necessary to increase the number of iterations. Even still, not all of the numbers match well, especially the very small numbers. You can, however, see that the constraints were satisfied pretty well.
&lt;/p&gt;


&lt;p&gt;
Interestingly there is a distribution of products! That is interesting because only steam and ethane enter the reactor, but a small fraction of methane is formed! The main product is hydrogen. The stoichiometry of steam reforming is ideally \(C_2H_6 + 4H_2O \rightarrow 2CO_2 + 7 H2\). Even though nearly all the ethane is consumed, we do not get the full yield of hydrogen. It appears that another equilibrium, one between CO, CO2, H2O and H2, may be limiting that, since the rest of the hydrogen is largely in the water. It is also of great importance that we have not said anything about reactions, i.e. how these products were formed. 
&lt;/p&gt;

&lt;p&gt;
The water gas shift reaction is: \(CO + H_2O \rightleftharpoons CO_2 + H_2\). We can compute the Gibbs free energy of the reaction from the heats of formation of each species. Assuming these are the formation energies at 1000K, this is the reaction free energy at 1000K.
&lt;/p&gt;

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

&lt;pre class="src src-python"&gt;&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;G_wgs&lt;/span&gt; = Gjo[&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;3&lt;/span&gt;] + Gjo[&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;6&lt;/span&gt;] - Gjo[&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;4&lt;/span&gt;] - Gjo[&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;7&lt;/span&gt;]
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; G_wgs

&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;K&lt;/span&gt; = np.exp(-G_wgs / (R*T))
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; K
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
-0.638
&amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; 1.37887528109
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;div id="outline-container-sec-3" class="outline-2"&gt;
&lt;h2 id="sec-3"&gt;&lt;span class="section-number-2"&gt;3&lt;/span&gt; Equilibrium constant based on mole numbers&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-3"&gt;
&lt;p&gt;
One normally uses activities to define the equilibrium constant. Since there are the same number of moles on each side of the reaction all factors that convert mole numbers to activity, concentration or pressure cancel, so we simply consider the ratio of mole numbers here.
&lt;/p&gt;

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

&lt;pre class="src src-python"&gt;&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; (X[&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;3&lt;/span&gt;] * X[&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;6&lt;/span&gt;]) / (X[&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;4&lt;/span&gt;] * X[&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;7&lt;/span&gt;])
&lt;/pre&gt;
&lt;/div&gt;

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

&lt;p&gt;
This is very close to the equilibrium constant computed above. 
&lt;/p&gt;

&lt;p&gt;
Clearly, there is an equilibrium between these species that prevents the complete reaction of steam reforming.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;div id="outline-container-sec-4" class="outline-2"&gt;
&lt;h2 id="sec-4"&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;
This is an appealing way to minimize the Gibbs energy of a mixture. No assumptions about reactions are necessary, and the constraints are easy to identify. The Gibbs energy function is especially easy to code.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Copyright (C) 2014 by John Kitchin. See the &lt;a href="/copying.html"&gt;License&lt;/a&gt; for information about copying.&lt;p&gt;&lt;p&gt;&lt;a href="/org/2013/03/01/Finding-equilibrium-composition-by-direct-minimization-of-Gibbs-free-energy-on-mole-numbers.org"&gt;org-mode source&lt;/a&gt;&lt;p&gt;&lt;p&gt;Org-mode version = 8.2.7c&lt;/p&gt;]]></content:encoded>
    </item>
    <item>
      <title>Constrained optimization</title>
      <link>https://kitchingroup.cheme.cmu.edu/blog/2013/02/27/Constrained-optimization</link>
      <pubDate>Wed, 27 Feb 2013 14:43:37 EST</pubDate>
      <category><![CDATA[optimization]]></category>
      <guid isPermaLink="false">Y_dxaZce3fpHk_5GasXmrgWEiDg=</guid>
      <description>Constrained optimization</description>
      <content:encoded><![CDATA[


&lt;p&gt;


&lt;a href="http://matlab.cheme.cmu.edu/2011/12/24/constrained-optimization/" &gt;Matlab post&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;
adapted from &lt;a href="http://en.wikipedia.org/wiki/Lagrange_multipliers" &gt;http://en.wikipedia.org/wiki/Lagrange_multipliers&lt;/a&gt;.
&lt;/p&gt;

&lt;p&gt;
Suppose we seek to minimize the function \(f(x,y)=x+y\) subject to the constraint that \(x^2 + y^2 = 1\). The function we seek to maximize is an unbounded plane, while the constraint is a unit circle. We could setup a Lagrange multiplier approach to solving this problem, but we will use a constrained optimization approach instead.
&lt;/p&gt;

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

&lt;pre class="src src-python"&gt;&lt;span style="color: #8b0000;"&gt;from&lt;/span&gt; scipy.optimize &lt;span style="color: #8b0000;"&gt;import&lt;/span&gt; fmin_slsqp

&lt;span style="color: #8b0000;"&gt;def&lt;/span&gt; &lt;span style="color: #8b2323;"&gt;objective&lt;/span&gt;(X):
    x, y = X
    &lt;span style="color: #8b0000;"&gt;return&lt;/span&gt; x + y

&lt;span style="color: #8b0000;"&gt;def&lt;/span&gt; &lt;span style="color: #8b2323;"&gt;eqc&lt;/span&gt;(X):
    &lt;span style="color: #228b22;"&gt;'equality constraint'&lt;/span&gt;
    x, y = X
    &lt;span style="color: #8b0000;"&gt;return&lt;/span&gt; x**2 + y**2 - 1.0

X0 = [-1, -1]
X = fmin_slsqp(objective, X0, eqcons=[eqc])
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; X
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
Optimization terminated successfully.    (Exit mode 0)
            Current function value: -1.41421356237
            Iterations: 5
            Function evaluations: 20
            Gradient evaluations: 5
[-0.70710678 -0.70710678]
&lt;/pre&gt;
&lt;p&gt;Copyright (C) 2013 by John Kitchin. See the &lt;a href="/copying.html"&gt;License&lt;/a&gt; for information about copying.&lt;p&gt;&lt;p&gt;&lt;a href="/org/2013/02/27/Constrained-optimization.org"&gt;org-mode source&lt;/a&gt;&lt;p&gt;]]></content:encoded>
    </item>
    <item>
      <title>The Gibbs free energy of a reacting mixture and the equilibrium composition</title>
      <link>https://kitchingroup.cheme.cmu.edu/blog/2013/02/18/The-Gibbs-free-energy-of-a-reacting-mixture-and-the-equilibrium-composition</link>
      <pubDate>Mon, 18 Feb 2013 09:00:00 EST</pubDate>
      <category><![CDATA[optimization]]></category>
      <guid isPermaLink="false">6p4az1VK91uyv4s50T5N3Bl2Do4=</guid>
      <description>The Gibbs free energy of a reacting mixture and the equilibrium composition</description>
      <content:encoded><![CDATA[


&lt;div id="table-of-contents"&gt;
&lt;h2&gt;Table of Contents&lt;/h2&gt;
&lt;div id="text-table-of-contents"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#sec-1"&gt;1. Summary&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
&lt;a href="http://matlab.cheme.cmu.edu/2011/12/20/the-gibbs-free-energy-of-a-reacting-mixture-and-the-equilibrium-composition/"&gt;Matlab post&lt;/a&gt; 
&lt;/p&gt;

&lt;p&gt;
In this post we derive the equations needed to find the equilibrium composition of a reacting mixture. We use the method of direct minimization of the Gibbs free energy of the reacting mixture.
&lt;/p&gt;

&lt;p&gt;
The Gibbs free energy of a mixture is defined as \(G = \sum\limits_j \mu_j n_j\) where \(\mu_j\) is the chemical potential of species \(j\), and it is temperature and pressure dependent, and \(n_j\) is the number of moles of species \(j\).
&lt;/p&gt;

&lt;p&gt;
We define the chemical potential as \(\mu_j = G_j^\circ + RT\ln a_j\), where \(G_j^\circ\) is the Gibbs energy in a standard state, and \(a_j\) is the activity of species \(j\) if the pressure and temperature are not at standard state conditions.
&lt;/p&gt;

&lt;p&gt;
If a reaction is occurring, then the number of moles of each species are related to each other through the reaction extent \(\epsilon\) and stoichiometric coefficients: \(n_j = n_{j0} + \nu_j \epsilon\). Note that the reaction extent has units of moles.
&lt;/p&gt;

&lt;p&gt;
Combining these three equations and expanding the terms leads to:
&lt;/p&gt;

&lt;p&gt;
$$G = \sum\limits_j n_{j0}G_j^\circ +\sum\limits_j \nu_j G_j^\circ \epsilon +RT\sum\limits_j(n_{j0} + \nu_j\epsilon)\ln a_j $$
&lt;/p&gt;

&lt;p&gt;
The first term is simply the initial Gibbs free energy that is present before any reaction begins, and it is a constant. It is difficult to evaluate, so we will move it to the left side of the equation in the next step, because it does not matter what its value is since it is a constant. The second term is related to the Gibbs free energy of reaction: \(\Delta_rG = \sum\limits_j \nu_j G_j^\circ\). With these observations we rewrite the equation as:
&lt;/p&gt;

&lt;p&gt;
$$G - \sum\limits_j n_{j0}G_j^\circ = \Delta_rG \epsilon +RT\sum\limits_j(n_{j0} + \nu_j\epsilon)\ln a_j $$
&lt;/p&gt;

&lt;p&gt;
Now, we have an equation that allows us to compute the change in Gibbs free energy as a function of the reaction extent, initial number of moles of each species, and the activities of each species. This difference in Gibbs free energy has no natural scale, and depends on the size of the system, i.e. on \(n_{j0}\). It is desirable to avoid this, so we now rescale the equation by the total initial moles present, \(n_{T0}\) and define a new variable \(\epsilon' = \epsilon/n_{T0}\), which is dimensionless. This leads to:
&lt;/p&gt;

&lt;p&gt;
$$ \frac{G - \sum\limits_j n_{j0}G_j^\circ}{n_{T0}} = \Delta_rG \epsilon' + RT \sum\limits_j(y_{j0} + \nu_j\epsilon')\ln a_j $$
&lt;/p&gt;

&lt;p&gt;
where \(y_{j0}\) is the initial mole fraction of species \(j\) present. The mole fractions are intensive properties that do not depend on the system size. Finally, we need to address \(a_j\). For an ideal gas, we know that \(A_j = \frac{y_j P}{P^\circ}\), where the numerator is the partial pressure of species \(j\) computed from the mole fraction of species \(j\) times the total pressure. To get the mole fraction we note:
&lt;/p&gt;

&lt;p&gt;
$$y_j = \frac{n_j}{n_T} = \frac{n_{j0} + \nu_j \epsilon}{n_{T0} + \epsilon \sum\limits_j \nu_j} = \frac{y_{j0} + \nu_j \epsilon'}{1 + \epsilon'\sum\limits_j \nu_j} $$
&lt;/p&gt;

&lt;p&gt;
This finally leads us to an equation that we can evaluate as a function of reaction extent:
&lt;/p&gt;

&lt;p&gt;
$$ \frac{G - \sum\limits_j n_{j0}G_j^\circ}{n_{T0}} = \widetilde{\widetilde{G}} = \Delta_rG \epsilon' + RT\sum\limits_j(y_{j0} + \nu_j\epsilon') \ln\left(\frac{y_{j0}+\nu_j\epsilon'}{1+\epsilon'\sum\limits_j\nu_j} \frac{P}{P^\circ}\right) $$
&lt;/p&gt;

&lt;p&gt;
we use a double tilde notation to distinguish this quantity from the quantity derived by Rawlings and Ekerdt which is further normalized by a factor of \(RT\). This additional scaling makes the quantities dimensionless, and makes the quantity have a magnitude of order unity, but otherwise has no effect on the shape of the graph.
&lt;/p&gt;

&lt;p&gt;
Finally, if we know the initial mole fractions, the initial total pressure, the Gibbs energy of reaction, and the stoichiometric coefficients, we can plot the scaled reacting mixture energy as a function of reaction extent. At equilibrium, this energy will be a minimum. We consider the example in Rawlings and Ekerdt where isobutane (I) reacts with 1-butene (B) to form 2,2,3-trimethylpentane (P). The reaction occurs at a total pressure of 2.5 atm at 400K, with equal molar amounts of I and B. The standard Gibbs free energy of reaction at 400K is -3.72 kcal/mol. Compute the equilibrium composition.
&lt;/p&gt;

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

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

&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;R&lt;/span&gt; = &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;8&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;314&lt;/span&gt;
&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;P&lt;/span&gt; = &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;250000&lt;/span&gt;  # &lt;span style="color: #ff0000; font-weight: bold;"&gt;Pa&lt;/span&gt;
&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;P0&lt;/span&gt; = &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;100000&lt;/span&gt; # &lt;span style="color: #ff0000; font-weight: bold;"&gt;Pa, approximately 1 atm&lt;/span&gt;
&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;T&lt;/span&gt; = &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;400&lt;/span&gt; # &lt;span style="color: #ff0000; font-weight: bold;"&gt;K&lt;/span&gt;

&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;Grxn&lt;/span&gt; = -&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;15564&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt; #&lt;span style="color: #ff0000; font-weight: bold;"&gt;J/mol&lt;/span&gt;
&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;yi0&lt;/span&gt; = &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;5&lt;/span&gt;; &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;yb0&lt;/span&gt; = &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;5&lt;/span&gt;; &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;yp0&lt;/span&gt; = &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;; # &lt;span style="color: #ff0000; font-weight: bold;"&gt;initial mole fractions&lt;/span&gt;

&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;yj0&lt;/span&gt; = np.array([yi0, yb0, yp0])
&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;nu_j&lt;/span&gt; = np.array([-&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;1&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;, -&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;1&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;, &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;1&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;])   # &lt;span style="color: #ff0000; font-weight: bold;"&gt;stoichiometric coefficients&lt;/span&gt;

&lt;span style="color: #8b0000;"&gt;def&lt;/span&gt; &lt;span style="color: #8b2323;"&gt;Gwigglewiggle&lt;/span&gt;(extentp):
    &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;diffg&lt;/span&gt; = Grxn * extentp
    &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;sum_nu_j&lt;/span&gt; = np.sum(nu_j)
    &lt;span style="color: #8b0000;"&gt;for&lt;/span&gt; i,y &lt;span style="color: #8b0000;"&gt;in&lt;/span&gt;&lt;span style="color: #cd0000;"&gt; enumerate&lt;/span&gt;(yj0):
        &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;x1&lt;/span&gt; = yj0[i] + nu_j[i] * extentp
        &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;x2&lt;/span&gt; = x1 / (&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;1&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt; + extentp*sum_nu_j)
        &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;diffg&lt;/span&gt; += R * T * x1 * np.log(x2 * P / P0)
    &lt;span style="color: #8b0000;"&gt;return&lt;/span&gt; diffg
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
There are bounds on how large \(\epsilon'\) can be. Recall that \(n_j = n_{j0} + \nu_j \epsilon\), and that \(n_j \ge 0\). Thus, \(\epsilon_{max} = -n_{j0}/\nu_j\), and the maximum value that \(\epsilon'\) can have is therefore \(-y_{j0}/\nu_j\) where \(y_{j0}&gt;0\). When there are multiple species, you need the smallest \(epsilon'_{max}\) to avoid getting negative mole numbers.
&lt;/p&gt;

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

&lt;pre class="src src-python"&gt;&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;epsilonp_max&lt;/span&gt; =&lt;span style="color: #cd0000;"&gt; min&lt;/span&gt;(-yj0[yj0 &amp;gt; &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;] / nu_j[yj0 &amp;gt; &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;])
&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;epsilonp&lt;/span&gt; = np.linspace(1e-&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;6&lt;/span&gt;, epsilonp_max, &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;1000&lt;/span&gt;);

&lt;span style="color: #8b0000;"&gt;import&lt;/span&gt; matplotlib.pyplot &lt;span style="color: #8b0000;"&gt;as&lt;/span&gt; plt

plt.plot(epsilonp,Gwigglewiggle(epsilonp))
plt.xlabel(&lt;span style="color: #228b22;"&gt;'$\epsilon$'&lt;/span&gt;)
plt.ylabel(&lt;span style="color: #228b22;"&gt;'Gwigglewiggle'&lt;/span&gt;)
plt.savefig(&lt;span style="color: #228b22;"&gt;'images/gibbs-minim-1.png'&lt;/span&gt;)
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
&amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; __main__:7: RuntimeWarning: divide by zero encountered in log
__main__:7: RuntimeWarning: invalid value encountered in multiply
[&amp;lt;matplotlib.lines.Line2D object at 0x10b1c7710&amp;gt;]
&amp;lt;matplotlib.text.Text object at 0x10b1c3d10&amp;gt;
&amp;lt;matplotlib.text.Text object at 0x10b1c9b90&amp;gt;
&lt;/pre&gt;


&lt;div class="figure"&gt;
&lt;p&gt;&lt;img src="/media/2013-02-18-The-Gibbs-free-energy-of-a-reacting-mixture-and-the-equilibrium-composition/gibbs-minim-1.png"&gt; 
&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;
Now we simply minimize our Gwigglewiggle function. Based on the figure above, the miminum is near 0.45.
&lt;/p&gt;

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

&lt;pre class="src src-python"&gt;&lt;span style="color: #8b0000;"&gt;from&lt;/span&gt; scipy.optimize &lt;span style="color: #8b0000;"&gt;import&lt;/span&gt; fminbound

&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;epsilonp_eq&lt;/span&gt; = fminbound(Gwigglewiggle, &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;4&lt;/span&gt;, &lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;5&lt;/span&gt;)
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; epsilonp_eq

plt.plot([epsilonp_eq], [Gwigglewiggle(epsilonp_eq)], &lt;span style="color: #228b22;"&gt;'ro'&lt;/span&gt;)
plt.savefig(&lt;span style="color: #228b22;"&gt;'images/gibbs-minim-2.png'&lt;/span&gt;)
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
&amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; 0.46959618249
&amp;gt;&amp;gt;&amp;gt; [&amp;lt;matplotlib.lines.Line2D object at 0x10d4d3e50&amp;gt;]
&lt;/pre&gt;


&lt;div class="figure"&gt;
&lt;p&gt;&lt;img src="/media/2013-02-18-The-Gibbs-free-energy-of-a-reacting-mixture-and-the-equilibrium-composition/gibbs-minim-2.png"&gt; 
&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;
To compute equilibrium mole fractions we do this:
&lt;/p&gt;
&lt;div class="org-src-container"&gt;

&lt;pre class="src src-python"&gt;&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;yi&lt;/span&gt; = (yi0 + nu_j[&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt;]*epsilonp_eq) / (&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;1&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt; + epsilonp_eq*np.sum(nu_j))
&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;yb&lt;/span&gt; = (yb0 + nu_j[&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;1&lt;/span&gt;]*epsilonp_eq) / (&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;1&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt; + epsilonp_eq*np.sum(nu_j))
&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;yp&lt;/span&gt; = (yp0 + nu_j[&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;2&lt;/span&gt;]*epsilonp_eq) / (&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;1&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt; + epsilonp_eq*np.sum(nu_j))

&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; yi, yb, yp

# &lt;span style="color: #ff0000; font-weight: bold;"&gt;or this&lt;/span&gt;
&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;y_j&lt;/span&gt; = (yj0 + np.dot(nu_j, epsilonp_eq)) / (&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;1&lt;/span&gt;.&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;0&lt;/span&gt; + epsilonp_eq*np.sum(nu_j))
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; y_j
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
&amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; 0.0573220186324 0.0573220186324 0.885355962735
&amp;gt;&amp;gt;&amp;gt; ... &amp;gt;&amp;gt;&amp;gt; [ 0.05732202  0.05732202  0.88535596]
&lt;/pre&gt;

&lt;p&gt;
\(K = \frac{a_P}{a_I a_B} = \frac{y_p P/P^\circ}{y_i P/P^\circ y_b P/P^\circ} = \frac{y_P}{y_i y_b}\frac{P^\circ}{P}\).
&lt;/p&gt;

&lt;p&gt;
We can express the equilibrium constant like this :\(K = \prod\limits_j a_j^{\nu_j}\), and compute it with a single line of code.
&lt;/p&gt;

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

&lt;pre class="src src-python"&gt;&lt;span style="color: #000000; background-color: #cccccc; font-weight: bold;"&gt;K&lt;/span&gt; = np.exp(-Grxn/R/T)
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; &lt;span style="color: #228b22;"&gt;'K from delta G '&lt;/span&gt;,K
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; &lt;span style="color: #228b22;"&gt;'K as ratio of mole fractions '&lt;/span&gt;,yp / (yi * yb) * P0 / P
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; &lt;span style="color: #228b22;"&gt;'compact notation: '&lt;/span&gt;,np.prod((y_j * P / P0)**nu_j)
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
K from delta G  107.776294742
K as ratio of mole fractions  107.779200065
compact notation:  107.779200065
&lt;/pre&gt;


&lt;p&gt;
These results are very close, and only disagree because of the default tolerance used in identifying the minimum of our function. You could tighten the tolerances by setting options to the fminbnd function.
&lt;/p&gt;

&lt;div id="outline-container-sec-1" class="outline-2"&gt;
&lt;h2 id="sec-1"&gt;&lt;span class="section-number-2"&gt;1&lt;/span&gt; Summary&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-1"&gt;
&lt;p&gt;
In this post we derived an equation for the Gibbs free energy of a reacting mixture and used it to find the equilibrium composition. In future posts we will examine some alternate forms of the equations that may be more useful in some circumstances.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Copyright (C) 2014 by John Kitchin. See the &lt;a href="/copying.html"&gt;License&lt;/a&gt; for information about copying.&lt;p&gt;&lt;p&gt;&lt;a href="/org/2013/02/18/The-Gibbs-free-energy-of-a-reacting-mixture-and-the-equilibrium-composition.org"&gt;org-mode source&lt;/a&gt;&lt;p&gt;&lt;p&gt;Org-mode version = 8.2.7c&lt;/p&gt;]]></content:encoded>
    </item>
    <item>
      <title>Find the minimum distance from a point to a curve.</title>
      <link>https://kitchingroup.cheme.cmu.edu/blog/2013/02/14/Find-the-minimum-distance-from-a-point-to-a-curve</link>
      <pubDate>Thu, 14 Feb 2013 09:00:00 EST</pubDate>
      <category><![CDATA[optimization]]></category>
      <guid isPermaLink="false">kHL8QHL_vZMcucCDIvaOOGqRcSY=</guid>
      <description>Find the minimum distance from a point to a curve.</description>
      <content:encoded><![CDATA[


&lt;p&gt;

A problem that can be cast as a constrained minimization problem is to find the minimum distance from a point to a curve. Suppose we have \(f(x) = x^2\), and the point (0.5, 2). what is the minimum distance from that point to \(f(x)\)?
&lt;/p&gt;

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

&lt;pre class="src src-python"&gt;&lt;span style="color: #8b0000;"&gt;import&lt;/span&gt; numpy &lt;span style="color: #8b0000;"&gt;as&lt;/span&gt; np
&lt;span style="color: #8b0000;"&gt;import&lt;/span&gt; matplotlib.pyplot &lt;span style="color: #8b0000;"&gt;as&lt;/span&gt; plt
&lt;span style="color: #8b0000;"&gt;from&lt;/span&gt; scipy.optimize &lt;span style="color: #8b0000;"&gt;import&lt;/span&gt; fmin_cobyla

P = (0.5, 2)

&lt;span style="color: #8b0000;"&gt;def&lt;/span&gt; &lt;span style="color: #8b2323;"&gt;f&lt;/span&gt;(x):
    &lt;span style="color: #8b0000;"&gt;return&lt;/span&gt; x**2

&lt;span style="color: #8b0000;"&gt;def&lt;/span&gt; &lt;span style="color: #8b2323;"&gt;objective&lt;/span&gt;(X):
    x,y = X
    &lt;span style="color: #8b0000;"&gt;return&lt;/span&gt; np.sqrt((x - P[0])**2 + (y - P[1])**2)

&lt;span style="color: #8b0000;"&gt;def&lt;/span&gt; &lt;span style="color: #8b2323;"&gt;c1&lt;/span&gt;(X):
    x,y = X
    &lt;span style="color: #8b0000;"&gt;return&lt;/span&gt; f(x) - y

X = fmin_cobyla(objective, x0=[0.5,0.5], cons=[c1])

&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; &lt;span style="color: #228b22;"&gt;'The minimum distance is {0:1.2f}'&lt;/span&gt;.format(objective(X))

&lt;span style="color: #ff0000; font-weight: bold;"&gt;# Verify the vector to this point is normal to the tangent of the curve&lt;/span&gt;
&lt;span style="color: #ff0000; font-weight: bold;"&gt;# position vector from curve to point&lt;/span&gt;
v1 = np.array(P) - np.array(X)
&lt;span style="color: #ff0000; font-weight: bold;"&gt;# position vector&lt;/span&gt;
v2 = np.array([1, 2.0 * X[0]])
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; &lt;span style="color: #228b22;"&gt;'dot(v1, v2) = '&lt;/span&gt;,np.dot(v1, v2)

x = np.linspace(-2, 2, 100)

plt.plot(x, f(x), &lt;span style="color: #228b22;"&gt;'r-'&lt;/span&gt;, label=&lt;span style="color: #228b22;"&gt;'f(x)'&lt;/span&gt;)
plt.plot(P[0], P[1], &lt;span style="color: #228b22;"&gt;'bo'&lt;/span&gt;, label=&lt;span style="color: #228b22;"&gt;'point'&lt;/span&gt;)
plt.plot([P[0], X[0]], [P[1], X[1]], &lt;span style="color: #228b22;"&gt;'b-'&lt;/span&gt;, label=&lt;span style="color: #228b22;"&gt;'shortest distance'&lt;/span&gt;)
plt.plot([X[0], X[0] + 1], [X[1], X[1] + 2.0 * X[0]], &lt;span style="color: #228b22;"&gt;'g-'&lt;/span&gt;, label=&lt;span style="color: #228b22;"&gt;'tangent'&lt;/span&gt;)
plt.axis(&lt;span style="color: #228b22;"&gt;'equal'&lt;/span&gt;)
plt.xlabel(&lt;span style="color: #228b22;"&gt;'x'&lt;/span&gt;)
plt.ylabel(&lt;span style="color: #228b22;"&gt;'y'&lt;/span&gt;)
plt.legend(loc=&lt;span style="color: #228b22;"&gt;'best'&lt;/span&gt;)
plt.savefig(&lt;span style="color: #228b22;"&gt;'images/min-dist-p-func.png'&lt;/span&gt;)
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
The minimum distance is 0.86
dot(v1, v2) =  0.000336477214214

   Normal return from subroutine COBYLA

   NFVALS =   44   F = 8.579598E-01    MAXCV = 0.000000E+00
   X = 1.300793E+00   1.692061E+00
&lt;/pre&gt;

&lt;p&gt;&lt;img src="/img/./images/min-dist-p-func.png"&gt;&lt;p&gt;

&lt;p&gt;
In the code above, we demonstrate that the point we find on the curve that minimizes the distance satisfies the property that a vector from that point to our other point is normal to the tangent of the curve at that point. This is shown by the fact that the dot product of the two vectors is very close to zero. It is not zero because of the accuracy criteria that is used to stop the minimization is not high enough.
&lt;/p&gt;
&lt;p&gt;Copyright (C) 2013 by John Kitchin. See the &lt;a href="/copying.html"&gt;License&lt;/a&gt; for information about copying.&lt;p&gt;&lt;p&gt;&lt;a href="/org/2013/02/14/Find-the-minimum-distance-from-a-point-to-a-curve..org"&gt;org-mode source&lt;/a&gt;&lt;p&gt;]]></content:encoded>
    </item>
  </channel>
</rss>
