Skip to Content

Numerical Integration

You will probably encounter many situations in which analytical integration of a function or a differential equation is difficult or impossible. In this section we show how Scientific Python can help through its high level mathematical algorithms. You will learn how to develop you own numerical integration method and how to get a specified accuracy. The package scipy.integrate can do integration in quadrature and can solve differential equations

1. The Basic Trapezium Rule

Scipy uses three methods to integrate a one-dimensional function: trapezoidal (integrate.trapz), Simpson (integrate.simps) and Romberg (integrate.romb). The trapezium (trapezoidal) method is the most straightforward of the three. The simple trapezium formula calculates the integral of a function f(x) as the area under the curve representing f(x) by approximating it with the sum of trapeziums:

picture.JPG

567068f12e42480b77ae782d93708781.gif

The area of each trapezium is calculated as width times the average height.
Example: Evaluate the integral:

feae82c3f00c871946e14fdfe53714d2.gif

using the basic trapezium rule.
We shall write a small program to evaluate the integral. Of course we have to estimate the number of trapeziums to use; the accuracy of our method depends on this number.

python code
            import math
#the function to be integrated:
def f(x):
    return x ** 4 * (1 - x) ** 4 / (1 + x ** 2)
 
#define a function to do integration of f(x) btw. 0 and 1:
def trap(f, n):
    h = 1 / float(n)
    intgr = 0.5 * h * (f(0) + f(1))
    for i in range(1, int(n)):
        intgr = intgr + h * f(i * h)
    return intgr
 
print(trap(f, 100))
        

Our basic integration program has the inconvenience of depending on the number of trapeziums that we have to change manually. As a basic rule, if we double the number of trapeziums and get the same answer within 1 in 1000000, the answer is probably correct.

Activity 1:

Run the program. How many trapeziums are needed to get a relative error of less than 1 part in 1,000,000?
We can do this as follows:

python code
            from math import *
n = 1
while abs(trap(f, n) - trap(f, n * 2)) > 1e-6:
    n += 1
print(n)
        

After running this code you should end up with a printed out value of 6.

Activity 2: Calculate

\[ \int_{-\infty}^\infty e^{-x^2} dx\]

You have to modify the previous program because of the infinite range of integration. Check the integrand to see where it becomes negligible. The modified code should look something like this.

python code
            import math
#the function to be integrated:
def f(x):
    return math.exp(-x**2)
 
#define a function to do integration of f(x) btw. a and b:
def trap(f, n, a, b):
    h = (b - a) / float(n)
    intgr = 0.5 * h * (f(a) + f(b))
    for i in range(1, int(n)):
        intgr = intgr + h * f(a + i * h)
    return intgr
 
a = -10
b = 10
n = 100
 
while(abs(trap(f, n, a, b) - trap(f, n * 4, a * 2, b * 2)) > 1e-6):
    n *= 4
    a *= 2
    b *= 2
 
print(trap(f, n, a, b))
        

And the printed result when the program is run should be something close to 1.77245385091.

2. Integrating a function with scipy.integrate

Let's look at:

c46ce83c15b93cb4c16d10c13082fd2a.gif

Our simple integration program will divide the interval 0 to 2 in equally spaced slices and spend the same time calculating the integrand in each of these slices. If we have a closer look at the integrand and plot it, we would notice that at low x-values the function hardly varies, so our program will waste time in that region. In contrast, the integrate.quad() routine from Scipy is arbitrary callable (adaptive), in the sense that it can adjust the function evaluations to concentrate on the more important regions (quad is short for quadrature, an older name for integration). Let’s see how Scipy could simplify our work:

python code
            import scipy
from scipy.integrate import quad
from math import *
 
def f(x):
    return x ** 4 * log(x + sqrt(x ** 2 + 1))
 
print(quad(f, 0, 2))
        

The output will be (8.153364119811167, 9.0520525739669813e-014). As you notice, we get both the integral value and the error estimate in only three lines of code, without bothering about the number of trapeziums or the accuracy. The integrate.quad() routine takes the function and the integration limits as input arguments.An overview of scipy.integrate modules can be accessed by typing in the shell window:

python code
            >>> from scipy import integrate
>>> help(integrate)
        

Activity 3:

The period of a pendulum of length l oscillating at a large angle α is given by

1d7c9b18b9f34aee2bf313e002b57005.gif

where

cb14916429ede83f377d5e4b0cdca520.gif

is the period of the same pendulum at small amplitudes. In Exercises 2 and 3 you will analyze both experimental data and the Pylab plot of the large-angle pendulum. Any numerical evaluation of the integral as is would fail (explain why). If we change the variable by writing:

255a16843a684081f90a8970a64efb52.gif

we can get:

db4d8705522077aa2204ea48c0845db1.gif

which is a well-behaved integral. Write a program to use the above integral to calculate the ratio T/T0 for integral amplitudes 0° ≤ α ≤ 90°. Output these values as a table showing the amplitude in degrees and radians as well as T/T0. Explain the result when α = 0. Think about this for a moment before you read the solution below.

python code
            from pylab import *
from math import *
import scipy
from scipy.integrate import quad
 
#the function to be integrated:
def f(x, a):
    return(2/pi)/sqrt(1-sin(a/2)**2*sin(x)**2)
 
print("%15s%15s%15s" % ("A (degrees)", "A (radians)", "T/T0"))
for i in range(0, 91):
    angle = pi * i / 180
    print("%15d%15.10f%15.10f" % (i, angle, quad(f,0,pi/2,args=(angle))[0]))
        

Some sample output can be found here. NumIntA3output.txt

Activity 4:

On the same graph, compare the plot of the sin function with the plot of the integral of the cos function in the range [-π, π].
This can be done as follows:

python code
            from pylab import *
from math import *
import scipy
from scipy.integrate import quad
 
#the function to be integrated:
def f(x):
    return cos(x)
 
x = arange(-pi, pi + 0.1, 0.1)
y_sin = []
y_cos_integrated = []
 
for element in x:
    y_sin.append(sin(element))
    y_cos_integrated.append(quad(f, -pi, element)[0] - sin(pi))
 
plot(x, y_sin)
plot(x, y_cos_integrated)
show()
        

3. Integrating ordinary differential equations with odeint

Many physical phenomena are modeled by differential equations: oscillations of simple systems (spring-mass, pendulum, etc.), fluid mechanics (Navier-Stokes, Laplace's, etc.), quantum mechanics (Schrödinger’s) and many others. Here we’ll show you how to numerically solve these equations. The example we shall use in this tutorial is the dynamics of a spring-mass system in the presence of a drag force.

Writing Newton’s second law for the system, we have to combine the elastic force

0609fd17f99f9bbe89d8d5e818f6ce93.gif

with the drag force whose model for a slowly moving object is

655b102ba0007406ce26ea0f53145c68.gif

We obtain the formula

2bdfcf3674f46d0aded1c8d2ff0adbad.gif

or

a2013fc414e57ae65a80dca9df571a2f.gif

where L is the length of the unstretched/uncompressed spring.

To find an approximate solution to the equation of motion above, we’ll have to use a finite difference approximation for the derivative, which will generate an algorithm for solving the equation. Most such algorithms are based on first order differential equations, so it will probably not be a bad idea to start by putting our second-order equation in the form of a system of two first-order differential equations:

d9ff6d7543b014a17a10c512de6a87dc.gif

77d9dd635424036c4d4b746d1d8cdabf.gif

To write the numerical integration program, we shall use odeint, which is part of scipy.integrate. A function call to odeint looks something like this:

python code
            scipy.integrate.odeint(func, y0, t, args=())
        

As you may see in the simplified syntax above, it takes a number of input arguments: function func defining the system of first order equations, initial values of variables y0 (put in an array), time t (an array of time values), and arguments args() which can be our parameters (mass, elastic constant, drag coefficient and initial length of the spring). The unknowns in a system of differential equations are functions; odeint will return to us the values of these functions at the values t provided, as an array.

Here is how we construct such a program:

python code
            from scipy import *
from scipy.integrate import odeint
from pylab import *
 
"""
A spring-mass system with damping
"""
 
def damped_osc(u,t): #defines the system of diff. eq.
    x, v = u
    return(v,-k*(x-L)/m-b*v) #the vector (dx/dt, dv/dt)
 
t = arange(0,20.1,0.1)
u0 = array([1,0])   #initial values of x, v
 
#assume certain values of parameters b, k, L, m; these would be given in the problem
b=0.4
k=8.0
L=0.5
m=1.0
 
u=odeint(damped_osc,u0,t) #solve ODE
#plot x, v, phase, using matplotlib
 
figure(1)
plot(t,u[:,0],t,u[:,1]) #u[:,0] is x; u[:,1] is v
xlabel('Time')
title('Damped Oscillator')
 
figure(2)
plot(u[:,0],u[:,1])
title('Phase-space')
xlabel('Position')
ylabel('Velocity')
show()
        

The phase-space plot shows the characteristic non-conservative spiral shape, while the displacement and velocity graphs show the expected damping. Practice numerical integration and solving differential equations with the following exercises:
numerical_integration.questions.pdf
numerical_integration.solutions.pdf