Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consolidate Newton-Raphson implementations #10859

Merged
7 changes: 4 additions & 3 deletions DIRECTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@
* [Deque Doubly](data_structures/linked_list/deque_doubly.py)
* [Doubly Linked List](data_structures/linked_list/doubly_linked_list.py)
* [Doubly Linked List Two](data_structures/linked_list/doubly_linked_list_two.py)
* [Floyds Cycle Detection](data_structures/linked_list/floyds_cycle_detection.py)
* [From Sequence](data_structures/linked_list/from_sequence.py)
* [Has Loop](data_structures/linked_list/has_loop.py)
* [Is Palindrome](data_structures/linked_list/is_palindrome.py)
Expand Down Expand Up @@ -394,6 +395,7 @@
* [Interest](financial/interest.py)
* [Present Value](financial/present_value.py)
* [Price Plus Tax](financial/price_plus_tax.py)
* [Simple Moving Average](financial/simple_moving_average.py)

## Fractals
* [Julia Sets](fractals/julia_sets.py)
Expand Down Expand Up @@ -640,10 +642,7 @@
* [Intersection](maths/numerical_analysis/intersection.py)
* [Nevilles Method](maths/numerical_analysis/nevilles_method.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 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)
* [Runge Kutta](maths/numerical_analysis/runge_kutta.py)
* [Runge Kutta Fehlberg 45](maths/numerical_analysis/runge_kutta_fehlberg_45.py)
Expand Down Expand Up @@ -706,6 +705,7 @@
* [Polygonal Numbers](maths/special_numbers/polygonal_numbers.py)
* [Pronic Number](maths/special_numbers/pronic_number.py)
* [Proth Number](maths/special_numbers/proth_number.py)
* [Triangular Numbers](maths/special_numbers/triangular_numbers.py)
* [Ugly Numbers](maths/special_numbers/ugly_numbers.py)
* [Weird Number](maths/special_numbers/weird_number.py)
* [Sum Of Arithmetic Series](maths/sum_of_arithmetic_series.py)
Expand Down Expand Up @@ -826,6 +826,7 @@
* [Shear Stress](physics/shear_stress.py)
* [Speed Of Sound](physics/speed_of_sound.py)
* [Speeds Of Gas Molecules](physics/speeds_of_gas_molecules.py)
* [Terminal Velocity](physics/terminal_velocity.py)

## Project Euler
* Problem 001
Expand Down
54 changes: 0 additions & 54 deletions maths/numerical_analysis/newton_method.py

This file was deleted.

142 changes: 105 additions & 37 deletions maths/numerical_analysis/newton_raphson.py
Original file line number Diff line number Diff line change
@@ -1,45 +1,113 @@
# Implementing Newton Raphson method in Python
# Author: Syed Haseeb Shah (github.com/QuantumNovice)
# The Newton-Raphson method (also known as Newton's method) is a way to
# quickly find a good approximation for the root of a real-valued function
from __future__ import annotations

from decimal import Decimal

from sympy import diff, lambdify, symbols


def newton_raphson(func: str, a: float | Decimal, precision: float = 1e-10) -> 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
"""
The Newton-Raphson method (aka the Newton method) is a root-finding algorithm that
approximates a root of a given real-valued function f(x). It is an iterative method
given by the formula

x_{n + 1} = x_n + f(x_n) / f'(x_n)

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 calc_derivative(f: RealFunc, x: float, delta_x: float = 1e-3) -> float:
"""
Approximate the derivative of a function f(x) at a point x using the finite
difference method

>>> import math
>>> tolerance = 1e-5
>>> derivative = calc_derivative(lambda x: x**2, 2)
>>> math.isclose(derivative, 4, abs_tol=tolerance)
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]]:
"""
x = symbols("x")
f = lambdify(x, func, "math")
f_derivative = lambdify(x, diff(func), "math")
x_curr = a
while True:
x_curr = Decimal(x_curr) - Decimal(f(x_curr)) / Decimal(f_derivative(x_curr))
if abs(f(x_curr)) < precision:
return float(x_curr)
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__":
import doctest
from math import exp, tanh

doctest.testmod()

# Find value of pi
print(f"The root of sin(x) = 0 is {newton_raphson('sin(x)', 2)}")
# 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)}")
# Find value of e
print(f"The root of log(x) - 1 = 0 is {newton_raphson('log(x) - 1', 2)}")
# Find root of exponential function
print(f"The root of exp(x) - 1 = 0 is {newton_raphson('exp(x) - 1', 0)}")
def func(x: float) -> float:
return tanh(x) ** 2 - exp(3 * x)

solution, err, steps = newton_raphson(
func, x0=10, max_iter=100, step=1e-6, log_steps=True
)
print(f"{solution=}, {err=}")
print("\n".join(str(x) for x in steps))
64 changes: 0 additions & 64 deletions maths/numerical_analysis/newton_raphson_2.py

This file was deleted.

83 changes: 0 additions & 83 deletions maths/numerical_analysis/newton_raphson_new.py

This file was deleted.