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>
This commit is contained in:
Tianyi Zheng 2023-10-23 16:37:17 -04:00 committed by GitHub
parent e5d6969f38
commit b98312ca9f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 101 additions and 237 deletions

View File

@ -642,10 +642,7 @@
* [Intersection](maths/numerical_analysis/intersection.py) * [Intersection](maths/numerical_analysis/intersection.py)
* [Nevilles Method](maths/numerical_analysis/nevilles_method.py) * [Nevilles Method](maths/numerical_analysis/nevilles_method.py)
* [Newton Forward Interpolation](maths/numerical_analysis/newton_forward_interpolation.py) * [Newton Forward Interpolation](maths/numerical_analysis/newton_forward_interpolation.py)
* [Newton Method](maths/numerical_analysis/newton_method.py)
* [Newton Raphson](maths/numerical_analysis/newton_raphson.py) * [Newton Raphson](maths/numerical_analysis/newton_raphson.py)
* [Newton Raphson 2](maths/numerical_analysis/newton_raphson_2.py)
* [Newton Raphson New](maths/numerical_analysis/newton_raphson_new.py)
* [Numerical Integration](maths/numerical_analysis/numerical_integration.py) * [Numerical Integration](maths/numerical_analysis/numerical_integration.py)
* [Runge Kutta](maths/numerical_analysis/runge_kutta.py) * [Runge Kutta](maths/numerical_analysis/runge_kutta.py)
* [Runge Kutta Fehlberg 45](maths/numerical_analysis/runge_kutta_fehlberg_45.py) * [Runge Kutta Fehlberg 45](maths/numerical_analysis/runge_kutta_fehlberg_45.py)

View File

@ -1,54 +0,0 @@
"""Newton's Method."""
# Newton's Method - https://en.wikipedia.org/wiki/Newton%27s_method
from collections.abc import Callable
RealFunc = Callable[[float], float] # type alias for a real -> real function
# function is the f(x) and derivative is the f'(x)
def newton(
function: RealFunc,
derivative: RealFunc,
starting_int: int,
) -> float:
"""
>>> newton(lambda x: x ** 3 - 2 * x - 5, lambda x: 3 * x ** 2 - 2, 3)
2.0945514815423474
>>> newton(lambda x: x ** 3 - 1, lambda x: 3 * x ** 2, -2)
1.0
>>> newton(lambda x: x ** 3 - 1, lambda x: 3 * x ** 2, -4)
1.0000000000000102
>>> import math
>>> newton(math.sin, math.cos, 1)
0.0
>>> newton(math.sin, math.cos, 2)
3.141592653589793
>>> newton(math.cos, lambda x: -math.sin(x), 2)
1.5707963267948966
>>> newton(math.cos, lambda x: -math.sin(x), 0)
Traceback (most recent call last):
...
ZeroDivisionError: Could not find root
"""
prev_guess = float(starting_int)
while True:
try:
next_guess = prev_guess - function(prev_guess) / derivative(prev_guess)
except ZeroDivisionError:
raise ZeroDivisionError("Could not find root") from None
if abs(prev_guess - next_guess) < 10**-5:
return next_guess
prev_guess = next_guess
def f(x: float) -> float:
return (x**3) - (2 * x) - 5
def f1(x: float) -> float:
return 3 * (x**2) - 2
if __name__ == "__main__":
print(newton(f, f1, 3))

View File

@ -1,45 +1,113 @@
# Implementing Newton Raphson method in Python """
# Author: Syed Haseeb Shah (github.com/QuantumNovice) The Newton-Raphson method (aka the Newton method) is a root-finding algorithm that
# The Newton-Raphson method (also known as Newton's method) is a way to approximates a root of a given real-valued function f(x). It is an iterative method
# quickly find a good approximation for the root of a real-valued function given by the formula
from __future__ import annotations
from decimal import Decimal x_{n + 1} = x_n + f(x_n) / f'(x_n)
from sympy import diff, lambdify, symbols with the precision of the approximation increasing as the number of iterations increase.
Reference: https://en.wikipedia.org/wiki/Newton%27s_method
"""
from collections.abc import Callable
RealFunc = Callable[[float], float]
def newton_raphson(func: str, a: float | Decimal, precision: float = 1e-10) -> float: def calc_derivative(f: RealFunc, x: float, delta_x: float = 1e-3) -> float:
"""Finds root from the point 'a' onwards by Newton-Raphson method
>>> newton_raphson("sin(x)", 2)
3.1415926536808043
>>> newton_raphson("x**2 - 5*x + 2", 0.4)
0.4384471871911695
>>> newton_raphson("x**2 - 5", 0.1)
2.23606797749979
>>> newton_raphson("log(x) - 1", 2)
2.718281828458938
""" """
x = symbols("x") Approximate the derivative of a function f(x) at a point x using the finite
f = lambdify(x, func, "math") difference method
f_derivative = lambdify(x, diff(func), "math")
x_curr = a >>> import math
while True: >>> tolerance = 1e-5
x_curr = Decimal(x_curr) - Decimal(f(x_curr)) / Decimal(f_derivative(x_curr)) >>> derivative = calc_derivative(lambda x: x**2, 2)
if abs(f(x_curr)) < precision: >>> math.isclose(derivative, 4, abs_tol=tolerance)
return float(x_curr) True
>>> derivative = calc_derivative(math.sin, 0)
>>> math.isclose(derivative, 1, abs_tol=tolerance)
True
"""
return (f(x + delta_x / 2) - f(x - delta_x / 2)) / delta_x
def newton_raphson(
f: RealFunc,
x0: float = 0,
max_iter: int = 100,
step: float = 1e-6,
max_error: float = 1e-6,
log_steps: bool = False,
) -> tuple[float, float, list[float]]:
"""
Find a root of the given function f using the Newton-Raphson method.
:param f: A real-valued single-variable function
:param x0: Initial guess
:param max_iter: Maximum number of iterations
:param step: Step size of x, used to approximate f'(x)
:param max_error: Maximum approximation error
:param log_steps: bool denoting whether to log intermediate steps
:return: A tuple containing the approximation, the error, and the intermediate
steps. If log_steps is False, then an empty list is returned for the third
element of the tuple.
:raises ZeroDivisionError: The derivative approaches 0.
:raises ArithmeticError: No solution exists, or the solution isn't found before the
iteration limit is reached.
>>> import math
>>> tolerance = 1e-15
>>> root, *_ = newton_raphson(lambda x: x**2 - 5*x + 2, 0.4, max_error=tolerance)
>>> math.isclose(root, (5 - math.sqrt(17)) / 2, abs_tol=tolerance)
True
>>> root, *_ = newton_raphson(lambda x: math.log(x) - 1, 2, max_error=tolerance)
>>> math.isclose(root, math.e, abs_tol=tolerance)
True
>>> root, *_ = newton_raphson(math.sin, 1, max_error=tolerance)
>>> math.isclose(root, 0, abs_tol=tolerance)
True
>>> newton_raphson(math.cos, 0)
Traceback (most recent call last):
...
ZeroDivisionError: No converging solution found, zero derivative
>>> newton_raphson(lambda x: x**2 + 1, 2)
Traceback (most recent call last):
...
ArithmeticError: No converging solution found, iteration limit reached
"""
def f_derivative(x: float) -> float:
return calc_derivative(f, x, step)
a = x0 # Set initial guess
steps = []
for _ in range(max_iter):
if log_steps: # Log intermediate steps
steps.append(a)
error = abs(f(a))
if error < max_error:
return a, error, steps
if f_derivative(a) == 0:
raise ZeroDivisionError("No converging solution found, zero derivative")
a -= f(a) / f_derivative(a) # Calculate next estimate
raise ArithmeticError("No converging solution found, iteration limit reached")
if __name__ == "__main__": if __name__ == "__main__":
import doctest import doctest
from math import exp, tanh
doctest.testmod() doctest.testmod()
# Find value of pi def func(x: float) -> float:
print(f"The root of sin(x) = 0 is {newton_raphson('sin(x)', 2)}") return tanh(x) ** 2 - exp(3 * x)
# Find root of polynomial
print(f"The root of x**2 - 5*x + 2 = 0 is {newton_raphson('x**2 - 5*x + 2', 0.4)}") solution, err, steps = newton_raphson(
# Find value of e func, x0=10, max_iter=100, step=1e-6, log_steps=True
print(f"The root of log(x) - 1 = 0 is {newton_raphson('log(x) - 1', 2)}") )
# Find root of exponential function print(f"{solution=}, {err=}")
print(f"The root of exp(x) - 1 = 0 is {newton_raphson('exp(x) - 1', 0)}") print("\n".join(str(x) for x in steps))

View File

@ -1,64 +0,0 @@
"""
Author: P Shreyas Shetty
Implementation of Newton-Raphson method for solving equations of kind
f(x) = 0. It is an iterative method where solution is found by the expression
x[n+1] = x[n] + f(x[n])/f'(x[n])
If no solution exists, then either the solution will not be found when iteration
limit is reached or the gradient f'(x[n]) approaches zero. In both cases, exception
is raised. If iteration limit is reached, try increasing maxiter.
"""
import math as m
from collections.abc import Callable
DerivativeFunc = Callable[[float], float]
def calc_derivative(f: DerivativeFunc, a: float, h: float = 0.001) -> float:
"""
Calculates derivative at point a for function f using finite difference
method
"""
return (f(a + h) - f(a - h)) / (2 * h)
def newton_raphson(
f: DerivativeFunc,
x0: float = 0,
maxiter: int = 100,
step: float = 0.0001,
maxerror: float = 1e-6,
logsteps: bool = False,
) -> tuple[float, float, list[float]]:
a = x0 # set the initial guess
steps = [a]
error = abs(f(a))
f1 = lambda x: calc_derivative(f, x, h=step) # noqa: E731 Derivative of f(x)
for _ in range(maxiter):
if f1(a) == 0:
raise ValueError("No converging solution found")
a = a - f(a) / f1(a) # Calculate the next estimate
if logsteps:
steps.append(a)
if error < maxerror:
break
else:
raise ValueError("Iteration limit reached, no converging solution found")
if logsteps:
# If logstep is true, then log intermediate steps
return a, error, steps
return a, error, []
if __name__ == "__main__":
from matplotlib import pyplot as plt
f = lambda x: m.tanh(x) ** 2 - m.exp(3 * x) # noqa: E731
solution, error, steps = newton_raphson(
f, x0=10, maxiter=1000, step=1e-6, logsteps=True
)
plt.plot([abs(f(x)) for x in steps])
plt.xlabel("step")
plt.ylabel("error")
plt.show()
print(f"solution = {{{solution:f}}}, error = {{{error:f}}}")

View File

@ -1,83 +0,0 @@
# Implementing Newton Raphson method in Python
# Author: Saksham Gupta
#
# The Newton-Raphson method (also known as Newton's method) is a way to
# quickly find a good approximation for the root of a functreal-valued ion
# The method can also be extended to complex functions
#
# Newton's Method - https://en.wikipedia.org/wiki/Newton's_method
from sympy import diff, lambdify, symbols
from sympy.functions import * # noqa: F403
def newton_raphson(
function: str,
starting_point: complex,
variable: str = "x",
precision: float = 10**-10,
multiplicity: int = 1,
) -> complex:
"""Finds root from the 'starting_point' onwards by Newton-Raphson method
Refer to https://docs.sympy.org/latest/modules/functions/index.html
for usable mathematical functions
>>> newton_raphson("sin(x)", 2)
3.141592653589793
>>> newton_raphson("x**4 -5", 0.4 + 5j)
(-7.52316384526264e-37+1.4953487812212207j)
>>> newton_raphson('log(y) - 1', 2, variable='y')
2.7182818284590455
>>> newton_raphson('exp(x) - 1', 10, precision=0.005)
1.2186556186174883e-10
>>> newton_raphson('cos(x)', 0)
Traceback (most recent call last):
...
ZeroDivisionError: Could not find root
"""
x = symbols(variable)
func = lambdify(x, function)
diff_function = lambdify(x, diff(function, x))
prev_guess = starting_point
while True:
if diff_function(prev_guess) != 0:
next_guess = prev_guess - multiplicity * func(prev_guess) / diff_function(
prev_guess
)
else:
raise ZeroDivisionError("Could not find root") from None
# Precision is checked by comparing the difference of consecutive guesses
if abs(next_guess - prev_guess) < precision:
return next_guess
prev_guess = next_guess
# Let's Execute
if __name__ == "__main__":
# Find root of trigonometric function
# Find value of pi
print(f"The root of sin(x) = 0 is {newton_raphson('sin(x)', 2)}")
# Find root of polynomial
# Find fourth Root of 5
print(f"The root of x**4 - 5 = 0 is {newton_raphson('x**4 -5', 0.4 +5j)}")
# Find value of e
print(
"The root of log(y) - 1 = 0 is ",
f"{newton_raphson('log(y) - 1', 2, variable='y')}",
)
# Exponential Roots
print(
"The root of exp(x) - 1 = 0 is",
f"{newton_raphson('exp(x) - 1', 10, precision=0.005)}",
)
# Find root of cos(x)
print(f"The root of cos(x) = 0 is {newton_raphson('cos(x)', 0)}")