



许多数学问题最终都会归结为求解形式为 f ( x )=0的方程,其中 f 是单变量函数。在这里,我们试图找到一个使方程成立的 x 值。使方程成立的 x 值有时称为方程的根。有许多算法可以找到这种形式的方程的解。在本例中,我们将使用牛顿法和割线法(secant method)求解形为 f ( x )=0的方程。
牛顿法和割线法是很好的标准求根算法,几乎适用于任何情况。它们都是迭代法,从根的近似值开始,迭代改进该近似值,直到它落在给定的容差范围内。
为了演示这些技术,我们将使用SymPy中的符号微分和积分函数,该函数由以下公式定义:
这个定义适用于 x 的所有实数值,并且正好有两个根,一个在 x= 0处,另一个在 x= 2处。
SciPy软件包中含有求解方程的例程以及许多具有其他功能的例程。求根例程可以在SciPy包的optimize模块中找到。像往常一样,我们将NumPy导入为np。
optimize软件包提供了用于数值求根的例程。以下说明介绍了如何使用该模块中的newton例程:
1.optimize模块未在scipy命名空间中列出,因此必须单独导入:
2.然后,我们必须在Python中定义这个函数及其导数:
3.该函数的导数在前面的例子中已经计算过了:
4.对于牛顿法和割线法,我们都使用optimize中的newton例程。牛顿法和割线法都要求将函数作为第一个参数,将初始近似值x0作为第二个参数。要使用牛顿法,我们必须使用fprime关键字参数提供 f 的导数:
5.要使用割线法,只需要提供函数作为参数,但必须提供根的前两个近似值,第二个近似值用x1关键字参数来提供:
注意
无论是牛顿法还是割线法,都不能保证收敛到真正的根。该方法的迭代完全可能只是在多个点上循环(周期性地)或剧烈波动(混沌)。
具有导数 f′ ( x )和初始近似值 x 0 的函数 f ( x ),使用牛顿法迭代求根的公式定义如下:
对于每个大于0的整数 i ,从几何角度看,如果 f ( x i ) > 0,则梯度方向为负(因此,函数是递减的);如果 f ( x i ) < 0,则梯度方向为正(因此,函数是递增的),从而得出这个公式。
割线法以牛顿法为基础,但用以下近似值代替了一阶导数:
当 x i -x i -1 足够小的时候,如果方法收敛,这就是一个很好的近似值。不需要函数 f 的导数的代价是,我们需要额外的初始猜测值来启动该方法。该方法的公式如下:
一般来说,如果给定的初始猜测值和根足够接近,那么这两种方法都会收敛到该根。如果在一次迭代中导数为零,牛顿法也会失效,在这种情况下,公式的定义并不完善。
本例中提到的方法是通用方法,但在某些情况下,其他方法可能会更快或更准确。广义上讲,求根算法可分为两类:一类是在每次迭代时使用函数梯度信息的算法(如牛顿法、割线法、Halley迭代法),另一类是需要对根的位置进行约束的算法(如二分法、Regula-Falsi法、Brent法)。到目前为止,我们讨论的算法都属于第一类,虽然通常它们的速度相当快,但可能无法收敛。
第二类算法是已知根存在于指定区间 a ≤ x ≤ b 的算法。我们可以通过检查 f ( a )和 f ( b )是否有不同的符号来确定根是否在这样的区间内,即 f ( a ) < 0 <f ( b )或 f ( b ) < 0 <f ( a )中的一个为真(当然,前提是函数是连续的,实际中的函数往往是如此连续的)。这类算法中最基本的是二分法,即反复对区间进行平分,直到找到足够好的根近似值为止。其基本前提是在 a 和 b 之间的中点处分割区间,并选择函数符号发生变化的区间。该算法不断重复,直到区间非常小为止。以下是该算法在Python中的基本实现:
这种方法会保证收敛,因为每走一步, b-a 的长度就减半。不过,与牛顿法或割线法相比,该方法可能需要更多次的迭代。在optimize模块中也可以找到二分法的对应版本。这个版本是用C语言实现的,比这里介绍的版本效率高得多,但在大多数情况下,二分法并不是最快的方法。
Brent法是对二分法的改进,在optimize模块中以brentq的形式提供。它结合使用了二分法和插值法,可以快速找到方程的根:
值得注意的是,涉及区间界定的技术(bisection、regula-falsi、Brent)不能用于复变函数求解,而不使用区间界定的方法(牛顿法、割线法、Halley法)则可以用于这样的函数求解。
最后,有些方程的形式不完全是 f ( x )=0,但仍可使用这些技巧求解。这是通过重新排列方程,使其具有所需的形式来完成的(如果有必要,重新命名函数)。这通常并不困难,只需将等式右边的项移到左边即可。例如,如果你想找到一个函数的不动点,即 g ( x )= x ,那么我们就可以将此方法应用于相关函数,即 f ( x )= g ( x )- x 。