跳转至

Jacobi 预处理共轭梯度法

约 610 个字 57 行代码 预计阅读时间 3 分钟

PCG 的基本概念

  • PCG预处理共轭梯度法(Preconditioned Conjugate Gradient)。
  • 它是在共轭梯度法(CG)基础上加入了 预处理算子,用以加快对大型稀疏对称正定线性方程组 \(\mathbf{A}\mathbf{x} = \mathbf{b}\) 的收敛速度。

预处理的作用

  • 预处理 的目标是把原方程组通过“变换”,使得新的线性系统的 谱性质更好(条件数更小),从而使迭代方法收敛更快、更稳定。
  • 实质是引入一个“预处理矩阵” \(\mathbf{M}\)(一般对称正定且易于求逆),将原系统左乘 \(\mathbf{M}^{-1}\),实际迭代时只需解 \(\mathbf{M}z = r\) 这样的小方程。

PCG 的算法流程(分量表达)

假设有 \(\mathbf{A}\mathbf{x} = \mathbf{b}\)\(\mathbf{M}\) 为预处理矩阵:

  1. 初始化 \(x^0\)\(r^0 = b - Ax^0\)
  2. 解预处理方程 \(\mathbf{M}z^0 = r^0\),令 \(p^0 = z^0\)
  3. \(k=0,1,2,\ldots\),迭代如下:
\[ \begin{aligned} & \alpha_k = \frac{(r^k, z^k)}{(p^k, Ap^k)} \\ & x^{k+1} = x^k + \alpha_k p^k \\ & r^{k+1} = r^k - \alpha_k A p^k \\ & \text{解}~ \mathbf{M}z^{k+1} = r^{k+1} \\ & \beta_k = \frac{(r^{k+1}, z^{k+1})}{(r^k, z^k)} \\ & p^{k+1} = z^{k+1} + \beta_k p^k \end{aligned} \]
  1. 直到 \(\|r^{k+1}\| < \epsilon\)

本系列文章主要讲述较为常见的Jacobi预处理器和Cholesky预处理器,本次推文主要分享Jacobi预处理器。

Jacobi 预处理 PCG 的分量形式

Jacobi预处理: 预处理矩阵 \(\mathbf{M}\)\(\mathbf{A}\) 的对角元组成的对角阵
\(\mathbf{M} = \mathrm{diag}(a_{11}, a_{22}, \ldots, a_{nn})\)

每步的PCG主要分量公式如下:

  1. 预处理向量 \(z\) 的计算(同步更新): $$ z_i = \frac{r_i}{a_{ii}} $$

  2. 步长 \(\alpha_k\) 计算:

\[ \alpha_k = \frac{\sum_{i=1}^n r_i^{(k)} z_i^{(k)}}{\sum_{i=1}^n p_i^{(k)} (A p^{(k)})_i} \]
  1. 新解向量更新: $$ x_i^{(k+1)} = x_i^{(k)} + \alpha_k p_i^{(k)} $$

  2. 残差向量更新: $$ r_i^{(k+1)} = r_i^{(k)} - \alpha_k (A p^{(k)})_i $$

  3. 新的预处理向量: $$ z_i^{(k+1)} = \frac{r_i^{(k+1)}}{a_{ii}} $$

  4. \(\beta_k\) 的计算:

\[ \beta_k = \frac{\sum_{i=1}^n r_i^{(k+1)} z_i^{(k+1)}}{\sum_{i=1}^n r_i^{(k)} z_i^{(k)}} \]
  1. 新的搜索方向: $$ p_i^{(k+1)} = z_i^{(k+1)} + \beta_k p_i^{(k)} $$
subroutine pcg_j(a, b, x, x0, n)
    ! 预处理共轭梯度法(PCG,Jacobi前置/斜前置)
    integer, intent(in) :: n
    real*8, intent(in) :: a(n,n), b(n), x0(n)
    real*8, intent(out) :: x(n)

    real*8 :: x1(n), x2(n), r0(n), r1(n)
    real*8 :: z0(n), z1(n), p0(n), p1(n)
    real*8 :: m(n)    ! 预处理向量,对角元的倒数
    integer :: i, k, iter_max = 1000
    real*8 :: eps = 1.0d-7
    real*8 :: tmp1, tmp2, tmp3, afa, beta, dr_s

    write(*,501)

    ! 构造预处理向量 M = diag(A) 的倒数
    do i = 1, n
        m(i) = 1.0d0 / a(i, i)
    end do

    x1 = x0
    r0 = b - matmul(a, x1)
    z0 = r0 * m          ! 逐元素相乘,z0 = M r0
    p0 = z0

    do k = 1, iter_max
        tmp1 = dot_product(r0, z0)
        tmp2 = dot_product(p0, matmul(a, p0))
        afa = tmp1 / tmp2

        x2 = x1 + afa * p0

        ! 收敛判据:残差欧氏范数
        dr_s = sqrt(dot_product(r0, r0))
        if (dr_s < eps) exit

        r1 = r0 - afa * matmul(a, p0)
        z1 = r1 * m        ! 预处理,z1 = M r1

        tmp3 = dot_product(r1, z1)
        beta = tmp3 / tmp1

        p1 = z1 + beta * p0

        write(*,503) k, (x2(i), i=1,n)

        ! 全部更新
        r0 = r1
        z0 = z1
        p0 = p1
        x1 = x2
    end do
    x = x2

501 format(//,18x,"Preconditioned Conjugate Gradient Iterative",//)
503 format(i5, 4(3x, f12.8))
end subroutine pcg_j

在进行有限元计算时,常常会遇到较大型的稀疏矩阵,这时需要我们在上面程序的基础上稍作修改,适应常见的稀疏矩阵存储方式,如COO、CSR、CSC等,后续推文都会讲到,感兴趣的读者,可持续关注,谢谢。