<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://charlesreid1.com/w/index.php?action=history&amp;feed=atom&amp;title=Numerics%2FIntegration_of_Functions</id>
	<title>Numerics/Integration of Functions - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://charlesreid1.com/w/index.php?action=history&amp;feed=atom&amp;title=Numerics%2FIntegration_of_Functions"/>
	<link rel="alternate" type="text/html" href="https://charlesreid1.com/w/index.php?title=Numerics/Integration_of_Functions&amp;action=history"/>
	<updated>2026-06-22T19:46:40Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.39.12</generator>
	<entry>
		<id>https://charlesreid1.com/w/index.php?title=Numerics/Integration_of_Functions&amp;diff=30748&amp;oldid=prev</id>
		<title>Admin: Create Numerics/Integration of Functions: comprehensive coverage of Gaussian quadrature, numerical integration, method of moments, adaptive quadrature, Monte Carlo, and more — Python-centric with code examples. (via create-page on MediaWiki MCP Server)</title>
		<link rel="alternate" type="text/html" href="https://charlesreid1.com/w/index.php?title=Numerics/Integration_of_Functions&amp;diff=30748&amp;oldid=prev"/>
		<updated>2026-06-22T04:46:30Z</updated>

		<summary type="html">&lt;p&gt;Create Numerics/Integration of Functions: comprehensive coverage of Gaussian quadrature, numerical integration, method of moments, adaptive quadrature, Monte Carlo, and more — Python-centric with code examples. (via create-page on MediaWiki MCP Server)&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;==Philosophy==&lt;br /&gt;
&lt;br /&gt;
Integration of functions is one of the three pillars of computational mathematics, alongside differentiation and root-finding. The central problem: given a function &amp;lt;math&amp;gt;f(x)&amp;lt;/math&amp;gt; defined on &amp;lt;math&amp;gt;[a,b]&amp;lt;/math&amp;gt;, compute&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
I = \int_{a}^{b} f(x) \, dx&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
with as few function evaluations as possible, to a prescribed accuracy. That&amp;#039;s it. Everything else — adaptive quadrature, Gaussian rules, Monte Carlo, moments — is just strategy for getting the answer cheap and honest.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Opinionated stance:&amp;#039;&amp;#039;&amp;#039; If you reach for &amp;lt;code&amp;gt;scipy.integrate.quad&amp;lt;/code&amp;gt; without understanding what it does under the hood, you are doing it wrong. That function is an adaptive Gauss-Kronrod scheme — knowing &amp;#039;&amp;#039;why&amp;#039;&amp;#039; that matters is the difference between trusting your answer and being a passenger in someone else&amp;#039;s code.&lt;br /&gt;
&lt;br /&gt;
==The Quadrature Mindset==&lt;br /&gt;
&lt;br /&gt;
All numerical integration reduces to a weighted sum:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
I \approx \sum_{i=0}^{n} w_i \, f(x_i)&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;x_i&amp;lt;/math&amp;gt; are the &amp;#039;&amp;#039;&amp;#039;abscissas&amp;#039;&amp;#039;&amp;#039; (nodes) and &amp;lt;math&amp;gt;w_i&amp;lt;/math&amp;gt; are the &amp;#039;&amp;#039;&amp;#039;weights&amp;#039;&amp;#039;&amp;#039;. Every method in this article — Newton-Cotes, Gaussian quadrature, Clenshaw-Curtis, Monte Carlo — is a particular choice of &amp;lt;math&amp;gt;\{x_i, w_i\}&amp;lt;/math&amp;gt;. The art is picking the right pair for your integrand.&lt;br /&gt;
&lt;br /&gt;
A quadrature rule of degree &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; integrates all polynomials up to order &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; &amp;#039;&amp;#039;exactly&amp;#039;&amp;#039;. This is the benchmark. If your integrand is smooth (analytic), high-degree rules crush low-degree ones. If your integrand has kinks, corners, or singularities, no amount of polynomial exactness will save you — you need to handle the nasty part analytically, split the interval, or change variables.&lt;br /&gt;
&lt;br /&gt;
==Newton-Cotes: The Obvious (and Usually Wrong) Choice==&lt;br /&gt;
&lt;br /&gt;
===Trapezoidal Rule (n=1)===&lt;br /&gt;
&lt;br /&gt;
The simplest closed Newton-Cotes formula. Fit a straight line through &amp;lt;math&amp;gt;f(a)&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;f(b)&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\int_a^b f(x) \, dx \approx \frac{b-a}{2} \left[ f(a) + f(b) \right]&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Error: &amp;lt;math&amp;gt;-\frac{(b-a)^3}{12} f&amp;#039;&amp;#039;(\xi)&amp;lt;/math&amp;gt; for some &amp;lt;math&amp;gt;\xi \in [a,b]&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Degree: 1 (exact for linear polynomials).&lt;br /&gt;
&lt;br /&gt;
===Simpson&amp;#039;s Rule (n=2)===&lt;br /&gt;
&lt;br /&gt;
Fit a parabola through three equally spaced points:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\int_a^b f(x) \, dx \approx \frac{b-a}{6} \left[ f(a) + 4f\!\left(\frac{a+b}{2}\right) + f(b) \right]&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Error: &amp;lt;math&amp;gt;-\frac{(b-a)^5}{2880} f^{(4)}(\xi)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Degree: 3. Simpson&amp;#039;s rule is exact for cubics, which is why it overperforms relative to the naive expectation. This is a happy accident of symmetry — the &amp;lt;math&amp;gt;x^3&amp;lt;/math&amp;gt; error term cancels.&lt;br /&gt;
&lt;br /&gt;
===Simpson&amp;#039;s 3/8 Rule (n=3)===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\int_a^b f(x) \, dx \approx \frac{3h}{8} \left[ f(a) + 3f(a+h) + 3f(a+2h) + f(b) \right]&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;h = (b-a)/3&amp;lt;/math&amp;gt;. Degree: 3 (same as Simpson&amp;#039;s, but with one extra function evaluation — almost never worth it).&lt;br /&gt;
&lt;br /&gt;
===Composite Rules===&lt;br /&gt;
&lt;br /&gt;
Applying a low-order rule over many subintervals is the workhorse approach:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\int_a^b f(x) \, dx = \sum_{k=0}^{N-1} \int_{x_k}^{x_{k+1}} f(x) \, dx&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Composite trapezoidal with &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; panels: error &amp;lt;math&amp;gt;\mathcal{O}(h^2)&amp;lt;/math&amp;gt; where &amp;lt;math&amp;gt;h = (b-a)/N&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Composite Simpson&amp;#039;s: error &amp;lt;math&amp;gt;\mathcal{O}(h^4)&amp;lt;/math&amp;gt;. This is the sweet spot for many engineering applications.&lt;br /&gt;
&lt;br /&gt;
===Why Newton-Cotes Is Usually Wrong===&lt;br /&gt;
&lt;br /&gt;
High-order Newton-Cotes rules (n &amp;gt; 8) suffer from &amp;#039;&amp;#039;&amp;#039;Runge&amp;#039;s phenomenon&amp;#039;&amp;#039;&amp;#039;: the weights oscillate and include negative values, making them numerically unstable. The weights grow exponentially in magnitude, and cancellation destroys precision. If you need high accuracy, you don&amp;#039;t use high-order Newton-Cotes — you use Gaussian quadrature.&lt;br /&gt;
&lt;br /&gt;
==Gaussian Quadrature: The Right Way==&lt;br /&gt;
&lt;br /&gt;
===The Big Idea===&lt;br /&gt;
&lt;br /&gt;
Gaussian quadrature chooses &amp;#039;&amp;#039;both&amp;#039;&amp;#039; the nodes &amp;lt;math&amp;gt;x_i&amp;lt;/math&amp;gt; and weights &amp;lt;math&amp;gt;w_i&amp;lt;/math&amp;gt; to maximize the polynomial degree of exactness. With &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; points, a Gaussian rule has degree &amp;lt;math&amp;gt;2n - 1&amp;lt;/math&amp;gt; — nearly double Newton-Cotes (which has degree &amp;lt;math&amp;gt;n-1&amp;lt;/math&amp;gt; for even &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
How? The nodes are the roots of orthogonal polynomials. This is not a coincidence — it&amp;#039;s a deep and beautiful connection between approximation theory and spectral methods.&lt;br /&gt;
&lt;br /&gt;
===Gauss-Legendre (The Default)===&lt;br /&gt;
&lt;br /&gt;
For integrals on &amp;lt;math&amp;gt;[-1, 1]&amp;lt;/math&amp;gt; with unit weight function &amp;lt;math&amp;gt;w(x) = 1&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\int_{-1}^{1} f(x) \, dx \approx \sum_{i=1}^{n} w_i f(x_i)&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;x_i&amp;lt;/math&amp;gt; are the roots of the Legendre polynomial &amp;lt;math&amp;gt;P_n(x)&amp;lt;/math&amp;gt;, and&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
w_i = \frac{2}{(1 - x_i^2) [P_n&amp;#039;(x_i)]^2}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Python implementation (opinionated: use NumPy&amp;#039;s polynomial tools, not tables):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
from numpy.polynomial.legendre import leggauss&lt;br /&gt;
&lt;br /&gt;
def gauss_legendre(f, a, b, n):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    Integrate f(x) from a to b using n-point Gauss-Legendre.&lt;br /&gt;
&lt;br /&gt;
    This is the workhorse. For smooth integrands, n=16 is usually&lt;br /&gt;
    more than enough for machine precision on [0,1].&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    x_ref, w_ref = leggauss(n)&lt;br /&gt;
    # Map from [-1, 1] to [a, b]&lt;br /&gt;
    x_mapped = 0.5 * (b - a) * x_ref + 0.5 * (a + b)&lt;br /&gt;
    w_mapped = 0.5 * (b - a) * w_ref&lt;br /&gt;
    return np.dot(w_mapped, f(x_mapped))&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Gauss-Kronrod: The Error Estimator You Actually Need===&lt;br /&gt;
&lt;br /&gt;
Plain Gaussian quadrature has no built-in error estimate. Gauss-Kronrod solves this by computing two rules simultaneously: an &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;-point Gauss rule (degree &amp;lt;math&amp;gt;2n-1&amp;lt;/math&amp;gt;), and a &amp;lt;math&amp;gt;(2n+1)&amp;lt;/math&amp;gt;-point Kronrod extension that reuses the &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; Gauss nodes and adds &amp;lt;math&amp;gt;n+1&amp;lt;/math&amp;gt; new ones. The difference between them estimates the error.&lt;br /&gt;
&lt;br /&gt;
The classic pair is G7/K15 (7 Gauss nodes, 15 Kronrod nodes). This is the engine inside &amp;lt;code&amp;gt;scipy.integrate.quad&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
# Tabulated G7/K15 nodes and weights on [-1, 1]&lt;br /&gt;
# (Generated via high-precision root-finding; these are standard.)&lt;br /&gt;
GK15_NODES = np.array([&lt;br /&gt;
    -0.9914553711208126, -0.9491079123427585, -0.8648644233597691,&lt;br /&gt;
    -0.7415311855993945, -0.5860872354676911, -0.4058451513773972,&lt;br /&gt;
    -0.2077849550078985,  0.0,&lt;br /&gt;
     0.2077849550078985,  0.4058451513773972,  0.5860872354676911,&lt;br /&gt;
     0.7415311855993945,  0.8648644233597691,  0.9491079123427585,&lt;br /&gt;
     0.9914553711208126&lt;br /&gt;
])&lt;br /&gt;
&lt;br /&gt;
GK15_WEIGHTS_KRONROD = np.array([&lt;br /&gt;
    0.022935322010529, 0.063092092629979, 0.104790010322250,&lt;br /&gt;
    0.140653259715525, 0.169004726639267, 0.190350578064785,&lt;br /&gt;
    0.204432940075298, 0.209482141084728,&lt;br /&gt;
    0.204432940075298, 0.190350578064785, 0.169004726639267,&lt;br /&gt;
    0.140653259715525, 0.104790010322250, 0.063092092629979,&lt;br /&gt;
    0.022935322010529&lt;br /&gt;
])&lt;br /&gt;
&lt;br /&gt;
def gauss_kronrod_15(f, a, b):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;15-point Gauss-Kronrod quadrature on [a, b], returns (estimate, error).&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    x = 0.5 * (b - a) * GK15_NODES + 0.5 * (a + b)&lt;br /&gt;
    w = 0.5 * (b - a) * GK15_WEIGHTS_KRONROD&lt;br /&gt;
    estimate = np.dot(w, f(x))&lt;br /&gt;
&lt;br /&gt;
    # Gauss 7-point rule for comparison (reuses subset of nodes)&lt;br /&gt;
    g7_idx = [1, 3, 5, 7, 9, 11, 13]&lt;br /&gt;
    g7_weights = np.array([0.129484966168870, 0.279705391489277,&lt;br /&gt;
                           0.381830050505119, 0.417959183673469,&lt;br /&gt;
                           0.381830050505119, 0.279705391489277,&lt;br /&gt;
                           0.129484966168870])&lt;br /&gt;
    g7_x = x[g7_idx]&lt;br /&gt;
    g7_w = 0.5 * (b - a) * g7_weights&lt;br /&gt;
    g7_est = np.dot(g7_w, f(g7_x))&lt;br /&gt;
&lt;br /&gt;
    error = np.abs(estimate - g7_est)&lt;br /&gt;
    return estimate, error&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Other Gaussian Flavors===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Quadrature !! Weight Function !! Interval !! Orthogonal Polynomial&lt;br /&gt;
|-&lt;br /&gt;
| Gauss-Legendre || &amp;lt;math&amp;gt;w(x)=1&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;[-1,1]&amp;lt;/math&amp;gt; || Legendre &amp;lt;math&amp;gt;P_n&amp;lt;/math&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Gauss-Chebyshev || &amp;lt;math&amp;gt;w(x)=1/\sqrt{1-x^2}&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;[-1,1]&amp;lt;/math&amp;gt; || Chebyshev &amp;lt;math&amp;gt;T_n&amp;lt;/math&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Gauss-Laguerre || &amp;lt;math&amp;gt;w(x)=e^{-x}&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;[0,\infty)&amp;lt;/math&amp;gt; || Laguerre &amp;lt;math&amp;gt;L_n&amp;lt;/math&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Gauss-Hermite || &amp;lt;math&amp;gt;w(x)=e^{-x^2}&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;(-\infty,\infty)&amp;lt;/math&amp;gt; || Hermite &amp;lt;math&amp;gt;H_n&amp;lt;/math&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Gauss-Jacobi || &amp;lt;math&amp;gt;w(x)=(1-x)^\alpha(1+x)^\beta&amp;lt;/math&amp;gt; || &amp;lt;math&amp;gt;[-1,1]&amp;lt;/math&amp;gt; || Jacobi &amp;lt;math&amp;gt;P_n^{(\alpha,\beta)}&amp;lt;/math&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;When to use each:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Gauss-Laguerre&amp;#039;&amp;#039;&amp;#039; — Integrals over &amp;lt;math&amp;gt;[0,\infty)&amp;lt;/math&amp;gt; with exponential decay. Ideal for Laplace transform inversion and radial integrals in quantum mechanics.&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Gauss-Hermite&amp;#039;&amp;#039;&amp;#039; — Integrals over &amp;lt;math&amp;gt;(-\infty,\infty)&amp;lt;/math&amp;gt; with Gaussian decay. The natural choice for expectation values under a normal distribution.&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Gauss-Chebyshev&amp;#039;&amp;#039;&amp;#039; — Integrands with endpoint singularities of type &amp;lt;math&amp;gt;1/\sqrt{1-x^2}&amp;lt;/math&amp;gt;. Great for functions with square-root branch cuts.&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Gauss-Jacobi&amp;#039;&amp;#039;&amp;#039; — Endpoint singularities of type &amp;lt;math&amp;gt;(1-x)^\alpha(1+x)^\beta&amp;lt;/math&amp;gt;. When you know the singularity structure, Jacabi rules are surgical.&lt;br /&gt;
&lt;br /&gt;
Pro tip: You can always map &amp;lt;math&amp;gt;[a,b]&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;[-1,1]&amp;lt;/math&amp;gt; with a linear change of variables:&lt;br /&gt;
&amp;lt;math&amp;gt;x = \frac{b-a}{2}t + \frac{a+b}{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Adaptive Quadrature: Let the Computer Do the Work==&lt;br /&gt;
&lt;br /&gt;
Fixed-order quadrature is naive. Adaptive quadrature recursively subdivides the interval where the error estimate is large and coarsens where it&amp;#039;s small. The algorithm:&lt;br /&gt;
&lt;br /&gt;
# Compute &amp;lt;math&amp;gt;I_{[a,b]}&amp;lt;/math&amp;gt; with rule of order &amp;lt;math&amp;gt;p&amp;lt;/math&amp;gt;&lt;br /&gt;
# Compute &amp;lt;math&amp;gt;I_{[a,\frac{a+b}{2}]} + I_{[\frac{a+b}{2},b]}&amp;lt;/math&amp;gt; with the same rule&lt;br /&gt;
# If &amp;lt;math&amp;gt;|I_{[a,b]} - (I_{left} + I_{right})| &amp;lt; \varepsilon&amp;lt;/math&amp;gt;, accept and return&lt;br /&gt;
# Otherwise, recurse on each half&lt;br /&gt;
&lt;br /&gt;
This is the algorithm behind &amp;lt;code&amp;gt;scipy.integrate.quad&amp;lt;/code&amp;gt; (which uses adaptive Gauss-Kronrod with the G7/K15 pair).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
def adaptive_quad(f, a, b, tol=1e-12, max_depth=50):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    Adaptive quadrature using the 15-point Gauss-Kronrod rule.&lt;br /&gt;
    Recurses on intervals where error exceeds tolerance.&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    def _recurse(a, b, depth):&lt;br /&gt;
        I, err = gauss_kronrod_15(f, a, b)&lt;br /&gt;
        if err &amp;lt; tol * (b - a) or depth &amp;gt;= max_depth:&lt;br /&gt;
            return I, err&lt;br /&gt;
&lt;br /&gt;
        mid = 0.5 * (a + b)&lt;br /&gt;
        I_left, err_left = _recurse(a, mid, depth + 1)&lt;br /&gt;
        I_right, err_right = _recurse(mid, b, depth + 1)&lt;br /&gt;
        return I_left + I_right, err_left + err_right&lt;br /&gt;
&lt;br /&gt;
    return _recurse(a, b, 0)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Clenshaw-Curtis (The FFT-Based Alternative)==&lt;br /&gt;
&lt;br /&gt;
Clenshaw-Curtis quadrature uses Chebyshev nodes &amp;lt;math&amp;gt;x_k = \cos(k\pi/N)&amp;lt;/math&amp;gt; (the extrema of &amp;lt;math&amp;gt;T_N(x)&amp;lt;/math&amp;gt;) and weights computed via FFT. With &amp;lt;math&amp;gt;n+1&amp;lt;/math&amp;gt; points, it has degree &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; — half that of Gauss-Legendre — but the weights are &amp;#039;&amp;#039;all positive&amp;#039;&amp;#039; and the nodes are nested (the &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;-point set is a subset of the &amp;lt;math&amp;gt;2n&amp;lt;/math&amp;gt;-point set), enabling efficient adaptive schemes.&lt;br /&gt;
&lt;br /&gt;
For smooth, non-pathological integrands, Gauss-Legendre converges roughly twice as fast. But Clenshaw-Curtis is trivially adaptive (reuse old evaluations), and the FFT weight computation makes it &amp;lt;math&amp;gt;\mathcal{O}(N \log N)&amp;lt;/math&amp;gt; to set up. In practice, the difference is often negligible — both converge exponentially for analytic integrands.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Opinion:&amp;#039;&amp;#039;&amp;#039; Use Gauss-Legendre if you can afford to pick &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; up front. Use Clenshaw-Curtis if you&amp;#039;re building an adaptive scheme and nested nodes matter.&lt;br /&gt;
&lt;br /&gt;
==Method of Moments==&lt;br /&gt;
&lt;br /&gt;
===The Statistical View===&lt;br /&gt;
&lt;br /&gt;
&amp;quot;Method of moments&amp;quot; in integration refers to two distinct things. The first is statistical: estimate an integral by matching moments of an approximating distribution to sample moments. For integration, this means:&lt;br /&gt;
&lt;br /&gt;
Given moments &amp;lt;math&amp;gt;\mu_k = \int x^k f(x) \, dx&amp;lt;/math&amp;gt; for &amp;lt;math&amp;gt;k = 0, 1, \dots, m&amp;lt;/math&amp;gt;, approximate &amp;lt;math&amp;gt;\int g(x) f(x) \, dx&amp;lt;/math&amp;gt; by constructing a quadrature rule that exactly integrates the first &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; monomials against &amp;lt;math&amp;gt;f(x)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
This is exactly what Gaussian quadrature does — the &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;-point Gauss rule with weight &amp;lt;math&amp;gt;f(x)&amp;lt;/math&amp;gt; is the unique rule that matches the first &amp;lt;math&amp;gt;2n&amp;lt;/math&amp;gt; moments. The nodes of Gaussian quadrature are the roots of the polynomial orthogonal with respect to the weight &amp;lt;math&amp;gt;f(x)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===The Quadrature Method of Moments (QMOM)===&lt;br /&gt;
&lt;br /&gt;
In engineering — especially reacting flow, aerosol dynamics, and population balance modeling — QMOM approximates a continuous distribution by a small number of delta functions:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
n(x) \approx \sum_{i=1}^{N} w_i \, \delta(x - x_i)&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;math&amp;gt;2N&amp;lt;/math&amp;gt; parameters &amp;lt;math&amp;gt;\{w_i, x_i\}&amp;lt;/math&amp;gt; are determined by matching the first &amp;lt;math&amp;gt;2N&amp;lt;/math&amp;gt; moments:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
m_k = \int x^k n(x) \, dx = \sum_{i=1}^{N} w_i \, x_i^k \qquad k = 0, 1, \dots, 2N-1&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is solved via the &amp;#039;&amp;#039;&amp;#039;Product-Difference (PD) algorithm&amp;#039;&amp;#039;&amp;#039; (Gordon, 1968) or the &amp;#039;&amp;#039;&amp;#039;Wheeler algorithm&amp;#039;&amp;#039;&amp;#039;, which computes the recurrence coefficients of the orthogonal polynomials from the moments, then finds the nodes and weights via Golub-Welsch (eigenvalues of the Jacobi matrix).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
def golub_welsch(alpha, beta):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    Compute Gaussian quadrature nodes and weights from recurrence&lt;br /&gt;
    coefficients alpha (diagonal) and sqrt(beta) (off-diagonal)&lt;br /&gt;
    of the Jacobi matrix J. Nodes = eigenvalues of J.&lt;br /&gt;
    Weights = w_0 * (first component of eigenvectors)^2.&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    n = len(alpha)&lt;br /&gt;
    J = np.diag(alpha)&lt;br /&gt;
    for i in range(n - 1):&lt;br /&gt;
        J[i, i + 1] = np.sqrt(beta[i + 1])&lt;br /&gt;
        J[i + 1, i] = np.sqrt(beta[i + 1])&lt;br /&gt;
&lt;br /&gt;
    eigenvalues, eigenvectors = np.linalg.eigh(J)&lt;br /&gt;
    weights = beta[0] * eigenvectors[0, :]**2&lt;br /&gt;
    # Sort by eigenvalue&lt;br /&gt;
    idx = np.argsort(eigenvalues)&lt;br /&gt;
    return eigenvalues[idx], weights[idx]&lt;br /&gt;
&lt;br /&gt;
def moments_to_quadrature(moments, n_nodes):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    Given the first 2*n_nodes moments, compute the n_nodes-point&lt;br /&gt;
    Gaussian quadrature rule via the Wheeler algorithm.&lt;br /&gt;
    moments[k] = ∫ x^k w(x) dx for k = 0..2n-1.&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    assert len(moments) &amp;gt;= 2 * n_nodes&lt;br /&gt;
    M = 2 * n_nodes&lt;br /&gt;
    # Modified Chebyshev algorithm / Wheeler&lt;br /&gt;
    alpha = np.zeros(n_nodes)&lt;br /&gt;
    beta = np.zeros(n_nodes)&lt;br /&gt;
    beta[0] = moments[0]&lt;br /&gt;
&lt;br /&gt;
    sigma = np.zeros((M, M))&lt;br /&gt;
    sigma[0, :] = moments[:M]&lt;br /&gt;
&lt;br /&gt;
    for k in range(1, M):&lt;br /&gt;
        for ell in range(k, M):&lt;br /&gt;
            sigma[k, ell] = sigma[k - 1, ell + 1] \&lt;br /&gt;
                          - alpha[k - 1] * sigma[k - 1, ell] \&lt;br /&gt;
                          - beta[k - 1] * sigma[k - 2, ell] \&lt;br /&gt;
                              if k &amp;gt; 1 else sigma[k - 1, ell + 1] \&lt;br /&gt;
                                         - alpha[k - 1] * sigma[k - 1, ell]&lt;br /&gt;
&lt;br /&gt;
        if k &amp;lt; n_nodes:&lt;br /&gt;
            alpha[k] = sigma[k, k + 1] / sigma[k, k] - sigma[k - 1, k] / sigma[k - 1, k - 1]&lt;br /&gt;
            beta[k] = sigma[k, k] / sigma[k - 1, k - 1]&lt;br /&gt;
&lt;br /&gt;
    return golub_welsch(alpha[:n_nodes], beta[:n_nodes])&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Why QMOM Wins===&lt;br /&gt;
&lt;br /&gt;
For tracking particle size distributions in CFD, QMOM with &amp;lt;math&amp;gt;N=3&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;N=4&amp;lt;/math&amp;gt; nodes (6-8 moments) often matches the accuracy of sectional methods with hundreds of bins. The cost: solving a small eigenvalue problem per cell per timestep, versus transporting hundreds of scalars. The tradeoff is heavily in QMOM&amp;#039;s favor for reacting multiphase flows.&lt;br /&gt;
&lt;br /&gt;
==Monte Carlo Integration: When Dimension Kills You==&lt;br /&gt;
&lt;br /&gt;
All the methods above scale &amp;#039;&amp;#039;exponentially&amp;#039;&amp;#039; with dimension — a product rule with &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; points per axis in &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; dimensions uses &amp;lt;math&amp;gt;n^d&amp;lt;/math&amp;gt; evaluations. For &amp;lt;math&amp;gt;d &amp;gt; 4&amp;lt;/math&amp;gt;, you need Monte Carlo.&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo estimator:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
I = \int_{\Omega} f(\mathbf{x}) \, d\mathbf{x} \approx \frac{V}{N} \sum_{i=1}^{N} f(\mathbf{x}_i), \quad \mathbf{x}_i \sim \text{Uniform}(\Omega)&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Error: &amp;lt;math&amp;gt;\mathcal{O}(1/\sqrt{N})&amp;lt;/math&amp;gt;, &amp;#039;&amp;#039;&amp;#039;independent of dimension&amp;#039;&amp;#039;&amp;#039;. This is the killer feature. For &amp;lt;math&amp;gt;d=1&amp;lt;/math&amp;gt;, MC is terrible compared to Gaussian quadrature. For &amp;lt;math&amp;gt;d=10&amp;lt;/math&amp;gt;, MC is your only option.&lt;br /&gt;
&lt;br /&gt;
Variance reduction techniques:&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Importance sampling:&amp;#039;&amp;#039;&amp;#039; Draw from &amp;lt;math&amp;gt;p(\mathbf{x}) \propto |f(\mathbf{x})|&amp;lt;/math&amp;gt; instead of uniform. Reweight by &amp;lt;math&amp;gt;1/p(\mathbf{x})&amp;lt;/math&amp;gt;.&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Stratified sampling:&amp;#039;&amp;#039;&amp;#039; Partition &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt; into strata, sample each, combine. Guarantees variance ≤ crude MC.&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Quasi-Monte Carlo (QMC):&amp;#039;&amp;#039;&amp;#039; Use low-discrepancy sequences (Sobol, Halton) instead of pseudorandom. Error drops to &amp;lt;math&amp;gt;\mathcal{O}((\log N)^d / N)&amp;lt;/math&amp;gt;, which for moderate &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; and large &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; beats &amp;lt;math&amp;gt;1/\sqrt{N}&amp;lt;/math&amp;gt; substantially.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
from scipy.stats import qmc  # Quasi-Monte Carlo&lt;br /&gt;
&lt;br /&gt;
def monte_carlo_integrate(f, bounds, n=100_000, method=&amp;#039;sobol&amp;#039;):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    Integrate f over a d-dimensional box defined by bounds = [(a1,b1), ...].&lt;br /&gt;
    Uses Sobol QMC by default. Crude MC if method=&amp;#039;random&amp;#039;.&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    dim = len(bounds)&lt;br /&gt;
    if method == &amp;#039;sobol&amp;#039;:&lt;br /&gt;
        sampler = qmc.Sobol(d=dim, scramble=True)&lt;br /&gt;
        points = sampler.random(n)&lt;br /&gt;
    else:&lt;br /&gt;
        points = np.random.uniform(size=(n, dim))&lt;br /&gt;
&lt;br /&gt;
    # Map to bounds&lt;br /&gt;
    volume = 1.0&lt;br /&gt;
    for i, (lo, hi) in enumerate(bounds):&lt;br /&gt;
        points[:, i] = lo + (hi - lo) * points[:, i]&lt;br /&gt;
        volume *= (hi - lo)&lt;br /&gt;
&lt;br /&gt;
    vals = np.array([f(p) for p in points])&lt;br /&gt;
    estimate = volume * np.mean(vals)&lt;br /&gt;
    stderr = volume * np.std(vals) / np.sqrt(n)&lt;br /&gt;
    return estimate, stderr&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Dealing with Singularities==&lt;br /&gt;
&lt;br /&gt;
===Subtract the Singularity===&lt;br /&gt;
&lt;br /&gt;
If &amp;lt;math&amp;gt;f(x) = g(x) / \sqrt{x}&amp;lt;/math&amp;gt; on &amp;lt;math&amp;gt;[0, 1]&amp;lt;/math&amp;gt;, separate out the nasty part:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\int_0^1 \frac{g(x)}{\sqrt{x}} dx = \int_0^1 \frac{g(0)}{\sqrt{x}} dx + \int_0^1 \frac{g(x) - g(0)}{\sqrt{x}} dx&lt;br /&gt;
= 2g(0) + \int_0^1 \frac{g(x) - g(0)}{\sqrt{x}} dx&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The first term is analytic; the second has a removable singularity (the numerator now vanishes at &amp;lt;math&amp;gt;x=0&amp;lt;/math&amp;gt;) and can be integrated with standard methods.&lt;br /&gt;
&lt;br /&gt;
===Change of Variables===&lt;br /&gt;
&lt;br /&gt;
For endpoint singularities, a nonlinear change of variables can remove them entirely. The classic transformation for &amp;lt;math&amp;gt;[0,1]&amp;lt;/math&amp;gt; with &amp;lt;math&amp;gt;\sqrt{x}&amp;lt;/math&amp;gt; singularities:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
x = t^2, \quad dx = 2t \, dt, \quad \int_0^1 \frac{g(x)}{\sqrt{x}} dx = 2 \int_0^1 g(t^2) \, dt&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The integrand is now analytic at &amp;lt;math&amp;gt;t=0&amp;lt;/math&amp;gt;. Gaussian quadrature or even Simpson will work perfectly.&lt;br /&gt;
&lt;br /&gt;
For a &amp;lt;math&amp;gt;1/x&amp;lt;/math&amp;gt; singularity, the double-exponential (tanh-sinh) transformation maps the interval to &amp;lt;math&amp;gt;(-\infty, \infty)&amp;lt;/math&amp;gt; with exponential decay, making the integrand amenable to trapezoidal rule (which converges exponentially for analytic functions on &amp;lt;math&amp;gt;\mathbb{R}&amp;lt;/math&amp;gt;):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
x = \tanh\left( \frac{\pi}{2} \sinh t \right), \quad dx = \frac{\pi}{2} \frac{\cosh t}{\cosh^2\left( \frac{\pi}{2} \sinh t \right)} dt&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is the basis of &amp;#039;&amp;#039;&amp;#039;tanh-sinh quadrature&amp;#039;&amp;#039;&amp;#039;, arguably the most robust black-box integrator for 1D problems with endpoint singularities.&lt;br /&gt;
&lt;br /&gt;
===Tellegen&amp;#039;s Integration (Complex Plane)===&lt;br /&gt;
&lt;br /&gt;
If you can evaluate &amp;lt;math&amp;gt;f(z)&amp;lt;/math&amp;gt; for complex &amp;lt;math&amp;gt;z&amp;lt;/math&amp;gt;, integrating along a contour in the complex plane that avoids a real-axis singularity is a clean way to handle integrable singularities without subtraction or variable transformations.&lt;br /&gt;
&lt;br /&gt;
==Infinite Intervals==&lt;br /&gt;
&lt;br /&gt;
For &amp;lt;math&amp;gt;\int_0^\infty f(x) dx&amp;lt;/math&amp;gt; with algebraic decay (&amp;lt;math&amp;gt;f(x) \sim x^{-\alpha}&amp;lt;/math&amp;gt;), map to &amp;lt;math&amp;gt;[0,1]&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
t = \frac{x}{1+x}, \quad x = \frac{t}{1-t}, \quad dx = \frac{dt}{(1-t)^2}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then &amp;lt;math&amp;gt;\int_0^\infty f(x) dx = \int_0^1 f\!\left(\frac{t}{1-t}\right) \frac{dt}{(1-t)^2}&amp;lt;/math&amp;gt;. Gaussian quadrature on &amp;lt;math&amp;gt;[0,1]&amp;lt;/math&amp;gt; handles the transformed integrand.&lt;br /&gt;
&lt;br /&gt;
For exponential decay, Gauss-Laguerre is purpose-built. For Gaussian decay, Gauss-Hermite.&lt;br /&gt;
&lt;br /&gt;
==Practical Decision Tree==&lt;br /&gt;
&lt;br /&gt;
Here is the opinionated flowchart for picking a method:&lt;br /&gt;
&lt;br /&gt;
# &amp;#039;&amp;#039;&amp;#039;1D, smooth, finite interval:&amp;#039;&amp;#039;&amp;#039; Gauss-Legendre, &amp;lt;math&amp;gt;n=16&amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt;n=64&amp;lt;/math&amp;gt;. Done.&lt;br /&gt;
# &amp;#039;&amp;#039;&amp;#039;1D, unknown smoothness:&amp;#039;&amp;#039;&amp;#039; Adaptive Gauss-Kronrod (G7/K15). &amp;lt;code&amp;gt;scipy.integrate.quad&amp;lt;/code&amp;gt;.&lt;br /&gt;
# &amp;#039;&amp;#039;&amp;#039;1D, known endpoint singularities:&amp;#039;&amp;#039;&amp;#039; Gauss-Jacobi with matching &amp;lt;math&amp;gt;\alpha, \beta&amp;lt;/math&amp;gt;, &amp;#039;&amp;#039;or&amp;#039;&amp;#039; variable transformation + Gauss-Legendre.&lt;br /&gt;
# &amp;#039;&amp;#039;&amp;#039;1D, periodic integrand:&amp;#039;&amp;#039;&amp;#039; Composite trapezoidal on uniform grid — it converges &amp;#039;&amp;#039;exponentially&amp;#039;&amp;#039; for smooth periodic functions (Euler-Maclaurin cancellation).&lt;br /&gt;
# &amp;#039;&amp;#039;&amp;#039;1D, infinite interval with known decay:&amp;#039;&amp;#039;&amp;#039; Gauss-Laguerre or Gauss-Hermite.&lt;br /&gt;
# &amp;#039;&amp;#039;&amp;#039;1D, infinite interval, algebraic decay:&amp;#039;&amp;#039;&amp;#039; Map to &amp;lt;math&amp;gt;[0,1]&amp;lt;/math&amp;gt; + Gauss-Legendre.&lt;br /&gt;
# &amp;#039;&amp;#039;&amp;#039;2D-4D:&amp;#039;&amp;#039;&amp;#039; Product Gauss-Legendre, or sparse grids (Smolyak) for high order.&lt;br /&gt;
# &amp;#039;&amp;#039;&amp;#039;&amp;gt;4D:&amp;#039;&amp;#039;&amp;#039; Quasi-Monte Carlo (Sobol sequence).&lt;br /&gt;
# &amp;#039;&amp;#039;&amp;#039;Integral over a distribution defined by moments:&amp;#039;&amp;#039;&amp;#039; QMOM (method of moments).&lt;br /&gt;
&lt;br /&gt;
==Romberg Integration: Richardson Extrapolation on Steroids==&lt;br /&gt;
&lt;br /&gt;
Romberg integration applies Richardson extrapolation to the composite trapezoidal rule. The trapezoidal rule with &amp;lt;math&amp;gt;h&amp;lt;/math&amp;gt; has an error expansion in even powers of &amp;lt;math&amp;gt;h&amp;lt;/math&amp;gt; (Euler-Maclaurin formula). By computing the trapezoidal approximation at successively halved step sizes &amp;lt;math&amp;gt;h, h/2, h/4, \dots&amp;lt;/math&amp;gt;, you can extrapolate away the &amp;lt;math&amp;gt;h^2, h^4, h^6, \dots&amp;lt;/math&amp;gt; error terms.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
def romberg(f, a, b, n_levels=6):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    Romberg integration. Returns the extrapolated integral and the&lt;br /&gt;
    full Romberg tableau.&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    R = np.zeros((n_levels, n_levels))&lt;br /&gt;
    h = b - a&lt;br /&gt;
    R[0, 0] = 0.5 * h * (f(a) + f(b))&lt;br /&gt;
&lt;br /&gt;
    for i in range(1, n_levels):&lt;br /&gt;
        h *= 0.5&lt;br /&gt;
        # Composite trapezoidal with 2^i panels&lt;br /&gt;
        n_panels = 2**i&lt;br /&gt;
        x_new = a + h * np.arange(1, n_panels, 2)  # Odd indices only (new points)&lt;br /&gt;
        R[i, 0] = 0.5 * R[i-1, 0] + h * np.sum(f(x_new))&lt;br /&gt;
&lt;br /&gt;
        # Richardson extrapolation&lt;br /&gt;
        for k in range(1, i + 1):&lt;br /&gt;
            R[i, k] = R[i, k-1] + (R[i, k-1] - R[i-1, k-1]) / (4**k - 1)&lt;br /&gt;
&lt;br /&gt;
    return R[n_levels-1, n_levels-1], R&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Romberg is elegant but has been largely superseded by adaptive Gaussian quadrature. Its one remaining niche: when you can only sample &amp;lt;math&amp;gt;f&amp;lt;/math&amp;gt; on a uniform grid and Richardson extrapolation is the only way to boost accuracy without additional evaluations.&lt;br /&gt;
&lt;br /&gt;
==Sparse Grids (Smolyak Quadrature)==&lt;br /&gt;
&lt;br /&gt;
For moderate dimensions (&amp;lt;math&amp;gt;d \leq 20&amp;lt;/math&amp;gt;), full tensor-product Gaussian quadrature is intractable. Smolyak sparse grids combine 1D rules of varying orders into a &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt;-dimensional rule with far fewer points — &amp;lt;math&amp;gt;\mathcal{O}(N (\log N)^{d-1})&amp;lt;/math&amp;gt; instead of &amp;lt;math&amp;gt;\mathcal{O}(N^d)&amp;lt;/math&amp;gt; — while preserving almost the same polynomial exactness.&lt;br /&gt;
&lt;br /&gt;
The construction: for a 1D quadrature sequence &amp;lt;math&amp;gt;Q_k&amp;lt;/math&amp;gt; (e.g., Gauss-Legendre with &amp;lt;math&amp;gt;2^k-1&amp;lt;/math&amp;gt; points), the level-&amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; Smolyak rule is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
Q_L^{(d)} = \sum_{\substack{\mathbf{k} \in \mathbb{N}^d \\ |\mathbf{k}|_1 \leq L + d - 1}} (-1)^{L+d-1-|\mathbf{k}|_1} \binom{d-1}{|\mathbf{k}|_1 - L} \bigotimes_{j=1}^{d} Q_{k_j}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In practice, use &amp;lt;code&amp;gt;sparsegrids&amp;lt;/code&amp;gt; or call into TASMANIAN (Oak Ridge&amp;#039;s sparse grid library).&lt;br /&gt;
&lt;br /&gt;
==Sinc Quadrature==&lt;br /&gt;
&lt;br /&gt;
For analytic functions on &amp;lt;math&amp;gt;\mathbb{R}&amp;lt;/math&amp;gt; with exponential decay, sinc quadrature (using &amp;lt;math&amp;gt;\operatorname{sinc}(x) = \sin(\pi x)/(\pi x)&amp;lt;/math&amp;gt; as the cardinal function) converges exponentially:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\int_{-\infty}^{\infty} f(x) \, dx \approx h \sum_{k=-\infty}^{\infty} f(kh), \qquad h = \mathcal{O}(1/\sqrt{N})&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The error is &amp;lt;math&amp;gt;\mathcal{O}(e^{-c\sqrt{N}})&amp;lt;/math&amp;gt;. Sinc quadrature is closely related to the trapezoidal rule on &amp;lt;math&amp;gt;\mathbb{R}&amp;lt;/math&amp;gt;, which also converges exponentially for analytic, decaying functions. If you find yourself reaching for sinc quadrature, double-check whether tanh-sinh (double-exponential) quadrature would do the job instead — it handles endpoint singularities and is more robust for finite-interval problems.&lt;br /&gt;
&lt;br /&gt;
==Numerical Differentiation of Integrals (Leibniz Rule)==&lt;br /&gt;
&lt;br /&gt;
When you need the derivative of an integral with respect to a parameter:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{d}{d\alpha} \int_{a(\alpha)}^{b(\alpha)} f(x, \alpha) \, dx =&lt;br /&gt;
f(b(\alpha), \alpha) \, b&amp;#039;(\alpha) - f(a(\alpha), \alpha) \, a&amp;#039;(\alpha) + \int_{a(\alpha)}^{b(\alpha)} \frac{\partial f}{\partial \alpha} \, dx&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The first two terms come from the moving boundaries; the third from differentiating inside. In practice, use automatic differentiation (JAX, PyTorch) rather than finite differences on the integral — the gradient can flow through the quadrature nodes and weights directly.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
# With JAX: automatic differentiation through quadrature is trivial&lt;br /&gt;
import jax.numpy as jnp&lt;br /&gt;
from jax import grad&lt;br /&gt;
&lt;br /&gt;
def integral(param):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;∫_0^1 sin(param * x) dx, computed via Gauss-Legendre.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    x, w = leggauss(32)  # precomputed&lt;br /&gt;
    x_mapped = 0.5 * (x + 1)  # map to [0,1]&lt;br /&gt;
    return 0.5 * jnp.dot(w, jnp.sin(param * x_mapped))&lt;br /&gt;
&lt;br /&gt;
dI_dparam = grad(integral)&lt;br /&gt;
print(dI_dparam(2.0))  # Exact derivative, no finite differences&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Summary Table==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Method !! Degrees of Freedom !! Degree/Convergence !! Best For&lt;br /&gt;
|-&lt;br /&gt;
| Trapezoidal || &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; points || &amp;lt;math&amp;gt;\mathcal{O}(h^2)&amp;lt;/math&amp;gt; || Periodic smooth functions (exponential)&lt;br /&gt;
|-&lt;br /&gt;
| Simpson&amp;#039;s || &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; panels || &amp;lt;math&amp;gt;\mathcal{O}(h^4)&amp;lt;/math&amp;gt; || Quick-and-dirty 1D&lt;br /&gt;
|-&lt;br /&gt;
| Gauss-Legendre || &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; nodes || degree &amp;lt;math&amp;gt;2n-1&amp;lt;/math&amp;gt; || 1D smooth, finite interval — gold standard&lt;br /&gt;
|-&lt;br /&gt;
| Gauss-Kronrod || &amp;lt;math&amp;gt;2n+1&amp;lt;/math&amp;gt; nodes || degree &amp;lt;math&amp;gt;3n+1&amp;lt;/math&amp;gt; || Adaptive 1D with error estimate&lt;br /&gt;
|-&lt;br /&gt;
| Clenshaw-Curtis || &amp;lt;math&amp;gt;n+1&amp;lt;/math&amp;gt; nodes || degree &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; || Adaptive with nested nodes&lt;br /&gt;
|-&lt;br /&gt;
| QMOM || &amp;lt;math&amp;gt;2N&amp;lt;/math&amp;gt; moments || matches &amp;lt;math&amp;gt;2N&amp;lt;/math&amp;gt; moments || Distributions defined by moments&lt;br /&gt;
|-&lt;br /&gt;
| Monte Carlo || &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; samples || &amp;lt;math&amp;gt;\mathcal{O}(1/\sqrt{N})&amp;lt;/math&amp;gt; || High dimension (&amp;lt;math&amp;gt;d &amp;gt; 4&amp;lt;/math&amp;gt;)&lt;br /&gt;
|-&lt;br /&gt;
| Quasi-Monte Carlo || &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; samples || &amp;lt;math&amp;gt;\mathcal{O}((\log N)^d/N)&amp;lt;/math&amp;gt; || Moderate-high dimension&lt;br /&gt;
|-&lt;br /&gt;
| Romberg || &amp;lt;math&amp;gt;2^L+1&amp;lt;/math&amp;gt; evals || Extrapolated trapezoidal || Uniform-grid data&lt;br /&gt;
|-&lt;br /&gt;
| Sparse Grids || &amp;lt;math&amp;gt;\mathcal{O}(N(\log N)^{d-1})&amp;lt;/math&amp;gt; || Near-full tensor product || &amp;lt;math&amp;gt;d \leq 20&amp;lt;/math&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Tanh-Sinh || &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; nodes || Exponential || Endpoint singularities&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==References==&lt;br /&gt;
&lt;br /&gt;
* Press, W.H., Teukolsky, S.A., Vetterling, W.T., Flannery, B.P. &amp;#039;&amp;#039;Numerical Recipes in C&amp;#039;&amp;#039;, 2nd ed. Cambridge University Press, 1992. (Ch. 4: Integration of Functions)&lt;br /&gt;
* Golub, G.H. and Welsch, J.H. &amp;quot;Calculation of Gauss Quadrature Rules.&amp;quot; &amp;#039;&amp;#039;Mathematics of Computation&amp;#039;&amp;#039;, 23(106):221-230, 1969.&lt;br /&gt;
* Gordon, R.G. &amp;quot;Error Bounds in Equilibrium Statistical Mechanics.&amp;quot; &amp;#039;&amp;#039;J. Math. Phys.&amp;#039;&amp;#039;, 9(5):655-663, 1968. (Product-Difference algorithm for QMOM)&lt;br /&gt;
* McGraw, R. &amp;quot;Description of Aerosol Dynamics by the Quadrature Method of Moments.&amp;quot; &amp;#039;&amp;#039;Aerosol Sci. Tech.&amp;#039;&amp;#039;, 27(2):255-265, 1997.&lt;br /&gt;
* Trefethen, L.N. &amp;quot;Is Gauss Quadrature Better than Clenshaw-Curtis?&amp;quot; &amp;#039;&amp;#039;SIAM Review&amp;#039;&amp;#039;, 50(1):67-87, 2008.&lt;br /&gt;
* Takahasi, H. and Mori, M. &amp;quot;Double Exponential Formulas for Numerical Integration.&amp;quot; &amp;#039;&amp;#039;Publ. RIMS, Kyoto Univ.&amp;#039;&amp;#039;, 9:721-741, 1974.&lt;br /&gt;
* Smolyak, S.A. &amp;quot;Quadrature and Interpolation Formulas for Tensor Products of Certain Classes of Functions.&amp;quot; &amp;#039;&amp;#039;Dokl. Akad. Nauk SSSR&amp;#039;&amp;#039;, 148(5):1042-1045, 1963.&lt;br /&gt;
* Stroud, A.H. &amp;#039;&amp;#039;Approximate Calculation of Multiple Integrals.&amp;#039;&amp;#039; Prentice-Hall, 1971.&lt;br /&gt;
&lt;br /&gt;
[[Category:Numerics]]&lt;br /&gt;
[[Category:Numerical Recipes]]&lt;br /&gt;
[[Category:Python]]&lt;/div&gt;</summary>
		<author><name>Admin</name></author>
	</entry>
</feed>