Processing math: 100%

Numerical stability of an integral solved by recursion

The integral In=10xnexdxn=0,1,2, suggests a recursion relation obtained by integration by parts: In=[xnex]10n10xn1exdx=enIn1 terminating with I0=e1. 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 In+ϵn then ϵn=InIn=(enIn1)(enIn1)=n(In1In1)=nϵn1, 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:

In1=1n(eIn)ϵn1=ϵnn. That is, errors in In are reduced on each step of the recursion. One can even start the algorithm at IN=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.

Numerical instability on forward algorithm