牛顿法是一个被广泛使用的求解超越方程的方法,其甚至存在于卡西欧计算器中,实属高考之福音。
import scipy
def f(x):
return x ** 2 - 666
print(scipy.optimize.newton(f, 6))
# 25.80697580112788
当然,scipy中的牛顿法虽然方便,但是也会有一些问题,比如方程的定义较为繁琐,有比如一些细节不够明确,同时速度实际上并不算快。
众所周知,牛顿法的基本公式可以由 推导出为,因此,我们就可以在Python里如是写道:
x近似解 = x预测值 - f函数值 / f导数
当然,这看起来不伦不类,且没头没尾的,实际上我们应当先定义方程
from sympy import symbols, Eq
x = symbols('x')
eq = Eq(x ** 2 - 666, 888)
以上代码使用sympy定义了一个de1的方程,但是不具备普适性。
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)
于是我定义了这么个类,实际上后面还有一系列继承,由于篇幅有限,就不一一列举了,直接上重点(实际上这段代码也不是重点)。
首先牛顿法最重要的一个部分就是求导,上过学的朋友们都知道,通用求导公式为,其中,当然,擅长导的朋友们可能会说公式啊什么的,但是为了普适性(也是为了写起来方便),所以采用了有限差分法。
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我所发布的模块。