PETSc 科学计算库实战指南

系统讲解 PETSc 体系结构与核心 API,涵盖稀疏矩阵格式、线性求解器选择、非线性求解、并行组装及性能剖析,附完整 C 代码示例。

1. PETSc 体系结构

PETSc(Portable, Extensible Toolkit for Scientific Computation)是 Argonne 国家实验室开发的大规模并行科学计算库,以 C 编写,提供 C/C++/Fortran/Python 绑定。其核心分层如下:

┌─────────────────────────────────────────────┐
│  Application / SLEPc / TS / TAO            │  // 特征值/时间步/优化
├─────────────────────────────────────────────┤
│  SNES  (非线性求解器:Newton-Krylov, Picard) │
├─────────────────────────────────────────────┤
│  KSP   (Krylov 子空间 + 预处理子系统)         │
├─────────────────────────────────────────────┤
│  PC    (预处理器:ILU, AMG, Jacobi, SOR)     │
├─────────────────────────────────────────────┤
│  Mat   (稀疏矩阵:AIJ, Block AIJ, Dense, SBAIJ) │
│  Vec   (分布式向量)                           │
├─────────────────────────────────────────────┤
│  DM    (分布式网格:DMDA, DMPlex, DMStag)    │
├─────────────────────────────────────────────┤
│  MPI / CUDA / Kokkos / OpenCL               │  // 后端并行运行时
└─────────────────────────────────────────────┘
核心对象作用类比
Vec分布式稠密向量NumPy ndarray / std::vector
Mat稀疏/稠密矩阵scipy.sparse / Eigen::SparseMatrix
KSP线性系统迭代求解器SciPy cg / gmres
SNES非线性方程组求解Newton-Raphson 封装
PC预处理上下文ilu / amg wrapper
DM网格离散化管理Fenics/COMSOL 网格层

2. 稀疏矩阵格式

PETSc 默认采用压缩稀疏行格式(CSR),在内部称为 AIJ(Adjacency IJ)。对于块结构(如多物理场问题),可使用 BAIJ 提升局部性和缓存效率。

格式存储方式适用场景
MATSEQAIJ / MATMPIAIJCSR,每行独立存储通用稀疏矩阵
MATSEQBAIJ / MATMPIBAIJ块 CSR,block size = bsPDE 块系统(如多块流体)
MATSEQSBAIJ对称块 CSR对称正定矩阵
MATAIJSELL / MATCUSPARSEGPU 友好格式CUDA 后端

创建与预分配矩阵:

#include <petsc.h>

PetscErrorCode CreateLaplacian2D(MPI_Comm comm, PetscInt N, Mat *A) {
    Mat            mat;
    PetscInt       n = N * N;          // 总自由度
    PetscInt       rank, size;
    PetscInt       istart, iend;

    MPI_Comm_rank(comm, &rank);
    MPI_Comm_size(comm, &size);

    // 行范围划分
    istart = rank * (n / size);
    iend   = (rank == size - 1) ? n : (rank + 1) * (n / size);

    MatCreate(comm, &mat);
    MatSetSizes(mat, iend - istart, iend - istart, n, n);
    MatSetType(mat, MATAIJ);           // 自动选择 SEQ or MPI
    MatMPIAIJSetPreallocation(mat, 5, NULL, 2, NULL);  // 5 非零/行(本地),2 非零/行(远端)
    MatSetOption(mat, MAT_NEW_NONZERO_LOCATIONS, PETSC_TRUE);

    // 遍历本地行,填充 5 点 stencil
    for (PetscInt idx = istart; idx < iend; idx++) {
        PetscInt i = idx % N;
        PetscInt j = idx / N;
        PetscScalar v = -1.0;
        PetscScalar center = 4.0;

        // 中心点
        MatSetValue(mat, idx, idx, center, INSERT_VALUES);

        // 邻居:上下左右
        if (i > 0)    MatSetValue(mat, idx, idx - 1, v, INSERT_VALUES);
        if (i < N-1)  MatSetValue(mat, idx, idx + 1, v, INSERT_VALUES);
        if (j > 0)    MatSetValue(mat, idx, idx - N, v, INSERT_VALUES);
        if (j < N-1)  MatSetValue(mat, idx, idx + N, v, INSERT_VALUES);
    }

    MatAssemblyBegin(mat, MAT_FINAL_ASSEMBLY);
    MatAssemblyEnd(mat, MAT_FINAL_ASSEMBLY);
    *A = mat;
    return 0;
}

关键贴士

  • 预先调用 MatMPIAIJSetPreallocation 告知 PETSc 每行非零元数量,避免动态内存重分配。
  • 组装分两阶段:MatAssemblyBegin 触发非零元通信交换,MatAssemblyEnd 完成矩阵结构定型。

3. 线性求解器选择

KSP 层封装了 Krylov 子空间 solver + 预处理器系统。通过运行时选项(无需重编译)即可切换算法:

KSP ksp;
Vec x, b;

KSPCreate(PETSC_COMM_WORLD, &ksp);
KSPSetOperators(ksp, A, A);         // 系统矩阵与预处理器矩阵

// 设置求解器类型(也可通过命令行 -ksp_type cg)
KSPSetType(ksp, KSPCG);              // 对称正定问题用 CG
// KSPSetType(ksp, KSPGMRES);        // 非对称用 GMRES
// KSPSetType(ksp, KSPBCGS);         // 非对称替代:BiCGSTAB

// 设置预处理
KSPGetPC(ksp, &pc);
PCSetType(pc, PCJACOBI);             // 简单并行可用 Jacobi
// PCSetType(pc, PCILU);             // 串行/小并行有效
// PCSetType(pc, PCHYPRE);           // BoomerAMG,大规模并行推荐

KSPSetFromOptions(ksp);              // 覆盖:优先采用命令行参数
KSPSolve(ksp, b, x);

常用solver选型矩阵

矩阵性质推荐 KSP推荐 PC说明
SPD (对称正定)CGAMG (Hypre/BoomerAMG), ICCCG 在每步仅需 1 次矩阵向量积
对称不定MINRESAMG, ILU(0)MINRES 对不定矩阵稳定
非对称GMRES(30) / BiCGSTABILU, AMGGMRES 需 restart,内存增长
鞍点问题GMRES + 块预处理PCFIELDSPLITStokes/Navier-Stokes
病态近奇异GMRES + 直接法预条件PCLU (KLU/SuperLU)小子问题直接求解

4. 并行组装策略

PETSc 的 Mat 天然支持 MPI 分布式存储。每进程仅持有本地行,非本进程的矩阵元素通过调用 MatSetValue 自动被路由到目标进程。无需手动管理 MPI send/recv。

对于有限元/有限体积程序,推荐采用 DMDA( Distributed Array) 自动处理网格分区和矩阵模式:

DM da;
DMDACreate2d(PETSC_COMM_WORLD,
             DM_BOUNDARY_NONE, DM_BOUNDARY_NONE,  // 边界条件
             DMDA_STENCIL_STAR,                   // 5 点 stencil
             N, N, PETSC_DECIDE, PETSC_DECIDE,    // 全局网格
             1, 2, NULL, NULL, &da);              // 自由度 1, 重叠层数 2
DMSetFromOptions(da);
DMSetUp(da);

// 从 DM 创建矩阵,PETSc 自动处理并行分布
DMCreateMatrix(da, &A);
// 通过 DMDA 遍历本地网格点,填充矩阵

5. 非线性求解器 SNES

对于非线性方程 $F(x) = 0$,SNES 自动构造 Newton 迭代:

SNES snes;
Mat  J;      // Jacobian 矩阵
Vec  r;      // 残差向量

SNESCreate(PETSC_COMM_WORLD, &snes);
SNESSetFunction(snes, r, FormFunction, &user_ctx);   // 用户提供 F(x)
SNESSetJacobian(snes, J, J, FormJacobian, &user_ctx); // 用户提供 J = dF/dx

SNESSetType(snes, SNESNEWTONLS);      // 牛顿法 + 线搜索
SNESSetFromOptions(snes);
SNESSolve(snes, NULL, x);

// 可在运行时切换:-snes_type ksponly (不求 Jacobian,纯线性迭代)

若解析 Jacobian 推导困难,可启用有限差分近似 -snes_fd 或着色后的稀疏有限差分 -snes_mf_operator

6. 性能剖析

PETSc 内置 PetscLog 系统,无需外部工具即可定位瓶颈。

# 运行并输出详细计时日志
mpiexec -n 64 ./poisson -log_view :poisson.log

# 通过 PETSc 内置 event 标记自定义代码段
PetscLogEvent  event;
PetscLogEventRegister("MyStencilCompute", 0, &event);
PetscLogEventBegin(event, 0, 0, 0, 0);
// ... 自定义计算 ...
PetscLogEventEnd(event, 0, 0, 0, 0);

典型日志输出解读

Event              Count      Time (sec)     Flop/s
--- --- --- --- --- --- --- --- --- --- --- --- --- ---
MatMult             200       1.234e+00      8.5e+09    // 矩阵向量积
KSPSolve             10       5.678e+00      ---        // 总线性求解时间
PCSetUp               1       2.100e-01      ---        // 预处理器设置
VecScatter          220       3.400e-01      ---        // 向量通信开销

VecScatter 时间占比高,说明负载不均或 halo 通信过密,应考虑网格分区优化或提升 *overlap 层数。若 PCSetUp 过高,可考虑矩阵结构复用(仅数值变化时设置 MatSetOption(A, MAT_REUSE_MATRIX, PETSC_TRUE))。

PETSc 将 PDE 求解的繁琐并行细节封装在对象层次之下,程序员聚焦数学建模,底层自动映射到 MPI/GPU/Kokkos。从矩阵组装到 Krylov 求解,掌握 PETSc 的核心对象生命周期与选项系统,是跨平台高效科学计算的关键。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「hpc」更多文章

  1. Slurm 集群调度系统深度解析与实战
  2. Roofline 性能模型:判定性能瓶颈与优化方向
  3. ROCm HIP GPU 编程实战