Numerical Integrations


Topics in Digital Heritage:
Numerical Methods for Digital Reconstruction

Jin Woo Lee

KAIST

Fall 2026

Recap

From Last Class: Interpolate, Then Integrate

Interpolation (last class)

  • We only know samples \(f(x_i)=y_i\).
  • Pick a basis and write \(f(x)\approx\sum_i y_i\,\phi_i(x)\).
  • Piecewise polynomials are local and stable.

Integration (today)

  • We want \(\displaystyle\int_a^b f(x)\,dx\) from the same samples.
  • The interpolant is a polynomial on each piece, so we can integrate it exactly.
  • Different interpolants give different rules.

Why Integration? Adding Up a Continuum

++ light rendering (light arriving from all directions), filter convolution…

Quadrature problem:

given samples \(f(x_1),\dots,f(x_n)\), approximate \(\displaystyle\int_a^b f(x)\,dx\).

Today’s ILO

  • Derive quadrature rules as weighted sums of samples whose weights come from integrating an interpolant, and implement the composite midpoint, trapezoidal, and Simpson’s rules.
  • Predict and measure the order of accuracy of each rule, and recognize when it breaks down (non-smooth integrands, noisy samples, high-degree rules).
  • Decide where to sample when we are free to choose: Gaussian and adaptive quadrature.
  • Integrate in many dimensions: iterated rules, the curse of dimensionality, and Monte Carlo integration.
  • Preview where quadrature reappears: numerical differentiation (next class), time stepping of ODEs, and finite elements for PDEs (after the midterm).

Quadrature Rules

Quadrature Rule = Approximating Integral

Every rule we see today has the same form:

\[ \int_a^b f(x)\,dx \;\approx\; Q[f] \equiv \sum_{i=1}^n w_i\, f(x_i) \]

Q = np.dot(w, f(x))      # nodes x, weights w

The Riemann sum from calculus is one example: \(w_i = x_{i+1}-x_i\).

Designing a rule = choosing where to sample (\(x_i\)) with what weight (\(w_i\)).

When samples are given

  • recorded audio, camera pixels, scan data, …
  • \(x_i\) fixed, we only choose \(w_i\)

When we can evaluate \(f\) anywhere

  • ray rendering, simulation, …
  • choose \(x_i\) and \(w_i\)
  • each evaluation is expensive: accuracy per evaluation matters

Newton-Cotes Rules

are Quadrature rules that integrate the interpolant on equally spaced nodes.

Midpoint rule

constant interpolant \[(b-a)\,f\!\left(\tfrac{a+b}{2}\right)\]

Trapezoid rule

linear interpolant \[(b-a)\,\frac{f(a)+f(b)}{2}\]

Simpson’s rule

quadratic interpolant \[\frac{b-a}{6}\left[f(a)+4f\!\left(\tfrac{a+b}{2}\right)+f(b)\right]\]

Demo: 1D Quadrature Playground

How Accurate Is It?

Error on One Interval

Taylor expand \(f\) about the midpoint \(c\) (interval width \(h\)):

\[f(x) = f(c) + f'(c)(x-c) + \frac{1}{2}f''(c)(x-c)^2 + \cdots\]

\[ \begin{aligned} \int_a^b f(x)\,dx &= \int_{-h/2}^{h/2}\left[ f(c) + f'(c) t + \frac{1}{2}f''(c) t^2 + \cdots\right]\,dt\\ &= \underbrace{h\,f(c)}_{\text{midpoint}\\ \equiv\ M} + \underbrace{\frac{h^3}{24}f''(c)}_{\text{error of midpoint}} + O(h^5) \end{aligned} \]

Now Taylor expand the trapezoid rule: \[f(a) = f(c) - \frac{h}{2}f'(c) + \frac{h^2}{8}f''(c) + O(h^3),\quad f(b) = f(c) + \frac{h}{2}f'(c) + \frac{h^2}{8}f''(c) + O(h^3)\] \[ \underbrace{h\,\frac{f(a)+f(b)}{2}}_{\text{trapezoid}\\ \equiv\ T} = h\,f(c) + \frac{h^3}{8}f''(c) + O(h^5),\qquad \int_a^b f(x)\,dx - h\,\frac{f(a)+f(b)}{2} = \underbrace{-\frac{h^3}{12}f''(c)}_{\text{error of trapezoid}} + O(h^5) \]

Same order, midpoint error is twice as accurate than trapezoid.

Mix to cancel \(h^3\): \(\ \tfrac{2M+T}{3} = \tfrac{h}{6}\left[f(a)+4f(c)+f(b)\right]\) is Simpson’s rule, error \(O(h^5)\).

Measure the Order of Accuracy

Show code
f = lambda x: np.exp(x) * np.cos(3 * x)                               # smooth, on [0, 2]
F = lambda x: np.exp(x) * (np.cos(3 * x) + 3 * np.sin(3 * x)) / 10    # its antiderivative
exact = F(2.0) - F(0.0)

ns = 2 ** np.arange(1, 13)
h = 2.0 / ns
rules = [("midpoint", midpoint, "purple"), ("trapezoid", trapezoid, "steelblue"), ("Simpson", simpson, "forestgreen")]

fig, axs = plt.subplots(1, 2, figsize=(9, 3.6))
for name, rule, col in rules:
    err = np.array([abs(rule(f, 0, 2, n) - exact) for n in ns])
    p = np.polyfit(np.log(h[2:7]), np.log(err[2:7]), 1)[0]            # slope in the asymptotic range
    axs[0].loglog(h, err, "-o", ms=3, color=col, label=f"{name}: slope {p:.2f}")
axs[0].set_xlabel("sample spacing $h$"); axs[0].set_ylabel("|error|")
axs[0].set_title("three rules, one smooth integrand", fontsize=9)
axs[0].legend(fontsize=8)

# the same rule on three different integrands over [0, 1]
tests = [("smooth: $e^x$", np.exp, np.e - 1, "steelblue"),
         ("kink: $\\sqrt{x}$", np.sqrt, 2 / 3, "firebrick"),
         ("periodic: $e^{\\sin 2\\pi x}$", lambda x: np.exp(np.sin(2 * np.pi * x)), i0(1.0), "darkorange")]
for name, g, ex, col in tests:
    err = np.array([abs(trapezoid(g, 0, 1, n) - ex) for n in ns])
    axs[1].loglog(1.0 / ns, np.maximum(err, 1e-17), "-o", ms=3, color=col, label=name)
axs[1].set_xlabel("sample spacing $h$")
axs[1].set_title("trapezoid rule, three integrands", fontsize=9)
axs[1].legend(fontsize=8)
plt.tight_layout()
plt.show()

Error \(\approx C h^p\) is a straight line of slope \(p\) on log-log axes.

Always check the slope: a wrong slope means a bug or a non-smooth \(f\).

Choosing Where to Sample

Gaussian Quadrature

Free the nodes: \(2n\) unknowns (\(x_i\) and \(w_i\))

\(\Rightarrow\) exact for poly up to degree \(2n-1\).

Example: integrate \(f(x)\) using \(n=2\) on \([-1,1]\)

Gaussian quadrature with 2 nodes \[\int_{-1}^{1} f(x)\,dx \approx w_1 f(x_1) + w_2 f(x_2)\tag{$\star$}\]

\(4=2n\) unknowns (\(w_1, w_2, x_1, x_2\)),

can be exact upto degree \(3=2n-1\) (\(1, x, x^2, x^3\))

Set \(f(x) = 1, x, x^2, x^3\) and solve for \(w_i\) and \(x_i\) on \((\star)\):

\[ \begin{aligned} w_1+w_2&=2, & w_1x_1+w_2x_2&=0,\\ w_1x_1^2+w_2x_2^2&=\tfrac23, & w_1x_1^3+w_2x_2^3&=0 \end{aligned} \] \(\Rightarrow\ w_1=w_2=1,\quad x_{1,2}=\mp\tfrac{1}{\sqrt3}\).

Show code
def gauss(f, a, b, n):
    t, w = np.polynomial.legendre.leggauss(n)
    return 0.5 * (b - a) * np.dot(w, f(0.5 * (b - a) * t + 0.5 * (a + b)))

n_tr = 2 ** np.arange(1, 8)
n_ga = np.arange(1, 17)
fig, ax = plt.subplots(figsize=(4.4, 3.3))
ax.loglog(n_tr + 1, [abs(trapezoid(f_test, 0, 2, n) - I_test) for n in n_tr],
          "-o", ms=3, color="steelblue", label="composite trapezoid")
ax.loglog(n_tr + 1, [abs(simpson(f_test, 0, 2, n) - I_test) for n in n_tr],
          "-o", ms=3, color="forestgreen", label="composite Simpson")
ax.loglog(n_ga, [max(abs(gauss(f_test, 0, 2, n) - I_test), 1e-17) for n in n_ga],
          "-o", ms=3, color="firebrick", label="Gaussian, $n$ nodes")
ax.set_xlabel("number of evaluations of $f$"); ax.set_ylabel("|error|")
ax.set_title("$\\int_0^2 e^x\\cos 3x\\,dx$", fontsize=9)
ax.legend(fontsize=7, loc="lower left")
plt.tight_layout()
plt.show()

Catch:

  • needs \(f\) at prescribed points (not for given samples)
  • nodes change completely with \(n\) (no reuse)
  • \(f\) must be smooth.

Adaptive Quadrature

Use the function values to decide where more samples are needed.

Error estimate without knowing the answer: compute the integral twice, coarse and fine. If they agree, accept. If not, split the interval and recurse.

def simpson1(f, a, b):       # one Simpson panel
    c = (a + b) / 2
    return (b - a) / 6 * (f(a) + 4 * f(c) + f(b))

def adaptive(f, a, b, tol):
    c = (a + b) / 2
    coarse = simpson1(f, a, b)
    fine = simpson1(f, a, c) + simpson1(f, c, b)
    if abs(fine - coarse) < 15 * tol:
        return fine
    return (adaptive(f, a, c, tol / 2)
            + adaptive(f, c, b, tol / 2))

In practice: cap the recursion depth, or call scipy.integrate.quad(f, a, b).

Show code
s, x0 = 0.02, 1 / np.pi               # a sharp peak of width s at x0
peak = lambda x: np.exp(-np.abs(x - x0) / s)
I_peak = s * (2 - np.exp(-x0 / s) - np.exp(-(1 - x0) / s))

def simpson1(f, a, b):
    return (b - a) / 6 * (f(a) + 4 * f((a + b) / 2) + f(b))

def adaptive(f, a, b, tol, nodes, depth=0):
    c = (a + b) / 2
    coarse, fine = simpson1(f, a, b), simpson1(f, a, c) + simpson1(f, c, b)
    if abs(fine - coarse) < 15 * tol or depth > 30:
        nodes.update([a, (a + c) / 2, c, (c + b) / 2, b])    # points this panel used
        return fine
    return adaptive(f, a, c, tol / 2, nodes, depth + 1) + adaptive(f, c, b, tol / 2, nodes, depth + 1)

nodes = set()
Q = adaptive(peak, 0.0, 1.0, 1e-7, nodes)
err_adaptive = abs(Q - I_peak)

n_uniform = 2                         # how many uniform samples reach the same error?
while abs(simpson(peak, 0, 1, n_uniform) - I_peak) > err_adaptive:
    n_uniform += 2

xs = np.linspace(0, 1, 800)
xa = np.array(sorted(nodes))
fig, ax = plt.subplots(figsize=(4.4, 3.6))
ax.plot(xs, peak(xs), color="gray", lw=2)
ax.plot(xa, peak(xa), "o", color="darkorange", ms=3)
ax.plot(xa, np.full_like(xa, -0.06), "|", color="darkorange", ms=10)
ax.set_xlabel("$x$")
ax.set_title(f"adaptive: {len(xa)} sample points, error {err_adaptive:.0e}\n"
             f"uniform Simpson needs {n_uniform + 1} for the same error", fontsize=9)
plt.tight_layout()
plt.show()

Many Dimensions

Iterated Integrals and the Curse of Dimensionality

Apply a 1D rule along each axis in turn: \[ \int_c^d\!\!\int_a^b f(x,y)\,dx\,dy \approx \sum_i\sum_j w_i\,w_j\,f(x_i,y_j) = \mathbf{w}_x^\top \mathbf{F}\,\mathbf{w}_y \]

Each axis with \(n\) points \(\Rightarrow n^d\) points in \(d\) dimensions.

Error of a \(p\)-th order rule: \(O(h^p) = O(n^{-p}) = O\!\left(N^{-p/d}\right)\).

Curse of dimensionality: the higher the dimension, exponentially more samples are needed for the same accuracy.

(analogous to the same terminology used in other fields, e.g., data analysis, machine learning, combinatorics, etc.)

Monte Carlo Integration

Draw \(N\) uniform random points \(\mathbf{x}_i\in\Omega\) and average:

\[ \int_\Omega f(\mathbf{x})\,d\mathbf{x} \approx \frac{\lvert\Omega\rvert}{N}\sum_{i=1}^N f(\mathbf{x}_i) \]

Complicated \(\Omega\)? Sample a bounding box and reject the points outside.

def monte_carlo(f, inside, lo, hi, d, N):
    X = rng.uniform(lo, hi, size=(N, d))  # box
    g = np.where(inside(X), f(X), 0.0)    # reject
    return (hi - lo) ** d * g.mean()

Monte Carlo: Error \(\sim N^{-1/2}\) (independent of \(d\))

Recall: Grid rule had error \(\sim N^{-p/d}\).

Show code
f_d = lambda X: np.exp(X.sum(-1))            # integrate over the unit cube [0, 1]^d
exact_d = lambda d: (np.e - 1) ** d

def grid_midpoint(f, d, n):                  # n^d points, all weights (1/n)^d
    g = (np.arange(n) + 0.5) / n
    X = np.stack(np.meshgrid(*[g] * d, indexing="ij"), -1).reshape(-1, d)
    return f(X).mean()

def mc(f, d, N):
    return f(rng.random((N, d))).mean()

fig, ax = plt.subplots(figsize=(4.6, 4))
for d, n_axis, col in [(2, [3, 10, 32, 100, 316, 1000], "steelblue"), (8, [2, 3, 4, 5, 6], "firebrick")]:
    N = np.array(n_axis) ** d
    e_grid = [abs(grid_midpoint(f_d, d, n) / exact_d(d) - 1) for n in n_axis]
    e_mc = [np.sqrt(np.mean([(mc(f_d, d, n) / exact_d(d) - 1) ** 2 for _ in range(6)])) for n in N]
    ax.loglog(N, e_grid, "-o", ms=4, color=col, label=f"grid midpoint, $d={d}$")
    ax.loglog(N, e_mc, "--s", ms=4, color=col, label=f"Monte Carlo, $d={d}$")
ax.set_xlabel("total number of samples $N$"); ax.set_ylabel("relative error")
ax.set_title("$\\int_{[0,1]^d} e^{x_1+\\cdots+x_d}\\,d\\mathbf{x}$", fontsize=9)
ax.legend(fontsize=7)
plt.tight_layout()
plt.show()

Monte Carlo wins when \(d>2p\).

Demo: Monte Carlo Integration

Monte Carlo Path Tracing

For more details see here and here.

Looking Ahead

Where You’ll See This Again

  • Numerical Differentiation (next class): the opposite character. Integration averages samples with positive weights and damps noise; differentiation divides small differences by \(h\) and amplifies it. Mixing two estimates to cancel the leading error term (as in \(\tfrac{2M+T}{3}\)) returns as Richardson extrapolation.
  • Ordinary Differential Equations: one time step is a quadrature, \[y(t+h) = y(t) + \int_t^{t+h} f\bigl(s, y(s)\bigr)\,ds .\] Left endpoint rule \(\Rightarrow\) forward Euler, trapezoidal rule \(\Rightarrow\) trapezoidal method, midpoint rule \(\Rightarrow\) midpoint method, with the same orders of accuracy.
  • Partial Differential Equations: finite element matrices are integrals of products of basis functions, evaluated with Gaussian quadrature on each element.
  • Wave Propagation and Instruments: the total energy of a simulated string is an integral that we monitor to check stability.

Application: Finite Element Model of a Violin Plate

Goal: predict the vibration modes (Chladni patterns) of a violin top plate, modeled as a flat 3 mm spruce plate with free edges.

Finite element method: Write deflection as \(w\approx\sum_j u_j\,\phi_j\) and solve \(K\mathbf{u}=\omega^2 M\mathbf{u}\), where \[K_{ij}=\int_\Omega \boldsymbol\kappa(\phi_i)^{\top} D\,\boldsymbol\kappa(\phi_j)\,dA,\quad M_{ij}=\int_\Omega \rho h\,\phi_i\,\phi_j\,dA,\quad \boldsymbol\kappa(w)=(w_{xx},\,w_{yy},\,2w_{xy}).\]

\(\boldsymbol\kappa\) is the curvature vector, \(D\) is the bending stiffness matrix, \(\rho\) is the density, and \(h\) is the thickness.

Show code
from scipy.interpolate import CubicSpline
from scipy.spatial import Delaunay
from scipy.linalg import eigh
from matplotlib.path import Path
from matplotlib.tri import Triangulation

def violin_mesh(h=0.01):
    # outline: periodic cubic spline through a few hand-picked points (interpolation, last class)
    half = np.array([[0, 0], [45, 3], [85, 18], [103, 50], [102, 85], [86, 118], [64, 145], [55, 172], [58, 200],
                     [72, 228], [83, 258], [82, 288], [68, 320], [40, 345], [0, 356]]) * 1e-3
    ctrl = np.vstack([half, (half * [-1, 1])[-2:0:-1], half[:1]])
    s = np.concatenate([[0], np.cumsum(np.linalg.norm(np.diff(ctrl, axis=0), axis=1))])
    spline = CubicSpline(s, ctrl, bc_type="periodic")
    fine = spline(np.linspace(0, s[-1], 4000, endpoint=False))
    arc = np.concatenate([[0], np.cumsum(np.linalg.norm(np.diff(fine, axis=0), axis=1))])
    nb = int(arc[-1] / h)
    bnd = np.stack([np.interp(np.arange(nb) * arc[-1] / nb, arc, fine[:, k]) for k in range(2)], 1)
    # interior: hexagonal lattice, then Delaunay, then drop triangles outside the outline
    gx, gy = np.meshgrid(np.arange(-0.12, 0.12, h), np.arange(0, 0.36, h * np.sqrt(3) / 2))
    gx[1::2] += h / 2
    P = np.stack([gx.ravel(), gy.ravel()], 1)
    keep = Path(bnd).contains_points(P) & (np.linalg.norm(P[:, None] - bnd[None], axis=2).min(1) > 0.7 * h)
    P = np.vstack([bnd, P[keep]])
    T = Delaunay(P).simplices
    T = T[Path(bnd).contains_points(P[T].mean(1))]
    return P, T, bnd

# 6 point Gauss rule on a triangle (exact up to degree 4), in barycentric coordinates
a1, a2, w1, w2 = 0.445948490915965, 0.091576213509771, 0.223381589678011, 0.109951743655322
tri_bary = np.array([[a, a, 1 - 2 * a] for a in (a1, a2)]).repeat(3, 0)
tri_bary = np.array([np.roll(r, k) for r, k in zip(tri_bary, [0, 1, 2] * 2)])
tri_w = np.array([w1] * 3 + [w2] * 3)

mono = lambda X: np.stack([np.ones(len(X)), X[:, 0], X[:, 1], X[:, 0]**2, X[:, 0] * X[:, 1], X[:, 1]**2], 1)
dmono = lambda X: (np.stack([0 * X[:, 0], 1 + 0 * X[:, 0], 0 * X[:, 0], 2 * X[:, 0], X[:, 1], 0 * X[:, 0]], 1),
                   np.stack([0 * X[:, 0], 0 * X[:, 0], 1 + 0 * X[:, 0], 0 * X[:, 0], X[:, 0], 2 * X[:, 1]], 1))

def plate_modes(P, T, D, rho_h, n_modes=4):
    """Free vibration of a thin plate. Quadratic (Morley) elements: unknowns are the
    deflection w at the vertices and the normal slope dw/dn at the edge midpoints."""
    edges = {}
    for tri in T:
        for k in range(3):
            edges.setdefault(tuple(sorted((tri[k - 2], tri[k - 1]))), len(P) + len(edges))
    n = len(P) + len(edges)
    K, M = np.zeros((n, n)), np.zeros((n, n))
    for tri in T:
        X = P[tri] - P[tri].mean(0)
        area = 0.5 * abs(np.linalg.det(X[1:] - X[0]))
        dof, A = list(tri), [mono(X)]
        for k in range(3):                                   # edge opposite to vertex k
            i, j = sorted((tri[k - 2], tri[k - 1]))
            t = (P[j] - P[i]) / np.linalg.norm(P[j] - P[i])
            mid = (0.5 * (P[i] + P[j]) - P[tri].mean(0))[None]
            dx, dy = dmono(mid)
            A.append(t[1] * dx - t[0] * dy)
            dof.append(edges[(i, j)])
        C = np.linalg.inv(np.vstack(A))                      # column j = coefficients of basis function phi_j
        B = np.stack([2 * C[3], 2 * C[5], 2 * C[4]])         # curvatures (w_xx, w_yy, 2 w_xy), constant on the triangle
        Phi = mono(tri_bary @ X) @ C                         # phi_j at the 6 Gauss points
        K[np.ix_(dof, dof)] += area * B.T @ D @ B                        # integral of kappa_i^T D kappa_j
        M[np.ix_(dof, dof)] += rho_h * area * (Phi.T * tri_w) @ Phi      # integral of rho h phi_i phi_j
    lam, U = eigh(K, M, subset_by_index=[0, n_modes + 2])
    return np.sqrt(np.abs(lam[3:])) / (2 * np.pi), U[: len(P), 3:]       # skip the 3 rigid body motions

P, T, outline = violin_mesh()

# spruce, grain along the length (y): bending stiffness matrix D = h^3/12 * Q
E_x, E_y, G, nu_yx, rho, thick = 0.9e9, 11e9, 0.7e9, 0.37, 420.0, 3e-3
c = 1 - nu_yx**2 * E_x / E_y
D = thick**3 / 12 * np.array([[E_x / c, nu_yx * E_x / c, 0], [nu_yx * E_x / c, E_y / c, 0], [0, 0, G]])
freq, modes = plate_modes(P, T, D, rho * thick)

fig, axs = plt.subplots(1, 6, figsize=(9, 2.5), width_ratios=[1, 1.15, 1, 1, 1, 1])
closed = np.vstack([outline, outline[:1]])
axs[0].triplot(P[:, 0], P[:, 1], T, color="steelblue", lw=0.4)
axs[0].plot(closed[:, 0], closed[:, 1], color="k", lw=0.8)
axs[0].set_title(f"{len(T)} triangles", fontsize=9)

e = np.argmin(np.linalg.norm(P[T].mean(1) - [0.0, 0.17], axis=1))       # zoom into one element
X = P[T[e]]
axs[0].fill(X[:, 0], X[:, 1], color="firebrick")
axs[1].fill(X[:, 0], X[:, 1], color="steelblue", alpha=0.15, ec="steelblue")
axs[1].scatter(*(tri_bary @ X).T, s=400 * tri_w, color="firebrick", label="Gauss points")
axs[1].plot(X[:, 0], X[:, 1], "o", color="k", ms=5, label="$w$")
mid = 0.5 * (X + np.roll(X, 1, 0))
axs[1].plot(mid[:, 0], mid[:, 1], "s", color="k", mfc="w", ms=5, label="$\\partial w/\\partial n$")
axs[1].margins(0.2)
axs[1].legend(fontsize=6, loc="upper center", bbox_to_anchor=(0.5, 0.0), ncol=1, frameon=False)
axs[1].set_title("one element", fontsize=9)

tri = Triangulation(P[:, 0], P[:, 1], T)
for k, ax in enumerate(axs[2:]):
    w = modes[:, k] / np.abs(modes[:, k]).max()
    ax.tricontourf(tri, w, levels=np.linspace(-1, 1, 21), cmap="RdBu_r")
    ax.tricontour(tri, w, levels=[0], colors="k", linewidths=1.2)        # nodal lines (Chladni pattern)
    ax.plot(closed[:, 0], closed[:, 1], color="k", lw=0.8)
    ax.set_title(f"mode {k + 1}: {freq[k]:.0f} Hz", fontsize=9)
for ax in axs:
    ax.set_aspect("equal"); ax.axis("off")
for ax in [axs[0], *axs[2:]]:
    ax.set_xlim(-0.115, 0.115); ax.set_ylim(-0.01, 0.366)
plt.tight_layout()
plt.show()

Triangular mesh \(\to\) quadratic basis functions \(\phi_j\) \(\to\) generalized eigenvalue problem \(K\mathbf{u}=\omega^2 M\mathbf{u}\).

Each entry is a sum of triangle integrals \(\int_T g\,dA\approx|T|\sum_q w_q\,g(\mathbf{x}_q)\).

Here \(\phi_i\phi_j\) has degree 4, so a 6 point Gauss rule is exact.

Recap

Recap on Today’s ILO

  • Derive quadrature rules as weighted sums of samples, and implement the composite rules.
    • Weights are areas under basis functions. Constant, linear, quadratic interpolants give midpoint, trapezoid, Simpson.
  • Predict and measure the order of accuracy, and recognize when it breaks down.
    • Midpoint and trapezoid: \(O(h^2)\). Simpson: \(O(h^4)\).
  • Decide where to sample.
    • Gaussian: \(n\) nodes, exact to degree \(2n-1\), for smooth \(f\) we can evaluate anywhere.
    • Adaptive: compare two estimates and refine only where they disagree.
  • Integrate in many dimensions.
    • Grid rules: \(O(N^{-p/d})\). Monte Carlo: \(O(N^{-1/2})\) in any dimension.
  • Next class: differentiation amplifies the noise that integration averages out.