Numerical Integration

Definition of the definite integral

For a function f(x)f(x) on the interval [l,r][l,r], its definite integral ∫lrf(x)dx\int_{l}^{r}f(x)\mathrm{d}x is the area of the region enclosed by f(x)f(x) over [l,r][l,r] and the xx-axis (the part above the xx-axis is positive, while the part below the xx-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 f(x)=ax2+bx+cf(x)=ax^2+bx+c, we have:

∫lrf(x)dx=(r−l)(f(l)+f(r)+4f(l+r2))6\int_l^r f(x) {\mathrm d}x = \frac{(r-l)(f(l)+f(r)+4 f(\frac{l+r}{2}))}{6}

Derivation: For a quadratic function f(x)=ax2+bx+cf(x)=ax^2+bx+c; Integrating gives F(x)=∫0xf(x)dx=a3x3+b2x2+cx+DF(x)=\int_0^x f(x) {\mathrm d}x = \frac{a}{3}x^3+\frac{b}{2}x^2+cx+D where D is a constant, so

∫lrf(x)dx=F(r)−F(l)=a3(r3−l3)+b2(r2−l2)+c(r−l)=(r−l)(a3(l2+r2+lr)+b2(l+r)+c)=r−l6(2al2+2ar2+2alr+3bl+3br+6c)=r−l6((al2+bl+c)+(ar2+br+c)+4(a(l+r2)2+b(l+r2)+c))=r−l6(f(l)+f(r)+4f(l+r2))\begin{aligned} \int_l^r f(x) {\mathrm d}x &= F(r)-F(l) \\ &= \frac{a}{3}(r^3-l^3)+\frac{b}{2}(r^2-l^2)+c(r-l) \\ &=(r-l)(\frac{a}{3}(l^2+r^2+lr)+\frac{b}{2}(l+r)+c) \\ &=\frac{r-l}{6}(2al^2+2ar^2+2alr+3bl+3br+6c)\\ &=\frac{r-l}{6}((al^2+bl+c)+(ar^2+br+c)+4(a(\frac{l+r}{2})^2+b(\frac{l+r}{2})+c)) \\ &=\frac{r-l}{6}(f(l)+f(r)+4f(\frac{l+r}{2})) \end{aligned}

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 nn, divide the interval [l,r][l, r] into 2n2n equal-length intervals xx.

xi=l+ih,  i=0…2n,x_i = l + i h, ~~ i = 0 \ldots 2n, h=r−l2n.h = \frac {r-l} {2n}.

We can compute the integral over each small interval [x2i−2,x2i][x_ {2i-2}, x_ {2i}], i=1…ni = 1\ldots n, and sum the values over all intervals to obtain the total integral.

For an interval [x2i−2,x2i][x_ {2i-2}, x_ {2i}], i=1…ni = 1\ldots n, choose its three points (x2i−2,x2i−1,x2i)(x_ {2i-2}, x_ {2i-1}, x_ {2i}). They determine a parabola and hence a function P(x)P(x), 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 P(x)P(x) over the same interval. We can approximate it using Simpson's formula.

∫x2i−2x2if(x) dx≈∫x2i−2x2iP(x) dx=(f(x2i−2)+4f(x2i−1)+(f(x2i))h3\int_{x_ {2i-2}} ^ {x_ {2i}} f (x) ~dx \approx \int_{x_ {2i-2}} ^ {x_ {2i}} P (x) ~dx = \left(f(x_{2i-2}) + 4f(x_{2i-1})+(f(x_{2i})\right)\frac {h} {3}

Summing the intervals gives the following conclusion:

∫lrf(x)dx≈(f(x0)+4f(x1)+2f(x2)+4f(x3)+2f(x4)+…+4f(x2N−1)+f(x2N))h3\int_l ^ r f (x) dx \approx \left(f (x_0) + 4 f (x_1) + 2 f (x_2) + 4f(x_3) + 2 f(x_4) + \ldots + 4 f(x_{2N-1}) + f(x_{2N}) \right)\frac {h} {3}

Error

We state directly that the error of the ordinary Simpson's method is:

−190(r−l2)5f(4)(ξ)-\tfrac{1}{90} \left(\tfrac{r-l}{2}\right)^5 f^{(4)}(\xi)

Here ξ\xi is some value in the interval [l,r][l,r].

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 nn 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.

This content comes from OI Wiki: Numerical Integration. See the original for authors and revision history. AIOJ has adapted the formatting, in-site links, and resource paths.

Content is shared under CC BY-SA 4.0 and SATA license; additional terms are governed by the original notice. Thanks to the OI Wiki authors and contributors; per the SATA terms, please visit the OI Wiki repository and star it to show your support.