<?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>Some of this, sum of that</title>
      <link>https://kitchingroup.cheme.cmu.edu/blog/2013/02/02/Some-of-this-sum-of-that</link>
      <pubDate>Sat, 02 Feb 2013 09:00:00 EST</pubDate>
      <category><![CDATA[recursive]]></category>
      <category><![CDATA[miscellaneous]]></category>
      <guid isPermaLink="false">jwqtEjOstEi3dSxDeCT9k6P1SCs=</guid>
      <description>Some of this, sum of that</description>
      <content:encoded><![CDATA[


&lt;p&gt;
&lt;a href="http://matlab.cheme.cmu.edu/2012/05/29/some-of-this-sum-of-that/" &gt;Matlab plot&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;
Python provides a sum function to compute the sum of a list. However, the sum function does not work on every arrangement of numbers, and it certainly does not work on nested lists. We will solve this problem with recursion.
&lt;/p&gt;

&lt;p&gt;
Here is a simple example.
&lt;/p&gt;

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

&lt;pre class="src src-python"&gt;v = [1, 2, 3, 4, 5, 6, 7, 8, 9] &lt;span style="color: #ff0000; font-weight: bold;"&gt;# a list&lt;/span&gt;
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; sum(v)

v = (1, 2, 3, 4, 5, 6, 7, 8, 9)  &lt;span style="color: #ff0000; font-weight: bold;"&gt;# a tuple&lt;/span&gt;
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; sum(v)
&lt;/pre&gt;
&lt;/div&gt;

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

&lt;p&gt;
If you have data in a dictionary, sum works by default on the keys. You can give the sum function the values like this.
&lt;/p&gt;

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

&lt;pre class="src src-python"&gt;v = {&lt;span style="color: #228b22;"&gt;'a'&lt;/span&gt;:1, &lt;span style="color: #228b22;"&gt;'b'&lt;/span&gt;:3, &lt;span style="color: #228b22;"&gt;'c'&lt;/span&gt;:4}
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; sum(v.values())
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
8
&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; Nested lists&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-1"&gt;
&lt;p&gt;
Suppose now we have nested lists. This kind of structured data might come up if you had grouped several things together. For example, suppose we have 5 departments, with 1, 5, 15, 7 and 17 people in them, and in each department they are divided into groups.
&lt;/p&gt;

&lt;p&gt;
Department 1: 1 person
Department 2: group of 2 and group of 3
Department 3: group of 4 and 11, with a subgroups of 5 and 6 making
              up the group of 11.
Department 4: 7 people
Department 5: one group of 8 and one group of 9.
&lt;/p&gt;

&lt;p&gt;
We might represent the data like this nested list. Now, if we want to compute the total number of people, we need to add up each group. We cannot simply sum the list, because some elements are single numbers, and others are lists, or lists of lists. We need to recurse through each entry until we get down to a number, which we can add to the running sum. 
&lt;/p&gt;
&lt;div class="org-src-container"&gt;

&lt;pre class="src src-python"&gt;v = [1, 
    [2, 3],
    [4, [5, 6]],
    7,
    [8,9]]

&lt;span style="color: #8b0000;"&gt;def&lt;/span&gt; recursive_&lt;span style="color: #8b0000;"&gt;sum&lt;/span&gt;(X):
    &lt;span style="color: #228b22;"&gt;'compute sum of arbitrarily nested lists'&lt;/span&gt;
    s = 0 &lt;span style="color: #ff0000; font-weight: bold;"&gt;# initial value of the sum&lt;/span&gt;

    &lt;span style="color: #8b0000;"&gt;for&lt;/span&gt; i &lt;span style="color: #8b0000;"&gt;in&lt;/span&gt; &lt;span style="color: #8b0000;"&gt;range&lt;/span&gt;(len(X)):
        &lt;span style="color: #8b0000;"&gt;import&lt;/span&gt; types  &lt;span style="color: #ff0000; font-weight: bold;"&gt;# we use this to test if we got a number&lt;/span&gt;
        &lt;span style="color: #8b0000;"&gt;if&lt;/span&gt; &lt;span style="color: #8b0000;"&gt;isinstance&lt;/span&gt;(X[i], (types.IntType,
                             types.LongType,
                             types.FloatType,
                             types.ComplexType)):
            &lt;span style="color: #ff0000; font-weight: bold;"&gt;# this is the terminal step&lt;/span&gt;
            s += X[i]
        &lt;span style="color: #8b0000;"&gt;else:&lt;/span&gt;
            &lt;span style="color: #ff0000; font-weight: bold;"&gt;# we did not get a number, so we recurse&lt;/span&gt;
            s += recursive_&lt;span style="color: #8b0000;"&gt;sum&lt;/span&gt;(X[i])
    &lt;span style="color: #8b0000;"&gt;return&lt;/span&gt; s

&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; recursive_&lt;span style="color: #8b0000;"&gt;sum&lt;/span&gt;(v)
&lt;span style="color: #8b0000;"&gt;print&lt;/span&gt; recursive_&lt;span style="color: #8b0000;"&gt;sum&lt;/span&gt;([1,2,3,4,5,6,7,8,9]) &lt;span style="color: #ff0000; font-weight: bold;"&gt;# test on non-nested list&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;

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

&lt;p&gt;
In &lt;a href="http://matlab.cheme.cmu.edu/2012/05/28/lather-rinse-and-repeat/" &gt;Post 1970&lt;/a&gt; we examined recursive functions that could be replaced by loops. Here we examine a function that can only work with recursion because the nature of the nested data structure is arbitrary. There are arbitary branches and depth in the data structure. Recursion is nice because you do not have to define that structure in advance.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Copyright (C) 2013 by John Kitchin. See the &lt;a href="/copying.html"&gt;License&lt;/a&gt; for information about copying.&lt;p&gt;&lt;p&gt;&lt;a href="/org/2013/02/02/Some-of-this,-sum-of-that.org"&gt;org-mode source&lt;/a&gt;&lt;p&gt;]]></content:encoded>
    </item>
    <item>
      <title>Smooth transitions between discontinuous functions</title>
      <link>https://kitchingroup.cheme.cmu.edu/blog/2013/01/31/Smooth-transitions-between-discontinuous-functions</link>
      <pubDate>Thu, 31 Jan 2013 09:00:00 EST</pubDate>
      <category><![CDATA[nonlinear algebra]]></category>
      <category><![CDATA[miscellaneous]]></category>
      <guid isPermaLink="false">rmiqZrxW635gVhJnFXGF_G_0V5Y=</guid>
      <description>Smooth transitions between discontinuous functions</description>
      <content:encoded><![CDATA[



&lt;p&gt;
&lt;a href="http://matlab.cheme.cmu.edu/2011/10/30/smooth-transitions-between-discontinuous-functions/" &gt;original post&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;
In &lt;a href="http://matlab.cheme.cmu.edu/2011/10/27/compute-pipe-diameter/" &gt;Post 1280&lt;/a&gt; we used a correlation for the Fanning friction factor for turbulent flow in a pipe. For laminar flow (Re &amp;lt; 3000), there is another correlation that is commonly used: \(f_F = 16/Re\). Unfortunately, the correlations for laminar flow and turbulent flow have different values at the transition that should occur at Re = 3000. This discontinuity can cause a lot of problems for numerical solvers that rely on derivatives.
&lt;/p&gt;

&lt;p&gt;
Today we examine a strategy for smoothly joining these two functions. First we define the two functions.
&lt;/p&gt;

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

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

&lt;span style="color: #8b0000;"&gt;def&lt;/span&gt; &lt;span style="color: #8b2323;"&gt;fF_laminar&lt;/span&gt;(Re):
    &lt;span style="color: #8b0000;"&gt;return&lt;/span&gt; 16.0 / Re

&lt;span style="color: #8b0000;"&gt;def&lt;/span&gt; &lt;span style="color: #8b2323;"&gt;fF_turbulent_unvectorized&lt;/span&gt;(Re):
    &lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;Nikuradse correlation for turbulent flow&lt;/span&gt;
    &lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;1/np.sqrt(f) = (4.0*np.log10(Re*np.sqrt(f))-0.4)&lt;/span&gt;
    &lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;we have to solve this equation to get f&lt;/span&gt;
    &lt;span style="color: #8b0000;"&gt;def&lt;/span&gt; &lt;span style="color: #8b2323;"&gt;func&lt;/span&gt;(f):
        &lt;span style="color: #8b0000;"&gt;return&lt;/span&gt; 1/np.sqrt(f) - (4.0*np.log10(Re*np.sqrt(f))-0.4)
    fguess = 0.01
    f, = fsolve(func, fguess)
    &lt;span style="color: #8b0000;"&gt;return&lt;/span&gt; f

&lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;this enables us to pass vectors to the function and get vectors as&lt;/span&gt;
&lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;solutions&lt;/span&gt;
fF_turbulent = np.vectorize(fF_turbulent_unvectorized)
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
Now we plot the correlations.
&lt;/p&gt;

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

&lt;pre class="src src-python"&gt;Re1 = np.linspace(500, 3000)
f1 = fF_laminar(Re1)

Re2 = np.linspace(3000, 10000)
f2 = fF_turbulent(Re2)

plt.figure(1); plt.clf()
plt.plot(Re1, f1, label=&lt;span style="color: #228b22;"&gt;'laminar'&lt;/span&gt;)
plt.plot(Re2, f2, label=&lt;span style="color: #228b22;"&gt;'turbulent'&lt;/span&gt;)
plt.xlabel(&lt;span style="color: #228b22;"&gt;'Re'&lt;/span&gt;)
plt.ylabel(&lt;span style="color: #228b22;"&gt;'$f_F$'&lt;/span&gt;)
plt.legend()
plt.savefig(&lt;span style="color: #228b22;"&gt;'images/smooth-transitions-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; &amp;gt;&amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt; &amp;lt;matplotlib.figure.Figure object at 0x051FF630&amp;gt;
[&amp;lt;matplotlib.lines.Line2D object at 0x05963C10&amp;gt;]
[&amp;lt;matplotlib.lines.Line2D object at 0x0576DD70&amp;gt;]
&amp;lt;matplotlib.text.Text object at 0x0577CFF0&amp;gt;
&amp;lt;matplotlib.text.Text object at 0x05798790&amp;gt;
&amp;lt;matplotlib.legend.Legend object at 0x05798030&amp;gt;
&lt;/pre&gt;

&lt;p&gt;&lt;img src="/img/./images/smooth-transitions-1.png"&gt;&lt;p&gt;

&lt;p&gt;
You can see the discontinuity at Re = 3000. What we need is a method to join these two functions smoothly. We can do that with a sigmoid function.
Sigmoid functions
&lt;/p&gt;

&lt;p&gt;
A sigmoid function smoothly varies from 0 to 1 according to the equation: \(\sigma(x) = \frac{1}{1 + e^{-(x-x0)/\alpha}}\). The transition is centered on \(x0\), and \(\alpha\) determines the width of the transition.
&lt;/p&gt;

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

&lt;pre class="src src-python"&gt;x = np.linspace(-4,4);
y = 1.0 / (1 + np.exp(-x / 0.1))
plt.figure(2); plt.clf()
plt.plot(x, y)
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.title(&lt;span style="color: #228b22;"&gt;'$\sigma(x)$'&lt;/span&gt;)
plt.savefig(&lt;span style="color: #228b22;"&gt;'images/smooth-transitions-sigma.png'&lt;/span&gt;)
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
&amp;gt;&amp;gt;&amp;gt; &amp;lt;matplotlib.figure.Figure object at 0x0596CF10&amp;gt;
[&amp;lt;matplotlib.lines.Line2D object at 0x05A26D90&amp;gt;]
&amp;lt;matplotlib.text.Text object at 0x059A6050&amp;gt;
&amp;lt;matplotlib.text.Text object at 0x059AF0D0&amp;gt;
&amp;lt;matplotlib.text.Text object at 0x059BEA30&amp;gt;
&lt;/pre&gt;

&lt;p&gt;&lt;img src="/img/./images/smooth-transitions-sigma.png"&gt;&lt;p&gt;

&lt;p&gt;
If we have two functions, \(f_1(x)\) and \(f_2(x)\) we want to smoothly join, we do it like this: \(f(x) = (1-\sigma(x))f_1(x) + \sigma(x)f_2(x)\). There is no formal justification for this form of joining, it is simply a mathematical convenience to get a numerically smooth function. Other functions besides the sigmoid function could also be used, as long as they smoothly transition from 0 to 1, or from 1 to zero.
&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;fanning_friction_factor&lt;/span&gt;(Re):
    &lt;span style="color: #228b22;"&gt;'''combined, continuous correlation for the fanning friction factor.&lt;/span&gt;
&lt;span style="color: #228b22;"&gt;    the alpha parameter is chosen to provide the desired smoothness.&lt;/span&gt;
&lt;span style="color: #228b22;"&gt;    The transition region is about +- 4*alpha. The value 450 was&lt;/span&gt;
&lt;span style="color: #228b22;"&gt;    selected to reasonably match the shape of the correlation&lt;/span&gt;
&lt;span style="color: #228b22;"&gt;    function provided by Morrison (see last section of this file)'''&lt;/span&gt;
    sigma =  1. / (1 + np.exp(-(Re - 3000.0) / 450.0));
    f = (1-sigma) * fF_laminar(Re) + sigma * fF_turbulent(Re)
    &lt;span style="color: #8b0000;"&gt;return&lt;/span&gt; f

Re = np.linspace(500,10000);
f = fanning_friction_factor(Re);

&lt;span style="color: #ff0000; font-weight: bold;"&gt;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;add data to figure 1&lt;/span&gt;
plt.figure(1)
plt.plot(Re,f, label=&lt;span style="color: #228b22;"&gt;'smooth transition'&lt;/span&gt;)
plt.xlabel(&lt;span style="color: #228b22;"&gt;'Re'&lt;/span&gt;)
plt.ylabel(&lt;span style="color: #228b22;"&gt;'$f_F$'&lt;/span&gt;)
plt.legend()
plt.savefig(&lt;span style="color: #228b22;"&gt;'images/smooth-transitions-3.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; &amp;gt;&amp;gt;&amp;gt; ... &amp;lt;matplotlib.figure.Figure object at 0x051FF630&amp;gt;
[&amp;lt;matplotlib.lines.Line2D object at 0x05786310&amp;gt;]
&amp;lt;matplotlib.text.Text object at 0x0577CFF0&amp;gt;
&amp;lt;matplotlib.text.Text object at 0x05798790&amp;gt;
&amp;lt;matplotlib.legend.Legend object at 0x05A302B0&amp;gt;
&lt;/pre&gt;

&lt;p&gt;&lt;img src="/img/./images/smooth-transitions-3.png"&gt;&lt;p&gt;

&lt;p&gt;
You can see that away from the transition the combined function is practically equivalent to the original two functions. That is because away from the transition the sigmoid function is 0 or 1. Near Re = 3000 is a smooth transition from one curve to the other curve.
&lt;/p&gt;

&lt;p&gt;
&lt;a href="http://www.chem.mtu.edu/~fmorriso/DataCorrelationForSmoothPipes2010.pdf" &gt;Morrison&lt;/a&gt; derived a single function for the friction factor correlation over all Re: \(f = \frac{0.0076\left(\frac{3170}{Re}\right)^{0.165}}{1 + \left(\frac{3171}{Re}\right)^{7.0}} + \frac{16}{Re}\). Here we show the comparison with the approach used above. The friction factor differs slightly at high Re, because Morrison's is based on the Prandlt correlation, while the work here is based on the Nikuradse correlation. They are similar, but not the same.
&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;# &lt;/span&gt;&lt;span style="color: #ff0000; font-weight: bold;"&gt;add this correlation to figure 1&lt;/span&gt;
h, = plt.plot(Re, 16.0/Re + (0.0076 * (3170 / Re)**0.165) / (1 + (3170.0 / Re)**7))

ax = plt.gca()
handles, labels = ax.get_legend_handles_labels()

handles.append(h)
labels.append(&lt;span style="color: #228b22;"&gt;'Morrison'&lt;/span&gt;)
ax.legend(handles, labels)
plt.savefig(&lt;span style="color: #228b22;"&gt;'images/smooth-transitions-morrison.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; &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;lt;matplotlib.legend.Legend object at 0x05A5AEB0&amp;gt;
&lt;/pre&gt;

&lt;p&gt;&lt;img src="/img/./images/smooth-transitions-morrison.png"&gt;&lt;p&gt;

&lt;div id="outline-container-1" class="outline-2"&gt;
&lt;h2 id="sec-1"&gt;&lt;span class="section-number-2"&gt;1&lt;/span&gt; Summary&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-1"&gt;
&lt;p&gt;
The approach demonstrated here allows one to smoothly join two discontinuous functions that describe physics in different regimes, and that must transition over some range of data. It should be emphasized that the method has no physical basis, it simply allows one to create a mathematically smooth function, which could be necessary for some optimizers or solvers to work.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Copyright (C) 2013 by John Kitchin. See the &lt;a href="/copying.html"&gt;License&lt;/a&gt; for information about copying.&lt;p&gt;&lt;p&gt;&lt;a href="/org/2013/01/31/Smooth-transitions-between-discontinuous-functions.org"&gt;org-mode source&lt;/a&gt;&lt;p&gt;]]></content:encoded>
    </item>
  </channel>
</rss>
