Path: blob/main/latex-templates/templates/numerical-methods/root_finding.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{bisect}{RGB}{231, 76, 60}11\definecolor{newton}{RGB}{46, 204, 113}12\definecolor{secant}{RGB}{52, 152, 219}13\definecolor{brent}{RGB}{155, 89, 182}1415\title{Root Finding Algorithms:\\16Convergence Analysis and Comparison}17\author{Department of Computational Mathematics\\Technical Report NM-2024-003}18\date{\today}1920\begin{document}21\maketitle2223\begin{abstract}24This report presents a comprehensive analysis of root-finding algorithms for nonlinear equations. We implement and compare bisection, Newton-Raphson, secant, and Brent's methods. Convergence rates are analyzed theoretically and verified computationally. The importance of initial guesses and potential pitfalls of each method are demonstrated through examples.25\end{abstract}2627\tableofcontents2829\chapter{Introduction}3031Root finding seeks solutions to $f(x) = 0$. We classify methods by their convergence rate:32\begin{itemize}33\item \textbf{Linear}: $|e_{n+1}| \leq C|e_n|$, where $C < 1$34\item \textbf{Superlinear}: $|e_{n+1}| \leq C|e_n|^p$, $1 < p < 2$35\item \textbf{Quadratic}: $|e_{n+1}| \leq C|e_n|^2$36\end{itemize}3738\chapter{Bisection Method}3940\section{Algorithm}41Given $f(a) \cdot f(b) < 0$:42\begin{enumerate}43\item Compute midpoint: $c = \frac{a + b}{2}$44\item If $f(a) \cdot f(c) < 0$, set $b = c$; else set $a = c$45\item Repeat until $|b - a| < \epsilon$46\end{enumerate}4748Convergence: Linear, $|e_{n+1}| = \frac{1}{2}|e_n|$4950\begin{pycode}51import numpy as np52import matplotlib.pyplot as plt53plt.rc('text', usetex=True)54plt.rc('font', family='serif')5556np.random.seed(42)5758def bisection(f, a, b, tol=1e-10, max_iter=100):59history = []60fa, fb = f(a), f(b)6162if fa * fb > 0:63return None, []6465for i in range(max_iter):66c = (a + b) / 267fc = f(c)68history.append(c)6970if abs(fc) < tol or (b - a) / 2 < tol:71return c, history7273if fa * fc < 0:74b, fb = c, fc75else:76a, fa = c, fc7778return c, history7980def newton(f, df, x0, tol=1e-10, max_iter=100):81history = [x0]82x = x08384for i in range(max_iter):85fx = f(x)86dfx = df(x)8788if abs(dfx) < 1e-14:89break9091x_new = x - fx / dfx92history.append(x_new)9394if abs(x_new - x) < tol:95return x_new, history9697x = x_new9899return x, history100101def secant(f, x0, x1, tol=1e-10, max_iter=100):102history = [x0, x1]103104for i in range(max_iter):105fx0, fx1 = f(x0), f(x1)106107if abs(fx1 - fx0) < 1e-14:108break109110x2 = x1 - fx1 * (x1 - x0) / (fx1 - fx0)111history.append(x2)112113if abs(x2 - x1) < tol:114return x2, history115116x0, x1 = x1, x2117118return x1, history119120def brent(f, a, b, tol=1e-10, max_iter=100):121fa, fb = f(a), f(b)122history = []123124if fa * fb > 0:125return None, []126127if abs(fa) < abs(fb):128a, b = b, a129fa, fb = fb, fa130131c = a132fc = fa133mflag = True134d = 0135136for i in range(max_iter):137history.append(b)138139if abs(fb) < tol or abs(b - a) < tol:140return b, history141142if fa != fc and fb != fc:143# Inverse quadratic interpolation144s = (a * fb * fc / ((fa - fb) * (fa - fc)) +145b * fa * fc / ((fb - fa) * (fb - fc)) +146c * fa * fb / ((fc - fa) * (fc - fb)))147else:148# Secant149s = b - fb * (b - a) / (fb - fa)150151# Conditions to accept s152cond1 = (s < (3*a + b) / 4 or s > b)153cond2 = mflag and abs(s - b) >= abs(b - c) / 2154cond3 = not mflag and abs(s - b) >= abs(c - d) / 2155cond4 = mflag and abs(b - c) < tol156cond5 = not mflag and abs(c - d) < tol157158if cond1 or cond2 or cond3 or cond4 or cond5:159s = (a + b) / 2160mflag = True161else:162mflag = False163164fs = f(s)165d = c166c = b167fc = fb168169if fa * fs < 0:170b, fb = s, fs171else:172a, fa = s, fs173174if abs(fa) < abs(fb):175a, b = b, a176fa, fb = fb, fa177178return b, history179\end{pycode}180181\section{Comparison of Methods}182183\begin{pycode}184# Test function: x^3 - x - 2185def f1(x):186return x**3 - x - 2187188def df1(x):189return 3*x**2 - 1190191# True root192true_root = 1.5213797068045676193194# Apply all methods195root_bis, hist_bis = bisection(f1, 1, 2)196root_new, hist_new = newton(f1, df1, 1.5)197root_sec, hist_sec = secant(f1, 1, 2)198root_brent, hist_brent = brent(f1, 1, 2)199200fig, axes = plt.subplots(2, 2, figsize=(12, 10))201202# Visualize bisection203ax = axes[0, 0]204x = np.linspace(0.5, 2.5, 200)205ax.plot(x, f1(x), 'b-', linewidth=2, label='$f(x) = x^3 - x - 2$')206ax.axhline(0, color='k', linestyle='-', alpha=0.3)207for i, xi in enumerate(hist_bis[:6]):208ax.axvline(xi, color='red', alpha=0.3 + 0.1*i, linestyle='--')209ax.scatter([true_root], [0], color='green', s=100, zorder=5, label=f'Root: {true_root:.6f}')210ax.set_xlabel('$x$')211ax.set_ylabel('$f(x)$')212ax.set_title('Bisection Method')213ax.legend()214ax.grid(True, alpha=0.3)215216# Newton iterations217ax = axes[0, 1]218ax.plot(x, f1(x), 'b-', linewidth=2)219ax.axhline(0, color='k', linestyle='-', alpha=0.3)220for i in range(min(4, len(hist_new)-1)):221xi = hist_new[i]222yi = f1(xi)223slope = df1(xi)224x_tang = np.linspace(xi - 0.5, xi + 0.5, 50)225y_tang = yi + slope * (x_tang - xi)226ax.plot(x_tang, y_tang, 'g--', alpha=0.5)227ax.scatter([xi], [yi], color='green', s=50)228ax.scatter([true_root], [0], color='red', s=100, zorder=5)229ax.set_xlabel('$x$')230ax.set_ylabel('$f(x)$')231ax.set_title('Newton-Raphson Method')232ax.set_xlim(0.5, 2.5)233ax.set_ylim(-5, 10)234ax.grid(True, alpha=0.3)235236# Convergence comparison237ax = axes[1, 0]238errors_bis = [abs(h - true_root) for h in hist_bis]239errors_new = [abs(h - true_root) for h in hist_new]240errors_sec = [abs(h - true_root) for h in hist_sec]241errors_brent = [abs(h - true_root) for h in hist_brent]242243ax.semilogy(range(len(errors_bis)), errors_bis, 'ro-', label='Bisection')244ax.semilogy(range(len(errors_new)), errors_new, 'gs-', label='Newton')245ax.semilogy(range(len(errors_sec)), errors_sec, 'b^-', label='Secant')246ax.semilogy(range(len(errors_brent)), errors_brent, 'mp-', label='Brent')247ax.set_xlabel('Iteration')248ax.set_ylabel('$|x_n - x^*|$')249ax.set_title('Convergence Comparison')250ax.legend()251ax.grid(True, alpha=0.3)252253# Convergence order estimation254ax = axes[1, 1]255# Newton convergence order256if len(errors_new) > 3:257log_e = np.log(errors_new[1:-1])258log_e_next = np.log(errors_new[2:])259log_e_prev = np.log(errors_new[:-2])260orders = log_e_next / log_e261ax.plot(range(len(orders)), orders, 'gs-', label='Newton')262263# Secant convergence order264if len(errors_sec) > 3:265log_e = np.log(errors_sec[1:-1])266log_e_next = np.log(errors_sec[2:])267orders = log_e_next / log_e268ax.plot(range(len(orders)), orders, 'b^-', label='Secant')269270ax.axhline(2, color='green', linestyle='--', alpha=0.5, label='Quadratic')271ax.axhline(1.618, color='blue', linestyle='--', alpha=0.5, label='Golden ratio')272ax.axhline(1, color='red', linestyle='--', alpha=0.5, label='Linear')273ax.set_xlabel('Iteration')274ax.set_ylabel('Estimated Order')275ax.set_title('Convergence Order')276ax.legend()277ax.grid(True, alpha=0.3)278ax.set_ylim(0, 3)279280plt.tight_layout()281plt.savefig('root_finding_comparison.pdf', dpi=150, bbox_inches='tight')282plt.close()283\end{pycode}284285\begin{figure}[htbp]286\centering287\includegraphics[width=0.95\textwidth]{root_finding_comparison.pdf}288\caption{Root finding methods: (a) bisection visualization, (b) Newton tangent lines, (c) error convergence, (d) convergence order estimation.}289\end{figure}290291\chapter{Newton-Raphson Method}292293\section{Algorithm}294\begin{equation}295x_{n+1} = x_n - \frac{f(x_n)}{f'(x_n)}296\end{equation}297298Convergence: Quadratic near root (when $f'(x^*) \neq 0$)299300\section{Pitfalls}301302\begin{pycode}303# Newton failures304fig, axes = plt.subplots(1, 3, figsize=(14, 4))305306# Bad initial guess307ax = axes[0]308def f_bad(x):309return x**3 - 2*x + 2310311def df_bad(x):312return 3*x**2 - 2313314x = np.linspace(-2, 2, 200)315ax.plot(x, f_bad(x), 'b-', linewidth=2)316ax.axhline(0, color='k', alpha=0.3)317318# Newton from x0=0 fails (derivative near 0)319x0 = 0.5320history = [x0]321xn = x0322for _ in range(5):323xn = xn - f_bad(xn) / df_bad(xn)324history.append(xn)325ax.scatter(history, [f_bad(h) for h in history], c=range(len(history)), cmap='Reds', s=50)326ax.set_xlabel('$x$')327ax.set_ylabel('$f(x)$')328ax.set_title('Newton Cycling')329ax.grid(True, alpha=0.3)330331# Multiple roots332ax = axes[1]333def f_mult(x):334return (x - 1)**2335336def df_mult(x):337return 2*(x - 1)338339x = np.linspace(-1, 3, 200)340ax.plot(x, f_mult(x), 'b-', linewidth=2)341ax.axhline(0, color='k', alpha=0.3)342root_mult, hist_mult = newton(f_mult, df_mult, 3.0, max_iter=20)343errors_mult = [abs(h - 1) for h in hist_mult]344ax.scatter(hist_mult, [f_mult(h) for h in hist_mult], c=range(len(hist_mult)), cmap='Greens', s=30)345ax.set_xlabel('$x$')346ax.set_ylabel('$f(x)$')347ax.set_title('Multiple Root (Linear Convergence)')348ax.grid(True, alpha=0.3)349350# Slow convergence for multiple roots351ax = axes[2]352ax.semilogy(range(len(errors_mult)), errors_mult, 'go-')353ax.set_xlabel('Iteration')354ax.set_ylabel('$|x_n - 1|$')355ax.set_title('Slow Convergence for $(x-1)^2 = 0$')356ax.grid(True, alpha=0.3)357358plt.tight_layout()359plt.savefig('newton_pitfalls.pdf', dpi=150, bbox_inches='tight')360plt.close()361362n_iterations = {363'Bisection': len(hist_bis),364'Newton': len(hist_new),365'Secant': len(hist_sec),366'Brent': len(hist_brent)367}368\end{pycode}369370\begin{figure}[htbp]371\centering372\includegraphics[width=0.95\textwidth]{newton_pitfalls.pdf}373\caption{Newton method pitfalls: cycling, slow convergence for multiple roots.}374\end{figure}375376\chapter{Secant Method}377378\section{Algorithm}379\begin{equation}380x_{n+1} = x_n - f(x_n)\frac{x_n - x_{n-1}}{f(x_n) - f(x_{n-1})}381\end{equation}382383Convergence: Superlinear, $p = \frac{1 + \sqrt{5}}{2} \approx 1.618$ (golden ratio)384385Advantage: No derivative required.386387\chapter{Brent's Method}388389Combines bisection, secant, and inverse quadratic interpolation. Guaranteed convergence with superlinear speed for smooth functions.390391\chapter{Numerical Results}392393\begin{pycode}394results_data = [395('Bisection', n_iterations['Bisection'], 'Linear (1)', 'Guaranteed'),396('Newton', n_iterations['Newton'], 'Quadratic (2)', 'Requires $f\'$'),397('Secant', n_iterations['Secant'], 'Superlinear (1.618)', 'No derivative'),398('Brent', n_iterations['Brent'], 'Superlinear', 'Robust'),399]400\end{pycode}401402\begin{table}[htbp]403\centering404\caption{Comparison for $f(x) = x^3 - x - 2$}405\begin{tabular}{@{}llll@{}}406\toprule407Method & Iterations & Order & Notes \\408\midrule409\py{results_data[0][0]} & \py{results_data[0][1]} & \py{results_data[0][2]} & \py{results_data[0][3]} \\410\py{results_data[1][0]} & \py{results_data[1][1]} & \py{results_data[1][2]} & \py{results_data[1][3]} \\411\py{results_data[2][0]} & \py{results_data[2][1]} & \py{results_data[2][2]} & \py{results_data[2][3]} \\412\py{results_data[3][0]} & \py{results_data[3][1]} & \py{results_data[3][2]} & \py{results_data[3][3]} \\413\bottomrule414\end{tabular}415\end{table}416417\chapter{Multiple Test Functions}418419\begin{pycode}420# Additional test functions421test_funcs = [422(lambda x: np.cos(x) - x, lambda x: -np.sin(x) - 1, 0, 1, "\\cos(x) - x"),423(lambda x: np.exp(x) - 3*x, lambda x: np.exp(x) - 3, 0, 2, "e^x - 3x"),424(lambda x: x**2 - 2, lambda x: 2*x, 1, 2, "x^2 - 2"),425]426427fig, axes = plt.subplots(1, 3, figsize=(14, 4))428429for i, (f, df, a, b, name) in enumerate(test_funcs):430ax = axes[i]431x = np.linspace(a - 0.5, b + 0.5, 200)432ax.plot(x, f(x), 'b-', linewidth=2)433ax.axhline(0, color='k', alpha=0.3)434435root_n, hist_n = newton(f, df, (a + b) / 2)436root_b, hist_b = bisection(f, a, b)437438ax.scatter([root_n], [0], color='red', s=100, zorder=5, label=f'Root: {root_n:.6f}')439ax.set_xlabel('$x$')440ax.set_ylabel('$f(x)$')441ax.set_title(f'$f(x) = {name}$')442ax.legend()443ax.grid(True, alpha=0.3)444445plt.tight_layout()446plt.savefig('multiple_functions.pdf', dpi=150, bbox_inches='tight')447plt.close()448\end{pycode}449450\begin{figure}[htbp]451\centering452\includegraphics[width=0.95\textwidth]{multiple_functions.pdf}453\caption{Root finding for various test functions.}454\end{figure}455456\chapter{Conclusions}457458\begin{enumerate}459\item Bisection: Always converges but slow (linear)460\item Newton: Fast (quadratic) but requires derivative and good initial guess461\item Secant: Superlinear without derivatives462\item Brent: Best general-purpose method (robust + fast)463\item Multiple roots reduce Newton to linear convergence464\item Choice depends on: derivative availability, robustness needs, speed requirements465\end{enumerate}466467\end{document}468469470