    <rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:admin="http://webns.net/mvcb/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:content="http://purl.org/rss/1.0/modules/content/">
     <channel>
        <title>ACCU  :: Why Polynomial Approximation Won't Cure Your Calculus Blues</title>
        <link>https://members.accu.org/index.php/articles/1946</link>
        <description>Professionalism in Programming</description>
        <dc:language>en-us</dc:language> 
        <dc:creator>Administrator</dc:creator> 
        <admin:generatorAgent rdf:resource="http://www.xaraya.org" /> 
        <admin:errorReportsTo rdf:resource="mailto:webeditor@accu.org" />
       <sy:updatePeriod>hourly</sy:updatePeriod>
       <sy:updateFrequency>1</sy:updateFrequency>
       <docs>http://backend.userland.com/rss</docs>




<div class="xar-mod-head"><span class="xar-mod-title">Programming Topics + Overload Journal #106 - December 2011</span></div>

<table border="0" cellpadding="1" cellspacing="0">
    <tbody>
    <tr>
        <td valign="top">
            Browse in :
       </td>
       <td valign="top">

                                            <a href="https://members.accu.org/index.php/articles/">All</a>

                     &gt;                         <a href="https://members.accu.org/index.php/articles/c13/">Topics</a>

                     &gt;                         <a href="https://members.accu.org/index.php/articles/c65/">Programming</a>
<br />

                                            <a href="https://members.accu.org/index.php/articles/">All</a>

                     &gt;                         <a href="https://members.accu.org/index.php/articles/c76/">Journals</a>

                     &gt;                         <a href="https://members.accu.org/index.php/articles/c78/">Overload</a>

                     &gt;                         <a href="https://members.accu.org/index.php/articles/c303/">o106</a>
<br />

                                            <a href="https://members.accu.org/index.php/articles/c65-303/">Any of these categories</a>

                    -                        <a href="https://members.accu.org/index.php/articles/c65+303/">All of these categories</a>
<br />
</td>
   </tr>
   </tbody>
</table>




<div class="xar-error">
   <p>
 <strong>Note:</strong> when you create a new publication type,
the articles module will automatically use the templates
<em>user-display-[publicationtype].xt</em>
and <em>user-summary-[publicationtype].xt</em>.
If those templates do not exist when you try to preview or display a new article,
you'll get this warning :-)  Please place your own templates in themes/<em>yourtheme</em>/modules/articles . The templates will get the extension .xt there. </p>
</div>
<div class="xar-norm xar-standard-box-padding">
   <h1><strong>Title:</strong>&nbsp;Why Polynomial Approximation Won't Cure Your Calculus Blues</h1>
<p><strong>Author:</strong>&nbsp;Martin Moene</p>
<p>
<strong>Date:</strong> 02 December 2011 21:51:23 +00:00 or Fri, 02 December 2011 21:51:23 +00:00</p>
<p><strong>Summary:</strong>&nbsp;Weâ€™re still trying to find a good way to approach numerical computing. Richard Harris tries to get as close as possible.</p>
<p><strong>Body:</strong>&nbsp;<p>We began this arc of articles with a potted history of the differential calculus; from its origin in the infinitesimals of the 17<sup>th</sup> century, through its formalisation with Analysis in the 19<sup>th</sup> and the eventual bringing of rigour to the infinitesimals of the 20<sup>th</sup>.</p>

<p>We then covered Taylorâ€™s theorem which states that, for a function <em>f</em></p>

<p style="margin-left:1em"><img style="max-width:75%" src="http://accu.org/content/images/journals/ol106/Harris/Eqn-01.png" /></p>

<p>where <em>f'</em>(<em>x</em>) stands for the first derivative of f at <em>x</em>, <em>f&quot;</em>(<em>x</em>) for the second and <em>f</em>(<em>n</em>)(<em>x</em>) for the <em>n</em><sup>th</sup> with the convention that the 0<sup>th</sup> derivative of a function is the function itself.</p>

<p>We went on to use it in a comprehensive analysis of finite difference approximations to the derivative in which we discovered that their accuracy is a balance between approximation error and cancellation error, that it always depends upon the unknown behaviour of higher derivatives of the function and that improving accuracy by increasing the number of terms in the approximation is a rather tedious exercise.</p>

<p>Of these issues, the last rather stands out; from <em>tedious</em> to <em>automated</em> is often but a simple matter of programming. Of course we shall first have to figure out an algorithm, but fortunately we shall be able to do so with relatively ease using, you guessed it, Taylorâ€™s theorem.</p>

<h2>Finding the truncated Taylor series </h2>

<p>The first step is to truncate the series to the first <em>n</em> terms and make the simple substitution of <em>y</em> for <em>x</em>+<em>Î´</em>, giving</p>

<p style="margin-left:1em"><img style="max-width:65%" src="http://accu.org/content/images/journals/ol106/Harris/Eqn-02.png" /></p>

<p>or</p>

<p style="margin-left:1em"><img style="max-width:55%" src="http://accu.org/content/images/journals/ol106/Harris/Eqn-03.png" /></p>

<p>to <em>n</em><sup>th</sup> order in <em>y</em>-<em>x</em>.</p>

<p>Next we evaluate <em>f</em> at <em>n</em> points in the vicinity of <em>x</em>, say <em>y</em><sub>1</sub> to <em>y<sub>n</sub></em>, yielding</p>

<p style="margin-left:1em"><img style="max-width:80%" src="http://accu.org/content/images/journals/ol106/Harris/Eqn-04.png" /></p>

<p>Now the only unknowns in these equations are the derivatives of <em>f</em> so they are effectively a set of simultaneous linear equations of those derivatives and can be solved using the standard technique of eliminating variables.</p>

<p>By way of an example, consider the equations</p>

<p style="margin-left:1em"><img style="max-width:33%" src="http://accu.org/content/images/journals/ol106/Harris/Eqn-05.png" /></p>

<p>To recover the value of <em>x</em>, we begin by eliminating <em>z</em> from the set of equations which we can do with</p>

<p style="margin-left:1em"><img style="max-width:45%" src="http://accu.org/content/images/journals/ol106/Harris/Eqn-06.png" /></p>

<p>Next, we eliminate <em>y</em> with</p>

<p style="margin-left:1em"><img style="max-width:65%" src="http://accu.org/content/images/journals/ol106/Harris/Eqn-07.png" /></p>

<p>and hence <em>x</em> is equal to 1.</p>

<p>At each step in this process we transform a system of <em>n</em> equations of <em>n</em> unknowns into a system of <em>n</em>-1 equations of <em>n</em>-1 unknowns and it is therefore supremely well suited to a recursive implementation, as shown in listing 1.</p>

<table class="sidebartable">
	<tr>
		<td>
			<pre class="programlisting">
template&lt;class T&gt;
T
solve_impl(std::vector&lt;std::vector&lt;T&gt; &gt; &amp;lhs,
           std::vector&lt;T&gt; &amp;rhs,
           const size_t n)
{
  for(size_t i=0;i!=n;++i)
  {
    for(size_t j=0;j!=n;++j)
    {
      lhs[i][j] -= lhs[i][n]*lhs[n][j]/lhs[n][n];
    }
    rhs[i] -= lhs[i][n]*rhs[n]/lhs[n][n];
  }
  return n!=0 ? solve_impl(lhs, rhs, n-1) 
              : rhs[0]/lhs[0][0];
}
template&lt;class T&gt;
T
solve(std::vector&lt;std::vector&lt;T&gt; &gt; lhs,
      std::vector&lt;T&gt; rhs)
{
  if(rhs.empty())
     throw std::invalid_argument(&quot;&quot;);
  if(lhs.size()!=rhs.size())
     throw std::invalid_argument(&quot;&quot;);
  for(size_t i=0;i!=lhs.size();++i)
  {
    if(lhs[i].size()!=rhs.size())
       throw std::invalid_argument(&quot;&quot;);
  }
  return solve_impl(lhs, rhs, rhs.size()-1);
}
			</pre>
		</td>
	</tr>
	<tr>
		<td class="title">Listing 1</td>
	</tr>
</table>

<p>The first thing we need to do before we can use this to compute the derivative of a function is to decide what values we should choose for each <em>y<sub>i</sub></em>.</p>

<p>We can do this by extrapolating our analysis of finite differences; for an <em>n</em><sup>th</sup> order approximation, we should choose</p>

<p style="margin-left:1em"><img style="max-width:18%" src="http://accu.org/content/images/journals/ol106/Harris/Eqn-08.png" /></p>

<p>to yield an error of order <img style="max-width:5%" src="http://accu.org/content/images/journals/ol106/Harris/Eqn-08a.png" />.</p>

<p>Following a similar argument to that we used to choose the values of <em>Î´</em> for our finite difference approximations, we shall choose</p>

<p style="margin-left:1em"><img style="max-width:60%" src="http://accu.org/content/images/journals/ol106/Harris/Eqn-09.png" /></p>

<h2>Polynomial derivative approximation </h2>

<p>Listing 2 gives the class definition for a polynomial derivative approximation function object based upon these observations.</p>

<table class="sidebartable">
	<tr>
		<td>
			<pre class="programlisting">
template&lt;class F&gt;
class polynomial_derivative
{
public:
  typedef F function_type;
  typedef typename F::argument_type argument_type;
  typedef typename F::result_type result_type;

  polynomial_derivative(const function_type &amp;f,
                        unsigned long n);

  result_type operator()(
     const argument_type &amp;x) const;

private:
  function_type f_;
  unsigned long n_;
  argument_type ef_;
};
			</pre>
		</td>
	</tr>
	<tr>
		<td class="title">Listing 2</td>
	</tr>
</table>

<p>The constructor is relatively straightforward, as shown in listing 3. Note that the <code>epsilon</code> function is represented here by the typesetter-friendly abbreviation <em>eps</em><code>&lt;T&gt;</code>).</p>

<table class="sidebartable">
	<tr>
		<td>
			<pre class="programlisting">
template&lt;class F&gt;
polynomial_derivative&lt;F&gt;::polynomial_derivative(
  const function_type &amp;f, const unsigned long n)
: f_(f),
  n_(n),
  ef_(pow(argument_type(n+1)*
          eps&lt;argument_type&gt;(),
          argument_type(1)/argument_type(n+1)))
{
  if(n==0) throw std::invalid_argument(&quot;&quot;);
}
			</pre>
		</td>
	</tr>
	<tr>
		<td class="title">Listing 3</td>
	</tr>
</table>

<p>Once again we assume that we have specialisations of both <code>std::numeric_limits</code> and <code>pow</code> for the functionâ€™s argument type.</p>

<p>Listing 4 provide the definition of the function call operator.</p>

<table class="sidebartable">
	<tr>
		<td>
			<pre class="programlisting">
template&lt;class F&gt;
typename polynomial_derivative&lt;F&gt;::result_type
polynomial_derivative&lt;F&gt;::operator()(
   const argument_type &amp;x) const
{
  std::vector&lt;std::vector&lt;result_type&gt; &gt;
     lhs(n, std::vector&lt;result_type&gt;(n));
  std::vector&lt;result_type&gt; rhs(n);
  const argument_type abs_x =
     (x&gt;argument_type(0)) ? x : -x;
  const result_type fx = f_(x);
  for(size_t i=0;i!=n_;++i)
  {
    const argument_type y = 
      x +argument_type(i+1)*ef_*(
         abs_x+argument_type(1));
    result_type fac(1);
    for(size_t j=0;j!=n_;++j)
    {
      fac *= result_type(j+1);
      const result_type dn = pow(result_type(y-x),
                                 result_type(j+1));
      lhs[i][j] = dn/fac;
    }
    rhs[i] = f_(y)-f_(x);
  }
  return solve_impl(lhs, rhs, n_-1);
}

			</pre>
		</td>
	</tr>
	<tr>
		<td class="title">Listing 4</td>
	</tr>
</table>

<p>Note that if the argument and result types are different we shall very likely introduce a potential source of numerical error. Consequently we should ideally only use this class for functions with the same argument and result types.</p>

<p>Figure 1 plots the negation of the base 10 logarithm of the absolute error in this approximate derivative of the exponential function at zero, equivalent to the number of accurate decimal places, against the order of the approximation. The dashed line shows the negation of the base 10 logarithm of the expected order of the error in the approximation, <img style="max-width:5%" src="http://accu.org/content/images/journals/ol106/Harris/Eqn-08a.png" />.</p>

<table class="sidebartable">
	<tr>
		<td><img src="http://accu.org/content/images/journals/ol106/Harris/Harris-01.png" /></td>
	</tr>
	<tr>
		<td class="title">Figure 1</td>
	</tr>
</table>

<p>This certainly seems to be a step in the right direction; as we increase the order of our polynomials the number of accurate digits grow exactly as expected. We should consequently expect that by increasing the order of the polynomials we should bring the error arbitrarily close to <em>Îµ</em>, as shown in figure 2.</p>

<table class="sidebartable">
	<tr>
		<td><img src="http://accu.org/content/images/journals/ol106/Harris/Harris-02.png" /></td>
	</tr>
	<tr>
		<td class="title">Figure 2</td>
	</tr>
</table>

<p>Something has gone horribly wrong! As we expend ever increasing effort in improving the approximation, the errors are getting <em>worse</em>.</p>

<p>The reason for this disastrous result should be apparent if you spend a little time studying our simultaneous equation solver. We are performing large numbers of subtractions and divisions; if this doesnâ€™t scream cancellation error at you then you havenâ€™t been paying attention!</p>

<p>We can demonstrate the problem quite neatly if we use interval arithmetic to keep track of floating point errors. You will recall with interval arithmetic we keep track of an upper and lower bound on a floating point calculation by choosing the most pessimistic bounds on the result of an operation and then rounding the lower bound down and the upper bound up.</p>

<p>Figure 3 plots the approximate number of decimal places of agreement between the upper and lower bound of the interval result of the approximation as a solid line and the expected order of the error as a dashed line.</p>

<table class="sidebartable">
	<tr>
		<td><img src="http://accu.org/content/images/journals/ol106/Harris/Harris-03.png" /></td>
	</tr>
	<tr>
		<td class="title">Figure 3</td>
	</tr>
</table>

<p>This clearly shows that our higher order polynomial approximations are suffering from massive loss of precision. We canâ€™t plot results above 11<sup>th</sup> order since the intervals are infinite; these calculations have effectively lost <em>all</em> digits of precision.</p>

<p>Note that the intersection of the two curves provides a reasonable choice of 4 for the polynomial order we should use to calculate this specific derivative.</p>


<h2>Improving the algorithm </h2>

<p>Our algorithm is, in fact, the first step in the Gaussian elimination technique for solving simultaneous linear equations. The second stage is that of back-substitution during which the remaining unknowns are recovered by recursively substituting back to the equations each unknown as it is revealed. We are able to skip this step because we are only interested in one of the unknowns.</p>

<p>However, as it stands, our algorithm is not particularly well implemented.</p>

<p>Firstly, if every value in a column in the left hand side is zero, an unlikely but not impossible prospect, we shall end up dividing zero by zero.</p>

<p>Secondly, and more importantly, by eliminating rows in reverse order of the magnitude of <em>Î´</em>, we risk dividing by small values and unnecessarily amplifying rounding errors; a far better scheme is to choose the row with the largest absolute value in the column we are eliminating.</p>

<p>Listing 5 provides a more robust implementation of our simultaneous equation solver that corrects these weaknesses.</p>

<table class="sidebartable">
	<tr>
		<td>
			<pre class="programlisting">
template&lt;class T&gt;
T
solve_impl(std::vector&lt;std::vector&lt;T&gt; &gt; &amp;lhs,
           std::vector&lt;T&gt; &amp;rhs,
           const size_t n)
{
  T abs_nn = 
     lhs[n][n]&gt;T(0) ? lhs[n][n] : -lhs[n][n];
  for(size_t i=0;i!=n;++i)
  {
    const T abs_in = 
       lhs[i][n]&gt;T(0) ? lhs[i][n] : -lhs[i][n];
    if(abs_in&gt;abs_nn)
    {
      abs_nn = abs_in;
      std::swap(lhs[i], lhs[n]);
      std::swap(rhs[i], rhs[n]);
    }
  }
  if(abs_nn&gt;T(0))
  {
    for(size_t i=0;i!=n;++i)
    {
      for(size_t j=0;j!=n;++j)
      {
        lhs[i][j] -=
           lhs[i][n]*lhs[n][j]/lhs[n][n];
      }
      rhs[i] -= lhs[i][n]*rhs[n]/lhs[n][n];
    }
  }
  return n!=0 ? solve_impl(lhs, rhs, n-1) :
     rhs[0]/lhs[0][0];
}
			</pre>
		</td>
	</tr>
	<tr>
		<td class="title">Listing 5</td>
	</tr>
</table>

<p>Unfortunately, whilst this certainly improves the general stability of our algorithm, it does not make much difference to its accuracy.</p>

<p>A more effective approach is to try and minimise the number of arithmetic operations we require for our algorithm. We can do this by exploiting the very same observation that led us to the symmetric finite difference; that the difference between a pair of evaluations of the function an equal distance above and below <em>x</em> depends only on the odd ordered derivatives.</p>

<p style="margin-left:1em"><img style="max-width:100%" src="http://accu.org/content/images/journals/ol106/Harris/Eqn-10.png" /></p>

<p>This will yield an error of the same order as our first implementation with approximately half the number of simultaneous equations and consequently roughly one eighth of the arithmetic operations.</p>

<p>Listing 6 shows the required changes in the <code>polynomial_derivative</code> member functions.</p>

<table class="sidebartable">
	<tr>
		<td>
			<pre class="programlisting">
template&lt;class F&gt;
polynomial_derivative&lt;F&gt;::polynomial_derivative(
  const function_type &amp;f,
  const unsigned long n)
: f_(f), n_(n),
  ef_(pow(argument_type(2*n+1)
      *eps&lt;argument_type&gt;(),
      argument_type(1)/argument_type(2*n+1))),
  lhs_(n, std::vector&lt;result_type&gt;(n)),
  rhs_(n)
{
  if(n==0) throw std::invalid_argument(&quot;&quot;);
}
template&lt;class F&gt;
typename polynomial_derivative&lt;F&gt;::result_type
polynomial_derivative&lt;F&gt;::operator()(
   const argument_type &amp;x) const
{
  const argument_type abs_x =
     (x&gt; result_type(0)) ? x : -x;
  for(size_t i=0;i!=n_;++i)
  {
    const argument_type y =
       abs_x + argument_type(i+1)*ef_*(
       abs_x+argument_type(1));
    const argument_type d = y-abs_x;
    result_type fac(1);
    for(size_t j=0;j!=n_;++j)
    {
      const result_type dn =
         pow(result_type(d), result_type(2*j+1));
      lhs_[i][j] = dn/fac;
      fac *= 
         result_type(2*j+2) * result_type(2*j+3);
    }
    rhs_[i] = (f_(x+d)-f_(x-d)) / result_type(2);
  }
  return solve_impl(lhs_, rhs_, n_-1);
}
			</pre>
		</td>
	</tr>
	<tr>
		<td class="title">Listing 6</td>
	</tr>
</table>

<p>Figure 4 illustrates the impact upon the number of accurate digits in the results of our improved approximations.</p>

<table class="sidebartable">
	<tr>
		<td><img src="http://accu.org/content/images/journals/ol106/Harris/Harris-04.png" /></td>
	</tr>
	<tr>
		<td class="title">Figure 4</td>
	</tr>
</table>

<p>Clearly, this is far better than our first attempt; the observed error is consistently smaller than expected. However, on performing the calculation using interval arithmetic, we find that we are still restricted by cancellation error, as shown in figure 5.</p>

<table class="sidebartable">
	<tr>
		<td><img src="http://accu.org/content/images/journals/ol106/Harris/Harris-05.png" /></td>
	</tr>
	<tr>
		<td class="title">Figure 5</td>
	</tr>
</table>

<p>If we choose the point at which the observed cancellation error crosses the expected error, shown as a dashed line, we should choose a 7<sup>th</sup> order polynomial, corresponding again to 4 simultaneous equations.</p>

<p>These polynomial approximation algorithms are certainly an improvement upon finite differences since we have automated the annoying process of working out the formulae. However, we are still clearly constrained by the trade off between approximation and cancellation error.</p>

<p>This problem is fundamental to the basic approach of these algorithms; computing the coefficients of the Taylor series by solving simultaneous equations is inherently numerically unstable.</p>

<p>To understand why, it is illuminating to recast the problem as one of linear algebra.</p>

<h2>A problem of linear algebra </h2>

<p>By the rules of matrix and vector arithmetic we have, for a matrix <strong>M</strong> and vectors <strong>v</strong> and <strong>w</strong></p>

<p style="margin-left:1em"><img style="max-width:27%" src="http://accu.org/content/images/journals/ol106/Harris/Eqn-11.png" /></p>

<p>where subscripts <em>i</em>,<em>j</em> of a matrix denote the <em>j</em><sup>th</sup> column of the <em>i</em><sup>th</sup> row, a subscript <em>i</em> of a vector denotes its <em>i</em><sup>th</sup> element and the capital sigma denotes the sum of the expression to its right for every valid value of the symbol beneath it.</p>

<p>We can consequently represent the simultaneous equations of our improved algorithm with a single matrix-vector equation if we choose the elements of <strong>M</strong>, <strong>v</strong> and <strong>w</strong> such that</p>

<p style="margin-left:1em"><img style="max-width:60%" src="http://accu.org/content/images/journals/ol106/Harris/Eqn-12.png" /></p>

<p>for <em>i</em> and <em>j</em> from one up to and including <em>n</em>.</p>

<p>Those matrices with the same number of rows and columns, say <em>n</em>, whose elements are equal to one if the column index is equal to the row index and zero otherwise we call identity matrices, denoted by <strong>I</strong>. These have the property that for all vectors <strong>v</strong> with <em>n</em> elements</p>

<p style="margin-left:1em"><img style="max-width:12%" src="http://accu.org/content/images/journals/ol106/Harris/Eqn-13.png" /></p>

<p>If we can find a matrix <strong>M</strong><sup>-1</sup> such that</p>

<p style="margin-left:1em"><img style="max-width:20%" src="http://accu.org/content/images/journals/ol106/Harris/Eqn-14.png" /></p>

<p>known as the inverse of <strong>M</strong>, we have</p>

<p style="margin-left:1em"><img style="max-width:37%" src="http://accu.org/content/images/journals/ol106/Harris/Eqn-15.png" /></p>

<p>Our algorithm is therefore, in some sense, equivalent to inverting the matrix <strong>M</strong>.</p>

<p>That this can be a source of errors is best demonstrated by considering the geometric interpretation of linear algebra in which we treat a vector as Cartesian coordinates identifying a point.</p>

<p>For example, in two dimensions we have</p>

<p style="margin-left:1em"><img style="max-width:32%" src="http://accu.org/content/images/journals/ol106/Harris/Eqn-16.png" /></p>

<p>Hence every two by two matrix represents a function that takes a point in the plane and returns another point in the plane.</p>

<p>If it so happens that <em>c</em>Ã·<em>a</em> equals <em>d</em>Ã·<em>b</em> then we are in trouble since the two dimensional plane is transformed into a one dimensional line.</p>

<p>For example, if</p>

<p style="margin-left:1em"><img style="max-width:50%" src="http://accu.org/content/images/journals/ol106/Harris/Eqn-17.png" /></p>

<p>then</p>

<p style="margin-left:1em"><img style="max-width:22%" src="http://accu.org/content/images/journals/ol106/Harris/Eqn-18.png" /></p>

<p>Given the result of such a function there is no possible way to identify which of the infinite number of inputs might have yielded it.</p>

<p>Such matrices are known as singular matrices and attempting to invert them is analogous to dividing by zero. They are not, however, the cause of our problems. Rather, we are troubled by matrices that are <em>nearly</em> singular.</p>

<p>To understand what this means, consider the two dimensional matrix</p>

<p style="margin-left:1em"><img style="max-width:20%" src="http://accu.org/content/images/journals/ol106/Harris/Eqn-19.png" /></p>

<p>Figure 6 shows the result of multiplying a set of equally spaced points on the unit square by this matrix. The points clearly end up very close to a straight line rather than exactly upon one.</p>

<table class="sidebartable">
	<tr>
		<td><img src="http://accu.org/content/images/journals/ol106/Harris/Harris-06a.png" /></td>
	</tr>
	<tr>
		<td><img src="http://accu.org/content/images/journals/ol106/Harris/Harris-06b.png" /></td>
	</tr>
	<tr>
		<td class="title">Figure 6</td>
	</tr>
</table>

<p>To a mathematician, who is equipped with the infinitely precise real numbers, this isnâ€™t really a problem. To a computer programmer, who is not, it causes no end of trouble. One dimension of the original data has been compressed to small deviations from the line. These deviations are represented by the least significant digits in the coordinates of the points and much information about the original positions is necessarily rounded off and lost. Any attempt to reverse the process cannot recover the original positions of the points with very much accuracy; cancellation error is the inevitable consequence.</p>

<p>That our approximations should lead to such matrices becomes clear when you consider the right hand side of the equation. We choose <em>Î´</em> as small as possible in order to minimise approximation error with the result that the function is evaluated at a set of relatively close points. These evaluations will generally lie close to a straight line since the behaviour of the function will largely be governed by the first two terms in its Taylor series expansion.</p>

<p>Unfortunately, the approach we have used to mitigate cancellation error is horribly inefficient since we must solve a new system of simultaneous equations each time we increase the order of the approximating polynomials.</p>

<p>The question is whether there is there a better way?</p>

<h2>Riddersâ€™ algorithm </h2>

<p>The trick is to turn the problem on its head; rather than approximate the function with a polynomial and use its coefficients to approximate the derivative we treat the symmetric finite difference itself as a function of <em>Î´</em> and approximate <em>it</em> with a polynomial.</p>

<p>Specifically, if we define</p>

<p style="margin-left:1em"><img style="max-width:50%" src="http://accu.org/content/images/journals/ol106/Harris/Eqn-20.png" /></p>

<p>and we vigorously wave our hands, we have</p>

<p style="margin-left:1em"><img style="max-width:23%" src="http://accu.org/content/images/journals/ol106/Harris/Eqn-21.png" /></p>

<p>Now, if we approximate <img style="max-width:6%" src="http://accu.org/content/images/journals/ol106/Harris/Eqn-21a.png" /> with an <em>n</em><sup>th</sup> order polynomial <img style="max-width:6%" src="http://accu.org/content/images/journals/ol106/Harris/Eqn-21b.png" /> then we can approximate the derivative of <em>f</em> by evaluating it at 0</p>

<p style="margin-left:1em"><img style="max-width:24%" src="http://accu.org/content/images/journals/ol106/Harris/Eqn-22.png" /></p>

<p>We could try to find an explicit formula for <img style="max-width:6%" src="http://accu.org/content/images/journals/ol106/Harris/Eqn-21b.png" /> by evaluating <img style="max-width:6%" src="http://accu.org/content/images/journals/ol106/Harris/Eqn-21a.png" /> at <em>n</em> non-zero values of <em>Î´</em> and solving the resulting set of linear equations, as we did in our first polynomial algorithm, but we would end up with the same inefficiency as before when seeking the optimal order.</p>

<p>Riddersâ€™ algorithm [<a href="Harris.xml#[Ridders82]">Ridders82</a>] avoids this problem by evaluating the polynomial at zero without explicitly computing its coefficients. That this is even possible might seem a little unlikely, but nevertheless there is more than one way to do so.</p>

<h2>Nevilleâ€™s algorithm </h2>

<p>We can easily compute the value of the polynomial that passes through a pair of points, trivially a straight line, without explicitly computing its coefficients with a kind of pro-rata formula.</p>

<p>Given a pair of points (<em>x</em><sub>0</sub>,<em>y</em><sub>0</sub>) and (<em>x</em><sub>1</sub>,<em>y</em><sub>1</sub>), the line that passes through them consists of the points (<em>x</em>,<em>y</em>) where</p>

<p style="margin-left:1em"><img style="max-width:27%" src="http://accu.org/content/images/journals/ol106/Harris/Eqn-23.png" /></p>

<p>This can be rearranged to yield an equation for the line</p>

<p style="margin-left:1em"><img style="max-width:47%" src="http://accu.org/content/images/journals/ol106/Harris/Eqn-24.png" /></p>

<p>Nevilleâ€™s algorithm, described in <em>Numerical Recipes in C</em> [<a href="Harris.xml#[Press92]">Press92</a>], generalises this approach to any number of points, or equivalently any order of polynomial, and takes the form of a recursive set of formulae.</p>

<p>Given a set of <em>n</em> points (<em>x<sub>i</sub></em>,<em>y<sub>i</sub></em>) and a value <em>x</em> at which we wish to evaluate the polynomial that passes through them, we define the formulae</p>

<p style="margin-left:1em"><img style="max-width:120%" src="http://accu.org/content/images/journals/ol106/Harris/Eqn-25.png" /></p>

<p>from which we retrieve the value of the polynomial at <em>x</em> with <em>p</em><sub>1</sub>,<em><sub>n</sub></em>(<em>x</em>).</p>

<p>If we arrange the formulae in a triangular tableau, we can see that as the calculation progresses each value is derived from the pair above and below to its left.</p>

<p style="margin-left:1em"><img style="max-width:65%" src="http://accu.org/content/images/journals/ol106/Harris/Eqn-26.png" /></p>

<p>This shows why this algorithm is so useful; adding a new point introduces a new set of values without affecting those already calculated. We can consequently increase the order of the polynomial approximation without having to recalculate the entire tableau.</p>

<p>Further noting that we actually only really need the lower diagonal values when we do so allows us to implement Nevilleâ€™s algorithm with minimal memory usage, as shown in listing 7.</p>

<table class="sidebartable">
	<tr>
		<td>
			<pre class="programlisting">
template&lt;class T&gt;
class neville_interpolate
{
public:
  typedef T value_type;
  explicit neville_interpolate(
     const value_type &amp;xc);
  value_type refine(
     const value_type &amp;x, value_type y);
private:
  value_type xc_;
  std::vector&lt;value_type&gt; x_;
  std::vector&lt;value_type&gt; p_;
};
			</pre>
		</td>
	</tr>
	<tr>
		<td class="title">Listing 7</td>
	</tr>
</table>

<p>The constructor simply initialises the point at which the polynomial will be evaluated, <code>xc_</code>, and pre-allocates some space for the <code>x_</code> and <code>p_</code> buffers, as shown in listing 8.</p>

<table class="sidebartable">
	<tr>
		<td>
			<pre class="programlisting">
template&lt;class T&gt;
neville_interpolate&lt;T&gt;::neville_interpolate(
   const value_type &amp;xc)
: xc_(xc)
{
  x_.reserve(8);
  p_.reserve(8);
}
			</pre>
		</td>
	</tr>
	<tr>
		<td class="title">Listing 8</td>
	</tr>
</table>

<p>The body of the algorithm is in the <code>refine</code> member function, given in listing 9.</p>

<table class="sidebartable">
	<tr>
		<td>
			<pre class="programlisting">
template&lt;class T&gt;
typename neville_interpolate&lt;T&gt;::value_type
neville_interpolate&lt;T&gt;::refine(
   const value_type &amp;x, value_type y)
{
  std::vector&lt;value_type&gt;::reverse_iterator
     xi = x_.rbegin();
  std::vector&lt;value_type&gt;::reverse_iterator
     x0 = x_.rend();
  std::vector&lt;value_type&gt;::iterator 
     pi = p_.begin();
  while(xi!=x0)
  {
    std::swap(*pi, y);
    y = (y*(xc_-x) + *pi*(*xi-xc_)) / (*xi-x);
    ++xi;
    ++pi;
  }
  x_.push_back(x);
  p_.push_back(y);
  return y;
}
			</pre>
		</td>
	</tr>
	<tr>
		<td class="title">Listing 9</td>
	</tr>
</table>

<p>It might not be immediately obvious that this function correctly applies Nevilleâ€™s algorithm due to its rather terse implementation. I must confess that once I realised that I didnâ€™t need to keep the entire tableau in memory I couldnâ€™t resist rewriting it a few times to improve its efficiency still further.</p>

<p>The key to understanding its operation is the fact that the <em>x</em> values are iterated over in reverse order.</p>

<p>The tableau representation of the algorithm is also helpful in demonstrating why Nevilleâ€™s algorithm works, as shown in derivation 1.</p>

<table class="sidebartable">
	<tr>
		<td>
			<p>The result of Nevilleâ€™s algorithm for <em>n</em> points is trivially an <em>n</em>-1th order polynomial since each value in the tableau is one order higher in <em>x</em> than those to its left used to calculate it and the leftmost values are constants and hence of order 0.</p>
			
			<p>All that remains is to prove that it passes through the set of points (<em>x<sub>i</sub></em>,<em>y<sub>i</sub></em>).</p>
			
			<p>Consider a single point (<em>x<sub>k</sub></em>,<em>y<sub>k</sub></em>), some <em>i</em> less than <em>k</em> and some <em>j</em> greater than <em>k</em></p>
			
			<p style="margin-left:1em"><img style="max-width:90%" src="http://accu.org/content/images/journals/ol106/Harris/Eqn-27.png" /></p>
			
			<p>If we apply these equalities recursively, we have</p>
			
			<p style="margin-left:1em"><img style="max-width:25%" src="http://accu.org/content/images/journals/ol106/Harris/Eqn-28.png" /></p>
			
			<p>so that there are two diagonal lines of <em>y<sub>k</sub></em>â€™s running up and down from <em>p</em><sub>k,<em>k</em></sub>(<em>x<sub>k</sub></em>) on the left of the tableau, albeit not necessarily of the same length or of more than the leftmost value.</p>
			
			<p>If the final value lies on one of these diagonals, we are done. If not, then consider the value calculated from the second on each of these diagonals</p>
			
			<p style="margin-left:1em"><img style="max-width:110%" src="http://accu.org/content/images/journals/ol106/Harris/Eqn-29.png" /></p>
			
			<p>If we continue to fill in the tableau we find that every value lying between the diagonals in the tableau, including <em>p</em><sub>1,<em>n</em></sub>(<em>x<sub>k</sub></em>), must therefore be equal to <em>y<sub>k</sub></em>.</p>
		</td>	
	</tr>
	<tr>
		<td class="title">Derivation 1</td>
	</tr>
</table>

<h2>A better polynomial approximation </h2>

<p>We could use interval arithmetic again to decide when to stop increasing the order of the approximating polynomial. Riddersâ€™ algorithm takes a different approach, however.</p>

<p>As the order of the polynomial increases we expect the result to get progressively closer to the correct value until the point at which cancellation error takes over. The absolute differences between successive approximations should consequently form a more or less decreasing sequence up to that point.</p>

<p>The algorithm therefore begins with a relatively large <em>Î´</em> and, shrinking it at each step, computes the symmetric finite difference and refines the polynomial approximation for <em>Î´</em> equal to 0. The value with the smallest absolute difference from that of the previous step is used as the approximation of the derivative with the algorithm terminating when the step starts to grow.</p>

<p>Now there is almost certainly going to be some numerical noise in the value of the polynomial at each step, so rather than stop as soon as the difference increases we shall terminate when the difference is twice the smallest found so far.</p>

<p>Note that the smallest absolute difference provides a rough estimate of the error in the approximation.</p>

<p>The rate at which we shrink <em>Î´</em> and the termination criterion are based upon the implementation of this algorithm given in <em>Numerical Recipes in C</em>. There are a couple of important differences however.</p>

<p>Firstly, they use the penultimate value in the final row of the Nevilleâ€™s algorithm tableau as a second approximation to the derivative, having the same polynomial order as the previous iterationâ€™s approximation but with a smaller <em>Î´</em>. This is used to improve the termination criterion for the algorithm.</p>

<p>Secondly, we exploit the fact that the symmetric finite difference doesnâ€™t depend upon the sign of <em>Î´</em>. At each step we can refine the polynomial twice with the same symmetric difference; once with the positive <em>Î´</em>, once with its negation. This doubles the order of the approximating polynomial and forces it to be symmetric about 0 with no additional evaluations of the function but may lead to increased cost in the application of Nevilleâ€™s algorithm. This is a reasonable trade-off if the cost of the function evaluations is the primary concern.</p>

<p>Listing 10 shows the class definition for our implementation of Riddersâ€™ algorithm.</p>

<table class="sidebartable">
	<tr>
		<td>
			<pre class="programlisting">
template&lt;class F&gt;
class ridders_derivative
{
public:
  typedef F function_type;
  typedef typename F::argument_type argument_type;
  typedef typename F::result_type result_type;
  explict ridders_derivative(
     const function_type &amp;f)
  ridders_derivative(const function_type &amp;f,
                     const argument_type &amp;d);
  result_type apply(const argument_type &amp;x,
                    result_type &amp;err) const;
  result_type operator()(
     const argument_type &amp;x) const;
private:
  result_type refine(
     neville_interpolate&lt;result_type&gt; &amp;f,
     const argument_type &amp;x,
     const argument_type &amp;dx) const;
  function_type f_;
  argument_type d_;
};
			</pre>
		</td>
	</tr>
	<tr>
		<td class="title">Listing 10</td>
	</tr>
</table>

<p>The constructors are responsible for determining the initial <em>Î´</em>. To avoid rounding error we shall again use a multiple of this and 1 plus the absolute value of the point at which we wish to compute the derivative.</p>

<p>If the function is reasonably well behaved from the perspective of the symmetric finite difference, a choice of 10% is fairly reasonable and is used in the first constructor as given in listing 11.</p>

<table class="sidebartable">
	<tr>
		<td>
			<pre class="programlisting">
template&lt;class F&gt;
ridders_derivative&lt;F&gt;::ridders_derivative(
   const function_type &amp;f) :
   f_(f),
   d_(argument_type(1)/argument_type(10))
{
}
template&lt;class F&gt;
ridders_derivative&lt;F&gt;::ridders_derivative(
   const function_type &amp;f, 
   const argument_type &amp;d) 
: f_(f), d_(d)
{
}
			</pre>
		</td>
	</tr>
	<tr>
		<td class="title">Listing 11</td>
	</tr>
</table>

<p>Note that if the approximate error is large it is worth reconsidering the choice of the initial step size.</p>

<p>The <code>refine</code> member function is used to refine a <code>neville_interpolate</code> approximation of the derivative with the pair of equivalent symmetric finite difference approximations, as shown in listing 12.</p>

<table class="sidebartable">
	<tr>
		<td>
			<pre class="programlisting">
template&lt;class F&gt;
typename ridders_derivative&lt;F&gt;::result_type
ridders_derivative&lt;F&gt;::refine(
   neville_interpolate&lt;result_type&gt; &amp;f,
   const argument_type &amp;x,
   const argument_type &amp;dx) const
{
  const argument_type xa = x+dx;
  const argument_type xb = x-dx;
  const result_type df =
    (f_(xa)-f_(xb))/result_type(xa-xb);
  f.refine(result_type(xb-xa), df);
  return f.refine(result_type(xa-xb), df);
}
			</pre>
		</td>
	</tr>
	<tr>
		<td class="title">Listing 12</td>
	</tr>
</table>

<p>The <code>apply</code> member function implements the body of the algorithm as described with the function call operator simply returning its value without providing an error estimate, as illustrated in listing 13.</p>

<table class="sidebartable">
	<tr>
		<td>
			<pre class="programlisting">
template&lt;class F&gt;
typename ridders_derivative&lt;F&gt;::result_type
ridders_derivative&lt;F&gt;::apply(
   const argument_type &amp;x,
   result_type &amp;err) const
{
  static const argument_type c1 = 
     argument_type(7)/argument_type(5);
  static const argument_type c2 = c1*c1;
  neville_interpolate&lt;result_type&gt;
     f(result_type(0));
  argument_type abs_x =
     (x&gt;argument_type(0)) ? x : -x;
  argument_type dx = d_ *
     (abs_x+argument_type(1));
  result_type y0 = refine(f, x, dx);
  result_type y1 = refine(f, x, dx/=c1);
  result_type y = y1;
  err = (y0&lt;y1) ? (y1-y0) : (y0-y1);
  result_type new_err = err;
  while(new_err&lt;err+err)
  {
    y0 = y1;
    y1 = refine(f, x, dx/=c2);
    new_err = (y0&lt;y1) ? (y1-y0) : (y0-y1);
    if(new_err&lt;err)
    {
      err = new_err;
      y = y1;
    }
  }
  return y;
}
template&lt;class F&gt;
typename ridders_derivative&lt;F&gt;::result_type
ridders_derivative&lt;F&gt;::operator()(
   const argument_type &amp;x) const
{
  result_type err;
  return apply(x, err);
}
			</pre>
		</td>
	</tr>
	<tr>
		<td class="title">Listing 13</td>
	</tr>
</table>

<p>Figure 7 shows the actual (solid line) and estimated (dashed line) decimal digits of accuracy in the approximate derivative of the exponential function using our implementation of Riddersâ€™ algorithm.</p>

<table class="sidebartable">
	<tr>
		<td><img src="http://accu.org/content/images/journals/ol106/Harris/Harris-07.png" /></td>
	</tr>
	<tr>
		<td class="title">Figure 7</td>
	</tr>
</table>

<p>This represents a vast improvement in both accuracy and error estimation over every algorithm we have studied thus far. Figure 8 demonstrates the former with a comparison of the results of Riddersâ€™ algorithm (solid line) and a 7<sub>0</sub> order polynomial approximation (dashed line) using the improved version of our original algorithm based on the truncated Taylor series.</p>

<table class="sidebartable">
	<tr>
		<td><img src="http://accu.org/content/images/journals/ol106/Harris/Harris-08.png" /></td>
	</tr>
	<tr>
		<td class="title">Figure 8</td>
	</tr>
</table>

<p>The relative error in these results is on average roughly 2E-15, just one decimal order of magnitude worse than the theoretical minimum.</p>

<p>We have thus very nearly escaped the clutches of numerical error and I therefore declare polynomial approximation of the derivative, in this its final and most effective form, a giant amongst ducks; QUAAAACK!</p>

<h2>References and further reading </h2>

<p class="bibliomixed"><a id="[Press92]"></a>[Press92] Press, W.H. et al, <em>Numerical Recipes in C</em> (2nd ed.), Cambridge University Press, 1992.</p>

<p class="bibliomixed"><a id="[Ridders82]"></a>[Ridders82] Ridders, C.J.F., <em>Advances in Engineering Software</em>, Volume 4, Number 2., Elsevier, 1982.</p>
</p>
<p><strong>Notes:</strong>&nbsp;</p>
<p><em>More fields may be available via dynamicdata ..</em></p>
</div>
</channel>
</rss>
