上周科学计算引论结课了,借着写上机报告的机会,我把书本上所有的求解非线性方程的方法(标量型)都用matlab实现了一下,并对一个实际工程问题进行求解。关于实现过程中遇到的问题以及注意事项,我已写在matlab笔记:久未使用之后踩的一堆坑内。
实际问题
在电机学中,凸极同步发电机的功角特性可表示为:
式中,$P_{em}$表示发电机的电磁功率;$E_{0}$表示发电机电势;$V$表示发电机端电压;$x_{q}$表示横轴同步电抗;$x_{d}$表示纵轴同步电抗;$\theta$表示功率角,$\theta \in \left ( 0,\frac{\pi }{2} \right )$。
如令$\frac{E_{0}V}{x_{d}}=P_{j}$,$V^{2}\left ( \frac{1}{x_{q}}-\frac{1}{x_{d}} \right )=P_{2e}$,则上式可以简化为:
在电力系统稳定计算中,我们往往要由上式求出功率角$\theta$。我们可以使用几何方法求解,也可以利用迭代法求解该非线性方程。
我们将上式变为:
以许实章编《电机学习题集》第367页26-1为例,将$P_{em}=1$,$P_{j}=1.878$,$P_{2e}=0.75$代入,得到方程:
问题求解
在《计算方法》第二章,我们学习了一些非线性方程的数值解法,这里我们分别使用几何法和迭代法求解上述问题并进行比较。设方程求解的预定精度为$0.001^{\circ}$即$1.7\times 10^{-3}rad$,由闭区间上连续函数的性质和初步估计,可确定方程的解位于区间$[0,\frac{\pi }{6}]$。
几何方法
由几何法的求解方法,我们定义函数:
求解$\theta$即求解$f(\theta )$在$[0,\frac{\pi }{6}]$上的零点。
在matlab中,将该函数实现如下:
1 | function [y] = func(x) |
由于matlab计算过程中默认保留$4$位小数,为了提高精度,我使用了format long
保留更多有效数字。值得注意的是,计算时统一使用弧度制,待求出解之后再使用rad2deg
函数转化为角度。
二分法
根据二分法的格式,编写二分法函数如下:
1 | function [errors, ans, time] = bisection(low, high, max, stopError) |
输入命令[errorsB, ans, time] = bisection(0, pi / 6, 50, 1.7e-5)
,求得结果如下:
1 | errorsB = |
使用二分法共迭代$15$次,求得结果为$22.909240722656250^{\circ}$。此外,迭代误差为$0.000015978966540$,符合预设精度要求。然而,此种方法计算次数较多,因此我又尝试了下面的方法。
弦截法
根据弦截法的计算方法,编写弦截法函数如下:
1 | function [errors, ans, time] = linecut(a, b, max, stopError) |
输入命令[errorsL, ans, time] = linecut(0, pi / 6, 50, 1.7e-5)
,求得结果如下:
1 | errorsL = |
与上面的二分法相比,弦截法只需计算$4$次,效率大大提高。计算所得结果为$22.909760215805360^{\circ}$,迭代误差为$0.000000005427336$,符合预设精度要求,而且比二分法的最终迭代误差更小,显然可以发现弦截法的收敛速度要快于二分法。下面我再用弦截法的改造方法Steffensen方法进行试验。
Steffensen方法
根据Steffensen方法的计算方法,编写函数如下:
1 | function [errors, ans, time] = steffensen(x, max, stopError) |
选取初始点为$0$,输入命令[errorsS1, ans, time] = steffensen(0, 50, 1.7e-5)
,求得结果如下:
1 | errorsS1 = |
发现计算次数没有比弦截法少,因此修改初值为$\frac{\pi }{6}$,再次计算,得结果:
1 | errorsS2 = |
一般而言,Steffensen方法的收敛速度要快于弦截法,但这与初始点的选取有关。对于这个问题,当设初始点为$0$时,Steffensen方法的迭代次数与弦截法持平;当设初始点为$\frac{\pi }{6}$时,迭代次数才小于弦截法。因此,想让Steffensen方法更快地收敛,需选取合适的初始点。在我看来,Steffensen方法的一大优势就是它是一种单步迭代方法,相比二步迭代方法的弦截法,Steffensen方法只需要一个初值就可以开始迭代。
Picard迭代法
上述的方法都是基于几何图形的求解方法,而下面的Picard迭代法则是基于不动点原理给出的。
首先,我们编写迭代函数:
1 | function [y] = interation(x) |
我们使用如下命令查看函数图像:
1 | >> x = 0 : pi / 36 : pi / 6; |
得到:
易验证,该函数在$[0,\frac{\pi }{6}]$上满足Picard迭代条件。
Picard迭代法
根据Picard迭代法原理,定义Picard迭代函数:
1 | function [errors, ans, time] = picard(x, max, stopError) |
输入命令[errorsP, ans, time] = picard(0, 50, 1.7e-5)
求解:
1 | errorsP = |
Picard迭代结果为$22.909757357777192^{\circ}$,且精度符合要求。下面使用Picard迭代法的改进Aitken加速迭代法进行试验。
Aitken加速迭代法
编写Aitken加速迭代法函数代码如下:
1 | function [errors, ans, time] = aitken(x, max, stopError) |
输入命令[errorsA, ans, time] = aitken(0, 50, 1.7e-5)
求解得到:
1 | errorsA = |
可以看到,Aitken加速迭代法仅需3次迭代就得到了符合条件的解,且它的迭代误差只用$0.000000000176165$,小于上述所有的方法,由此该方法的优势得以体现。
Newton迭代法
Newton迭代法也是一种求解非线性方程的高效算法,因此我也对其进行实现。
这里要用到func
的导数,经计算,编写func
的导函数为程序df
:
1 | function [y] = df(x) |
Newton迭代法
编写Newton迭代法的计算程序如下:
1 | function [errors, ans, time] = newton(x, max, stopError) |
输入命令[errorsN, ans, time] = newton(0, 50, 1.7e-5)
进行计算,得解:
1 | errorsN = |
Newton下山法
为尽可能避免因初值选取不当导致计算过程缓慢收敛或者发散(经上面计算,此问题不存在这种情况),引入下山因子$\lambda \in (0,1]$,得到改进后的Newton下山法,其计算程序如下:
1 | function [errors, ans, time] = hill(x, max, stopError) |
计算得到结果如下:
1 | errorsH = |
由于此问题初值选取得当,即初值取$0$时使用一般Newton迭代法不存在缓慢收敛或者发散的问题,因此Newton下山法在这里并没有发挥作用。可以看到,Newton迭代法仅$3$步就完成了求解的任务,非常高效。
问题的解
以上方法都得到了一致的答案,根据精度要求,我们得出此条件下功率角$\theta$为$22.910^{\circ}$,与许实章编《电机学习题集》中的例题答案$22.9^{\circ}$相吻合。
方法比较
将上述各种方法的误差记录绘制成图表,由于Newton下山法在该问题中没有发挥作用,因此仅作出Newton迭代法的迭代误差图像。
根据图片,我们可以观察到以上各个方法均收敛。其中,表现较为优越的有Aitken加速迭代法、Newton迭代法和Steffensen迭代法,均用了$3$次迭代就达到了精度要求。然而这里Steffensen法的收敛速度更依赖于初值的选取,当初值选为$0$时,它的收敛速度就类似于弦截法在该问题上的收敛速度了。因此,总的来说,对于这个问题,最高效的算法是Aitken加速迭代法和Newton迭代法。另外,二分法最简单却也最低效,迭代了$15$次才达到预设的精度要求。可见,各种算法的效率大致上与它们的复杂度和高级程度成正比关系。