The integral In=∫10xnexdxn=0,1,2,⋯ suggests a recursion relation obtained by integration by parts: In=[xnex]10−n∫10xn−1exdx=e−nIn−1 terminating with I0=e−1. However, this algorithm, applied "forwards" for increasing n is numerically unstable since small errors (such as floating point rounding errors) are magnified at each step: if the error in In is ϵn such that the estimated value of In is I′n+ϵn then ϵn=I′n−In=(e−nI′n−1)−(e−nIn−1)=n(In−1−I′n−1)=−nϵn−1, and hence |ϵn|=n!ϵ0. Even if the error in ϵ0 is small, that in ϵn is larger by a factor n! which can be huge.
The numerically stable solution, in this case, is to apply the recursion backwards for decreasing n:
In−1=1n(e−In)⇒ϵn−1=−ϵnn. That is, errors in In are reduced on each step of the recursion. One can even start the algorithm at I′N=0 and providing enough steps are taken between N and the desired n it will converge on the correct In.
import numpy as np
import matplotlib.pyplot as plt
def Iforward(n):
if n == 0:
return np.e - 1
return np.e - n * Iforward(n-1)
def Ibackward(n):
if n >= 99:
return 0
return (np.e - Ibackward(n+1)) / (n+1)
N = 35
Iforward = [np.e - 1]
for n in range(1, N+1):
Iforward.append(np.e - n * Iforward[n-1])
Ibackward = [0] * (N+1)
for n in range(N-1,-1,-1):
Ibackward[n] = (np.e - Ibackward[n+1]) / (n+1)
n = range(N+1)
plt.plot(n, Iforward, label='Forward algorithm')
plt.plot(n, Ibackward, label='Backward algorithm')
plt.ylim(-0.5, 2)
plt.xlabel('$n$')
plt.ylabel('$I(n)$')
plt.legend()
plt.show()
The figure below shows the forwards algorithm becoming extremely unstable for n>16 and fluctuating between very large positive and negative values; conversely, the backwards algorithm is well-behaved.