《OpenFOAM 源码深度剖析》系列-第 11 章 `lduMatrix` 与线性求解器:PCG / PBiCGStab / GAMG

3 阅读9分钟

第 11 章 lduMatrix 与线性求解器:PCG / PBiCGStab / GAMG

第 9 章把 PDE 离散成了 fvMatrix,而 fvMatrix 直接继承自 lduMatrix。本章深入这个"下-对角-上"(Lower-Diagonal-Upper)矩阵格式,以及建立在它之上的 Krylov 迭代求解器(PCG、PBiCGStab)与多重网格求解器(GAMG)。这是 OpenFOAM 数值性能的最后一公里:把稀疏线性方程组真正解出来。

11.1 lduMatrix:三数组的稀疏矩阵

11.1.1 存储结构

LduMatrix.H(src/OpenFOAM/matrices/lduMatrix/LduMatrix/,第 30–39 行):

Description
    lduMatrix is a general matrix class in which the coefficients are
    stored as three arrays, one for the upper triangle, one for the
    lower triangle and a third for the diagonal.

    Addressing arrays must be supplied for the upper and lower triangles.

    It might be better if this class were organised as a hierarchy starting
    from an empty matrix, then deriving diagonal, symmetric and asymmetric
    matrices.

它只有三组系数数组:

数组长度含义
diag_nCells对角
upper_nInternalFaces上三角(owner→neighbour)
lower_nInternalFaces下三角(neighbour→owner)

外加接口系数(用于并行/耦合边界):interfaceBouCoeffs_、interfaceIntCoeffs_、interfaces(lduInterfaceFieldPtrsList)。

注释还透露了一个历史遗憾:本可以把 lduMatrix 组织成"空矩阵 → 对角 → 对称 → 非对称"的层次(即用继承区分对称/非对称),但现实中用运行期判断处理。这解释了后面 solver 为什么有 symMatrix/asymMatrix 两张表。

11.1.2 lduAddressing:寻址与 losort

lduAddressing.H(第 30–66 行)说明寻址的组织方式。除了 upperAddr()/lowerAddr()(面的 owner/neighbour),还有一个优化——owner-start 寻址:

        owner     neighbour
        0         1
        0         20
        1         2
        1         21
        ...

不重复存 owner,而是存"每个点的邻居起始位置":

        Owner start list: 0 2 4 6 8 10 12 14 16 18

于是"已知 owner 和 neighbour 找面索引"变得高效:从 ownerStart[owner] 开始,线性扫描到 ownerStart[owner+1],匹配到 neighbour 即得面号。

另一个关键概念是 losort:由于矩阵在 upper/lower 里分别按 owner/neighbour 排序,遍历时需要两套顺序(losortAddr()),以便在两个方向上都能顺序访问。这套寻址设计是针对有限体积的"面-体"结构量身定制的。

11.1.3 Amul:矩阵乘向量

求解器迭代的核心是"计算 A·ψ"。LduMatrixATmul.C 第 122–137 行给出最直观的版本:

for (label cell=0; cell<nCells; cell++)
{
    ApsiPtr[cell] = diagPtr[cell]*psiPtr[cell];
}

const label nFaces = upper().size();

for (label face=0; face<nFaces; face++)
{
    ApsiPtr[uPtr[face]] += lowerPtr[face]*psiPtr[lPtr[face]];
    ApsiPtr[lPtr[face]] += upperPtr[face]*psiPtr[uPtr[face]];
}

即:

(Aψ)_i = diag[i]·ψ_i  +  Σ_{面 f: owner(f)=i} lower[f]·ψ_{neighbour(f)}
                      +  Σ_{面 f: neighbour(f)=i} upper[f]·ψ_{owner(f)}

还有一个cell-based 变体(第 76–121 行,hasLowerCSR() 分支):把 lower 转成 CSR(按单元分组)以改善缓存局部性。两种实现都出现在 Amul 里,按矩阵特征选择。所有指针都标了 __restrict__,提示编译器可积极向量化——这是 OpenFOAM 内层循环的标准手法。

并行/耦合边界的贡献由 initMatrixInterfaces / updateMatrixInterfaces 在两个方向上处理(第 61–72、139–149 行)。

11.2 solverPerformance:迭代的"仪表盘"

每个求解器都返回 solverPerformance,它记录 initialResidual()、finalResidual()、nIterations(),并提供 checkConvergence(tolerance, relTol, log)、checkSingularity()、great_(一个极大的初值)。

收敛判据是"相对收敛":当 finalResidual < tolerance 或 finalResidual/initialResidual < relTol 时停止。这正是 fvSolution 里 tolerance 与 relTol 的语义——relTol 0.05 表示"残差降到初始的 5% 即可"(用于外迭代内层,省时间)。

11.3 PCG:对称矩阵的共轭梯度

11.3.1 算法

PCG(Preconditioned Conjugate Gradient)用于对称正定矩阵(如拉普拉斯离散出的矩阵)。PCG.C(solvers/PCG/)的 scalarSolve(第 69–217 行)逐行对应标准 CG 公式:

数学代码(PCG.C)
r = b − AψrA = source - wA(wA = A·psi,第 97–100 行)
归一化残差gSumMag(rA)/normFactor(第 119–121 行)
w = M⁻¹r(预条件)preconPtr_->precondition(wA, rA, cmpt)(第 148 行)
ρ = r·wwArA = gSumProd(wA, rA, ...)(第 151 行)
p = w(首步)/ p = w + βpbeta = wArA/wArAold; p = w + beta*p(第 162–167 行)
Apmatrix_.Amul(wA, pA, ...)(第 172 行)
α = ρ / (p·Ap)wApA = gSumProd(wA, pA); alpha = wArA/wApA(第 174、182 行)
ψ += αp;r −= αAppsi += alpha*p; rA -= alpha*w(第 186–187 行)

代码与公式一一对应,这是理解 OpenFOAM 求解器最省力的部分。注意:

  • gSumProd / gSumMag 是并行归约(第 12 章):它们把所有进程的局部内积/范数求和,得到全局值。这保证并行下收敛判据一致。
  • 求解器用 solveScalar 类型而非 scalar——在 SPDP 混合精度构建下,存储用单精度、求解用双精度,兼顾内存与精度。

11.3.2 预条件子

PCG 通过 lduMatrix::preconditioner::New(*this, controlDict_) 创建预条件子(第 132–139 行),由 RTS 按字典里的名字选择。对称矩阵的常用预条件子是 DIC(对角不完全 Cholesky)。位置:src/OpenFOAM/matrices/lduMatrix/Preconditioners/:

预条件子适用特点
DIC对称对角不完全 Cholesky
DILU非对称对角不完全 LU
FDIC / FDILU—"final" 变体(用于分离式/特定场景)
diagonal通用仅用对角
GAMG通用用 GAMG 作预条件子
none通用无预条件

用法(fvSolution):

solvers
{
    p
    {
        solver          PCG;
        preconditioner  DIC;
        tolerance       1e-06;
        relTol          0.05;
    }
}

11.4 PBiCGStab:非对称矩阵的稳定双共轭梯度

11.4.1 为什么需要它

对流项(div)离散出的矩阵是非对称的,PCG 不适用。PBiCGStab(Preconditioned Bi-Conjugate Gradient Stabilized)用"稳定化"策略处理非对称系统。PBiCGStab.C 的 scalarSolve(第 73–260 行)核心公式与代码对应:

数学代码
r = b − Aψ;r₀ = rrA = source - yA;rA0(rA)(第 100、144 行)
ρ = r₀·rrA0rA = gSumProd(rA0, rA)(第 167 行)
β = (ρ/ρ_old)(α/ω)beta = (rA0rA/rA0rAold)*(alpha/omega)(第 191 行)
p = r + β(p − ω·Ap)pA = rA + beta*(pA - omega*AyA)(第 195–196 行)
ŷ = M⁻¹p;Aŷprecondition(yA, pA);Amul(AyA, yA)(第 201、204 行)
α = ρ / (r₀·Aŷ)alpha = rA0rA/rA0AyA(第 209 行)
s = r − α·AŷsA = rA - alpha*AyA(第 214 行)
ż = M⁻¹s;t = Ażprecondition(zA, sA);Amul(tA, zA)(第 238、241 行)
ω = (t·s)/(t·t)(下一段)

并在中途用 s 的范数判断是否已收敛(第 218–235 行:若收敛,先更新 ψ 再返回)。这套"每次迭代做两步(BiCG + 稳定化 ω)"的结构,就是 "Stabilized" 的含义。

OpenFOAM 还有一批变体求解器:PBiCG、PBiCICG、PBiCCCG(solvers/ 目录),分别对应不同的双共轭/共轭形式,供特定场景选用。

11.5 GAMG:几何聚合代数多重网格

11.5.1 定位与特征

GAMGSolver.H(第 33–49 行)给出了它的全部特征:

Description
    Geometric agglomerated algebraic multigrid solver.

    Characteristics:
      - Requires positive definite, diagonally dominant matrix.
      - Agglomeration algorithm: selectable and optionally cached.
      - Restriction operator: summation.
      - Prolongation operator: injection.
      - Smoother: Gauss-Seidel.
      - Coarse matrix creation: central coefficient: summation of fine grid
        central coefficients with the removal of intra-cluster face;
        off-diagonal coefficient: summation of off-diagonal faces.
      - Coarse matrix scaling: performed by correction scaling, using steepest
        descent optimisation.
      - Type of cycle: V-cycle with optional pre-smoothing.
      - Coarsest-level matrix solved using any lduSolver (PCG, PBiCGStab,
        smoothSolver) or direct solver on master processor

逐条解读:

  • 几何聚合(agglomerated):不像纯代数多重网格那样从矩阵结构推导粗化,而是基于网格几何把相邻单元聚成簇(cluster),逐层粗化。聚合算法可配置(GAMGAgglomeration 派生:pairGAMGAgglomeration、algebraicPairGAMGAgglomeration 等)。
  • 限制算子 = 求和(summation):细层残差在簇内求和成粗层。
  • 延拓算子 = 注入(injection):粗层解直接注入到簇内。
  • 平滑器 = Gauss-Seidel:粗/细层都用 Gauss-Seidel 平滑。
  • 粗矩阵构造:簇内面的系数被"消去"(对角吸收簇内耦合),簇间面系数求和为粗矩阵的 off-diagonal。
  • V-cycle:可选前置平滑,标准 V 形循环。
  • 最粗层:用任意 lduSolver(PCG/PBiCGStab/smoothSolver)或直接求解器(主进程上用 LUscalarMatrix 做 LU 分解直接解)求解。

GAMGSolver 的私有数据(第 84–102 行)暴露了大量旋钮:nPreSweeps_、preSweepsLevelMultiplier_、maxPreSweeps_、nPostSweeps_、postSweepsLevelMultiplier_、maxPostSweeps_、nFinestSweeps_……这些正对应 fvSolution 里 GAMG 的高级参数(nPreSweeps、nPostSweeps、nFinestSweeps、cacheAgglomeration、agglomerator、nCellsInCoarsestLevel、mergeLevels 等)。

GAMG 对压力方程(大、椭圆、病态)通常是最快的,所以几乎每个求解器的 p 方程都会用 GAMG 或 GAMG + DIC。

11.6 平滑求解器与对角求解器

求解器用途
SmoothSolver各种平滑迭代(Gauss-Seidel、symGaussSeidel、DIC、DILU…),常作预条件/平滑
DiagonalSolver只解对角(对角占优/显式场景)
PPCG/PPCR并行/投影类变体
PCICG/PBiCICG带不完全 Cholesky 的 CG 变体
FPCG"flexible" PCG

11.7 求解器如何被选中:RTS 与 fvSolution

回到第 4 章:lduMatrix::solver 有两张 RTS 表(symMatrix / asymMatrix)。当 fvSolution 里写:

solvers
{
    p   { solver PCG;   preconditioner DIC;  tolerance 1e-06; relTol 0.01; }
    U   { solver PBiCGStab; preconditioner DILU; tolerance 1e-06; relTol 0.1; }
    k   { solver PBiCGStab; preconditioner DILU; tolerance 1e-08; relTol 0.1; }
    "(U|k|epsilon)" { solver smoothSolver; smoother symGaussSeidel; ... }
}
  • 矩阵对称(拉普拉斯类)→ 查 symMatrix 表;
  • 矩阵非对称(对流类)→ 查 asymMatrix 表;
  • 具体类由 solver 名字经 RTS 构造。

solution 类还支持正则表达式的场名匹配("(U|k|epsilon)"),这靠 HashPtrTable + 模式匹配实现(第 9 章)。

11.8 归约与精度:两个易被忽视的细节

  1. 归约(reduction):gSumProd、gSumMag 等把局部内积/范数汇总为全局值。没有它们,并行下各进程会各自判断收敛,导致不一致。这是"求解器正确性依赖并行框架"的直接体现(第 12 章)。
  2. solveScalar:求解器内部用 solveScalar,它在 SPDP 构建下可以与 scalar 不同(存储单精度、求解双精度),用于在超大网格上平衡内存与精度。

11.9 小结:一条完整的求解链

把第 9–11 章连起来,一个方程从离散到求解的全流程是:

PDE
 └─(fvSchemes 选格式)─▶ fvMatrix (= lduMatrix:diag/upper/lower + 边界系数)
        └─(fvSolution 选 solver)─▶ lduMatrix::solver(RTS)
              ├─ Amul  ← 网格拓扑决定的稀疏乘
              ├─ preconditioner(RTS:DIC/DILU/GAMG…)
              ├─ reduction(gSumProd/gSumMag)→ 并行一致
              └─ PCG / PBiCGStab / GAMG … 
                     └─▶ solverPerformance(残差/迭代数)

"有限体积离散"与"线性代数求解"在 lduMatrix 处无缝衔接,这正是 OpenFOAM 架构之美:同一套矩阵格式既服务离散器,又服务求解器。


本章要点回顾

  • lduMatrix 用三个数组(diag/upper/lower)+ 接口系数表达任意网格上的离散矩阵;lduAddressing 提供 owner/neighbour、owner-start 寻址与 losort。
  • Amul 实现 A·ψ(有 cell-based/CSR 与 face-based 两种),是迭代的基石。
  • solverPerformance 记录残差与迭代数;收敛判据是相对残差(tolerance/relTol)。
  • PCG(对称,配 DIC)与 PBiCGStab(非对称,配 DILU)的迭代公式与代码一一对应;变体有 PBiCG/PBiCICG/PBiCCCG/PPCG/PPCR/FPCG。
  • GAMG:几何聚合多重网格,限制=求和、延拓=注入、平滑=Gauss-Seidel、V-cycle、最粗层用直接解;是压力方程的首选。
  • 求解器与预条件子都由 RTS 从 fvSolution 选择;并行一致性依赖 gSumProd/gSumMag 归约;求解用 solveScalar 支持混合精度。