Path: blob/main/latex-templates/templates/numerical-methods/integration.tex
51 views
unlisted
\documentclass[a4paper, 11pt]{report}1\usepackage[utf8]{inputenc}2\usepackage[T1]{fontenc}3\usepackage{amsmath, amssymb}4\usepackage{graphicx}5\usepackage{siunitx}6\usepackage{booktabs}7\usepackage{xcolor}8\usepackage[makestderr]{pythontex}910\definecolor{trap}{RGB}{231, 76, 60}11\definecolor{simp}{RGB}{46, 204, 113}12\definecolor{gauss}{RGB}{52, 152, 219}13\definecolor{romberg}{RGB}{155, 89, 182}1415\title{Numerical Integration Methods:\\16Quadrature Algorithms and Error Analysis}17\author{Department of Computational Mathematics\\Technical Report NM-2024-001}18\date{\today}1920\begin{document}21\maketitle2223\begin{abstract}24This report presents a comprehensive analysis of numerical integration (quadrature) methods. We implement and compare the trapezoidal rule, Simpson's rule, Romberg integration, and Gaussian quadrature. Error analysis demonstrates convergence rates, and we verify results against analytically known integrals. All computations use PythonTeX for reproducibility.25\end{abstract}2627\tableofcontents2829\chapter{Introduction}3031Numerical integration approximates definite integrals when analytical solutions are unavailable or impractical. We seek to compute:32\begin{equation}33I = \int_a^b f(x) \, dx34\end{equation}3536\section{Newton-Cotes Formulas}37These methods interpolate $f(x)$ with polynomials at equally spaced nodes:38\begin{itemize}39\item Trapezoidal rule: Linear interpolation40\item Simpson's rule: Quadratic interpolation41\item Higher-order rules: Cubic, quartic, etc.42\end{itemize}4344\chapter{Trapezoidal Rule}4546\section{Formula}47The composite trapezoidal rule with $n$ subintervals:48\begin{equation}49T_n = h\left[\frac{f(a) + f(b)}{2} + \sum_{i=1}^{n-1} f(a + ih)\right], \quad h = \frac{b-a}{n}50\end{equation}5152Error: $E_T = -\frac{(b-a)h^2}{12}f''(\xi) = O(h^2)$5354\begin{pycode}55import numpy as np56import matplotlib.pyplot as plt57from scipy import integrate58from scipy.special import roots_legendre59plt.rc('text', usetex=True)60plt.rc('font', family='serif')6162np.random.seed(42)6364def trapezoidal(f, a, b, n):65h = (b - a) / n66x = np.linspace(a, b, n + 1)67y = f(x)68return h * (0.5*y[0] + np.sum(y[1:-1]) + 0.5*y[-1])6970def simpson(f, a, b, n):71if n % 2 == 1:72n += 173h = (b - a) / n74x = np.linspace(a, b, n + 1)75y = f(x)76return h/3 * (y[0] + 4*np.sum(y[1:-1:2]) + 2*np.sum(y[2:-1:2]) + y[-1])7778def romberg(f, a, b, max_iter=10, tol=1e-10):79R = np.zeros((max_iter, max_iter))80h = b - a81R[0, 0] = 0.5 * h * (f(a) + f(b))8283for i in range(1, max_iter):84h = h / 285sum_new = sum(f(a + (2*k-1)*h) for k in range(1, 2**(i-1) + 1))86R[i, 0] = 0.5 * R[i-1, 0] + h * sum_new8788for j in range(1, i + 1):89R[i, j] = R[i, j-1] + (R[i, j-1] - R[i-1, j-1]) / (4**j - 1)9091if i > 0 and abs(R[i, i] - R[i-1, i-1]) < tol:92return R[i, i], i, R[:i+1, :i+1]9394return R[max_iter-1, max_iter-1], max_iter, R9596def gauss_legendre(f, a, b, n):97nodes, weights = roots_legendre(n)98# Transform from [-1, 1] to [a, b]99x = 0.5 * (b - a) * nodes + 0.5 * (a + b)100return 0.5 * (b - a) * np.sum(weights * f(x))101\end{pycode}102103\section{Visualization}104105\begin{pycode}106# Test function107def f_test(x):108return np.exp(-x**2)109110a, b = 0, 2111exact = 0.5 * np.sqrt(np.pi) * (1 - 2/np.sqrt(np.pi) * integrate.quad(lambda t: np.exp(-t**2), 2, np.inf)[0])112exact_value = integrate.quad(f_test, a, b)[0]113114fig, axes = plt.subplots(2, 2, figsize=(12, 10))115116# Trapezoidal visualization117ax = axes[0, 0]118x_plot = np.linspace(a, b, 200)119ax.plot(x_plot, f_test(x_plot), 'b-', linewidth=2, label='$f(x) = e^{-x^2}$')120n_trap = 6121x_trap = np.linspace(a, b, n_trap + 1)122y_trap = f_test(x_trap)123for i in range(n_trap):124ax.fill([x_trap[i], x_trap[i], x_trap[i+1], x_trap[i+1]],125[0, y_trap[i], y_trap[i+1], 0], alpha=0.3, color='red')126ax.scatter(x_trap, y_trap, color='red', s=50, zorder=5)127ax.set_xlabel('$x$')128ax.set_ylabel('$f(x)$')129ax.set_title(f'Trapezoidal Rule ($n = {n_trap}$)')130ax.legend()131ax.grid(True, alpha=0.3)132133# Simpson visualization134ax = axes[0, 1]135ax.plot(x_plot, f_test(x_plot), 'b-', linewidth=2, label='$f(x) = e^{-x^2}$')136n_simp = 4137h_simp = (b - a) / n_simp138for i in range(0, n_simp, 2):139x0, x1, x2 = a + i*h_simp, a + (i+1)*h_simp, a + (i+2)*h_simp140y0, y1, y2 = f_test(x0), f_test(x1), f_test(x2)141# Quadratic fit142coeffs = np.polyfit([x0, x1, x2], [y0, y1, y2], 2)143x_para = np.linspace(x0, x2, 50)144y_para = np.polyval(coeffs, x_para)145ax.fill_between(x_para, 0, y_para, alpha=0.3, color='green')146x_simp = np.linspace(a, b, n_simp + 1)147ax.scatter(x_simp, f_test(x_simp), color='green', s=50, zorder=5)148ax.set_xlabel('$x$')149ax.set_ylabel('$f(x)$')150ax.set_title(f"Simpson's Rule ($n = {n_simp}$)")151ax.legend()152ax.grid(True, alpha=0.3)153154# Convergence comparison155ax = axes[1, 0]156n_values = np.array([4, 8, 16, 32, 64, 128, 256])157trap_errors = []158simp_errors = []159gauss_errors = []160161for n in n_values:162trap_errors.append(abs(trapezoidal(f_test, a, b, n) - exact_value))163simp_errors.append(abs(simpson(f_test, a, b, n) - exact_value))164if n <= 20:165gauss_errors.append(abs(gauss_legendre(f_test, a, b, n) - exact_value))166167ax.loglog(n_values, trap_errors, 'ro-', label='Trapezoidal $O(h^2)$')168ax.loglog(n_values, simp_errors, 'gs-', label="Simpson's $O(h^4)$")169ax.loglog(n_values[:len(gauss_errors)], gauss_errors, 'b^-', label='Gauss-Legendre')170ax.set_xlabel('Number of subintervals $n$')171ax.set_ylabel('Absolute Error')172ax.set_title('Convergence Comparison')173ax.legend()174ax.grid(True, alpha=0.3)175176# Romberg tableau177ax = axes[1, 1]178result, iters, R = romberg(f_test, a, b)179# Show Romberg tableau as heatmap180R_display = R.copy()181R_display[R_display == 0] = np.nan182im = ax.imshow(np.abs(R_display - exact_value), cmap='viridis_r', aspect='auto')183ax.set_xlabel('Richardson Extrapolation Level $j$')184ax.set_ylabel('Refinement Level $i$')185ax.set_title('Romberg Tableau (Error)')186plt.colorbar(im, ax=ax, label='$|R_{i,j} - I|$')187188plt.tight_layout()189plt.savefig('integration_methods.pdf', dpi=150, bbox_inches='tight')190plt.close()191\end{pycode}192193\begin{figure}[htbp]194\centering195\includegraphics[width=0.95\textwidth]{integration_methods.pdf}196\caption{Numerical integration methods: (a) trapezoidal approximation, (b) Simpson's parabolic approximation, (c) error convergence, (d) Romberg tableau.}197\end{figure}198199\chapter{Simpson's Rule}200201\section{Formula}202Composite Simpson's 1/3 rule (requires even $n$):203\begin{equation}204S_n = \frac{h}{3}\left[f(a) + 4\sum_{i=1,3,5,...}^{n-1} f(a+ih) + 2\sum_{i=2,4,6,...}^{n-2} f(a+ih) + f(b)\right]205\end{equation}206207Error: $E_S = -\frac{(b-a)h^4}{180}f^{(4)}(\xi) = O(h^4)$208209\chapter{Romberg Integration}210211\section{Richardson Extrapolation}212Romberg integration uses the trapezoidal rule with Richardson extrapolation:213\begin{equation}214R_{i,j} = R_{i,j-1} + \frac{R_{i,j-1} - R_{i-1,j-1}}{4^j - 1}215\end{equation}216217\begin{pycode}218# Test multiple integrals219test_functions = [220(lambda x: np.exp(-x**2), 0, 2, "e^{-x^2}"),221(lambda x: np.sin(x)/x if x != 0 else 1.0, 0.001, np.pi, "\\sin(x)/x"),222(lambda x: 1/(1 + x**2), 0, 1, "1/(1+x^2)"),223]224225results_table = []226for func, a, b, name in test_functions:227f_vec = np.vectorize(func)228exact = integrate.quad(f_vec, a, b)[0]229230trap_32 = trapezoidal(f_vec, a, b, 32)231simp_32 = simpson(f_vec, a, b, 32)232gauss_8 = gauss_legendre(f_vec, a, b, 8)233romb_val, _, _ = romberg(f_vec, a, b)234235results_table.append({236'name': name,237'exact': exact,238'trap_err': abs(trap_32 - exact),239'simp_err': abs(simp_32 - exact),240'gauss_err': abs(gauss_8 - exact),241'romb_err': abs(romb_val - exact)242})243\end{pycode}244245\begin{table}[htbp]246\centering247\caption{Error comparison for different integrals}248\begin{tabular}{@{}lcccc@{}}249\toprule250Function & Trapezoidal & Simpson & Gauss-8 & Romberg \\251\midrule252$\py{results_table[0]['name']}$ & \py{f"{results_table[0]['trap_err']:.2e}"} & \py{f"{results_table[0]['simp_err']:.2e}"} & \py{f"{results_table[0]['gauss_err']:.2e}"} & \py{f"{results_table[0]['romb_err']:.2e}"} \\253$\py{results_table[1]['name']}$ & \py{f"{results_table[1]['trap_err']:.2e}"} & \py{f"{results_table[1]['simp_err']:.2e}"} & \py{f"{results_table[1]['gauss_err']:.2e}"} & \py{f"{results_table[1]['romb_err']:.2e}"} \\254$\py{results_table[2]['name']}$ & \py{f"{results_table[2]['trap_err']:.2e}"} & \py{f"{results_table[2]['simp_err']:.2e}"} & \py{f"{results_table[2]['gauss_err']:.2e}"} & \py{f"{results_table[2]['romb_err']:.2e}"} \\255\bottomrule256\end{tabular}257\end{table}258259\chapter{Gaussian Quadrature}260261\section{Theory}262Gaussian quadrature chooses nodes and weights optimally:263\begin{equation}264\int_{-1}^{1} f(x) \, dx \approx \sum_{i=1}^{n} w_i f(x_i)265\end{equation}266267Nodes $x_i$ are roots of Legendre polynomials. An $n$-point formula is exact for polynomials of degree $\leq 2n-1$.268269\begin{pycode}270# Gaussian quadrature analysis271fig, axes = plt.subplots(1, 3, figsize=(14, 4))272273# Gauss nodes and weights274ax = axes[0]275for n in [2, 3, 4, 5]:276nodes, weights = roots_legendre(n)277ax.scatter(nodes, [n]*len(nodes), s=weights*500, alpha=0.7, label=f'$n = {n}$')278ax.set_xlabel('Node position $x_i$')279ax.set_ylabel('Number of points $n$')280ax.set_title('Gauss-Legendre Nodes')281ax.set_xlim(-1.1, 1.1)282ax.legend()283ax.grid(True, alpha=0.3)284285# Polynomial exactness286ax = axes[1]287n_points = range(2, 11)288degrees_exact = []289for n in n_points:290# Test polynomial of degree 2n-1291deg = 2*n - 1292poly = lambda x, d=deg: x**d293exact_poly = 2 / (deg + 1) if deg % 2 == 0 else 0294gauss_result = gauss_legendre(poly, -1, 1, n)295error = abs(gauss_result - exact_poly)296degrees_exact.append(error < 1e-10)297ax.bar(n_points, [2*n-1 for n in n_points], color='blue', alpha=0.7)298ax.set_xlabel('Number of points $n$')299ax.set_ylabel('Exact polynomial degree')300ax.set_title('Polynomial Exactness: $2n-1$')301ax.grid(True, alpha=0.3)302303# Error vs number of Gauss points304ax = axes[2]305n_gauss = range(2, 16)306gauss_errors_exp = [abs(gauss_legendre(f_test, a, b, n) - exact_value) for n in n_gauss]307ax.semilogy(n_gauss, gauss_errors_exp, 'b^-')308ax.set_xlabel('Number of Gauss points $n$')309ax.set_ylabel('Absolute Error')310ax.set_title('Gauss-Legendre Convergence')311ax.grid(True, alpha=0.3)312313plt.tight_layout()314plt.savefig('gaussian_quadrature.pdf', dpi=150, bbox_inches='tight')315plt.close()316\end{pycode}317318\begin{figure}[htbp]319\centering320\includegraphics[width=0.95\textwidth]{gaussian_quadrature.pdf}321\caption{Gaussian quadrature: node positions (left), polynomial exactness (center), convergence for $e^{-x^2}$ (right).}322\end{figure}323324\chapter{Adaptive Quadrature}325326\begin{pycode}327# Adaptive Simpson's rule328def adaptive_simpson(f, a, b, tol=1e-8, max_depth=50):329def simpson_recursive(a, b, fa, fm, fb, S, tol, depth):330c = (a + b) / 2331d = (a + c) / 2332e = (c + b) / 2333fd = f(d)334fe = f(e)335h = b - a336337S_left = h/12 * (fa + 4*fd + fm)338S_right = h/12 * (fm + 4*fe + fb)339S_new = S_left + S_right340341if depth >= max_depth or abs(S_new - S) < 15*tol:342return S_new + (S_new - S)/15343344return (simpson_recursive(a, c, fa, fd, fm, S_left, tol/2, depth+1) +345simpson_recursive(c, b, fm, fe, fb, S_right, tol/2, depth+1))346347c = (a + b) / 2348fa, fm, fb = f(a), f(c), f(b)349S = (b - a)/6 * (fa + 4*fm + fb)350return simpson_recursive(a, b, fa, fm, fb, S, tol, 0)351352# Test on oscillatory function353def f_osc(x):354return np.sin(10*x) * np.exp(-x)355356exact_osc = integrate.quad(f_osc, 0, 2*np.pi)[0]357adaptive_result = adaptive_simpson(f_osc, 0, 2*np.pi)358fixed_result = simpson(f_osc, 0, 2*np.pi, 100)359360fig, ax = plt.subplots(figsize=(10, 4))361x = np.linspace(0, 2*np.pi, 500)362ax.plot(x, f_osc(x), 'b-', linewidth=1)363ax.fill_between(x, 0, f_osc(x), alpha=0.3)364ax.set_xlabel('$x$')365ax.set_ylabel('$f(x)$')366ax.set_title('$f(x) = \\sin(10x) e^{-x}$')367ax.grid(True, alpha=0.3)368plt.tight_layout()369plt.savefig('adaptive_function.pdf', dpi=150, bbox_inches='tight')370plt.close()371\end{pycode}372373\begin{figure}[htbp]374\centering375\includegraphics[width=0.8\textwidth]{adaptive_function.pdf}376\caption{Oscillatory test function for adaptive quadrature.}377\end{figure}378379Adaptive Simpson error: $\py{f'{abs(adaptive_result - exact_osc):.2e}'}$ \\380Fixed Simpson (n=100) error: $\py{f'{abs(fixed_result - exact_osc):.2e}'}$381382\chapter{Summary}383384\begin{pycode}385summary_data = [386('Trapezoidal', '$O(h^2)$', 'Simple, low accuracy'),387("Simpson's", '$O(h^4)$', 'Good accuracy, even $n$'),388('Romberg', '$O(h^{2k})$', 'High accuracy, extrapolation'),389('Gauss-Legendre', 'Exponential', 'Optimal for smooth functions'),390]391\end{pycode}392393\begin{table}[htbp]394\centering395\caption{Summary of quadrature methods}396\begin{tabular}{@{}lll@{}}397\toprule398Method & Convergence & Notes \\399\midrule400\py{summary_data[0][0]} & \py{summary_data[0][1]} & \py{summary_data[0][2]} \\401\py{summary_data[1][0]} & \py{summary_data[1][1]} & \py{summary_data[1][2]} \\402\py{summary_data[2][0]} & \py{summary_data[2][1]} & \py{summary_data[2][2]} \\403\py{summary_data[3][0]} & \py{summary_data[3][1]} & \py{summary_data[3][2]} \\404\bottomrule405\end{tabular}406\end{table}407408\chapter{Conclusions}409410\begin{enumerate}411\item Trapezoidal rule: $O(h^2)$, simple but low accuracy412\item Simpson's rule: $O(h^4)$, good balance of simplicity and accuracy413\item Romberg: achieves high accuracy through Richardson extrapolation414\item Gaussian quadrature: optimal for smooth functions, exponential convergence415\item Adaptive methods: efficient for oscillatory or peaked integrands416\end{enumerate}417418\end{document}419420421