diff --git a/DIRECTORY.md b/DIRECTORY.md index dfd1a2c0c..f0b1f7c13 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -642,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) diff --git a/maths/numerical_analysis/newton_method.py b/maths/numerical_analysis/newton_method.py deleted file mode 100644 index 5127bfcaf..000000000 --- a/maths/numerical_analysis/newton_method.py +++ /dev/null @@ -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)) diff --git a/maths/numerical_analysis/newton_raphson.py b/maths/numerical_analysis/newton_raphson.py index 8491ca800..feee38f90 100644 --- a/maths/numerical_analysis/newton_raphson.py +++ b/maths/numerical_analysis/newton_raphson.py @@ -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 +""" +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 -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: - """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 +def calc_derivative(f: RealFunc, x: float, delta_x: float = 1e-3) -> 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) + 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]]: + """ + 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)) diff --git a/maths/numerical_analysis/newton_raphson_2.py b/maths/numerical_analysis/newton_raphson_2.py deleted file mode 100644 index f6b227b5c..000000000 --- a/maths/numerical_analysis/newton_raphson_2.py +++ /dev/null @@ -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}}}") diff --git a/maths/numerical_analysis/newton_raphson_new.py b/maths/numerical_analysis/newton_raphson_new.py deleted file mode 100644 index f61841e2e..000000000 --- a/maths/numerical_analysis/newton_raphson_new.py +++ /dev/null @@ -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)}")