<?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>Potential gotchas in linear algebra in numpy</title>
      <link>https://kitchingroup.cheme.cmu.edu/blog/2013/03/12/Potential-gotchas-in-linear-algebra-in-numpy</link>
      <pubDate>Tue, 12 Mar 2013 22:19:53 EDT</pubDate>
      <category><![CDATA[linear algebra]]></category>
      <category><![CDATA[gotcha]]></category>
      <guid isPermaLink="false">TZM2RkZNaMrwMjC4SLUQnVHywSU=</guid>
      <description>Potential gotchas in linear algebra in numpy</description>
      <content:encoded><![CDATA[


&lt;p&gt;
Numpy has some gotcha features for linear algebra purists. The first is that a 1d array is neither a row, nor a column vector. That is, \(a\) = \(a^T\) if \(a\) is a 1d array. That means you can take the dot product of \(a\) with itself, without transposing the second argument. This would not be allowed in Matlab.
&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

a = np.array([0, 1, 2])
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; a.shape
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; a
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; a.T

&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt;
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; np.dot(a, a)
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; np.dot(a, a.T)
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
&amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; (3L,)
[0 1 2]
[0 1 2]
&amp;gt;&amp;gt;&amp;gt;
5
5
&lt;/pre&gt;

&lt;p&gt;
Compare the previous behavior with this 2d array. In this case, you cannot take the dot product of \(b\) with itself, because the dimensions are incompatible. You must transpose the second argument to make it dimensionally consistent. Also, the result of the dot product is not a simple scalar, but a 1 &amp;times; 1 array.
&lt;/p&gt;

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

&lt;pre class="src src-python"&gt;b = np.array([[0, 1, 2]])
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; b.shape
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; b
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; b.T

&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; np.dot(b, b)    &lt;span style="color: #ff0000; font-weight: bold;"&gt;# this is not ok, the dimensions are wrong.&lt;/span&gt;
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; np.dot(b, b.T)
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; np.dot(b, b.T).shape
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
(1L, 3L)
[[0 1 2]]
[[0]
 [1]
 [2]]
&amp;gt;&amp;gt;&amp;gt; Traceback (most recent call last):
  File "&amp;lt;stdin&amp;gt;", line 1, in &amp;lt;module&amp;gt;
ValueError: objects are not aligned
[[5]]
(1L, 1L)
&lt;/pre&gt;

&lt;p&gt;
Try to figure this one out! x is a column vector, and y is a 1d vector. Just by adding them you get a 2d array.
&lt;/p&gt;
&lt;div class="org-src-container"&gt;

&lt;pre class="src src-python"&gt;x = np.array([[2], [4], [6], [8]])
y = np.array([1, 1, 1, 1, 1, 2])
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; x + y
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
&amp;gt;&amp;gt;&amp;gt; [[ 3  3  3  3  3  4]
 [ 5  5  5  5  5  6]
 [ 7  7  7  7  7  8]
 [ 9  9  9  9  9 10]]
&lt;/pre&gt;

&lt;p&gt;
Or this crazy alternative way to do the same thing.
&lt;/p&gt;
&lt;div class="org-src-container"&gt;

&lt;pre class="src src-python"&gt;x = np.array([2, 4, 6, 8])
y = np.array([1, 1, 1, 1, 1, 1, 2])

&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; x[:, np.newaxis] + y
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
&amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; [[ 3  3  3  3  3  3  4]
 [ 5  5  5  5  5  5  6]
 [ 7  7  7  7  7  7  8]
 [ 9  9  9  9  9  9 10]]
&lt;/pre&gt;

&lt;p&gt;
In the next example,  we have a 3 element vector and a 4 element vector. We convert \(b\) to a 2D array with np.newaxis, and compute the outer product of the two arrays. The result is a 4 &amp;times; 3 array.
&lt;/p&gt;
&lt;div class="org-src-container"&gt;

&lt;pre class="src src-python"&gt;a = np.array([1, 2, 3])
b = np.array([10, 20, 30, 40])

&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; a * b[:, np.newaxis]
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
&amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; [[ 10  40  90]
 [ 20  80 180]
 [ 30 120 270]
 [ 40 160 360]]
&lt;/pre&gt;

&lt;p&gt;
These concepts are known in numpy as array broadcasting. See &lt;a href="http://www.scipy.org/EricsBroadcastingDoc" &gt;http://www.scipy.org/EricsBroadcastingDoc&lt;/a&gt; and &lt;a href="http://docs.scipy.org/doc/numpy/user/basics.broadcasting.html" &gt;http://docs.scipy.org/doc/numpy/user/basics.broadcasting.html&lt;/a&gt; for more details.
&lt;/p&gt;

&lt;p&gt;
These are points to keep in mind, as the operations do not strictly follow the conventions of linear algebra, and may be confusing at times.
&lt;/p&gt;
&lt;p&gt;Copyright (C) 2013 by John Kitchin. See the &lt;a href="/copying.html"&gt;License&lt;/a&gt; for information about copying.&lt;p&gt;&lt;p&gt;&lt;a href="/org/2013/03/12/Potential-gotchas-in-linear-algebra-in-numpy.org"&gt;org-mode source&lt;/a&gt;&lt;p&gt;]]></content:encoded>
    </item>
    <item>
      <title>Integrating the Fermi distribution to compute entropy</title>
      <link>https://kitchingroup.cheme.cmu.edu/blog/2013/03/06/Integrating-the-Fermi-distribution-to-compute-entropy</link>
      <pubDate>Wed, 06 Mar 2013 09:39:42 EST</pubDate>
      <category><![CDATA[gotcha]]></category>
      <category><![CDATA[integration]]></category>
      <category><![CDATA[dft]]></category>
      <guid isPermaLink="false">8aXB-dJ08KwHSy4vBZ0lsSIkMoU=</guid>
      <description>Integrating the Fermi distribution to compute entropy</description>
      <content:encoded><![CDATA[



&lt;p&gt;
The Fermi distribution is defined by \(f(\epsilon) = \frac{1}{e^{(\epsilon - \mu)/(k T)} + 1}\). This function describes the occupation of energy levels at temperatures above absolute zero. We use this function to compute electronic entropy in a metal, which contains an integral of \(\int n(\epsilon) (f \ln f + (1 - f) \ln (1-f)) d\epsilon\), where \(n(\epsilon)\) is the electronic density of states. Here we plot the Fermi distribution function. It shows that well below the Fermi level the states are fully occupied, and well above the Fermi level, they are unoccupied. Near the Fermi level, the states go from occupied to unoccupied smoothly.
&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

mu = 0
k = 8.6e-5
T = 1000

&lt;span style="color: #8b0000;"&gt;def&lt;/span&gt; &lt;span style="color: #8b2323;"&gt;f&lt;/span&gt;(e):
    &lt;span style="color: #8b0000;"&gt;return&lt;/span&gt; 1.0 / (np.exp((e - mu)/(k*T)) + 1)

espan = np.linspace(-10, 10, 200)
plt.plot(espan, f(espan))
plt.ylim([-0.1, 1.1])
plt.savefig(&lt;span style="color: #228b22;"&gt;'images/fermi-entropy-integrand-1.png'&lt;/span&gt;)
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;&lt;img src="/img/./images/fermi-entropy-integrand-1.png"&gt;&lt;p&gt;

&lt;p&gt;
Let us consider a simple density of states function, just a parabola. This could represent a s-band for example. We will use this function to explore the integral.
&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

mu = 0
k = 8.6e-5
T = 1000

&lt;span style="color: #8b0000;"&gt;def&lt;/span&gt; &lt;span style="color: #8b2323;"&gt;f&lt;/span&gt;(e):
    &lt;span style="color: #8b0000;"&gt;return&lt;/span&gt; 1.0 / (np.exp((e - mu)/(k*T)) + 1)

&lt;span style="color: #8b0000;"&gt;def&lt;/span&gt; &lt;span style="color: #8b2323;"&gt;dos&lt;/span&gt;(e):
    d = (np.ones(e.shape) - 0.03 * e**2) 
    &lt;span style="color: #8b0000;"&gt;return&lt;/span&gt; d * (d &amp;gt; 0)
espan = np.linspace(-10, 10)

plt.plot(espan, dos(espan), label=&lt;span style="color: #228b22;"&gt;'Total dos'&lt;/span&gt;)
plt.plot(espan, f(espan) * dos(espan), label=&lt;span style="color: #228b22;"&gt;'Occupied states'&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/fermi-entropy-integrand-2.png'&lt;/span&gt;)
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
&lt;p&gt;&lt;img src="/img/./images/fermi-entropy-integrand-2.png"&gt;&lt;p&gt;
Now, we consider the integral to compute the electronic entropy. The entropy is proportional to this integral.
&lt;/p&gt;

&lt;p&gt;
\( \int n(\epsilon) (f \ln f + (1 - f) \ln (1-f)) d\epsilon \)
&lt;/p&gt;

&lt;p&gt;
It looks straightforward to compute, but it turns out there is a wrinkle. Evaluating the integrand leads to &lt;code&gt;nan&lt;/code&gt; elements because the ln(0) is -&amp;infin;. 
&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
mu = 0
k = 8.6e-5
T = 100

&lt;span style="color: #8b0000;"&gt;def&lt;/span&gt; &lt;span style="color: #8b2323;"&gt;fermi&lt;/span&gt;(e):
    &lt;span style="color: #8b0000;"&gt;return&lt;/span&gt; 1.0 / (np.exp((e - mu)/(k*T)) + 1)

espan = np.array([-20, -10, -5, 0.0, 5, 10])
f = fermi(espan)

&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; f * np.log(f)
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; (1 - f) * np.log(1 - f)
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
[  0.00000000e+000   0.00000000e+000   0.00000000e+000  -3.46573590e-001
  -1.85216532e-250               nan]
[        nan         nan         nan -0.34657359  0.          0.        ]
&lt;/pre&gt;

&lt;p&gt;
In this case, these &lt;code&gt;nan&lt;/code&gt; elements should be equal to zero (x ln(x) goes to zero as x goes to zero). So, we can just ignore those elements in the integral. Here is how to do that.
&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

mu = 0
k = 8.6e-5
T = 1000

&lt;span style="color: #8b0000;"&gt;def&lt;/span&gt; &lt;span style="color: #8b2323;"&gt;fermi&lt;/span&gt;(e):
    &lt;span style="color: #8b0000;"&gt;return&lt;/span&gt; 1.0 / (np.exp((e - mu)/(k*T)) + 1)

&lt;span style="color: #8b0000;"&gt;def&lt;/span&gt; &lt;span style="color: #8b2323;"&gt;dos&lt;/span&gt;(e):
    d = (np.ones(e.shape) - 0.03 * e**2) 
    &lt;span style="color: #8b0000;"&gt;return&lt;/span&gt; d * (d &amp;gt; 0)

espan = np.linspace(-20, 10)
f = fermi(espan)
n = dos(espan)

g = n * (f * np.log(f) + (1 - f) * np.log(1 - f))

&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; np.trapz(espan, g) &lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;nan because of the nan in the g vector&lt;/span&gt;
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; g

plt.plot(espan, g)
plt.savefig(&lt;span style="color: #228b22;"&gt;'images/fermi-entropy-integrand-3.png'&lt;/span&gt;)

&lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;find the elements that are not nan&lt;/span&gt;
ind = np.logical_not(np.isnan(g))

&lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;evaluate the integrand for only those points&lt;/span&gt;
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; np.trapz(espan[ind], g[ind])
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
nan
[             nan              nan              nan              nan
              nan              nan              nan              nan
              nan              nan              nan              nan
              nan              nan              nan              nan
              nan              nan              nan              nan
              nan              nan              nan              nan
              nan              nan              nan              nan
  -9.75109643e-14  -1.05987106e-10  -1.04640574e-07  -8.76265644e-05
  -4.92684641e-02  -2.91047740e-01  -7.75652579e-04  -1.00962241e-06
  -1.06972936e-09  -1.00527877e-12  -8.36436686e-16  -6.48930917e-19
  -4.37946336e-22  -2.23285389e-25  -1.88578082e-29   0.00000000e+00
   0.00000000e+00   0.00000000e+00   0.00000000e+00   0.00000000e+00
   0.00000000e+00   0.00000000e+00]
0.208886080897
&lt;/pre&gt;

&lt;p&gt;&lt;img src="/img/./images/fermi-entropy-integrand-3.png"&gt;&lt;p&gt;

&lt;p&gt;
The integrand is pretty well behaved in the figure above. You do not see the full range of the x-axis, because the integrand evaluates to &lt;code&gt;nan&lt;/code&gt; for very negative numbers. This causes the &lt;code&gt;trapz&lt;/code&gt; function to return &lt;code&gt;nan&lt;/code&gt; also. We can solve the problem by only integrating the parts that are not &lt;code&gt;nan&lt;/code&gt;. We have to use numpy.logical&lt;sub&gt;not&lt;/sub&gt; to get an element-wise array of which elements are not &lt;code&gt;nan&lt;/code&gt;. In this example, the integrand is not well sampled, so the area under that curve may not be very accurate. 
&lt;/p&gt;
&lt;p&gt;Copyright (C) 2013 by John Kitchin. See the &lt;a href="/copying.html"&gt;License&lt;/a&gt; for information about copying.&lt;p&gt;&lt;p&gt;&lt;a href="/org/2013/03/06/Integrating-the-Fermi-distribution-to-compute-entropy.org"&gt;org-mode source&lt;/a&gt;&lt;p&gt;]]></content:encoded>
    </item>
  </channel>
</rss>
