Definition of the definite integral
For a function on the interval , its definite integral is the area of the region enclosed by over and the -axis (the part above the -axis is positive, while the part below the -axis is negative).
In many situations, we need to compute an approximate value of an integral efficiently and accurately. The Simpson's method introduced below is one such numerical integration method.
Simpson's method
The idea is to divide the interval of integration into several smaller intervals and apply the integral formula for a quadratic function to each one.
Integral formula for a quadratic function (Simpson's formula)
For a quadratic function , we have:
Derivation: For a quadratic function ; Integrating gives where D is a constant, so
Based on Simpson's formula, we first introduce the ordinary Simpson integration method.
Ordinary Simpson's method
This method was published in a paper by Thomas Simpson in 1743.
Description
Given a natural number , divide the interval into equal-length intervals .
We can compute the integral over each small interval , , and sum the values over all intervals to obtain the total integral.
For an interval , , choose its three points . They determine a parabola and hence a function , which exists and is unique. Computing the integral of the original function over this interval is then reduced to computing the integral of the new quadratic function over the same interval. We can approximate it using Simpson's formula.
Summing the intervals gives the following conclusion:
Error
We state directly that the error of the ordinary Simpson's method is:
Here is some value in the interval .
Implementation
C++
constexpr int N = 1000 * 1000;
double simpson_integration(double a, double b) {
double h = (b - a) / N;
double s = f(a) + f(b);
for (int i = 1; i <= N - 1; ++i) {
double x = a + h * i;
s += f(x) * ((i & 1) ? 4 : 2);
}
s *= h / 3;
return s;
}
Python
N = 1000 * 1000
def simpson_integration(a, b):
h = (b - a) / N
s = f(a) + f(b)
for i in range(1, N):
x = a + h * i
if i & 1:
s = s + f(x) * 4
else:
s = s + f(x) * 2
s = s * (h / 3)
return s
Adaptive Simpson's method
The ordinary method is inevitably limited by when accuracy must be guaranteed, so we should look for a more suitable method.
The only remaining question is how to divide the interval. Too few segments lead to a large error, while too many reduce time efficiency. We need to find a balance between accuracy and efficiency.
Consider a portion of the graph that is already very close to a quadratic function. Substituting it directly into the formula gives a highly accurate integral, so there is no need to subdivide that portion further.
This gives the following subdivision method: each time, judge how similar the current segment is to a quadratic function. If it is sufficiently similar, substitute it directly into the formula; otherwise, split it into left and right segments and solve them recursively.
There is only one problem left: how do we determine whether each segment is similar to a quadratic function?
Substitute the current segment directly into the formula to obtain its integral, then split the segment at its midpoint and apply the formula directly to the two resulting segments. If the integral over the current segment differs only slightly from the sum of the two split integrals, we can regard the current segment as very similar to a quadratic function and stop the recursion.
This is the idea behind adaptive Simpson's method. When making divide-and-conquer decisions, in addition to checking the required accuracy, one generally also forces a minimum number of iterations.
Reference code follows:
C++
double simpson(double l, double r) {
double mid = (l + r) / 2;
return (r - l) * (f(l) + 4 * f(mid) + f(r)) / 6; // Simpson's formula
}
double asr(double l, double r, double eps, double ans, int step) {
double mid = (l + r) / 2;
double fl = simpson(l, mid), fr = simpson(mid, r);
if (abs(fl + fr - ans) <= 15 * eps && step < 0)
return fl + fr + (fl + fr - ans) / 15; // Return directly if sufficiently similar
return asr(l, mid, eps / 2, fl, step - 1) +
asr(mid, r, eps / 2, fr, step - 1); // Otherwise, split into two segments and solve recursively
}
double calc(double l, double r, double eps) {
return asr(l, r, eps, simpson(l, r), 12);
}
Python
def simpson(l, r):
mid = (l + r) / 2
return (r - l) * (f(l) + 4 * f(mid) + f(r)) / 6 # Simpson's formula
def asr(l, r, eps, ans, step):
mid = (l + r) / 2
fl = simpson(l, mid)
fr = simpson(mid, r)
if abs(fl + fr - ans) <= 15 * eps and step < 0:
return fl + fr + (fl + fr - ans) / 15 # return directly if sufficiently close
return asr(l, mid, eps / 2, fl, step - 1) + asr(
mid, r, eps / 2, fr, step - 1
) # otherwise split into two intervals and solve recursively
def calc(l, r, eps):
return asr(l, r, eps, simpson(l, r), 12)
Exercises
References
https://doi.org/10.1145/321526.321537: This paper discusses improvements to adaptive Simpson's method, including a detailed explanation of the origin and advantages of the constant 15 in the code above.