Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
afeiguin
GitHub Repository: afeiguin/comp-phys
Path: blob/master/01_00_numerical_differentiation.ipynb
374 views
Kernel: Python 3

A primer on numerical differentiation

In order to numerically evaluate a derivative y′(x)=dy/dxy'(x)=dy/dx at point x0x_0, we approximate is by using finite differences: Therefore we find: dx≈Δx=x1−x0,dy≈Δy=y1−y0=y(x1)−y(x0)=y(x0+Δx)−y(x0),\begin{darray}{rcl} && dx \approx \Delta x &=&x_1-x_0, \\ && dy \approx \Delta y &=&y_1-y_0 = y(x_1)-y(x_0) = y(x_0+\Delta_x)-y(x_0),\end{darray}

Then we re-write the derivative in terms of discrete differences as: dydx≈ΔyΔx\frac{dy}{dx} \approx \frac{\Delta y}{\Delta x}

Example

Let's look at the accuracy of this approximation in terms of the interval Δx\Delta x. In our first example we will evaluate the derivative of y=x2y=x^2 at x=1x=1.

dx = 1. x = 1. while(dx > 1.e-10): dy = (x+dx)*(x+dx)-x*x d = dy / dx print("%6.0e %20.16f %20.16f" % (dx, d, d-2.)) dx = dx / 10.
1e+00 3.0000000000000000 1.0000000000000000 1e-01 2.1000000000000019 0.1000000000000019 1e-02 2.0100000000000007 0.0100000000000007 1e-03 2.0009999999996975 0.0009999999996975 1e-04 2.0000999999991720 0.0000999999991720 1e-05 2.0000100000139298 0.0000100000139298 1e-06 2.0000009999243669 0.0000009999243669 1e-07 2.0000001010878061 0.0000001010878061 1e-08 1.9999999878450576 -0.0000000121549424 1e-09 2.0000001654807416 0.0000001654807416 1e-10 2.0000001654807416 0.0000001654807416

Why is it that the sequence does not converge? This is due to the round-off errors in the representation of the floating point numbers. To see this, we can simply type:

((1.+0.0001)*(1+0.0001)-1)
0.0002000099999999172

Let's try using powers of 1/2

dx = 1. x = 1. while(dx > 1.e-10): dy = (x+dx)*(x+dx)-x*x d = dy / dx print("%8.5e %20.16f %20.16f" % (dx, d, d-2.)) dx = dx / 2.
1.00000e+00 3.0000000000000000 1.0000000000000000 5.00000e-01 2.5000000000000000 0.5000000000000000 2.50000e-01 2.2500000000000000 0.2500000000000000 1.25000e-01 2.1250000000000000 0.1250000000000000 6.25000e-02 2.0625000000000000 0.0625000000000000 3.12500e-02 2.0312500000000000 0.0312500000000000 1.56250e-02 2.0156250000000000 0.0156250000000000 7.81250e-03 2.0078125000000000 0.0078125000000000 3.90625e-03 2.0039062500000000 0.0039062500000000 1.95312e-03 2.0019531250000000 0.0019531250000000 9.76562e-04 2.0009765625000000 0.0009765625000000 4.88281e-04 2.0004882812500000 0.0004882812500000 2.44141e-04 2.0002441406250000 0.0002441406250000 1.22070e-04 2.0001220703125000 0.0001220703125000 6.10352e-05 2.0000610351562500 0.0000610351562500 3.05176e-05 2.0000305175781250 0.0000305175781250 1.52588e-05 2.0000152587890625 0.0000152587890625 7.62939e-06 2.0000076293945312 0.0000076293945312 3.81470e-06 2.0000038146972656 0.0000038146972656 1.90735e-06 2.0000019073486328 0.0000019073486328 9.53674e-07 2.0000009536743164 0.0000009536743164 4.76837e-07 2.0000004768371582 0.0000004768371582 2.38419e-07 2.0000002384185791 0.0000002384185791 1.19209e-07 2.0000001192092896 0.0000001192092896 5.96046e-08 2.0000000596046448 0.0000000596046448 2.98023e-08 2.0000000298023224 0.0000000298023224 1.49012e-08 2.0000000149011612 0.0000000149011612 7.45058e-09 2.0000000000000000 0.0000000000000000 3.72529e-09 2.0000000000000000 0.0000000000000000 1.86265e-09 2.0000000000000000 0.0000000000000000 9.31323e-10 2.0000000000000000 0.0000000000000000 4.65661e-10 2.0000000000000000 0.0000000000000000 2.32831e-10 2.0000000000000000 0.0000000000000000 1.16415e-10 2.0000000000000000 0.0000000000000000

In addition, one could consider the midpoint difference, defined as: dy≈Δy=y(x0+Δx2)−y(x0−Δx2). dy \approx \Delta y = y(x_0+\frac{\Delta_x}{2})-y(x_0-\frac{\Delta_x}{2}).

For a more complex function we need to import it from the math module. For instance, let's calculate the derivative of sin(x)sin(x) at x=Ï€/4x=\pi/4, including both the forward and midpoint differences.

from math import sin, sqrt, pi dx = 1. while(dx > 1.e-10): x = pi/4. d1 = sin(x+dx) - sin(x); #forward d2 = sin(x+dx*0.5) - sin(x-dx*0.5); # midpoint d1 = d1 / dx; d2 = d2 / dx; print("%8.5e %20.16f %20.16f %20.16f %20.16f" % (dx, d1, d1-sqrt(2.)/2., d2, d2-sqrt(2.)/2.) ) dx = dx / 2.
1.00000e+00 0.2699544827129282 -0.4371522984736194 0.6780100988420897 -0.0290966823444578 5.00000e-01 0.5048856975964859 -0.2022210835900616 0.6997640691250939 -0.0073427120614536 2.50000e-01 0.6118351194488110 -0.0952716617377366 0.7052667953545546 -0.0018399858319930 1.25000e-01 0.6611301360648314 -0.0459766451217162 0.7066465151141266 -0.0004602660724210 6.25000e-02 0.6845566203276618 -0.0225501608588857 0.7069916978116613 -0.0001150833748863 3.12500e-02 0.6959440534591224 -0.0111627277274252 0.7070780092891873 -0.0000287718973603 1.56250e-02 0.7015538499518499 -0.0055529312346977 0.7070995881463560 -0.0000071930401916 7.81250e-03 0.7043374663312676 -0.0027693148552800 0.7071049829223881 -0.0000017982641595 3.90625e-03 0.7057239167465070 -0.0013828644400405 0.7071063316202526 -0.0000004495662950 1.95312e-03 0.7064157978737740 -0.0006909833127736 0.7071066687949497 -0.0000001123915979 9.76562e-04 0.7067614018394579 -0.0003453793470897 0.7071067530886239 -0.0000000280979237 4.88281e-04 0.7069341196006462 -0.0001726615859013 0.7071067741617298 -0.0000000070248177 2.44141e-04 0.7070204574165473 -0.0000863237700003 0.7071067794299779 -0.0000000017565697 1.22070e-04 0.7070636210573866 -0.0000431601291609 0.7071067807464715 -0.0000000004400761 6.10352e-05 0.7070852015604032 -0.0000215796261444 0.7071067810775276 -0.0000000001090200 3.05176e-05 0.7070959914854029 -0.0000107897011447 0.7071067811557441 -0.0000000000308035 1.52588e-05 0.7071013863605913 -0.0000053948259563 0.7071067811848479 -0.0000000000016996 7.62939e-06 0.7071040837909095 -0.0000026973956381 0.7071067811775720 -0.0000000000089756 3.81470e-06 0.7071054324915167 -0.0000013486950309 0.7071067811921239 0.0000000000055763 1.90735e-06 0.7071061068563722 -0.0000006743301754 0.7071067811921239 0.0000000000055763 9.53674e-07 0.7071064440533519 -0.0000003371331957 0.7071067811921239 0.0000000000055763 4.76837e-07 0.7071066126227379 -0.0000001685638097 0.7071067811921239 0.0000000000055763 2.38419e-07 0.7071066969074309 -0.0000000842791167 0.7071067811921239 0.0000000000055763 1.19209e-07 0.7071067392826080 -0.0000000419039395 0.7071067811921239 0.0000000000055763 5.96046e-08 0.7071067616343498 -0.0000000195521977 0.7071067802608013 -0.0000000009257463 2.98023e-08 0.7071067690849304 -0.0000000121016172 0.7071067802608013 -0.0000000009257463 1.49012e-08 0.7071067765355110 -0.0000000046510366 0.7071067690849304 -0.0000000121016172 7.45058e-09 0.7071067690849304 -0.0000000121016172 0.7071067690849304 -0.0000000121016172 3.72529e-09 0.7071067690849304 -0.0000000121016172 0.7071067988872528 0.0000000177007052 1.86265e-09 0.7071068286895752 0.0000000475030276 0.7071067690849304 -0.0000000121016172 9.31323e-10 0.7071068286895752 0.0000000475030276 0.7071067094802856 -0.0000000717062619 4.65661e-10 0.7071068286895752 0.0000000475030276 0.7071065902709961 -0.0000001909155515 2.32831e-10 0.7071065902709961 -0.0000001909155515 0.7071070671081543 0.0000002859216067 1.16415e-10 0.7071075439453125 0.0000007627587649 0.7071065902709961 -0.0000001909155515

A more in-depth discussion about round-off errors in numerical differentiation can be found here

Special functions in numpy

numpy provides a simple method diff() to calculate the numerical derivatives of a dataset stored in an array by forward differences. The function gradient() will calculate the derivatives by midpoint (or central) difference, that provides a more accurate result.

%matplotlib inline import numpy as np from matplotlib import pyplot y = lambda x: x*x x1 = np.arange(0,10,1) x2 = np.arange(0,10,0.1) y1 = np.gradient(y(x1), 1.) print (y1) pyplot.plot(x1,np.gradient(y(x1),1.),'r--o'); pyplot.plot(x1[:x1.size-1],np.diff(y(x1))/np.diff(x1),'b--x');
[ 1. 2. 4. 6. 8. 10. 12. 14. 16. 17.]
Image in a Jupyter notebook

Notice above that gradient() uses forward and backward differences at the two ends.

pyplot.plot(x2,np.gradient(y(x2),0.1),'b--o');
Image in a Jupyter notebook

More discussion about numerical differenciation, including higher order methods with error extrapolation can be found here.

The module scipy also includes methods to accurately calculate derivatives:

from scipy.misc import derivative y = lambda x: x**2 dx = 1. x = 1. while(dx > 1.e-10): d = derivative(y, x, dx, n=1, order=3) print("%6.0e %20.16f %20.16f" % (dx, d, d-2.)) dx = dx / 10.
1e+00 2.0000000000000000 0.0000000000000000 1e-01 2.0000000000000004 0.0000000000000004 1e-02 2.0000000000000018 0.0000000000000018 1e-03 1.9999999999998352 -0.0000000000001648 1e-04 1.9999999999992246 -0.0000000000007754 1e-05 2.0000000000020002 0.0000000000020002 1e-06 2.0000000000019997 0.0000000000019997 1e-07 2.0000000000575109 0.0000000000575109 1e-08 1.9999999933961727 -0.0000000066038273 1e-09 2.0000000544584391 0.0000000544584391 1e-10 2.0000001654807416 0.0000001654807416

One way to improve the roundoff errors is by simply using the decimal package

from decimal import Decimal dx = Decimal("1.") while(dx >= Decimal("1.e-10")): x = Decimal("1.") dy = (x+dx)*(x+dx)-x*x d = dy / dx print("%6.0e %20.16f %20.16f" % (dx, d, d-Decimal("2."))) dx = dx / Decimal("10.")
1e+00 3.0000000000000000 1.0000000000000000 1e-01 2.1000000000000001 0.1000000000000000 1e-02 2.0099999999999998 0.0100000000000000 1e-03 2.0009999999999999 0.0010000000000000 1e-04 2.0001000000000002 0.0001000000000000 1e-05 2.0000100000000001 0.0000100000000000 1e-06 2.0000010000000001 0.0000010000000000 1e-07 2.0000000999999998 0.0000001000000000 1e-08 2.0000000099999999 0.0000000100000000 1e-09 2.0000000010000001 0.0000000010000000 1e-10 2.0000000001000000 0.0000000001000000