跳转至

病态线性方程组

约 612 个字 73 行代码 预计阅读时间 3 分钟

对于大型线性方程组,在计算中不断有误差累积,造成解丢失两、三位有效数字的情况是时有发生的。对于这种 丢失精度 的情况,一个简单可靠的方法可以使解能迅速恢复到计算机精度,即采用解的 迭代改进 方法,也可用于求解*病态线性方程组*。

计算原理

设线性方程组
$$ \mathbf{A x} = \mathbf{b} $$
的精确解为 \(\mathbf{x}\),但实际计算中只能得到带误差的解 \(\mathbf{x} + \delta \mathbf{x}\)

将误差解代入原方程会导致右向量 \(\mathbf{b}\) 产生摄动:
$$ \mathbf{A}(\mathbf{x} + \delta \mathbf{x}) = \mathbf{b} + \delta \mathbf{b} $$

由此可建立 残差方程
$$ \mathbf{A} \delta \mathbf{x} = \mathbf{A}(\mathbf{x} + \delta \mathbf{x}) - \mathbf{b} $$

迭代改进过程

  1. 初始化阶段:使用高斯消元法计算初始近似解
  2. 迭代改进循环(默认最多5次)
    • 计算当前解的残差向量:\(\delta \mathbf{b} = Ax^{(k)} - b\)
    • 求解修正量方程:\(AΔx = \delta \mathbf{b}\)
    • 更新解向量:\(x^{(k+1)} = x^{(k)} - Δx\)
  3. 终止条件
    • 达到预设的最大迭代次数

虽然无法求得完全精确的改正量 \(\delta \mathbf{x}\),但经过一次迭代后改进量会显著减小。实践中通常循环改进1-2次即可获得理想结果。

Fortran数值实现

subroutine iterprove(a,b,x,n)
! 对线性方程组Ax=b的直接法进行迭代改进
    ! 计算过程:
    ! 1. 调用选主元消去法计算
    ! 2. 计算残差方程
    ! 3. 迭代改进
    integer, intent(in) :: n
    real*8, intent(in) :: a(n,n), b(n)
    real*8, intent(out) :: x(n)

    real*8 :: x1(n), x2(n), db(n), dx(n) ! db为残差向量
    integer :: i
    integer :: iter_max = 5 ! 默认迭代次
    character(len=32) :: fmt_str

    write(fmt_str, '(a,I0,a)') '(" ",I4,2X,', n, 'F10.6)' 

    !先调用一次计算,得到初值
    call gauss(A,b,x1,N)
    ! x1=1d0 ! 若由gauss得到的初值非常差,可以手动设置初值
    write(*,'(/,T25,"Iterative Improvement Sequence",//,"Iter",T10,"Solution Vector")')
    write(*,fmt_str) 0, x1
    do i=1,iter_max
        db=matmul(A,x1)-b
        call gauss(A,db,dx,N)
        !由此得到改进量 dx
        !x2 为改进后的新解
        x2=x1-dx
        !更新变量
        x1=x2
        write(*,fmt_str) i, x1
    end do
    x = x1

end subroutine iterprove

数值案例

\[ \mathbf{A} = \left( \begin{array}{cccccc} 3.3435 & 1.4946 & 0.3834 & -1.9537 & -2.4013 & -3.5446 \\ 1.0198 & 6.1618 & 4.9613 & 2.7491 & 3.0007 & -3.6393 \\ 2.3703 & 2.7102 & 4.2182 & 1.1730 & -0.6859 & 3.6929 \\ 1.5408 & 4.1334 & 0.5732 & 5.6869 & 3.1065 & 0.7970 \\ 3.8921 & 3.4762 & 3.9335 & -2.1556 & -6.1815 & 0.4986 \\ 2.4815 & 8.2582 & 4.6190 & -0.0022 & -0.3620 & -8.5505 \end{array} \right) \]
\[ \mathbf{b} = \left( \begin{array}{c} 5.0537 \\ 0.0228 \\ -3.4177 \\ 7.6380 \\ 0.5575 \\ 9.8252 \end{array} \right) \]

主程序:

program main
    use matrix
    implicit none

    integer, parameter :: n = 6
    real*8 :: a(n,n), b(n), x(n)

    ! 使用reshape按行填充矩阵A和B
    a = reshape([3.3435d0, 1.4946d0, 0.3834d0, -1.9537d0, -2.4013d0, -3.5446d0, &    
                1.0198d0, 6.1618d0, 4.9613d0, 2.7491d0, 3.0007d0, -3.6393d0, &    
                2.3703d0, 2.7102d0, 4.2182d0, 1.1730d0, -0.6859d0, 3.6929d0, &    
                1.5408d0, 4.1334d0, 0.5732d0, 5.6869d0, 3.1065d0, 0.7970d0, &      
                3.8921d0, 3.4762d0, 3.9335d0, -2.1556d0, -6.1815d0, 0.4986d0, &  
                2.4815d0, 8.2582d0, 4.6190d0, -0.0022d0, -0.3620d0, -8.5505d0], [n,n], order=[2,1])  

    b = [5.0537d0, 0.0228d0, -3.4177d0, 7.6380d0, 0.5575d0, 9.8252d0]

    call iterprove(a, b, x, n)

end program main

输出:

                        Iterative Improvement Sequence

Iter     Solution Vector
    0    0.461329  5.216169 -4.628165 -3.063036  1.374872  1.465094
    1    0.461329  5.216169 -4.628165 -3.063036  1.374872  1.465094
    2    0.461329  5.216169 -4.628165 -3.063036  1.374872  1.465094
    3    0.461329  5.216169 -4.628165 -3.063036  1.374872  1.465094
    4    0.461329  5.216169 -4.628165 -3.063036  1.374872  1.465094
    5    0.461329  5.216169 -4.628165 -3.063036  1.374872  1.465094

在这里是首次获取初值使用高斯求解,初值已经很好了,每次迭代,变化不是很明显。可以将初值设为1,再次迭代运行:

                        Iterative Improvement Sequence

Iter     Solution Vector
    0    1.000000  1.000000  1.000000  1.000000  1.000000  1.000000
    1    0.461329  5.216169 -4.628165 -3.063036  1.374872  1.465094
    2    0.461329  5.216169 -4.628165 -3.063036  1.374872  1.465094
    3    0.461329  5.216169 -4.628165 -3.063036  1.374872  1.465094
    4    0.461329  5.216169 -4.628165 -3.063036  1.374872  1.465094
    5    0.461329  5.216169 -4.628165 -3.063036  1.374872  1.465094

稍加迭代,解向量就可以达到较为理想的效果。

这里使用的 迭代改进 与方程组的迭代求解技术是不同的概念。仍然采用直接方法计算线性方程,只是如果方程解不是太理想,可以通过求解残差方程以改进原先的计算结果,或者检验方程解的收敛性。

注意:子程序中默认:implicit real*8(a-z)

参考文献

  1. 宋叶志,茅永兴,赵秀杰.Fortran 95/2003科学计算与工程[M].清华大学出版社,2011.