1+ import math
2+ from collections .abc import Callable
3+
4+ def brent_method (
5+ f : Callable [[float ], float ],
6+ a : float ,
7+ b : float ,
8+ tol : float = 1e-14 ,
9+ max_iter : int = 100
10+ ) -> float :
11+ """
12+ Root finding using Brent's method.
13+
14+ >>> brent_method(lambda x: x**3 - 1, -5, 5)
15+ 1.0
16+ >>> brent_method(lambda x: x**2 - 4*x + 3, 0, 2)
17+ 1.0
18+ >>> brent_method(lambda x: x**2 - 4*x + 3, 2, 4)
19+ 3.0
20+ >>> brent_method(lambda x: x**2 - 4*x + 3, 4, 1000)
21+ Traceback (most recent call last):
22+ ...
23+ ValueError: Root is not bracketed.
24+ """
25+
26+ fa = f (a )
27+ fb = f (b )
28+ if fa * fb >= 0 :
29+ raise ValueError ("Root is not bracketed." )
30+
31+ if abs (fa ) < abs (fb ):
32+ a , b = b , a
33+ fa , fb = fb , fa
34+
35+ c = a
36+ fc = fa
37+ d = e = b - a
38+ mflag = True
39+
40+ for _ in range (max_iter ):
41+ if fb == 0 :
42+ return b
43+ if fa != fc and fb != fc :
44+ # Inverse quadratic interpolation
45+ s = (
46+ a * fb * fc / ((fa - fb ) * (fa - fc )) +
47+ b * fa * fc / ((fb - fa ) * (fb - fc )) +
48+ c * fa * fb / ((fc - fa ) * (fc - fb ))
49+ )
50+ else :
51+ # Secant method
52+ s = b - fb * (b - a ) / (fb - fa )
53+
54+ conditions = [
55+ not ((3 * a + b ) / 4 < s < b if b > a else b < s < (3 * a + b ) / 4 ),
56+ mflag and abs (s - b ) >= abs (b - c ) / 2 ,
57+ not mflag and abs (s - b ) >= abs (c - d ) / 2 ,
58+ mflag and abs (b - c ) < tol ,
59+ not mflag and abs (c - d ) < tol ,
60+ ]
61+ if any (conditions ):
62+ s = (a + b ) / 2
63+ mflag = True
64+ else :
65+ mflag = False
66+
67+ fs = f (s )
68+ d , c = c , b
69+ fc = fb
70+
71+ if fa * fs < 0 :
72+ b = s
73+ fb = fs
74+ else :
75+ a = s
76+ fa = fs
77+
78+ if abs (fa ) < abs (fb ):
79+ a , b = b , a
80+ fa , fb = fb , fa
81+
82+ if abs (b - a ) < tol or fb == 0 :
83+ return b
84+
85+ return b
86+
87+ if __name__ == "__main__" :
88+ from doctest import testmod
89+ testmod ()
0 commit comments