Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions maths/numerical_analysis/brent_method.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
"""
Brent's Method for Root Finding
-------------------------------

Brent's method is a robust and efficient algorithm for finding a zero of a
function in a given interval [left, right]. It combines bisection,
secant, and inverse quadratic interpolation methods.

References:
- https://en.wikipedia.org/wiki/Brent%27s_method
- https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.brentq.html

Example usage:
>>> def cubic(x):
... return x**3 - x - 2
>>> round(brent_root(cubic, 1, 2), 5)
1.52138
"""

from typing import Callable

Check failure on line 20 in maths/numerical_analysis/brent_method.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (I001)

maths/numerical_analysis/brent_method.py:20:1: I001 Import block is un-sorted or un-formatted

Check failure on line 20 in maths/numerical_analysis/brent_method.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (UP035)

maths/numerical_analysis/brent_method.py:20:1: UP035 Import from `collections.abc` instead: `Callable`

def brent_root(
function: Callable[[float], float],
left: float,
right: float,
tolerance: float = 1e-5,
max_iterations: int = 100,
) -> float:
value_left, value_right = function(left), function(right)
if value_left * value_right >= 0:
raise ValueError("Function must have opposite signs at endpoints left and right.")

Check failure on line 31 in maths/numerical_analysis/brent_method.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (E501)

maths/numerical_analysis/brent_method.py:31:89: E501 Line too long (90 > 88)

previous_point = current_point = left
value_previous = value_current = value_left
distance = interval_length = right - left

for iteration in range(max_iterations):

Check failure on line 37 in maths/numerical_analysis/brent_method.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (B007)

maths/numerical_analysis/brent_method.py:37:9: B007 Loop control variable `iteration` not used within loop body
if value_current * value_previous > 0:
previous_point, value_previous = left, value_left
distance = interval_length = right - left

if abs(value_previous) < abs(value_current):
left, current_point, previous_point = current_point, previous_point, current_point

Check failure on line 43 in maths/numerical_analysis/brent_method.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (E501)

maths/numerical_analysis/brent_method.py:43:89: E501 Line too long (94 > 88)
value_left, value_current, value_previous = value_current, value_previous, value_current

Check failure on line 44 in maths/numerical_analysis/brent_method.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (E501)

maths/numerical_analysis/brent_method.py:44:89: E501 Line too long (100 > 88)

tolerance1 = 2.0 * 1e-16 * abs(current_point) + 0.5 * tolerance
midpoint = 0.5 * (previous_point - current_point)

if abs(midpoint) <= tolerance1 or value_current == 0.0:
return current_point

if abs(interval_length) >= tolerance1 and abs(value_left) > abs(value_current):
ratio = value_current / value_left
if left == previous_point:
numerator = 2 * midpoint * ratio
denominator = 1 - ratio
else:
q = value_left / value_previous
r = value_current / value_previous
numerator = ratio * (2 * midpoint * q * (q - r) - (current_point - left) * (r - 1))

Check failure on line 60 in maths/numerical_analysis/brent_method.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (E501)

maths/numerical_analysis/brent_method.py:60:89: E501 Line too long (99 > 88)
denominator = (q - 1) * (r - 1) * (ratio - 1)

if numerator > 0:
denominator = -denominator
numerator = abs(numerator)

if 2 * numerator < min(3 * midpoint * denominator - abs(tolerance1 * denominator), abs(interval_length * denominator)):

Check failure on line 67 in maths/numerical_analysis/brent_method.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (E501)

maths/numerical_analysis/brent_method.py:67:89: E501 Line too long (131 > 88)
interval_length = distance
distance = numerator / denominator
else:
distance = midpoint
interval_length = midpoint
else:
distance = midpoint
interval_length = midpoint

left, value_left = current_point, value_current
if abs(distance) > tolerance1:
current_point += distance
else:
current_point += tolerance1 if midpoint > 0 else -tolerance1
value_current = function(current_point)

raise RuntimeError("Maximum iterations exceeded in Brent's method.")

if __name__ == "__main__":
import doctest
doctest.testmod()
Loading