写在开头: 非线性方程,就是因变量与自变量之间的关系不是线性的关系,这类方程很多,例如平方关系、对数关系、指数关系、三角函数关系等等。求解此类方程往往很难得到精确解,经常需要求近似解问题。本文将从一道数列题开始,引出一种求解非线性方程的基础方法:简单迭代法。然后讲解科学计算器的求解方法:牛顿切线法。最后会用python实现两种方法并可视化。
先来看一道简单的数列题。
已知数列 a n 的递推公式 a n + 1 = 1 a n + 1 , 且 a 1 = 1 , 求 lim x → ∞ a n 已知数列a_{n}的递推公式a_{n+1}=\frac{1}{a_{n}+1} ,且a_{1}=1,求\lim_{x \to \infty}a_{n} 已知数列an的递推公式an+1=an+11,且a1=1,求x→∞liman
解此题只需令 lim x → ∞ a n = A , 解方程 A = 1 A + 1 \lim_{x \to \infty}a_{n} =A,解方程A=\frac{1}{A+1} limx→∞an=A,解方程A=A+11即可。但借助科学计算器还有一种投机取巧的方法:利用递推公式不断求 a n a_{n} an。当n足够大时, a n ( 如 a 100 ) a_{n}(如a_{100}) an(如a100)即可近似为 a n a_{n} an的极限。而这一过程借助科学计算器的“Ans”功能可以很方便地实现。
可以发现:我们用这种方法绕开了二次方程的求根公式解出了方程 x = 1 x + 1 x=\frac{1}{x+1} x=x+11的一个实数数值解。那么对于任意形如 x = g ( x ) x=g(x) x=g(x)的方程,是不是也可以用构造数列 a n + 1 = g ( a n ) a_{n+1}=g(a_{n}) an+1=g(an)来求解呢?这就是不动点迭代法了。
不动点迭代又称为简单迭代,用以求解方程 f ( x ) = 0 f(x)=0 f(x)=0。方法如下:
- 方程转换为 x = g ( x ) x=g(x) x=g(x)
- 给 x 0 x_{0} x0设定一个初值
- x i + 1 = g ( x i ) x_{i+1}=g(x_{i}) xi+1=g(xi)
- 若满足结束条件(i大于某个预设的值,或 ∣ x i + 1 − x i ∣ |x_{i+1}-x_{i}| ∣xi+1−xi∣小于某个预设的值) x i + 1 x_{i+1} xi+1为解,否则返回第3步
这里将方程 f ( x ) = 0 f(x)=0 f(x)=0转换为 x = g ( x ) x=g(x) x=g(x)是很容易的,比如对于 f ( x ) = x − c o s ( x ) f(x)=x-cos(x) f(x)=x−cos(x),求解 f ( x ) = 0 f(x)=0 f(x)=0即为求解 x − c o s ( x ) = 0 x-cos(x)=0 x−cos(x)=0,即 x = c o s ( x ) x=cos(x) x=cos(x),因此 g ( x ) = c o s ( x ) g(x)=cos(x) g(x)=cos(x);但是需要注意转换方法不唯一,这可能会影响计算结果。
鉴于不动点迭代法有时无法满足的收敛条件,科学计算器在求解非线性方程时会运用另一种方法–牛顿切线法,又称牛顿法。
牛顿切线法也是一种迭代法。它的操作很简单,首先通过移项把方程转化成求函数 f ( x ) f(x) f(x)的零点的问题。在给定一个迭代点 x i x_{i} xi时,作 f ( x ) f(x) f(x)在 x i x_{i} xi的切线 y − f ( x i ) = f ′ ( x i ) ( x − x i ) y-f(x_{i})=f'(x_{i})(x-x_{i}) y−f(xi)=f′(xi)(x−xi), x i + 1 x_{i+1} xi+1为切线与x轴的交点 x i − f ( x i ) f ′ ( x i ) x_{i}-\frac{f(x_{i})}{f'(x_{i})} xi−f′(xi)f(xi)。与不动点迭代法一样,在满足结束条件时终止迭代。
如何理解这种操作呢?可以把 f ( x ) f(x) f(x)的切线看作 f ( x ) f(x) f(x)在 x i x_{i} xi的一阶泰勒近似,既然函数的切线与函数是相似的,他们的零点也是相近的。那就把切线的零点当做目标值的近似。
接下来是一个例子,所求方程为 0.1 x 2 − x − 3 = 0 , x 0 = 1 0.1x^2-x-3=0,x_{0}=1 0.1x2−x−3=0,x0=1,共迭代了两次。
可以看到,迭代两次后的 x 2 x_{2} x2已经相当接近 x t a r g e t x_{target} xtarget了。
一般我们希望x尽可能接近理论值 x t a r g e t x_{target} xtarget,但是有时确切的理论值是未知的,所以也可以令 ∣ y ∣ |y| ∣y∣尽可能小。现在我们知道了在使用卡西欧计算器时,可以使用L-R的功能来估算 ∣ y ∣ |y| ∣y∣(即误差)。而输入x的功能是为了设置迭代的初始点。
除了求解方程,牛顿法还有更广泛的应用。在已知某函数的导数时,可以令导数为零,解方程得函数的极值。将一元函数推广到多元函数,用向量,梯度,Heese矩阵替换自变量,导数,二阶导数,就可以把牛顿法用于多维无约束最优化问题。
调用了numpy库与matplotlib库。使用同一个函数接口solve实现牛顿法与不动点迭代法,作为示例的方程为 a r c t a n ( x − 7 ) − 0.3 x = 0 arctan(x-7)-0.3x=0 arctan(x−7)−0.3x=0。在以迭代次数作为结束条件的基础上,这里增加了一种终止迭代的条件:两次x的差值 ∣ x i + 1 − x i ∣ |x_{i+1}-x_{i}| ∣xi+1−xi∣小于某个预设的值 d e l t a delta delta。
由于牛顿法涉及导数运算,我们用数值微分公式 f ′ ( x ) ≈ f ( x + h ) − f ( x − h ) 2 h f'(x)≈\frac{f(x+h)-f(x-h)}{2h} f′(x)≈2hf(x+h)−f(x−h)近似导数,由二阶泰勒展开公式可推得其误差为 O ( h 2 ) O(h^2) O(h2)。
主要函数与大致流程:
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
def func(x):#需要为0的函数
y=np.arctan(x-7)-0.3*x#自定义要求解的函数
return y
def derivate(x):#数值微分求导,h取1e-6
return (func(x+1e-6)-func(x-1e-6))/2e-6
def solve(x,iteration=100,delta=1e-5,method='fixed_point'):#绘制迭代点和下降曲线
doty=[func(x)]#保存迭代点的纵坐标,用来绘制折线图,先记录初始点
n=1#记录迭代次数
for i in range(iteration):
if(method=='fixed_point'):#不动点迭代
xnext=func(x)+x
colors="blue"
if(method=='newton'):#牛顿迭代
xnext=x-func(x)/derivate(x)
colors="orange"
ax[0].scatter(xnext,func(xnext),marker='o',s=10,color=colors,alpha=1,zorder=3)
doty.append(func(xnext))#添加迭代点的纵坐标
n=n+1
if(abs(x-xnext)<delta):#新增的终止条件
break
x=xnext
ax[1].plot(np.arange(0,n),doty,'o-',markersize=3)#绘制函数值下降曲线
return x;
#画布的基础设置
X=np.linspace(-15,20,500) # X轴坐标数据
Y =func(X) # Y轴坐标数据
fig, ax = plt.subplots(1, 2, figsize=(10, 4),dpi=120)#创建画布
ax[0].tick_params(labelsize=12)#坐标字号
ax[1].tick_params(labelsize=12)
ax[0].set_xlabel('$x$')#坐标名称
ax[0].set_ylabel('$y$')
ax[1].set_xlabel('迭代次数')
ax[1].set_ylabel('函数值')
ax[0].grid()#增加网格
ax[1].grid()
ax[0].plot(X,0*X,color="black",linewidth=1,zorder=1)#作图y=0
ax[0].plot(X,Y,label="$f(x)$",color="black",linewidth=1,zorder=2)#作出函数图像
#绘制两种方法的迭代点与函数下降曲线
ax[0].scatter(8,func(8),color="red",s=10,label="初始点")#绘制初始点
ans1=solve(8,100,method='fixed_point')
ans2=solve(8,100,method='newton')
ax[0].legend(fontsize=8)#设置图例
ax[1].legend(['不动点迭代法','牛顿法'],fontsize=8)
print('不动点迭代法的结果:'+str(ans1))
print('牛顿法的结果:'+str(ans2))
plt.show()