Skip to content

Commit b98312c

Browse files
tianyizheng02github-actions
and
github-actions
authored
Consolidate Newton-Raphson implementations (#10859)
* updating DIRECTORY.md * updating DIRECTORY.md * Consolidate Newton-Raphson duplicates * Rename consolidated Newton-Raphson file * updating DIRECTORY.md * updating DIRECTORY.md * Fix doctest precision * Fix doctest precision again --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com>
1 parent e5d6969 commit b98312c

File tree

5 files changed

+105
-241
lines changed

5 files changed

+105
-241
lines changed

DIRECTORY.md

-3
Original file line numberDiff line numberDiff line change
@@ -642,10 +642,7 @@
642642
* [Intersection](maths/numerical_analysis/intersection.py)
643643
* [Nevilles Method](maths/numerical_analysis/nevilles_method.py)
644644
* [Newton Forward Interpolation](maths/numerical_analysis/newton_forward_interpolation.py)
645-
* [Newton Method](maths/numerical_analysis/newton_method.py)
646645
* [Newton Raphson](maths/numerical_analysis/newton_raphson.py)
647-
* [Newton Raphson 2](maths/numerical_analysis/newton_raphson_2.py)
648-
* [Newton Raphson New](maths/numerical_analysis/newton_raphson_new.py)
649646
* [Numerical Integration](maths/numerical_analysis/numerical_integration.py)
650647
* [Runge Kutta](maths/numerical_analysis/runge_kutta.py)
651648
* [Runge Kutta Fehlberg 45](maths/numerical_analysis/runge_kutta_fehlberg_45.py)

maths/numerical_analysis/newton_method.py

-54
This file was deleted.
+105-37
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,113 @@
1-
# Implementing Newton Raphson method in Python
2-
# Author: Syed Haseeb Shah (github.com/QuantumNovice)
3-
# The Newton-Raphson method (also known as Newton's method) is a way to
4-
# quickly find a good approximation for the root of a real-valued function
5-
from __future__ import annotations
6-
7-
from decimal import Decimal
8-
9-
from sympy import diff, lambdify, symbols
10-
11-
12-
def newton_raphson(func: str, a: float | Decimal, precision: float = 1e-10) -> float:
13-
"""Finds root from the point 'a' onwards by Newton-Raphson method
14-
>>> newton_raphson("sin(x)", 2)
15-
3.1415926536808043
16-
>>> newton_raphson("x**2 - 5*x + 2", 0.4)
17-
0.4384471871911695
18-
>>> newton_raphson("x**2 - 5", 0.1)
19-
2.23606797749979
20-
>>> newton_raphson("log(x) - 1", 2)
21-
2.718281828458938
1+
"""
2+
The Newton-Raphson method (aka the Newton method) is a root-finding algorithm that
3+
approximates a root of a given real-valued function f(x). It is an iterative method
4+
given by the formula
5+
6+
x_{n + 1} = x_n + f(x_n) / f'(x_n)
7+
8+
with the precision of the approximation increasing as the number of iterations increase.
9+
10+
Reference: https://en.wikipedia.org/wiki/Newton%27s_method
11+
"""
12+
from collections.abc import Callable
13+
14+
RealFunc = Callable[[float], float]
15+
16+
17+
def calc_derivative(f: RealFunc, x: float, delta_x: float = 1e-3) -> float:
18+
"""
19+
Approximate the derivative of a function f(x) at a point x using the finite
20+
difference method
21+
22+
>>> import math
23+
>>> tolerance = 1e-5
24+
>>> derivative = calc_derivative(lambda x: x**2, 2)
25+
>>> math.isclose(derivative, 4, abs_tol=tolerance)
26+
True
27+
>>> derivative = calc_derivative(math.sin, 0)
28+
>>> math.isclose(derivative, 1, abs_tol=tolerance)
29+
True
30+
"""
31+
return (f(x + delta_x / 2) - f(x - delta_x / 2)) / delta_x
32+
33+
34+
def newton_raphson(
35+
f: RealFunc,
36+
x0: float = 0,
37+
max_iter: int = 100,
38+
step: float = 1e-6,
39+
max_error: float = 1e-6,
40+
log_steps: bool = False,
41+
) -> tuple[float, float, list[float]]:
2242
"""
23-
x = symbols("x")
24-
f = lambdify(x, func, "math")
25-
f_derivative = lambdify(x, diff(func), "math")
26-
x_curr = a
27-
while True:
28-
x_curr = Decimal(x_curr) - Decimal(f(x_curr)) / Decimal(f_derivative(x_curr))
29-
if abs(f(x_curr)) < precision:
30-
return float(x_curr)
43+
Find a root of the given function f using the Newton-Raphson method.
44+
45+
:param f: A real-valued single-variable function
46+
:param x0: Initial guess
47+
:param max_iter: Maximum number of iterations
48+
:param step: Step size of x, used to approximate f'(x)
49+
:param max_error: Maximum approximation error
50+
:param log_steps: bool denoting whether to log intermediate steps
51+
52+
:return: A tuple containing the approximation, the error, and the intermediate
53+
steps. If log_steps is False, then an empty list is returned for the third
54+
element of the tuple.
55+
56+
:raises ZeroDivisionError: The derivative approaches 0.
57+
:raises ArithmeticError: No solution exists, or the solution isn't found before the
58+
iteration limit is reached.
59+
60+
>>> import math
61+
>>> tolerance = 1e-15
62+
>>> root, *_ = newton_raphson(lambda x: x**2 - 5*x + 2, 0.4, max_error=tolerance)
63+
>>> math.isclose(root, (5 - math.sqrt(17)) / 2, abs_tol=tolerance)
64+
True
65+
>>> root, *_ = newton_raphson(lambda x: math.log(x) - 1, 2, max_error=tolerance)
66+
>>> math.isclose(root, math.e, abs_tol=tolerance)
67+
True
68+
>>> root, *_ = newton_raphson(math.sin, 1, max_error=tolerance)
69+
>>> math.isclose(root, 0, abs_tol=tolerance)
70+
True
71+
>>> newton_raphson(math.cos, 0)
72+
Traceback (most recent call last):
73+
...
74+
ZeroDivisionError: No converging solution found, zero derivative
75+
>>> newton_raphson(lambda x: x**2 + 1, 2)
76+
Traceback (most recent call last):
77+
...
78+
ArithmeticError: No converging solution found, iteration limit reached
79+
"""
80+
81+
def f_derivative(x: float) -> float:
82+
return calc_derivative(f, x, step)
83+
84+
a = x0 # Set initial guess
85+
steps = []
86+
for _ in range(max_iter):
87+
if log_steps: # Log intermediate steps
88+
steps.append(a)
89+
90+
error = abs(f(a))
91+
if error < max_error:
92+
return a, error, steps
93+
94+
if f_derivative(a) == 0:
95+
raise ZeroDivisionError("No converging solution found, zero derivative")
96+
a -= f(a) / f_derivative(a) # Calculate next estimate
97+
raise ArithmeticError("No converging solution found, iteration limit reached")
3198

3299

33100
if __name__ == "__main__":
34101
import doctest
102+
from math import exp, tanh
35103

36104
doctest.testmod()
37105

38-
# Find value of pi
39-
print(f"The root of sin(x) = 0 is {newton_raphson('sin(x)', 2)}")
40-
# Find root of polynomial
41-
print(f"The root of x**2 - 5*x + 2 = 0 is {newton_raphson('x**2 - 5*x + 2', 0.4)}")
42-
# Find value of e
43-
print(f"The root of log(x) - 1 = 0 is {newton_raphson('log(x) - 1', 2)}")
44-
# Find root of exponential function
45-
print(f"The root of exp(x) - 1 = 0 is {newton_raphson('exp(x) - 1', 0)}")
106+
def func(x: float) -> float:
107+
return tanh(x) ** 2 - exp(3 * x)
108+
109+
solution, err, steps = newton_raphson(
110+
func, x0=10, max_iter=100, step=1e-6, log_steps=True
111+
)
112+
print(f"{solution=}, {err=}")
113+
print("\n".join(str(x) for x in steps))

maths/numerical_analysis/newton_raphson_2.py

-64
This file was deleted.

maths/numerical_analysis/newton_raphson_new.py

-83
This file was deleted.

0 commit comments

Comments
 (0)