4. Integration#
Numerical integration is a fundamental skill in computational physics because it allows one to evaluate increasingly complex integrals with a high accuracy in seconds to minutes. If you’ve ever tried to evaluate particularly nasty integrals, you notice that those can take pages of algebra, transformations, and still only get to an approximate form. It is especially frustrating when one makes algebraic mistakes and you have to do it over. In this chapter, we will review basic integration methods that you’ve learned in Calculus and develop those methods in python. After reading this chapter, check out some python packages that others have developed to make the process of numerical integration easier to access using quadpy.
4.1. Quadrature as box counting#
The integration of a function usually requires some trickery, where you have to know which variable transformation to apply and when to apply it. Due to the precise repetitiveness of a computer, an integral can be evaluated in a straightforward manner. You may recall that you first performed integration by counting the number of boxes (or quadrilaterals) that you could place beneath a curve. As a result, numerical integration is also called quadrature even when it comes to more complex methods.
We’ll start our review with the Riemann definition of an integral, which is given as the limit of the sum over boxes of width h:

Fig. 4.1 Illustration of a basic integral with 4 subdivisions. Figure adapted from Landau, Paez, & Bordeianu (2007).#
The numerical integral of a function
You will notice that this is similar to the Riemann definition, but the limit for an infinitesimal box size is removed and the upper limit for the summation is redefined as the number
A computer holds numbers to a finite precision and some values in
won’t increase the accuracy of the integration.We’ll find that more calculations take more wall time (i.e., our time to do work) and we may have to wait an extremely long time for the integration to finish.
Eventually we have to truncate the sum to get the “best” approximation, and we have to remember that we are making a tradeoff.
Something that you should never do is to attempt a numerical integration when the integrand contains a singularity without first removing the singularity by hand. Some ways to remove the singularity is to assign intervals so that the singularity lies at an endpoints or by a change of variable:
On the other hand, you might have an integrand that changes very quickly/slowly and scaling the boxes according to the variation may be necessary. There are several approaches to scale the boxes for your integration and we’ll explore two of them in the following sections:
Trapezoidal Rule, and
Simpson’s Rule
that you may recall from Calculus.
4.2. Trapezoidal Rule#
The trapezoid integration rule uses
and the location of each point along the interval is defined linearly because each interval has a fixed width. Thus, we write the iterative formula for
where we start our counting at

Fig. 4.2 Straight line sections are used to approximate the area under the curve via the trapezoidal rule. Figure adapted from Landau, Paez, & Bordeianu (2007).#
The trapezoid rule takes each integration interval and constructs a trapezoid of width
In terms of our standard integration formula (Eq. (4.2)), the weights
You will notice that some interval points are counted twice as the end of one interval and as the beginning of the next, whereas the endpoints are just counted once. We could now define all the weights for our standard function as:
These weights combine the double counted intervals (
#TrapMethods; Trapezoid integration of a function
import numpy as np
#Define the boundary values [a,b] and the number of intervals N
a, b, N = 0., 3., 4
def func(x):
#f(x) is a function of x
#x is the independent variable
print(" x, f(x) = %1.1f, %1.1f" % (x,x*x))
return x*x
def func_weight(i,h):
#weights for the trapezoid rule
if i==1 or i== N:
weight = h/2.
else:
weight = h
return weight
#width of each interval
h = (b-a)/float(N-1)
tot_sum = 0
for i in range(1,N+1):
x_i = a + (i-1)*h
tot_sum += func(x_i)*func_weight(i,h)
print(' Total sum = ', tot_sum)
print(" Analytical sum = ", (b**3/3.));
x, f(x) = 0.0, 0.0
x, f(x) = 1.0, 1.0
x, f(x) = 2.0, 4.0
x, f(x) = 3.0, 9.0
Total sum = 9.5
Analytical sum = 9.0
In the code, we can see each of the elements of the trapezoid rule. Most importantly, you should notice that the standard integration formula includes a summation for
loop. Everything else maps directly to what you might expect. The analytical sum is also calculated because the function
4.3. Simpson’s Rule#
Simpson’s integration rule approximates the integrand
with equally spaced intervals, where weights are

Fig. 4.3 Two parabolas are used to approximate the area under the curve via Simpson’s rule. Figure adapted from Landau, Paez, & Bordeianu (2007).#
To determine the weights, let’s use the interval [-1,1] and the integral becomes:
But we know that
The weight
Now, we can substitute the values for the weights into Eq. (4.11), but in terms of function evaluations over three points:
Since three points were required, we generalize this solution to include the two endpoints of a given interval and the midpoint (which lies at
or
where the first point
Simpson’s rule requires the integration to be over pairs of intervals because it uses 3 points. This implies that the total number of points
Here, the points (
or
#SimpMethods; Simpson's integration of a function
import numpy as np
#Define the boundary values [a,b] and the number of intervals N
a, b, N = 0., 3., 4
def func(x):
#f(x) is a function of x
#x is the independent variable
#print(" x, f(x) = %1.1f, %1.1f" % (x,x*x))
return x*x
#width of each interval
h = (b-a)/float(N-1)
tot_sum = 0
for i in range(1,N):
x_left = a + (i-1)*h
x_right = a + i*h
x_mid = x_left + h/2
tot_sum += (h/6.)*(func(x_left)+4*func(x_mid)+func(x_right))
print(' Total sum = ', tot_sum)
print(" Analytical sum = ", (b**3/3.));
Total sum = 9.0
Analytical sum = 9.0
4.4. Gaussian Quadrature#
The Trapezoid Rule and Simpson’s Rule use equidistant points and weights, which is adequate for some cases. But, we can generalize those methods by relaxing the requirement for equidistant points. Recall that our standard integration formula is composed of a set of chosen points
we choose a series of points from a complex function that can be represented as a series of polynomials (e.g., Legendre, Laguerre, etc.),
the benefit is that these functions can be solved on the interval [-1,1] exactly,
perform a variable transformation to map the known solution onto the unknown solution [-1,1]–>[a,b],
the error is reduced because the approximation is more exact.
Let’s start with an example. We want to select points and weights using only 2 points within the integration region. From this goal, we write:
where there will be some number of polynomials of degree
Now we have 4 equations and 4 unknowns. Through some algebra, we can find that
(not that surprising from degree 0) (from degree 2) (from degree 2; other root)
This is the beginning of a known procedure using Legendre polynomials. Now we have to apply a mapping. The easiest map is from [-1,1]–>[a,b] uniformly, which uses the midpoint =
Other mappings can be found in the textbook: Computational Physics: Problem Solving with Computers.
Let’s turn this into some code and see how well it does.
#IntegGauss.py: Gaussian quadrature up to degree 4 points and weights
import numpy as np
#Define the boundary values [a,b] and the degree N
a, b, N = 0., 3., 3
def func(x):
#function to be integrated
return x*x
def gauss(a,b,N):
#given an interval [a,b] this function will return the scaled points x and weights w
x = np.zeros(N)
w = np.zeros(N)
if N == 2:
x[0] = np.sqrt(3.)/3.
x[1] = -x[0]
w = np.ones(2)
elif N == 3:
x[1] = np.sqrt(15.)/5.
x[2] = -x[1]
w[0] = 8./9.
w[1:] = 5./9.
elif N == 4:
x[0] = np.sqrt(525.-70.*np.sqrt(30.))/35.
x[1] = -x[0]
w[:2] = (18. + np.sqrt(30.))/36.
x[2] = np.sqrt(525.+70.*np.sqrt(30.))/35.
x[3] = -x[2]
w[2:] = (18. - np.sqrt(30.))/36.
print(x,w)
#scale to [a,b]
for i in range(0,N):
x[i] = (b+a)/2. + (b-a)/2.*x[i]
w[i] = (b-a)/2.*w[i]
return x,w
x_scaled, w_scaled = gauss(a,b,N) #get points and weights
tot_sum = 0
for i in range(0,N):
tot_sum += w_scaled[i]*func(x_scaled[i])
print(' Total sum = ', tot_sum)
print(" Analytical sum = ", (b**3/3.));
[ 0. 0.77459667 -0.77459667] [0.88888889 0.55555556 0.55555556]
Total sum = 9.0
Analytical sum = 9.0
4.5. Integration Error#
If you value your own time, then you should choose an integration rule that gives an accurate answer using the least number of integration points. Simply, fewer integration steps implies that less work needs to be done by the computer and you obtain your result faster. Additionally, you want an accurate answer and you must realize that we are obtaining estimates of the true answer. Computers are able to store a finite set of numbers and this results in errors. We obtain a crude estimate of the approximation or algorithmic error
for the approximation errors and the relative error is simply:
A more sophisticated integration rule leads to an error that decreases rapidly (due to the higher power of
Another source of error is the round-off error, which we assume is random and can thus take the form
Assuming double precision, we want to determine an
for the Trapezoid rule for Simpson’s rule.
These results imply the following:
Simpson’s rule is an improvement over the trapezoid rule
Obtaining the best numerical approximation can be achieved with
instead of for
4.6. Integration by Stone Throwing#
Sometimes you need to find the area of a shape with an irregular boundary. The standard methods can be quite complex and there are simpler means of obtaining an approximate value. Another context is when you want to perform multidimensional integrals. These types of integrals are easily solved through a sampling technique with random numbers.

Fig. 4.4 Illustration of an irregular shaped pond, where the area could be determined more easily via stone throwing.#
Suppose you need to find the area of a pond, where most ponds are not regularly shaped due to weathering and erosion. You can use the following algorithm:
Walk off a box that completely encloses the pond and remove any pebbles lying on the ground within the box.
Measure the lengths of the sides in natural units (feet or meters). This tells you the area of the enclosing box
.Grab a bunch of pebbles, count their number, and throw them up in the air in random directions. (Wear a hat!)
Count the number of splashes in the pond
and the number of pebbles lying on the ground within your box .Assuming that you threw the pebbles randomly enough, the number of pebbles falling into the pond should be proportional to the area of the pond
.
The fraction of pebbles that splash in the pond relative to the total number of pebbles tells you the area within a unit box. Multiplying by the area of the box
Let’s use the sampling technique to perform a 2D integration to determine
import numpy as np
import matplotlib.pyplot as plt
def stone_throw(trial_idx):
#algorithm of stone throwing
#performing n number of trials to see convergence
A_box = 4 #area of the enclosing square (r = 1 or d = 2)
N_pebbles = 100 #total number of pebbles
N_pond = 0 #initial count of the pebbles
for i in range(0,N_pebbles):
x_samp = np.random.uniform(-1,1)
y_samp = np.random.uniform(-1,1)
r_samp = np.sqrt(x_samp**2+y_samp**2)
if r_samp<1:
N_pond += 1
A_pond[trial_idx] = (N_pond/N_pebbles)*A_box
N_trials = 10
A_pond = np.zeros(N_trials)
for t in range(0,N_trials):
stone_throw(t)
print(A_pond)
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(range(0,N_trials),A_pond,'k.',ms=10)
ax.axhline(np.pi,color='r',linestyle='--',lw=3)
ax.set_xlim(0,N_trials)
ax.set_ylim(2.8,3.4)
ax.set_xlabel("Trial Number",fontsize=24)
ax.set_ylabel("Estimate of $\pi$",fontsize=24);
[3.16 2.88 3.24 3.08 3.16 3. 3.56 3. 3.12 3.28]

We found a very approximate value for
Will increasing the number of trials help?
Would we want to increase the number of pebbles thrown?
How could we find the number of pebbles needed to get 3 decimal places of precision?
4.7. Integrating by Mean Value#
In the previous section, we integrated using random values. You might be thinking, how did that even work? It is the standard Monte Carlo (random value) technique, which is based on the mean value theorem from Calculus. Here’s the equation in case you’ve forgotten:
The theorem states: the value of the integral of some function

Fig. 4.5 Illustration of integration by the mean value.#
The integration algorithm uses Monte Carlo techniques to evaluate the mean, which can be written as a summation by:
This gives the very simple integration rule:
This looks like our standard algorithm
4.8. Problems#
Complete the following problems in a Jupyter notebook, where you will save your results as an external file (*.png).
Create a
an abstract summary
sections for each problem that state the problem, explain your approach to solving the problem, and display the results
include a reference for each solution (this can be textbooks)
Problem 1
Evaluate the following integrals using the a) Trapezoid rule, b) Simpson’s Rule, and c) Gaussian quadrature. Include a graph showing each function and shade the area. Vary the number of intervals needed to give an ‘acceptable’ answer. In your submitted work, describe your procedure for varying the intervals, create a table summarizing how many intervals were required for each method, and explain why each method(s) require more/less than the others.
Problem 2
Write a Python program to study projectile motion without air resistance using Simpson’s rule. Then apply your program to the following scenario:
You are a pirate cannoneer and your Captain (Hector Barbossa) has ordered you to fire the cannon to begin the siege of Port Royal. Your Captain wants a short battle, where the British armory is destroyed first and he can begin pillaging right away to find a big hat. Thus, you must make a direct hit on the first shot. The cannon has a muzzle speed of about 200 m/s and the firing deck is 50 m above sea level. The Captain has anchored 2000 m off shore relative to the fort walls for the initial battle. The armory is located at sea level an additional 57 m beyond the fort walls.
(a) At what angle (from the horizontal) must you fire the cannon to hit the armory?
(b) What is the maximum height (from sea level) will the cannon fly?
(c) Compare your results to expectations from theory (i.e., the kinematics you learned in pirate school). Plot the trajectory (x,y), maximum height, and range determined from your program along with analytic values.