Python实现使用牛顿法无限迭代求任意方程近似解

概述

牛顿法是一个被广泛使用的求解超越方程的方法,其甚至存在于卡西欧计算器中,实属高考之福音。

Python实现使用牛顿法无限迭代求任意方程近似解_第1张图片

scipy中的牛顿法

import scipy


def f(x):
    return x ** 2 - 666


print(scipy.optimize.newton(f, 6))
# 25.80697580112788

当然,scipy中的牛顿法虽然方便,但是也会有一些问题,比如方程的定义较为繁琐,有比如一些细节不够明确,同时速度实际上并不算快。

自定义的牛顿法

众所周知,牛顿法的基本公式可以由x = x_{0} - \frac{f(x_{0})}{f'(x_{0})} 推导出为x_{n+1} = x_{n} - \frac{f(x_{n})}{f'(x_{n})},因此,我们就可以在Python里如是写道:

x近似解 = x预测值 - f函数值 / f导数

当然,这看起来不伦不类,且没头没尾的,实际上我们应当先定义方程

定义方程

from sympy import symbols, Eq

x = symbols('x')
eq = Eq(x ** 2 - 666, 888)

以上代码使用sympy定义了一个x^{2} - 666 = 888de1的方程,但是不具备普适性。

from sympy import symbols, sympify

x = symbols('x')
f = sympify('x ** 2 - 666')

显然可以解析字符串的sympify方法更加灵活,但是遗憾的是这只能定义半边,另外半边在实际使用中得默认为0,那么这时候输入"x ** 2 - 666 = 888"是无法解析的。

方程解析

但是没有大碍,Python是灵活的,我们可以自定义解析方法。

import re


class MathCompile:
    equalSign = re.compile(r'(.*?)=(.*)')

我选择使用正则表达式来进行解析,当然这只是其中最最基础的一条,为了方便,实际上需要进行多次匹配,更好的解析,给用户以便捷,例如可以将"x^2"解析为"x ** 2"之类的,篇幅有限,就不一一列举了。

class MathEquation(metaclass=Types.MathEquationType):
    def __init__(self, function, unknown):
        """
        Parameters:
            function(str): eg. "x + 1", "sin(x)", "sin(x) + 1 = 0", "cos(x) + 1 / 2 = 0.5"

            unknown(str or Symbol): eg. "x", "t", "i", symbols("x")
        """
        self.raw = function
        self.function = None
        self.unknown = None
        self.initialize(function, unknown)

    def initialize(self, function, unknown):
        self.function = self.process(function)
        self.unknown = self.symbol(unknown)

    @staticmethod
    def process(function):
        if MathCompile.equalSign.match(function) is not None:
            function = f'{MathCompile.equalSign.match(function)[1]} - ({MathCompile.equalSign.match(function)[2]})'

        return sympify(function)

    @staticmethod
    def symbol(unknown):
        if isinstance(unknown, Symbol):
            return unknown
        else:
            if isinstance(unknown, str):
                return symbols(unknown)
            else:
                raise TypeError(f'Disallowed type "{type(unknown)}", should be string or Symbol.')

    def eval(self, x):
        return self.function.subs(self.unknown, x)

于是我定义了这么个类,实际上后面还有一系列继承,由于篇幅有限,就不一一列举了,直接上重点(实际上这段代码也不是重点)。

牛顿法实现

首先牛顿法最重要的一个部分就是求导,上过学的朋友们都知道,通用求导公式为f'(x) = \frac{f(x + \Delta h) - f(x)}{\Delta h},其中\Delta h \rightarrow 0,当然,擅长导的朋友们可能会说公式啊什么的,但是为了普适性(也是为了写起来方便),所以采用了有限差分法。

from sympy import symbols, sympify

x = symbols('x')
h = symbols('h')

f = sympify('x ** 2 - 666')
ff = (f.subs(x, x + h) - f) / h
print(ff.subs(h, 1e-6))

例如在以上代码中使用了1e-6这种微小值来估算导数。

当然,实际上sympy自带diff方法来求导。

from sympy import symbols, sympify, diff

x = symbols('x')
f = sympify('x ** 2 - 6')

tolerance = 1e-6
xn = 2

while 1 + 1 == 2:
    xn1 = xn - f.subs(x, xn) / diff(f, x).subs(x, xn)

    if abs(xn1 - xn) < tolerance:
        print(xn1)
        break

    xn = xn1

 在以上代码中,tolerance代表容忍度,是所允许的近似解与真解之间存在的最大误差。

误差与容忍

使用牛顿法,误差往往在所难免,设置合理的容忍度是非常关键的。

class Tolerance(metaclass=Types.ToleranceType):
    def __init__(self, tolerance=None, level=None):
        if tolerance is not None:
            self.tolerance = tolerance

        elif level is not None:
            self.tolerance = 10 ** -level

    def more(self, level=1):
        self.tolerance /= 10 ** level

    def less(self, level=1):
        self.tolerance *= 10 ** level

    def __repr__(self):
        return f''

    def __str__(self):
        return str(self.tolerance)

    def __eq__(self, other):
        return self.tolerance == other

    def __lt__(self, other):
        return self.tolerance < other

    def __le__(self, other):
        return self.tolerance <= other

    def __gt__(self, other):
        return self.tolerance > other

    def __ge__(self, other):
        return self.tolerance >= other

    def __add__(self, other):
        if isinstance(other, int):
            self.tolerance += other
        return self.tolerance

    def __sub__(self, other):
        if isinstance(other, int):
            self.tolerance -= other
        return self.tolerance

    def __mul__(self, other):
        if isinstance(other, int):
            self.tolerance *= other
        return self.tolerance

    def __truediv__(self, other):
        if isinstance(other, int):
            self.tolerance /= other
        return self.tolerance

    def __pow__(self, other):
        if isinstance(other, int):
            self.tolerance **= other
        return self.tolerance

于是我专门设计了一个容忍度管理器,方便管理容忍度。

总要有个结尾吧

篇幅有限,而言之无尽,故点到为止,后续可自行pip我所发布的模块。

你可能感兴趣的:(Python,技术分享,数学,python,算法,学习,数学建模,开源)