<?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%2FEvaluation_of_Functions</id>
	<title>Numerics/Evaluation 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%2FEvaluation_of_Functions"/>
	<link rel="alternate" type="text/html" href="https://charlesreid1.com/w/index.php?title=Numerics/Evaluation_of_Functions&amp;action=history"/>
	<updated>2026-06-22T19:52:21Z</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/Evaluation_of_Functions&amp;diff=30749&amp;oldid=prev</id>
		<title>Admin: Create Numerics/Evaluation of Functions page covering Gaussian quadrature, numerical integration, method of moments, and related topics with opinionated Python-centric approach (via create-page on MediaWiki MCP Server)</title>
		<link rel="alternate" type="text/html" href="https://charlesreid1.com/w/index.php?title=Numerics/Evaluation_of_Functions&amp;diff=30749&amp;oldid=prev"/>
		<updated>2026-06-22T04:47:00Z</updated>

		<summary type="html">&lt;p&gt;Create Numerics/Evaluation of Functions page covering Gaussian quadrature, numerical integration, method of moments, and related topics with opinionated Python-centric approach (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;&amp;#039;&amp;#039;&amp;#039;Evaluation of Functions&amp;#039;&amp;#039;&amp;#039; covers numerical integration (quadrature), the method of moments for parameter estimation, and techniques for computing functions when closed-form evaluation is impractical. The unifying theme: you have a function \(f(x)\) — maybe it&amp;#039;s expensive, maybe it&amp;#039;s only known at sampled points, maybe it&amp;#039;s defined by an integral — and you need numbers from it.&lt;br /&gt;
&lt;br /&gt;
== The One-Sentence Opinion ==&lt;br /&gt;
&lt;br /&gt;
Use &amp;#039;&amp;#039;&amp;#039;Gaussian quadrature&amp;#039;&amp;#039;&amp;#039; when you can evaluate \(f\) anywhere; use &amp;#039;&amp;#039;&amp;#039;adaptive quadrature&amp;#039;&amp;#039;&amp;#039; (&amp;lt;code&amp;gt;scipy.integrate.quad&amp;lt;/code&amp;gt;) when you don&amp;#039;t know how nasty your function is; use &amp;#039;&amp;#039;&amp;#039;method of moments&amp;#039;&amp;#039;&amp;#039; when you need a quick, consistent parameter estimate and don&amp;#039;t want to mess with MLE optimizers; and &amp;#039;&amp;#039;&amp;#039;never use high-order Newton-Cotes rules&amp;#039;&amp;#039;&amp;#039; — they&amp;#039;re a numerical trap.&lt;br /&gt;
&lt;br /&gt;
== Numerical Integration (Quadrature) ==&lt;br /&gt;
&lt;br /&gt;
Quadrature approximates the definite integral&lt;br /&gt;
&lt;br /&gt;
\[&lt;br /&gt;
I = \int_a^b f(x)\,dx \approx \sum_{i=1}^n w_i f(x_i)&lt;br /&gt;
\]&lt;br /&gt;
&lt;br /&gt;
The art is choosing the nodes \(x_i\) and weights \(w_i\). Get them right and you extract 15 digits with 10 evaluations. Get them wrong and you&amp;#039;re summing noise.&lt;br /&gt;
&lt;br /&gt;
=== Newton-Cotes: The Obvious (and Usually Wrong) Approach ===&lt;br /&gt;
&lt;br /&gt;
Newton-Cotes rules use equally-spaced points and fit an interpolating polynomial. Simple, intuitive, and mostly a bad idea beyond the lowest orders.&lt;br /&gt;
&lt;br /&gt;
==== Trapezoidal Rule ====&lt;br /&gt;
&lt;br /&gt;
Fit a line between \((a, f(a))\) and \((b, f(b))\). Integrate the line:&lt;br /&gt;
&lt;br /&gt;
\[&lt;br /&gt;
\int_a^b f(x)\,dx \approx \frac{b-a}{2}\big[f(a) + f(b)\big]&lt;br /&gt;
\]&lt;br /&gt;
&lt;br /&gt;
Error: \(O(h^3 f&amp;#039;&amp;#039;(\xi))\) on a single panel. Composite version: chop \([a,b]\) into \(n\) panels of width \(h\):&lt;br /&gt;
&lt;br /&gt;
\[&lt;br /&gt;
\int_a^b f(x)\,dx \approx \frac{h}{2}\left[f(a) + 2\sum_{i=1}^{n-1} f(a+ih) + f(b)\right]&lt;br /&gt;
\]&lt;br /&gt;
&lt;br /&gt;
Error drops to \(O(h^2)\). Surprisingly, for periodic functions integrated over a full period, the trapezoidal rule converges &amp;#039;&amp;#039;&amp;#039;exponentially fast&amp;#039;&amp;#039;&amp;#039;. This is the one situation where it beats Gaussian quadrature.&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 trapezoidal(f, a, b, n):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Composite trapezoidal rule with n panels.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    x = np.linspace(a, b, n + 1)&lt;br /&gt;
    h = (b - a) / n&lt;br /&gt;
    return h * (0.5 * f(x[0]) + np.sum(f(x[1:-1])) + 0.5 * f(x[-1]))&lt;br /&gt;
&lt;br /&gt;
# Or just use SciPy:&lt;br /&gt;
from scipy.integrate import trapezoid&lt;br /&gt;
result = trapezoid(f(np.linspace(a, b, 1000)), np.linspace(a, b, 1000))&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Simpson&amp;#039;s Rule ====&lt;br /&gt;
&lt;br /&gt;
Fit a quadratic through three equally-spaced points. Exact for cubics (degree of precision = 3):&lt;br /&gt;
&lt;br /&gt;
\[&lt;br /&gt;
\int_a^b f(x)\,dx \approx \frac{h}{3}\big[f(a) + 4f(a+h) + f(b)\big], \quad h = \frac{b-a}{2}&lt;br /&gt;
\]&lt;br /&gt;
&lt;br /&gt;
Composite version (\(n\) even):&lt;br /&gt;
&lt;br /&gt;
\[&lt;br /&gt;
\int_a^b f(x)\,dx \approx \frac{h}{3}\left[f(a) + 4\sum_{\text{odd }i} f(a+ih) + 2\sum_{\text{even }i} f(a+ih) + f(b)\right]&lt;br /&gt;
\]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
def simpson(f, a, b, n):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Composite Simpson&amp;#039;s rule. n must be even.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    if n % 2 != 0:&lt;br /&gt;
        raise ValueError(&amp;quot;n must be even for Simpson&amp;#039;s rule&amp;quot;)&lt;br /&gt;
    x = np.linspace(a, b, n + 1)&lt;br /&gt;
    h = (b - a) / n&lt;br /&gt;
    result = f(x[0]) + f(x[-1])&lt;br /&gt;
    result += 4.0 * np.sum(f(x[1:-1:2]))   # odd indices&lt;br /&gt;
    result += 2.0 * np.sum(f(x[2:-2:2]))   # even indices&lt;br /&gt;
    return h * result / 3.0&lt;br /&gt;
&lt;br /&gt;
# SciPy equivalent:&lt;br /&gt;
from scipy.integrate import simpson as simps&lt;br /&gt;
result = simps(f(np.linspace(a, b, 101)), x=np.linspace(a, b, 101))&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Why Higher-Order Newton-Cotes Is a Trap ====&lt;br /&gt;
&lt;br /&gt;
Simpson&amp;#039;s 3/8 rule (cubic fit), Boole&amp;#039;s rule (quartic fit), and beyond all exist. They all use equally-spaced points with &amp;#039;&amp;#039;&amp;#039;alternating sign weights&amp;#039;&amp;#039;&amp;#039; for orders 9 and above. Those sign alternations produce catastrophic cancellation — you subtract nearly-equal large numbers, losing precision. Meanwhile, the interpolating polynomial suffers from Runge&amp;#039;s phenomenon at the edges (see [[Numerics/Interpolation Extrapolation]]).&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Moral:&amp;#039;&amp;#039;&amp;#039; Trapezoidal is fine for dense data or periodic functions. Simpson&amp;#039;s is okay for a quick estimate. Beyond Simpson&amp;#039;s, stop and use Gaussian quadrature.&lt;br /&gt;
&lt;br /&gt;
=== Gaussian Quadrature: The Gold Standard ===&lt;br /&gt;
&lt;br /&gt;
Gaussian quadrature answers the question: &amp;#039;&amp;#039;if I can evaluate \(f\) at \(n\) points of my choosing, where should those points go and how should I weight them to maximize accuracy?&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
The answer: choose the nodes \(x_i\) as the roots of an orthogonal polynomial from a family matched to the integration interval and weight function, and compute weights \(w_i\) from the same polynomials. An \(n\)-point Gauss rule is &amp;#039;&amp;#039;&amp;#039;exact for polynomials of degree \(2n-1\)&amp;#039;&amp;#039;&amp;#039; — double the degree of any Newton-Cotes rule with the same number of evaluations.&lt;br /&gt;
&lt;br /&gt;
==== Why It Works (Intuition) ====&lt;br /&gt;
&lt;br /&gt;
An \(n\)-point rule has \(2n\) degrees of freedom (\(n\) nodes + \(n\) weights). A polynomial of degree \(2n-1\) has \(2n\) coefficients. Gaussian quadrature solves for nodes and weights simultaneously so that all \(2n\) monomials \(1, x, x^2, \ldots, x^{2n-1}\) are integrated exactly. This is optimal — no rule with \(n\) points can do better.&lt;br /&gt;
&lt;br /&gt;
==== Gauss-Legendre (The Default) ====&lt;br /&gt;
&lt;br /&gt;
For \(\int_{-1}^1 f(x)\,dx\) with weight \(\omega(x) = 1\). The orthogonal polynomials are Legendre polynomials \(P_n(x)\). Nodes are the roots of \(P_n\); weights are:&lt;br /&gt;
&lt;br /&gt;
\[&lt;br /&gt;
w_i = \frac{2}{(1-x_i^2)[P_n&amp;#039;(x_i)]^2}&lt;br /&gt;
\]&lt;br /&gt;
&lt;br /&gt;
To integrate over arbitrary \([a, b]\), map to \([-1, 1]\):&lt;br /&gt;
&lt;br /&gt;
\[&lt;br /&gt;
\int_a^b f(x)\,dx = \frac{b-a}{2} \int_{-1}^1 f\!\left(\frac{b-a}{2}\xi + \frac{a+b}{2}\right) d\xi&lt;br /&gt;
\approx \frac{b-a}{2} \sum_{i=1}^n w_i \, f\!\left(\frac{b-a}{2}x_i + \frac{a+b}{2}\right)&lt;br /&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;
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;n-point Gauss-Legendre quadrature over [a, b].&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;
    return 0.5 * (b - a) * np.dot(w_ref, f(x_mapped))&lt;br /&gt;
&lt;br /&gt;
# SciPy fixed-order Gaussian quadrature:&lt;br /&gt;
from scipy.integrate import fixed_quad&lt;br /&gt;
result, _ = fixed_quad(f, a, b, n=32)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Gauss-Hermite: Integrals Over \((-\infty, \infty)\) ====&lt;br /&gt;
&lt;br /&gt;
For \(\int_{-\infty}^\infty f(x)\,e^{-x^2}\,dx\). Nodes are roots of Hermite polynomials. The exponential weight is baked in:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
from numpy.polynomial.hermite import hermgauss&lt;br /&gt;
&lt;br /&gt;
def gauss_hermite(f, n):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Integrate f(x) * exp(-x^2) over (-∞, ∞).&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    x, w = hermgauss(n)&lt;br /&gt;
    return np.dot(w, f(x))&lt;br /&gt;
&lt;br /&gt;
# For integrals WITHOUT the exp(-x^2) weight, absorb it:&lt;br /&gt;
# ∫ g(x) dx = ∫ [g(x) * exp(x^2)] * exp(-x^2) dx&lt;br /&gt;
def gauss_hermite_unweighted(g, n):&lt;br /&gt;
    x, w = hermgauss(n)&lt;br /&gt;
    return np.dot(w, g(x) * np.exp(x**2))&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Opinion:&amp;#039;&amp;#039;&amp;#039; Gauss-Hermite with the unweighted trick is fragile — the \(\exp(x^2)\) factor amplifies errors in the tails. Use adaptive quadrature (&amp;lt;code&amp;gt;quad&amp;lt;/code&amp;gt;) with infinite limits unless you have a strong structural reason.&lt;br /&gt;
&lt;br /&gt;
==== Gauss-Laguerre: Integrals Over \([0, \infty)\) ====&lt;br /&gt;
&lt;br /&gt;
For \(\int_0^\infty f(x)\,e^{-x}\,dx\). Nodes are roots of Laguerre polynomials:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
from numpy.polynomial.laguerre import laggauss&lt;br /&gt;
&lt;br /&gt;
def gauss_laguerre(f, n):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Integrate f(x) * exp(-x) over [0, ∞).&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    x, w = laggauss(n)&lt;br /&gt;
    return np.dot(w, f(x))&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The generalized Gauss-Laguerre quadrature handles weight \(x^\alpha e^{-x}\):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
from scipy.special import roots_laguerre&lt;br /&gt;
x, w = roots_laguerre(n, alpha=0.5)  # weight x^0.5 * exp(-x)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Gauss-Chebyshev: Integrals with Endpoint Singularities ====&lt;br /&gt;
&lt;br /&gt;
For \(\int_{-1}^1 \frac{f(x)}{\sqrt{1-x^2}}\,dx\). Nodes and weights have closed forms:&lt;br /&gt;
&lt;br /&gt;
\[&lt;br /&gt;
x_i = \cos\left(\frac{2i-1}{2n}\pi\right), \quad w_i = \frac{\pi}{n}&lt;br /&gt;
\]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
def gauss_chebyshev(f, n):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Integrate f(x) / sqrt(1-x^2) over [-1, 1].&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    i = np.arange(1, n + 1)&lt;br /&gt;
    x = np.cos((2*i - 1) * np.pi / (2*n))&lt;br /&gt;
    w = np.full(n, np.pi / n)&lt;br /&gt;
    return np.dot(w, f(x))&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Summary of Gaussian Quadrature Families ====&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Interval !! Weight \(\omega(x)\) !! Orthogonal Polynomials !! NumPy/SciPy Function&lt;br /&gt;
|-&lt;br /&gt;
| \([-1, 1]\) || \(1\) || Legendre || &amp;lt;code&amp;gt;leggauss(n)&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| \((-\infty, \infty)\) || \(e^{-x^2}\) || Hermite || &amp;lt;code&amp;gt;hermgauss(n)&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| \([0, \infty)\) || \(e^{-x}\) || Laguerre || &amp;lt;code&amp;gt;laggauss(n)&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| \([-1, 1]\) || \((1-x)^\alpha(1+x)^\beta\) || Jacobi || &amp;lt;code&amp;gt;roots_jacobi(n, alpha, beta)&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| \([-1, 1]\) || \(1/\sqrt{1-x^2}\) || Chebyshev (1st kind) || &amp;lt;code&amp;gt;roots_chebyt(n)&amp;lt;/code&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==== Computing Nodes and Weights: The Golub-Welsch Algorithm ====&lt;br /&gt;
&lt;br /&gt;
Computing Gaussian quadrature nodes from polynomial root-finding is numerically unstable for large \(n\). The Golub-Welsch algorithm solves this elegantly via eigenvalues of a symmetric tridiagonal matrix built from the three-term recurrence coefficients of the orthogonal polynomial family. The nodes are the eigenvalues; the weights are proportional to the squared first components of the eigenvectors.&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.linalg import eigh_tridiagonal&lt;br /&gt;
&lt;br /&gt;
def golub_welsch(alpha, beta, n):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    Compute Gauss quadrature nodes and weights from recurrence coefficients.&lt;br /&gt;
    alpha: diagonal entries (shape n)&lt;br /&gt;
    beta:  off-diagonal entries (shape n-1), squared for the off-diagonals&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # Build symmetric tridiagonal matrix&lt;br /&gt;
    d = alpha[:n]&lt;br /&gt;
    e = np.sqrt(beta[:n-1])&lt;br /&gt;
    eigenvalues, eigenvectors = eigh_tridiagonal(d, e)&lt;br /&gt;
    # Weights from squared first components&lt;br /&gt;
    weights = beta[0] * eigenvectors[0, :]**2&lt;br /&gt;
    return eigenvalues, weights&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is what SciPy and NumPy use internally. For standard families, just call the library functions.&lt;br /&gt;
&lt;br /&gt;
=== Clenshaw-Curtis: The Pragmatic Alternative ===&lt;br /&gt;
&lt;br /&gt;
Clenshaw-Curtis quadrature uses Chebyshev polynomial expansion and samples at the Chebyshev nodes \(x_k = \cos(k\pi/n)\). It integrates polynomials of degree \(n\) exactly (vs. \(2n-1\) for Gauss), but:&lt;br /&gt;
&lt;br /&gt;
* Weights can be computed in \(O(n \log n)\) via FFT (vs. \(O(n^2)\) for Golub-Welsch)&lt;br /&gt;
* Nodes are nested — an \((n+1)\)-point rule reuses all points from the \(n\)-point rule, which enables efficient adaptive schemes&lt;br /&gt;
* In practice, Clenshaw-Curtis often matches Gauss accuracy for smooth functions&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Trefethen&amp;#039;s verdict (2008):&amp;#039;&amp;#039;&amp;#039; &amp;#039;&amp;#039;&amp;quot;The supposed factor-of-2 advantage of Gauss quadrature is rarely realized in practice.&amp;quot;&amp;#039;&amp;#039; Gauss is theoretically twice as accurate for the same number of points, but the difference is often negligible for well-behaved integrands.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
def clenshaw_curtis(f, a, b, n):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;n-point Clenshaw-Curtis quadrature over [a, b].&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    k = np.arange(n)&lt;br /&gt;
    theta = k * np.pi / (n - 1)&lt;br /&gt;
    x_cheb = np.cos(theta)          # nodes on [-1, 1]&lt;br /&gt;
    # Compute weights via FFT (simplified; SciPy does this better internally)&lt;br /&gt;
    w = np.zeros(n)&lt;br /&gt;
    for j in range(n):&lt;br /&gt;
        c = np.ones(n)&lt;br /&gt;
        c[0] *= 0.5&lt;br /&gt;
        c[-1] *= 0.5&lt;br /&gt;
        w[j] = (2.0 / (n - 1)) * np.dot(c, np.cos(j * theta))&lt;br /&gt;
        if j == 0 or j == n - 1:&lt;br /&gt;
            w[j] *= 0.5&lt;br /&gt;
    x_mapped = 0.5 * (b - a) * x_cheb + 0.5 * (a + b)&lt;br /&gt;
    return 0.5 * (b - a) * np.dot(w, f(x_mapped))&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Opinion:&amp;#039;&amp;#039;&amp;#039; Use Gauss-Legendre as your default. Use Clenshaw-Curtis when you need nested sampling for adaptive refinement, or when \(n\) is so large that the \(O(n \log n)\) weight computation matters.&lt;br /&gt;
&lt;br /&gt;
=== Adaptive Quadrature: When You Don&amp;#039;t Know How Bad \(f\) Is ===&lt;br /&gt;
&lt;br /&gt;
The workhorse of practical integration: &amp;lt;code&amp;gt;scipy.integrate.quad&amp;lt;/code&amp;gt;. It uses an adaptive Gauss-Kronrod scheme — a Gauss rule paired with a higher-order Kronrod extension that reuses the Gauss nodes. The difference between the two estimates gives an error estimate; the algorithm subdivides the interval where the error is largest.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
from scipy.integrate import quad&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
# Basic usage&lt;br /&gt;
result, error_estimate = quad(lambda x: np.exp(-x**2), -np.inf, np.inf)&lt;br /&gt;
print(f&amp;quot;{result:.14f} ± {error_estimate:.2e}&amp;quot;)  # sqrt(pi) ≈ 1.77245...&lt;br /&gt;
&lt;br /&gt;
# With extra arguments&lt;br /&gt;
def damped_oscillator(t, zeta, omega):&lt;br /&gt;
    return np.exp(-zeta * t) * np.cos(omega * t)&lt;br /&gt;
&lt;br /&gt;
result, err = quad(damped_oscillator, 0, np.inf, args=(0.1, 2.0))&lt;br /&gt;
&lt;br /&gt;
# Handling singularities: specify points where integrand misbehaves&lt;br /&gt;
result, err = quad(lambda x: np.log(x) / np.sqrt(x), 0, 1,&lt;br /&gt;
                   points=[0])  # alerts quad to the singularity at x=0&lt;br /&gt;
&lt;br /&gt;
# Double/triple/n-fold integration&lt;br /&gt;
from scipy.integrate import dblquad, tplquad, nquad&lt;br /&gt;
&lt;br /&gt;
# Double: ∫₀^π ∫₀^sin(y) x²y dx dy&lt;br /&gt;
area, err = dblquad(lambda x, y: x**2 * y, 0, np.pi,&lt;br /&gt;
                    lambda y: 0, lambda y: np.sin(y))&lt;br /&gt;
&lt;br /&gt;
# n-fold: arbitrary dimension&lt;br /&gt;
def integrand(x, y, z):&lt;br /&gt;
    return x * y * z&lt;br /&gt;
result, err = nquad(integrand, [[0, 1], [0, 1], [0, 1]])&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== The &amp;quot;Gaussian Integral Trap&amp;quot; ====&lt;br /&gt;
&lt;br /&gt;
Adaptive quadrature samples the integrand at a finite set of points. If your function is narrow and you set limits too wide, the sampler misses it entirely:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
# Wrong: limits too wide, sampler misses the peak&lt;br /&gt;
quad(lambda x: np.exp(-x**2), -10000, 10000)&lt;br /&gt;
# → (1.975e-203, 0.0)  ← completely wrong!&lt;br /&gt;
&lt;br /&gt;
# Right: tight limits around the action&lt;br /&gt;
quad(lambda x: np.exp(-x**2), -15, 15)&lt;br /&gt;
# → (1.77245..., 8.47e-11)  ← correct&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Moral: know your integrand. If it&amp;#039;s localized, don&amp;#039;t make the integrator hunt for it.&lt;br /&gt;
&lt;br /&gt;
=== Romberg Integration: Richardson Extrapolation for Quadrature ===&lt;br /&gt;
&lt;br /&gt;
Romberg integration applies Richardson extrapolation to the trapezoidal rule. Compute trapezoidal approximations with step sizes \(h, h/2, h/4, \ldots\) and extrapolate to \(h \to 0\), eliminating successive powers of \(h^2\) from the error:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
from scipy.integrate import romb&lt;br /&gt;
&lt;br /&gt;
# Requires 2^k + 1 equally-spaced samples&lt;br /&gt;
k = 8&lt;br /&gt;
x = np.linspace(0, 1, 2**k + 1)&lt;br /&gt;
y = np.exp(x) * np.sin(3 * x)&lt;br /&gt;
result = romb(y, dx=(x[1] - x[0]))&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Romberg is elegant but inflexible — you must commit to \(2^k+1\) function evaluations up front. Adaptive quadrature (&amp;lt;code&amp;gt;quad&amp;lt;/code&amp;gt;) is usually better. Use Romberg when you already have a table of equally-spaced data and want a high-accuracy integral from it.&lt;br /&gt;
&lt;br /&gt;
=== Monte Carlo Integration: When Dimension Kills ===&lt;br /&gt;
&lt;br /&gt;
For high-dimensional integrals, all the rules above fail — the curse of dimensionality makes tensor-product grids exponentially expensive. Monte Carlo integration converges at \(O(1/\sqrt{N})\) regardless of dimension:&lt;br /&gt;
&lt;br /&gt;
\[&lt;br /&gt;
\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;
\]&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 monte_carlo_integrate(f, bounds, N=100000):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    Monte Carlo integration over a hyper-rectangle.&lt;br /&gt;
    bounds: list of (low, high) tuples for each dimension.&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    dim = len(bounds)&lt;br /&gt;
    volume = np.prod([hi - lo for lo, hi in bounds])&lt;br /&gt;
    samples = np.random.uniform(&lt;br /&gt;
        low=[b[0] for b in bounds],&lt;br /&gt;
        high=[b[1] for b in bounds],&lt;br /&gt;
        size=(N, dim)&lt;br /&gt;
    )&lt;br /&gt;
    values = np.apply_along_axis(lambda x: f(*x), 1, samples)&lt;br /&gt;
    estimate = volume * np.mean(values)&lt;br /&gt;
    error = volume * np.std(values) / np.sqrt(N)&lt;br /&gt;
    return estimate, error&lt;br /&gt;
&lt;br /&gt;
# Example: area of a quarter-circle in 2D&lt;br /&gt;
def indicator_quarter_circle(x, y):&lt;br /&gt;
    return 1.0 if x**2 + y**2 &amp;lt;= 1.0 else 0.0&lt;br /&gt;
&lt;br /&gt;
est, err = monte_carlo_integrate(indicator_quarter_circle,&lt;br /&gt;
                                  [(0, 1), (0, 1)], N=200000)&lt;br /&gt;
# est ≈ π/4 ≈ 0.785, err ≈ 0.001&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For low dimensions (1D–3D), quadrature is always better. Monte Carlo starts winning around dimension 5–8 depending on the smoothness of \(f\). For very high dimensions with smooth integrands, &amp;#039;&amp;#039;&amp;#039;quasi-Monte Carlo&amp;#039;&amp;#039;&amp;#039; using low-discrepancy sequences (Sobol&amp;#039;, Halton) converges as \(O((\log N)^d / N)\):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
from scipy.stats import qmc&lt;br /&gt;
&lt;br /&gt;
sampler = qmc.Sobol(d=5, scramble=True)&lt;br /&gt;
samples = sampler.random(n=4096)&lt;br /&gt;
# Transform to your integration domain and average&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Method of Moments (Parameter Estimation) ==&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;method of moments&amp;#039;&amp;#039;&amp;#039; (MoM) is a technique for estimating parameters of a probability distribution by equating sample moments to theoretical moments. It was introduced by Karl Pearson in 1894 and predates maximum likelihood estimation.&lt;br /&gt;
&lt;br /&gt;
=== The Core Idea ===&lt;br /&gt;
&lt;br /&gt;
If a distribution has \(k\) unknown parameters \(\theta_1, \ldots, \theta_k\), you:&lt;br /&gt;
&lt;br /&gt;
# Write the first \(k\) theoretical moments \(\mu_j = \mathbb{E}[X^j]\) as functions of the parameters&lt;br /&gt;
# Compute the first \(k\) sample moments \(\hat{\mu}_j = \frac{1}{n}\sum_{i=1}^n X_i^j\)&lt;br /&gt;
# Solve \(\mu_j(\theta_1, \ldots, \theta_k) = \hat{\mu}_j\) for \(j = 1, \ldots, k\)&lt;br /&gt;
&lt;br /&gt;
The resulting estimators are &amp;#039;&amp;#039;&amp;#039;consistent&amp;#039;&amp;#039;&amp;#039; (by the law of large numbers) and asymptotically normal (by the central limit theorem). They are generally not efficient compared to MLE, but they&amp;#039;re simple, closed-form for many distributions, and don&amp;#039;t require numerical optimization.&lt;br /&gt;
&lt;br /&gt;
=== Opinion ===&lt;br /&gt;
&lt;br /&gt;
MoM is your &amp;#039;&amp;#039;&amp;#039;first-pass estimator&amp;#039;&amp;#039;&amp;#039;. If the closed-form solution is trivial (Normal, Poisson, Gamma, Beta), use it. If you need efficiency (minimum variance), switch to MLE. If you have censored or truncated data, MoM becomes messier and MLE is usually the right call.&lt;br /&gt;
&lt;br /&gt;
=== Example: Normal Distribution ===&lt;br /&gt;
&lt;br /&gt;
Parameters: \(\mu, \sigma^2\). Moments: \(\mathbb{E}[X] = \mu\), \(\mathbb{E}[X^2] = \mu^2 + \sigma^2\).&lt;br /&gt;
&lt;br /&gt;
\[&lt;br /&gt;
\hat{\mu} = \frac{1}{n}\sum_{i=1}^n X_i = \bar{X}, \qquad&lt;br /&gt;
\hat{\sigma}^2 = \frac{1}{n}\sum_{i=1}^n (X_i - \bar{X})^2&lt;br /&gt;
\]&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Note:&amp;#039;&amp;#039;&amp;#039; The MoM variance estimator divides by \(n\), not \(n-1\). It&amp;#039;s biased but consistent. The \(n-1\) correction (Bessel&amp;#039;s correction) gives an unbiased estimator, which is &amp;#039;&amp;#039;not&amp;#039;&amp;#039; the MoM estimator. Use MoM when consistency matters more than unbiasedness.&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 mom_normal(data):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Method of moments estimators for Normal(μ, σ²).&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    mu_hat = np.mean(data)&lt;br /&gt;
    var_hat = np.mean((data - mu_hat)**2)  # divide by n, NOT n-1&lt;br /&gt;
    return mu_hat, var_hat&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Example: Gamma Distribution ===&lt;br /&gt;
&lt;br /&gt;
Parameters: shape \(\alpha\), scale \(\beta\) (or rate \(\theta = 1/\beta\)). The PDF is:&lt;br /&gt;
&lt;br /&gt;
\[&lt;br /&gt;
f(x; \alpha, \beta) = \frac{x^{\alpha-1} e^{-x/\beta}}{\beta^\alpha \Gamma(\alpha)}, \quad x &amp;gt; 0&lt;br /&gt;
\]&lt;br /&gt;
&lt;br /&gt;
Moments: \(\mathbb{E}[X] = \alpha\beta\), \(\text{Var}(X) = \alpha\beta^2\).&lt;br /&gt;
&lt;br /&gt;
Solving:&lt;br /&gt;
&lt;br /&gt;
\[&lt;br /&gt;
\hat{\alpha} = \frac{\bar{X}^2}{s^2}, \qquad&lt;br /&gt;
\hat{\beta} = \frac{s^2}{\bar{X}}&lt;br /&gt;
\]&lt;br /&gt;
&lt;br /&gt;
where \(s^2 = \frac{1}{n}\sum (X_i - \bar{X})^2\) (divide by \(n\)).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
def mom_gamma(data):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;MoM estimators for Gamma(α, β). Returns (alpha_hat, scale_hat).&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    x_bar = np.mean(data)&lt;br /&gt;
    s2 = np.mean((data - x_bar)**2)  # MoM variance (divide by n)&lt;br /&gt;
    alpha_hat = x_bar**2 / s2&lt;br /&gt;
    scale_hat = s2 / x_bar           # scale β (NOT rate θ)&lt;br /&gt;
    return alpha_hat, scale_hat&lt;br /&gt;
&lt;br /&gt;
# Verify with SciPy&lt;br /&gt;
from scipy import stats&lt;br /&gt;
data = stats.gamma.rvs(a=6, scale=2, size=5000)&lt;br /&gt;
alpha_hat, scale_hat = mom_gamma(data)&lt;br /&gt;
print(f&amp;quot;True: α=6, β=2  |  MoM: α={alpha_hat:.3f}, β={scale_hat:.3f}&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Example: Poisson Distribution ===&lt;br /&gt;
&lt;br /&gt;
Parameter: \(\lambda\). Moment: \(\mathbb{E}[X] = \lambda\).&lt;br /&gt;
&lt;br /&gt;
\[&lt;br /&gt;
\hat{\lambda} = \bar{X}&lt;br /&gt;
\]&lt;br /&gt;
&lt;br /&gt;
Trivial, but it works.&lt;br /&gt;
&lt;br /&gt;
=== Example: Beta Distribution ===&lt;br /&gt;
&lt;br /&gt;
Parameters: \(\alpha, \beta\). PDF on \([0,1]\):&lt;br /&gt;
&lt;br /&gt;
\[&lt;br /&gt;
f(x; \alpha, \beta) = \frac{x^{\alpha-1}(1-x)^{\beta-1}}{B(\alpha, \beta)}&lt;br /&gt;
\]&lt;br /&gt;
&lt;br /&gt;
Moments: \(\mathbb{E}[X] = \frac{\alpha}{\alpha+\beta}\), \(\text{Var}(X) = \frac{\alpha\beta}{(\alpha+\beta)^2(\alpha+\beta+1)}\).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
def mom_beta(data):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;MoM estimators for Beta(α, β).&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    x_bar = np.mean(data)&lt;br /&gt;
    s2 = np.mean((data - x_bar)**2)  # MoM variance&lt;br /&gt;
    # Solving the moment equations&lt;br /&gt;
    common = x_bar * (1 - x_bar) / s2 - 1&lt;br /&gt;
    alpha_hat = x_bar * common&lt;br /&gt;
    beta_hat = (1 - x_bar) * common&lt;br /&gt;
    return alpha_hat, beta_hat&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== When MoM Fails ===&lt;br /&gt;
&lt;br /&gt;
# &amp;#039;&amp;#039;&amp;#039;No closed-form moments:&amp;#039;&amp;#039;&amp;#039; Some distributions (e.g., the log-normal&amp;#039;s moments exist but solving for parameters requires numerical root-finding, negating MoM&amp;#039;s simplicity).&lt;br /&gt;
# &amp;#039;&amp;#039;&amp;#039;Moments don&amp;#039;t exist:&amp;#039;&amp;#039;&amp;#039; Cauchy distribution — \(\mathbb{E}[X]\) is undefined. MoM is impossible.&lt;br /&gt;
# &amp;#039;&amp;#039;&amp;#039;Sample moments are unstable:&amp;#039;&amp;#039;&amp;#039; For heavy-tailed distributions, high-order sample moments can have huge variance. MoM with higher moments can give absurd estimates.&lt;br /&gt;
# &amp;#039;&amp;#039;&amp;#039;Parameters out of bounds:&amp;#039;&amp;#039;&amp;#039; MoM can produce \(\hat{\sigma}^2 &amp;lt; 0\) or \(\hat{\alpha} &amp;lt; 0\) for some data realizations. MLE respects parameter constraints by construction.&lt;br /&gt;
&lt;br /&gt;
=== MoM vs. MLE: Quick Comparison ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Property !! Method of Moments !! Maximum Likelihood&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;Consistency&amp;#039;&amp;#039;&amp;#039; || Yes (under mild conditions) || Yes (under mild conditions)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;Efficiency&amp;#039;&amp;#039;&amp;#039; || Usually not efficient || Asymptotically efficient (attains Cramér-Rao bound)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;Computation&amp;#039;&amp;#039;&amp;#039; || Often closed-form || Often requires numerical optimization&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;Bias&amp;#039;&amp;#039;&amp;#039; || Can be biased (small samples) || Can be biased (small samples); often less so&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;Robustness to model misspecification&amp;#039;&amp;#039;&amp;#039; || Decent || Can be fragile&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;Handles censoring/truncation&amp;#039;&amp;#039;&amp;#039; || Messy || Straightforward (modify likelihood)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;Needs starting values&amp;#039;&amp;#039;&amp;#039; || No || Yes (ironically, MoM estimates make great starting values for MLE!)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Opinionated workflow:&amp;#039;&amp;#039;&amp;#039; Compute MoM estimates first. They take 3 lines of Python. If you need statistical efficiency, use them as starting values for an MLE optimizer (&amp;lt;code&amp;gt;scipy.optimize.minimize&amp;lt;/code&amp;gt; on the negative log-likelihood).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
# MoM → MLE pipeline for Gamma distribution&lt;br /&gt;
from scipy.optimize import minimize&lt;br /&gt;
from scipy.stats import gamma&lt;br /&gt;
&lt;br /&gt;
def neg_log_likelihood_gamma(params, data):&lt;br /&gt;
    alpha, scale = params&lt;br /&gt;
    if alpha &amp;lt;= 0 or scale &amp;lt;= 0:&lt;br /&gt;
        return np.inf&lt;br /&gt;
    return -np.sum(gamma.logpdf(data, a=alpha, scale=scale))&lt;br /&gt;
&lt;br /&gt;
# Start from MoM&lt;br /&gt;
alpha0, scale0 = mom_gamma(data)&lt;br /&gt;
result = minimize(neg_log_likelihood_gamma, x0=[alpha0, scale0],&lt;br /&gt;
                  args=(data,), method=&amp;#039;L-BFGS-B&amp;#039;,&lt;br /&gt;
                  bounds=[(1e-6, None), (1e-6, None)])&lt;br /&gt;
alpha_mle, scale_mle = result.x&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Evaluating Functions Defined by Integrals ==&lt;br /&gt;
&lt;br /&gt;
Sometimes the function you need &amp;#039;&amp;#039;is&amp;#039;&amp;#039; an integral. For example:&lt;br /&gt;
&lt;br /&gt;
* The error function: \(\text{erf}(x) = \frac{2}{\sqrt{\pi}}\int_0^x e^{-t^2}\,dt\)&lt;br /&gt;
* The exponential integral: \(E_n(x) = \int_1^\infty \frac{e^{-xt}}{t^n}\,dt\)&lt;br /&gt;
* Cumulative distribution functions: \(\Phi(x) = \int_{-\infty}^x \phi(t)\,dt\)&lt;br /&gt;
&lt;br /&gt;
=== When a Library Already Has It, Use It ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
from scipy.special import erf, erfc, expn, gammainc, betainc&lt;br /&gt;
# These are hyper-optimized. Do not reimplement them.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== When You Must Compute It Yourself ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
from scipy.integrate import quad&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
def my_special_function(x):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Compute ∫₀ˣ sin(t²) dt (Fresnel-type integral).&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    result, _ = quad(lambda t: np.sin(t**2), 0, x)&lt;br /&gt;
    return result&lt;br /&gt;
&lt;br /&gt;
# Vectorize for array input&lt;br /&gt;
vec_my_func = np.vectorize(my_special_function)&lt;br /&gt;
y = vec_my_func(np.linspace(0, 5, 100))&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For repeated evaluation, precompute on a grid and use a [[Numerics/Interpolation Extrapolation|cubic spline]]:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
from scipy.interpolate import CubicSpline&lt;br /&gt;
&lt;br /&gt;
x_grid = np.linspace(0, 5, 200)&lt;br /&gt;
y_grid = np.array([quad(lambda t: np.sin(t**2), 0, xi)[0] for xi in x_grid])&lt;br /&gt;
spline = CubicSpline(x_grid, y_grid)&lt;br /&gt;
# Now evaluation is O(1)&lt;br /&gt;
y_fast = spline(2.5)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Error Function and Related Functions: A Case Study ==&lt;br /&gt;
&lt;br /&gt;
The error function \(\text{erf}(x)\) and complementary error function \(\text{erfc}(x) = 1 - \text{erf}(x)\) illustrate why numerical evaluation needs care.&lt;br /&gt;
&lt;br /&gt;
For large \(x\), \(\text{erfc}(x) \approx \frac{e^{-x^2}}{x\sqrt{\pi}}\). Computing \(\text{erfc}(x) = 1 - \text{erf}(x)\) by subtracting \(\text{erf}(x)\) from 1 is catastrophic cancellation — for \(x = 10\), \(\text{erf}(10) \approx 1 - 2 \times 10^{-45}\), and in double precision you get exactly 1, giving \(\text{erfc}(10) = 0\) — wrong by 44 orders of magnitude.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
from scipy.special import erf, erfc&lt;br /&gt;
&lt;br /&gt;
x = 10.0&lt;br /&gt;
print(1 - erf(x))   # → 0.0  (WRONG)&lt;br /&gt;
print(erfc(x))      # → 2.088487583762545e-45  (correct)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Moral:&amp;#039;&amp;#039;&amp;#039; Use purpose-built library functions. SciPy&amp;#039;s &amp;lt;code&amp;gt;special&amp;lt;/code&amp;gt; module implements asymptotic expansions, continued fractions, and Chebyshev approximations tuned for each regime.&lt;br /&gt;
&lt;br /&gt;
== Opinionated Decision Flowchart ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
What are you trying to do?&lt;br /&gt;
│&lt;br /&gt;
├─ Integrate f(x) numerically&lt;br /&gt;
│   ├─ Is f smooth and you can evaluate it anywhere?&lt;br /&gt;
│   │   ├─ 1D, need high accuracy? → scipy.integrate.quad (adaptive)&lt;br /&gt;
│   │   ├─ 1D, known smoothness, want speed? → fixed_quad (Gauss-Legendre, n=32 or 64)&lt;br /&gt;
│   │   ├─ Periodic over full period? → trapezoidal rule (exponentially convergent!)&lt;br /&gt;
│   │   └─ High dimension (d ≥ 5)? → Monte Carlo or quasi-Monte Carlo&lt;br /&gt;
│   │&lt;br /&gt;
│   ├─ Do you have equally-spaced sampled data?&lt;br /&gt;
│   │   ├─ Dense data (h small)? → scipy.integrate.simpson&lt;br /&gt;
│   │   ├─ Need high accuracy from samples? → romb (if 2^k+1 points)&lt;br /&gt;
│   │   └─ Sparse data? → Interpolate (CubicSpline) then quad&lt;br /&gt;
│   │&lt;br /&gt;
│   └─ Do you have scattered data?&lt;br /&gt;
│       └─ Interpolate (RBF or spline) then integrate&lt;br /&gt;
│&lt;br /&gt;
├─ Estimate distribution parameters from data&lt;br /&gt;
│   ├─ Quick and dirty, closed-form? → Method of Moments&lt;br /&gt;
│   ├─ Need efficiency, have priors, censored data? → Maximum Likelihood (use MoM as starting point)&lt;br /&gt;
│   └─ MoM gave nonsense (negative variance, etc.)? → MLE with bounds&lt;br /&gt;
│&lt;br /&gt;
└─ Evaluate a special function&lt;br /&gt;
    ├─ Is it in scipy.special? → Use it. Done.&lt;br /&gt;
    ├─ Is it defined by an integral? → quad, then spline if repeated evaluation&lt;br /&gt;
    └─ Is it defined by an infinite series? → Truncate, estimate remainder, validate&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Summary Table ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Method !! Best For !! Precision !! Speed !! Recommendation&lt;br /&gt;
|-&lt;br /&gt;
| Trapezoidal (composite) || Periodic functions, dense samples || \(O(h^2)\) || Fast || Special cases only&lt;br /&gt;
|-&lt;br /&gt;
| Simpson&amp;#039;s (composite) || Sampled data, quick estimate || \(O(h^4)\) || Fast || Fine for smooth data&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;Gauss-Legendre&amp;#039;&amp;#039;&amp;#039; || Smooth f, any interval || \(O(n^{2n-1})\) exact for polys || Fast || &amp;#039;&amp;#039;&amp;#039;Default for known f&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;Adaptive (quad)&amp;#039;&amp;#039;&amp;#039; || Unknown smoothness, singularities || Near machine precision || Moderate || &amp;#039;&amp;#039;&amp;#039;Default for general use&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
|-&lt;br /&gt;
| Gauss-Hermite/Laguerre || Infinite/half-infinite intervals with weight || Excellent || Fast || When the weight matches&lt;br /&gt;
|-&lt;br /&gt;
| Clenshaw-Curtis || Nested adaptive refinement || Near Gauss || Fast || When nested nodes matter&lt;br /&gt;
|-&lt;br /&gt;
| Romberg || Pre-sampled \(2^k+1\) data || Extrapolated to high order || Fast || Niche but elegant&lt;br /&gt;
|-&lt;br /&gt;
| Monte Carlo || d ≥ 5 || \(O(1/\sqrt{N})\) || Varies || High dimensions only&lt;br /&gt;
|-&lt;br /&gt;
| Quasi-Monte Carlo || d ≥ 5, smooth f || \(O((\log N)^d/N)\) || Varies || Better than MC for smooth f&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;Method of Moments&amp;#039;&amp;#039;&amp;#039; || Parameter estimation || Consistent || Instant (closed-form) || &amp;#039;&amp;#039;&amp;#039;First pass estimator&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
|-&lt;br /&gt;
| MLE || Parameter estimation, efficiency || Asymptotically efficient || Optimization needed || Use MoM as starting point&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== See Also ==&lt;br /&gt;
&lt;br /&gt;
* [[Numerics/Interpolation Extrapolation]] — splines are essential for integrating sampled data&lt;br /&gt;
* [[Numerics/Special Functions]] — erf, gamma, Bessel and friends&lt;br /&gt;
* [[Numerics/Random Numbers]] — Monte Carlo needs good RNG&lt;br /&gt;
* [[Numerics/Integration of Functions]] — more depth on specific quadrature schemes&lt;br /&gt;
* [[Numerics/Statistical Descriptions of Data]] — moments, quantiles, descriptive statistics&lt;br /&gt;
* [[Numerics/Minimization Maximization]] — MLE requires optimization&lt;br /&gt;
* [https://docs.scipy.org/doc/scipy/tutorial/integrate.html SciPy integration tutorial]&lt;br /&gt;
* [https://docs.scipy.org/doc/scipy/reference/special.html SciPy special functions]&lt;br /&gt;
* Trefethen, L.N. (2008). &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.&lt;br /&gt;
&lt;br /&gt;
[[Category:Numerics]]&lt;br /&gt;
[[Category:Python]]&lt;br /&gt;
[[Category:Statistics]]&lt;/div&gt;</summary>
		<author><name>Admin</name></author>
	</entry>
</feed>