Numerical Derivatives

How to take the numerical derivative of a function $f(x)$.

Taylor series approach

Review Taylor Series

$$f(x) = f(x_0) + f^{\prime}(x_0)(x-x_0) + \frac{1}{2}f^{\prime\prime}(x_0)(x-x_0)^2 + \frac{1}{3!}f^{\prime\prime\prime}(x_0)(x-x_0)^3+\ldots$$

$$ f(x) = \sum_{m=0}^n \frac{1}{m!}f^{(m)}(x_0)(x-x_0)^m + R_n(\xi),$$

$$ R_n(\xi) = \frac{1}{(n+1)!}f^{(n+1)}(\xi)(\xi-x_0)^{n+1},\,\,\, x\le\xi\le x_0.$$

Goal:

Example 1

Solve for $f^{\prime}(x_i)$:

$$ f^{\prime}(x_i) = \frac{f(x_{i+1})-f(x_i)}{\Delta x} - \left[\frac{1}{2}f^{\prime\prime}\Delta x^2 / \Delta x+\ldots\right].$$ $$ f^{\prime}(x_i) = \frac{f(x_{i+1})-f(x_i)}{\Delta x} + \mathcal{O}(\Delta x).$$

Example 2

Subtract the second equation from the first $\rightarrow$

$$ f_{i+1}-f_{i-1} = 2f_i^{\prime}\Delta x + \left[\frac{2}{6}f_i^{\prime\prime\prime}\Delta x^3+\ldots\right].$$

Solve for $f_i^{\prime}$:

$$f_i^{\prime} = \frac{f_{i+1}-f_{i-1}}{2\Delta x} - \left[\frac{1}{6}f_i^{\prime\prime\prime}\Delta x^3+\ldots\right]/\Delta x.$$

Or,

$$f_i^{\prime} = \frac{f_{i+1}-f_{i-1}}{2\Delta x} + \mathcal{O}(\Delta x^2).$$

Example 3

$$f_{i+1}+f_{i-1} = 2f_i + \frac{2}{2}f_i^{\prime\prime}\Delta x^2 + \left[\frac{2}{24}f_i^{(iv)}\Delta x^4+\ldots\right].$$

Solve for $f_i^{\prime\prime}$

$$f_{i}^{\prime\prime} = \frac{f_{i-1}-2f_i+f_{i+1}}{\Delta x^2} - \mathcal{O}(\Delta x^2).$$

Taylor Table

fd aprox $$f_0^{\prime} = (c_{-2}f_{-2} + c_{-1}f_{-1} + c_0f_0 + c_1f_1 + c_2f_2]/\Delta x,$$

$$f_0^{\prime\prime} = (d_{-2}f_{-2} + d_{-1}f_{-1} + d_0f_0 + d_1f_1 + d_2f_2]/\Delta x^2.$$

$$\ldots$$ taylor table $$f_{-2} = (1)f_0 + (-2)f_0^{\prime}\Delta x + \frac{(-2)^2}{2!}f_0^{\prime\prime}\Delta x^2 + \frac{(-2)^3}{3!}f_0^{\prime\prime\prime}\Delta x^3 + \frac{(-2)^4}{4!}f_0^{iv}\Delta x^4 + \frac{(-2)^5}{5!}f_0^{v}\Delta x^5 \ldots.$$

Example

$$\rightarrow c = \left[ \begin{array}{c} -\frac{1}{2} \\ 0 \\ \frac{1}{2} \end{array} \right] $$

This gives

$$f_0^{\prime}\approx\frac{1}{\Delta x}\left[-\frac{1}{2}f_{-1} + 0f_0 + \frac{1}{2}f_1\right] = \frac{f_1 - f_{-1}}{2\Delta x}.$$

* Here, we have a 3 point stencil, where the base point is 2 of 1, 2, or 3 (the middle). * Order: $\vec{R}_3^T\cdot\vec{c} = 1/6 \ne 0\rightarrow k=3 \rightarrow$ order = 3-1 = 2$^{nd}$ order approximation. * That is row 3 dotted with vector c = $1/6\ne0$. * $[-1/6,\, 0,\, 1/6]\cdot [-1/2,\,0,\,1/2] = 1/6$.

Example

$$\left[ \begin{array}{ccc} 1 & 1 & 1 \\ 0 & 1 & 2 \\ 0 & \frac{1}{2} & 2 \\ \end{array} \right] \left[ \begin{array}{c} c_{-1} \\ c_0 \\ c_1 \end{array} \right] = \left[ \begin{array}{c} 0 \\ 1 \\ 0 \end{array} \right] $$$$\rightarrow c = \left[ \begin{array}{c} -\frac{3}{2} \\ 2 \\ -\frac{1}{2} \end{array} \right] $$

This gives

$$f_0^{\prime}\approx\frac{1}{\Delta x}\left[-\frac{3}{2}f_0 + 2f_1 - \frac{1}{2}f_2\right] $$

Taylor table code

Finite difference approximations to arbitrary order derivatives, using arbitrary stencils.

$$\frac{d^n u}{dx^n} = \frac{1}{\Delta x^n}\sum_k c_ku_k$$

As an example, a second order central second difference:

$$\frac{d^2 u}{dx^2} = \frac{1}{\Delta x^2}(c_{j-1} + c_ju_j + c_{j+1}u_{j+1}),$$

with $c$’s are 1, -2, 1
We assume here that $j=0$ is the base point.

import numpy as np

#----------------------

oDer = 1     # order of the derivative to approximate (like 1 for f', or 2 for f", etc.)
npts = 9     # number of points in the stencil. Min is oDer + 1.
jb   = 5     # position of the base point in the stencil, starting at 1
             # the setup: oDer=1, npts=3, jb=2, will give a second order central difference approx to f'

#----------------------

jp = np.arange(1-jb, npts-jb+1)   # index in grid: like [-2 -1 0 1 2 ] for jp=3 for npts=5
a_kj = np.zeros((npts,npts))
    
j = np.arange(npts)
for k in range(npts) :
    a_kj[k,j] = jp[j]**k / np.math.factorial(k)
b = np.zeros(npts)
b[oDer] = 1
c_u = np.linalg.solve(a_kj,b)
                             #  check the order
aa = np.zeros(npts)
for k in range(oDer+1,100) :           
    aa[j] = (jp[j]**k)/np.math.factorial(k)
    if np.abs(np.sum(aa*c_u)) > 1.0E-10 :
        break
        
#----------------------
                            # output the results
print('Grid_position (top), coefficient (bottom) =\n')
print(np.vstack([jp, c_u]))
print('\nOrder = ', k-oDer)
Grid_position (top), coefficient (bottom) =

[[-4.00000000e+00 -3.00000000e+00 -2.00000000e+00 -1.00000000e+00
   0.00000000e+00  1.00000000e+00  2.00000000e+00  3.00000000e+00
   4.00000000e+00]
 [ 3.57142857e-03 -3.80952381e-02  2.00000000e-01 -8.00000000e-01
  -4.91828800e-15  8.00000000e-01 -2.00000000e-01  3.80952381e-02
  -3.57142857e-03]]

Order =  8

/var/folders/xr/cv4msgs94jq0nw15cwkskrbc0000gn/T/ipykernel_28096/850803756.py:17: DeprecationWarning: `np.math` is a deprecated alias for the standard library `math` module (Deprecated Numpy 1.25). Replace usages of `np.math` with `math`
  a_kj[k,j] = jp[j]**k / np.math.factorial(k)
/var/folders/xr/cv4msgs94jq0nw15cwkskrbc0000gn/T/ipykernel_28096/850803756.py:24: DeprecationWarning: `np.math` is a deprecated alias for the standard library `math` module (Deprecated Numpy 1.25). Replace usages of `np.math` with `math`
  aa[j] = (jp[j]**k)/np.math.factorial(k)
import sympy as sp
sp.init_printing(long_frac_ratio=1)
import numpy as np
from sympy.printing.latex import LatexPrinter
from IPython.display import Markdown as md                         # hack to pretty output the latex...

#----------------------

oDer = 1     # order of the derivative to approximate (like 1 for f', or 2 for f", etc.)
npts = 9     # number of points in the stencil. Min is oDer + 1.
jb   = 5     # position of the base point in the stencil, starting at 1
             # the setup: oDer=1, npts=3, jb=2, will give a second order central difference approx to f'

#----------------------

jp = np.arange(1-jb, npts-jb+1, dtype=np.int32)   # index in grid: like [-2 -1 0 1 2 ] for jp=3 for npts=5

A = sp.zeros(npts,npts)
for k in range(npts):
    for j in range(npts):
        A[k,j] = jp[j]**k / sp.factorial(k)
B = sp.zeros(npts,1)
B[oDer] = 1
C = A**-1 * B
c = np.array(C.T).astype(np.float64)[0]           # numpy array of coefficients

#---------------------- check the order
j = np.arange(npts)
for k in range(oDer+1,100) :           
    aa = (jp[j]**k)/np.math.factorial(k)
    if np.abs(np.sum(aa*c)) > 1.0E-10 :
        break
        
#---------------------- output results

points = [sp.symbols(f'u_{j}') for j in jp]
Δx, u, x, = sp.symbols('Δx, u, x')
u0 = sp.symbols('u_0')

rr = []
for i in range(npts):
    rr.append(C[i]*points[i])
rhs = sp.Add(*rr,evaluate=False) # keep the order of the additive terms...

lhs = LatexPrinter(dict(order='none'))._print(sp.Derivative(u0,x,oDer))
rhs = LatexPrinter(dict(order='none'))._print_Add(rhs)
expr = lhs + "=" + rhs + f"+O(\Delta x^{k-oDer})"

md(f"$${expr}$$")
<>:47: SyntaxWarning: invalid escape sequence '\D'
<>:47: SyntaxWarning: invalid escape sequence '\D'
/var/folders/xr/cv4msgs94jq0nw15cwkskrbc0000gn/T/ipykernel_28096/2778617435.py:47: SyntaxWarning: invalid escape sequence '\D'
  expr = lhs + "=" + rhs + f"+O(\Delta x^{k-oDer})"
/var/folders/xr/cv4msgs94jq0nw15cwkskrbc0000gn/T/ipykernel_28096/2778617435.py:30: DeprecationWarning: `np.math` is a deprecated alias for the standard library `math` module (Deprecated Numpy 1.25). Replace usages of `np.math` with `math`
  aa = (jp[j]**k)/np.math.factorial(k)
$$\frac{d}{d x} u_{0}=\frac{u_{-4}}{280} - \frac{4 u_{-3}}{105} + \frac{u_{-2}}{5} - \frac{4 u_{-1}}{5} + 0 + \frac{4 u_{1}}{5} - \frac{u_{2}}{5} + \frac{4 u_{3}}{105} - \frac{u_{4}}{280}+O(\Delta x^8)$$
C

$\displaystyle \left[\begin{matrix}\frac{1}{280}\\\\- \frac{4}{105}\\\\ \frac{1}{5}\\\\- \frac{4}{5}\\\\0\\\\ \frac{4}{5}\\\\- \frac{1}{5}\\\\ \frac{4}{105}\\\\- \frac{1}{280}\end{matrix}\right]$