5. PETSc#
PETSc, the Portable, Extensible Toolkit for Scientific Computation, pronounced PET-see (/ˈpɛt-siː/), is for the scalable (parallel) solution of scientific applications modeled by partial differential equations.
概要
Firedrake 底层用到的 PETSc 对象一览 (通过 petsc4py): 向量与矩阵、KSP 求解器、查看器、star forest PetscSF, 以及 PCPatch 预条件子.
Overview
A tour of the PETSc objects used by Firedrake (via petsc4py): vectors and matrices, KSP solvers, viewers, the star forest PetscSF, and the PCPatch preconditioner.
PETSc 是由阿贡国家实验室开发的便携可扩展科学计算工具包, 提供了并行求解大规模方程组的许多算法, 并且可调用外部包对方程组进行求解. 另外, PETSc 也提供了用于数值求解偏微分方程的组件, 包括结构化网格数据结构, 非结构化网格数据结构, 和有限元空间等.
Firedrake 可以看作是 PETSc 的高层次封装. 在 Firedrake 中, 一般无需操作 PETSc 对象, 但有些特殊情况必须直接操纵 PETSc 对象, 并且有时候直接操纵 PETSc 对象会更高效.
PETSc 是 c 语言包, 也提供了 Fortran 接口. petsc4py 是 PETSc 的 python 封装.
学习 PETSc 可以从 Texas Advanced Computing Center (TACC) 发布的 PETSc 入门课程开始
PETSc 网站的手册和入门讲义
C/Fortran API: https://petsc.org/release/manualpages/
petsc4py: https://petsc.org/release/petsc4py/
PETSc 代码库有许多示例可以作为学习材料, 如 PETSc 仓库中 petsc4py 的示例:
PETSc is a portable, extensible toolkit for scientific computation developed at Argonne National Laboratory. It provides many algorithms for solving large systems of equations in parallel, and it can also call external packages to solve them. PETSc further provides components for the numerical solution of partial differential equations, including structured mesh data structures, unstructured mesh data structures and finite element spaces, and so on.
Firedrake can be regarded as a high-level wrapper around PETSc. In Firedrake
one usually does not need to manipulate PETSc objects, but in some special cases
PETSc objects have to be manipulated directly, and sometimes doing so is more
efficient.
PETSc is a C library that also provides a Fortran interface. petsc4py is the
python wrapper of PETSc.
A good starting point for learning PETSc is the introductory PETSc course published by the Texas Advanced Computing Center (TACC)
Video: https://youtu.be/4Y8g-DcTreY
Slides: https://web.corral.tacc.utexas.edu/CompEdu/pdf/pcse/petsc_p_course.pdf
The manual and the introductory tutorials on the PETSc website
Tutorials: https://petsc.org/release/tutorials/
C/Fortran API: https://petsc.org/release/manualpages/
petsc4py: https://petsc.org/release/petsc4py/
The PETSc code base contains many examples that can serve as learning material,
such as the petsc4py demos in the PETSc repository:
Firedrake 已经把 PETSc 包好了, 日常使用时通过 solver_parameters 字典设置求解器就够了. 需要往下一层直接操作 PETSc 对象的, 通常是下面几种情形:
矩阵或向量本身: 例如把组装好的矩阵取出来做谱分析、算行列式, 或者按 PETSc 的格式存盘交给别的程序处理;
求解器为什么失败: Firedrake 只会告诉你没有收敛, 具体的收敛状态和预条件子的失败原因要从
KSP、PC对象上读;网格拓扑: 网格分区、粗化、编号以及并行的数据交换都发生在
DMPlex和PetscSF这一层, Firedrake 的Mesh只是它们的包装;Firedrake 尚未包装的功能: 例如
Viewer的各种输出格式, 或者PCPatch这类要求用户自行提供拓扑信息的预条件子.
各节彼此独立, 可以按需查阅:
Vector和Matrix: 取出底层的Mat、Vec, 以及与numpy/scipy之间的转换;Options: PETSc 的选项数据库, 是所有 PETSc 参数的统一入口;KSP: 线性求解器对象, 重点是求解失败之后如何取得原因;DMPlex、Viewer、PetscSF: 网格拓扑、对象输出与进程间的数据交换;PCPatch: 单元片预条件子的内部数据结构, 是最深的一层.
不过代码上并不独立: 后面带 %%px 的单元都跑在 Viewer 一节里启动的 ipyparallel 集群上, 想单独执行它们, 要先运行启动集群的那一格.
Firedrake already wraps PETSc, and for everyday use it is enough to configure
the solver through the solver_parameters dictionary. Going one level down and
manipulating PETSc objects directly is usually needed in these situations:
The matrix or the vector itself: for example taking the assembled matrix out for a spectral analysis or for computing a determinant, or saving it in PETSc format for another program to process;
Why a solver failed: Firedrake only tells you that it did not converge; the actual convergence status and the reason for the failure of the preconditioner have to be read from the
KSPandPCobjects;The mesh topology: mesh partitioning, coarsening, numbering and the parallel data exchange all happen at the level of
DMPlexandPetscSF, and Firedrake’sMeshis only a wrapper around them;Functionality Firedrake has not wrapped yet: for example the various output formats of
Viewer, or preconditioners such asPCPatchthat require the user to supply the topological information.
The sections are independent of each other and can be consulted as needed:
VectorandMatrix: taking out the underlyingMatandVec, and converting to and fromnumpy/scipy;Options: the PETSc options database, the single entry point for all PETSc parameters;KSP: the linear solver object, with the emphasis on how to obtain the reason after a failed solve;DMPlex,Viewer,PetscSF: mesh topology, object output and data exchange between processes;PCPatch: the internal data structures of the patch preconditioner, the deepest level.
The code, however, is not independent: all the cells below that carry %%px run
on the ipyparallel cluster started in the Viewer section, so in order to execute
them on their own the cell that starts the cluster has to be run first.
Tip
PETSc 目录中有用的工具, 如 h5dump, petsc_gen_xdmf.py, PetscBinaryIO.py 等.Useful tools live in the PETSc directory, such as h5dump, petsc_gen_xdmf.py and PetscBinaryIO.py.
在 PETSc 环境中, 运行如下命令添加这些工具所在路径到 PATH:In a PETSc environment, running the following commands adds the directories of these tools to PATH:
export PATH="$PATH:$PETSC_DIR/lib/petsc/bin"
export PATH="$PATH:$PETSC_DIR/${PETSC_ARCH-default}/bin"
在激活的 Firedrake 环境可以运行如下命令的输出, 添加工具所在路径到环境变量 PATH.In an activated Firedrake environment, running the output of the following command adds the directories of these tools to the environment variable PATH.
python -c "from firedrake import *; \
import os; \
PETSC_DIR = os.environ['PETSC_DIR']; \
PETSC_ARCH = os.environ['PETSC_ARCH']; \
print('\nRun the follwoing code to add petsc/bin to path:\n'); \
print(f' export PATH=\"\$PATH:{PETSC_DIR}/lib/petsc/bin\"'); \
print(f' export PATH=\"\$PATH:{PETSC_DIR}/{PETSC_ARCH}/bin\"'); \
print('');"
5.1. Vector 和 Matrix Vector and Matrix#
Firedrake 组装出来的矩阵和右端项都是 PETSc 对象的包装. 下面用一个 4×4 的矩形网格上的 Poisson 问题作为例子.
PETSc 官方的矩阵、向量读写示例见 matvecio.py.
The matrices and right-hand sides assembled by Firedrake are all wrappers around PETSc objects. A Poisson problem on a 4×4 rectangular mesh is used as the example below.
The official PETSc example for reading and writing matrices and vectors is matvecio.py.
from firedrake import *
from firedrake.petsc import PETSc
test_mesh = RectangleMesh(nx=4, ny=4, Lx=1, Ly=1)
x, y = SpatialCoordinate(test_mesh)
f = sin(pi*x)*sin(pi*y)
V = FunctionSpace(test_mesh, 'CG', degree=1)
u, v = TrialFunction(V), TestFunction(V)
a = inner(grad(u), grad(v))*dx
L = inner(f, v)*dx
assemble 作用在双线性形式上得到矩阵, 作用在线性形式上得到右端项. 注意两者的类型不同: 矩阵是 firedrake.matrix.Matrix, 右端项是 firedrake.cofunction.Cofunction (对偶空间中的元素, 不是 Function). 它们都只是包装, 数据存在内部的 PETSc 对象里.
assemble applied to a bilinear form gives a matrix, and applied to a linear form
gives a right-hand side. Note that the two have different types: the matrix is a
firedrake.matrix.Matrix, while the right-hand side is a
firedrake.cofunction.Cofunction (an element of the dual space, not a Function).
Both are only wrappers; the data lives in the PETSc objects inside them.
A = assemble(a)
b = assemble(L)
type(A), type(b)
(firedrake.matrix.Matrix, firedrake.cofunction.Cofunction)
5.1.1. Matrix#
type(A.petscmat)
petsc4py.PETSc.Mat
单进程运行且矩阵不大时, 可以把 PETSc 矩阵转换为 numpy 数组
In a single-process run, and when the matrix is not large, the PETSc matrix can be
converted into a numpy array
import numpy as np
from scipy.sparse import csr_matrix
m, n = A.petscmat.getSize()
indptr, indices, data = A.petscmat.getValuesCSR()
A_numpy = csr_matrix((data, indices, indptr), shape=(m, n)).toarray()
下面把同一行的两种表示放在一起对照. getRow 直接从 PETSc 矩阵读出一行, 返回 (列号数组, 数值数组); A_numpy[0, :] 则是转换后的稠密行. 对照可以看出, 稀疏表示里只出现属于矩阵稀疏模式的列, 而稀疏模式内的元素即使数值恰好为 0 也会被存下来.
The two representations of the same row are put side by side below. getRow reads
a row directly from the PETSc matrix and returns (an array of column indices, an
array of values); A_numpy[0, :] is the dense row after the conversion. The
comparison shows that only the columns belonging to the sparsity pattern of the
matrix appear in the sparse representation, and that an entry inside the sparsity
pattern is stored even when its value happens to be 0.
A.petscmat.getRow(0), A_numpy[0, :]
((array([0, 1, 2, 3, 5], dtype=int32), array([ 2. , 0. , -0.5, -1. , -0.5])),
array([ 2. , 0. , -0.5, -1. , 0. , -0.5, 0. , 0. , 0. , 0. , 0. ,
0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. ,
0. , 0. , 0. ]))
Saving a matrix to a file with MatViewFromOptions
https://petsc.org/main/manualpages/Mat/MatViewFromOptions/
Add the following line to the code
A.petscmat.viewFromOptions('-A_view')
那么在命令行可以通过选项 -A_view binary:A.bin 保存 A 到文件 A.bin.
and then the option -A_view binary:A.bin on the command line saves A to the
file A.bin.
5.1.2. 把 PETSc 矩阵转换为 csr_matrix 并计算行列式 Converting a PETSc matrix to a csr_matrix and computing the determinant#
下面是一个完整的脚本: 照常组装并求解 Poisson 问题, 再从求解器的 KSP 上取回系数矩阵 (ksp.getOperators() 返回算子矩阵和预条件子矩阵两个对象), 转成 scipy 的稀疏矩阵后做 LU 分解, 用 L、U 对角元的乘积得到行列式的绝对值. 这样取到的矩阵已经施加了 Dirichlet 边界条件, 与直接 assemble(a) 得到的矩阵不同.
这里有一个容易忽略的陷阱: splu 分解的是 Pr*A*Pc = L*U, 因此 L、U 对角元的乘积只给出行列式的绝对值, 符号还取决于 lu.perm_r 和 lu.perm_c 两个置换的奇偶性. 本例中这两个置换的奇偶性恰好相同, 符号互相抵消, 直接相乘才看起来是对的; 换一个矩阵就未必了.
后面的 mpiexec 命令演示了如何用命令行选项观察求解过程: -ksp_view 打印求解器的完整配置, -ksp_monitor 打印每步残差, -ksp_error_if_not_converged 让不收敛时直接报错, -mat_mumps_icntl_33 1 让 MUMPS 顺便算出行列式, -mat_mumps_icntl_4 4 提高 MUMPS 的输出详细程度.
The following is a complete script: it assembles and solves the Poisson problem as
usual, takes the coefficient matrix back from the KSP of the solver
(ksp.getOperators() returns two objects, the operator matrix and the
preconditioner matrix), converts it into a scipy sparse matrix and factorizes it
with LU, and obtains the absolute value of the determinant from the product of the
diagonal entries of L and U. The matrix obtained in this way already has the
Dirichlet boundary conditions applied, so it differs from the matrix obtained
directly from assemble(a).
There is a pitfall here that is easily overlooked: splu factorizes
Pr*A*Pc = L*U, so the product of the diagonal entries of L and U only gives the
absolute value of the determinant; the sign further depends on the parities of the
two permutations lu.perm_r and lu.perm_c. In this example the two permutations
happen to have the same parity, so the signs cancel each other and the plain
product happens to look right; for another matrix this need not be the case.
The mpiexec command below demonstrates how to watch the solution process with
command-line options: -ksp_view prints the full configuration of the solver,
-ksp_monitor prints the residual at every step, -ksp_error_if_not_converged
makes a failure to converge raise an error immediately, -mat_mumps_icntl_33 1
makes MUMPS compute the determinant along the way, and -mat_mumps_icntl_4 4
increases the verbosity of the MUMPS output.
# %load py/poisson_example1.py
from firedrake import *
from firedrake.petsc import PETSc
from scipy.sparse import csr_matrix
from scipy.sparse.linalg import splu
import numpy as np
N = 8
test_mesh = RectangleMesh(nx=N, ny=N, Lx=1, Ly=1)
x, y = SpatialCoordinate(test_mesh)
f = sin(pi*x)*sin(pi*y)
g = Constant(0)
V = FunctionSpace(test_mesh, 'CG', degree=1)
u, v = TrialFunction(V), TestFunction(V)
a = inner(grad(u), grad(v))*dx
L = inner(f, v)*dx # or f*v*dx
bc = DirichletBC(V, g=g, sub_domain='on_boundary')
u_h = Function(V, name='u_h')
problem = LinearVariationalProblem(a, L, u_h, bcs=bc)
solver = LinearVariationalSolver(problem, options_prefix='')
solver.solve()
ksp = solver.snes.getKSP()
A, P = ksp.getOperators()
m, n = A.getSize()
indptr, indices, data = A.getValuesCSR()
A_numpy = csr_matrix((data, indices, indptr), shape=(m, n)).toarray()
print(A_numpy)
print(data)
lu = splu(A_numpy)
diagL = lu.L.diagonal()
diagU = lu.U.diagonal()
det=diagL.prod()*diagU.prod()
print(det)
mpiexec --bind-to none -n 1 \
python3 a.py \
-ksp_view \
-ksp_error_if_not_converged \
-mat_mumps_icntl_33 1 \
-mat_mumps_icntl_4 4 \
-ksp_monitor 2>&1 | tee "my-$(date +%Y%m%d-%H:%M:%S).log"
5.1.3. Vector#
Function 和 Cofunction 把数据存在 dat 中, 通过上下文管理器借出底层的 PETSc Vec: dat.vec 可读写, dat.vec_ro 只读, dat.vec_wo 只写. 以可写方式退出 with 块时, Firedrake 会把 halo 标记为需要重新交换 (并不是当场同步, 真正的交换推迟到下次用到 halo 时), 因此对 Vec 的操作要写在块内, 不要把它保存到块外使用. 另外这三个属性都带 @mpi.collective, 并行时必须所有进程一起进入 with 块.
Function and Cofunction store their data in dat, and lend out the underlying
PETSc Vec through context managers: dat.vec is read-write, dat.vec_ro is
read-only and dat.vec_wo is write-only. On leaving a with block that was
entered for writing, Firedrake marks the halo as needing to be exchanged again (it
is not synchronized on the spot; the actual exchange is deferred until the halo is
used next), so the operations on the Vec have to be written inside the block and
it must not be kept for use outside it. In addition, all three properties carry
@mpi.collective, so in parallel all processes have to enter the with block
together.
with b.dat.vec_ro as vec:
print(type(vec))
<class 'petsc4py.PETSc.Vec'>
5.1.4. ISLocalToGlobalMapping#
局部到全局映射 (local-to-global map) 记录本进程上局部编号与全局编号的对应关系, 并行组装时靠它把本地的贡献累加到全局矩阵、向量的正确位置. 下面手工构造一个: 每个进程持有 rank + 3 个自由度, 先用 Scan 求出本进程在全局编号中的起始偏移, 再据此生成一段连续的全局编号. lgmap.view() 输出的每一行是 “进程号 局部编号 全局编号”. 笔记本里是单进程执行, 因此只有 3 个自由度, 全局编号就是 0, 1, 2.
The local-to-global map records the correspondence between the local numbering and
the global numbering on the current process; during parallel assembly it is what
accumulates the local contributions into the right places of the global matrix and
vector. One is constructed by hand below: every process owns rank + 3 degrees of
freedom, Scan first computes the starting offset of this process within the
global numbering, and a contiguous range of global numbers is generated from it.
Every line printed by lgmap.view() is “
import firedrake as fd
from firedrake.petsc import PETSc
from pyop2.datatypes import IntType, ScalarType
import numpy as np
rank = COMM_WORLD.rank
owned_sz = np.array(rank+3, dtype=IntType)
offset = np.empty_like(owned_sz)
COMM_WORLD.Scan(owned_sz, offset)
offset -= owned_sz
indices = np.arange(offset, offset + owned_sz, dtype=IntType)
lgmap = PETSc.LGMap()
lgmap.create(indices, bsize=1, comm=COMM_WORLD)
lgmap.view()
ISLocalToGlobalMapping Object: 1 MPI process
type not yet set
[0] 0 0
[0] 1 1
[0] 2 2
5.2. Options#
PETSc.Options() 用于读取命令行 (或脚本内设置) 传入的选项, 在 Firedrake 脚本中很常用. Firedrake 的 solver_parameters 最终也是写进同一个选项数据库的, 因此求解器参数既可以写在脚本里, 也可以在命令行上给出, 调参时不必反复改代码. 但命令行要生效有一个前提: Firedrake 默认会给每个求解器自动生成一个选项前缀 (形如 firedrake_1_), 此时命令行上裸写 -ksp_type cg 匹配不上, 而且不会报错, 求解器仍按默认的 preonly + lu 运行. 想在命令行上调参, 建求解器时要显式写 options_prefix='' (本章后面的两个脚本都是这么做的), 或者自定一个前缀并在命令行上带上它. 下面的例子读取两个自定义选项 -lcs 和 -datapath:
PETSc.Options() reads the options passed on the command line (or set inside the
script), and is very common in Firedrake scripts. Firedrake’s solver_parameters
also end up in the same options database, so the solver parameters can be written
either in the script or given on the command line, and there is no need to edit the
code over and over while tuning. There is a precondition for the command line to
take effect, though: by default Firedrake generates an options prefix for every
solver automatically (of the form firedrake_1_), and a bare -ksp_type cg on the
command line then does not match, and no error is reported; the solver still
runs with the default preonly + lu. In order to tune parameters on the command
line, the solver has to be created with an explicit options_prefix='' (both
scripts later in this chapter do this), or with a prefix of one’s own that is then
given on the command line. The example below reads two custom options, -lcs and
-datapath:
import os
import sys
from firedrake import *
if __name__ == '__main__':
opts = PETSc.Options()
if opts.hasName('lcs'): # -lcs
lcs_str = opts.getString('lcs')
lcs = eval(lcs_str)
else:
lcs = None
if opts.hasName('datapath'): # -datapath
datapath = opts.getString('datapath')
assert os.path.exists(datapath)
else:
datapath = None
print(lcs, datapath)
5.3. KSP#
自定义 KSP 进行线性方程组求解请参考 PETSc 的文档. Firedrake 通过 solver_parameters 配出来的求解器最终也是一个 PETSc KSP 对象, 下面记录两个常用的做法.
求解完成需要检查是否收敛
下面的例子故意构造一个无法完成 LU 分解的 2×2 矩阵, 用来演示失败之后如何取得原因: ksp.getConvergedReason() 给出 KSP 层面的收敛状态, ksp.getPC().getFailedReason() 给出预条件子层面的失败原因. 本例输出的是 DIVERGED_PCSETUP_FAILED 和 FACTOR_NUMERIC_ZEROPIVOT, 即预条件子 (这里是 LU 分解) 在数值分解时遇到了零主元.
代码中的 OptionsManager 负责把参数字典写进 PETSc 的选项数据库, with om.inserted_options() 保证这些选项只在该代码块内生效; 它在新版本中已经移到 petsctools 包, 因此这里用 try/except ImportError 做了兼容导入. 另外, PETSc 自身的错误以 PETSc.Error 抛出, 其 ierr 属性是 PETSc 的错误码, 例子中按错误码分别给出提示.
For building a custom KSP to solve a linear system, please refer to the PETSc
documentation. The solver that Firedrake configures through solver_parameters is
in the end also a PETSc KSP object; two common practices are recorded below.
Checking whether the solve converged
The example below deliberately constructs a 2×2 matrix whose LU factorization
cannot be completed, in order to demonstrate how to obtain the reason after a
failure: ksp.getConvergedReason() gives the convergence status at the KSP
level, and ksp.getPC().getFailedReason() gives the reason for the failure at the
preconditioner level. This example prints DIVERGED_PCSETUP_FAILED and
FACTOR_NUMERIC_ZEROPIVOT: the preconditioner (an LU factorization here)
encountered a zero pivot during the numerical factorization.
The OptionsManager in the code writes the parameter dictionary into the PETSc
options database, and with om.inserted_options() makes sure that these options
are only in effect inside that block; OptionsManager has been moved to the petsctools package in recent versions, which is why the import here is guarded by a try/except ImportError. Furthermore, PETSc’s own errors are raised as
PETSc.Error, whose ierr attribute is the PETSc error code; the example gives a
separate message for each error code.
from firedrake.exceptions import ConvergenceError
from firedrake.petsc import PETSc
from firedrake.solving_utils import KSPReasons
import numpy as np
try:
from petsctools.options import OptionsManager
except ImportError:
from firedrake.petsc import OptionsManager
def _make_reasons(reasons):
return dict([(getattr(reasons, r), r)
for r in dir(reasons) if not r.startswith('_')])
PCFailedReason = _make_reasons(PETSc.PC.FailedReason())
def get_ksp_reason(ksp):
r = ksp.getConvergedReason()
pc = ksp.getPC()
r_pc = pc.getFailedReason()
return KSPReasons[r], PCFailedReason[r_pc]
A = PETSc.Mat()
A.create(PETSc.COMM_WORLD)
A.setSizes([2, 2])
A.setType('aij') # sparse
# A.setPreallocationNNZ(4)
A.setUp()
A.setValue(1, 0, 1)
A.setValue(0, 1, np.inf) # to make the solver failed
A.assemble()
ksp = PETSc.KSP().create()
ksp.setOperators(A) # solve A*x=b by ksp.solve(b,x)
om = OptionsManager(
{
'ksp_type': 'preonly',
'pc_type': 'lu',
# 'ksp_view': None,
'pc_factor_mat_solver_type': 'mumps',
# 'ksp_error_if_not_converged': None,
},
options_prefix='test')
om.set_from_options(ksp)
x, b = A.createVecs()
b.setValue(0, 1)
# ksp.view()
with om.inserted_options():
try:
ksp.solve(b, x)
r = ksp.getConvergedReason()
if r < 0:
raise ConvergenceError(KSPReasons[r])
except ConvergenceError as e:
r, r_pc = get_ksp_reason(ksp)
PETSc.Sys.Print(f"Error: solver did not converged: {r}, PC: {r_pc}")
except PETSc.Error as e:
if e.ierr == 91: # https://petsc.org/release/include/petscerror.h.html
PETSc.Sys.Print(f"Error from PETSc: solver did not converged: {KSPReasons[ksp.getConvergedReason()]}")
elif e.ierr == 76:
PETSc.Sys.Print(f"Error from PETSc:")
PETSc.Sys.Print(f" ksp reason: {KSPReasons[ksp.getConvergedReason()]}")
PETSc.Sys.Print(f" error in library called by PETSc:")
PETSc.Sys.Print(" "*4 + str(e).replace("\n", "\n" + " "*4))
# We should terminate the process when an error occured in petsc
# as suggested by Matt https://lists.mcs.anl.gov/pipermail/petsc-users/2023-March/048146.html
raise
Error: solver did not converged: DIVERGED_PCSETUP_FAILED, PC: FACTOR_NUMERIC_ZEROPIVOT
查看特征值和残差变化, 并保存图片Viewing the eigenvalues and the residual history, and saving the pictures
python test.py -ksp_type gmres -pc_type jacobi -ksp_view_eigenvalues draw -ksp_monitor draw::draw_lg -draw_save .png
5.3.1. 在 Firedrake 中检查 ksp 状态 Inspecting the ksp status in Firedrake#
在 Firedrake 里, 同一个 KSP 对象可以从求解器上取到: solver.snes.getKSP(). 下面故意使用不带预条件子的共轭梯度法, 并把迭代次数限制为 ksp_max_it: 4, 让求解必定不收敛, 再在循环中捕获异常继续往下跑. 网格规模由 PETSc.Options() 读到的 -N 决定, 默认取 32*size.
输出中有两行来源不同: Linear solve did not converge due to DIVERGED_ITS iterations 4 是选项 ksp_converged_reason 让 PETSc 自己打印的; DIVERGED_MAX_IT 则是用 KSPReasons 按收敛原因码查出来的名字. 二者对应的是同一个原因码 (-3), 只是 PETSc 的 C 端与 petsc4py 端对它的命名不同, 不是两个不同的失败.
In Firedrake the same KSP object can be obtained from the solver with
solver.snes.getKSP(). The conjugate gradient method is used below without a
preconditioner on purpose, with the number of iterations limited by
ksp_max_it: 4 so that the solve is bound not to converge, and the exception is
then caught inside the loop so that the run carries on. The mesh size is determined
by the -N read through PETSc.Options(), and defaults to 32*size.
Two of the lines in the output come from different places:
Linear solve did not converge due to DIVERGED_ITS iterations 4 is printed by
PETSc itself because of the option ksp_converged_reason, whereas
DIVERGED_MAX_IT is the name looked up by the converged reason code with
KSPReasons. The two correspond to the same reason code (-3); it is only that
PETSc’s C side and the petsc4py side name it differently, not two different
failures.
from firedrake import *
from firedrake.petsc import PETSc
from firedrake.solving_utils import KSPReasons
import numpy as np
def printf(*args, **kwargs):
PETSc.Sys.Print(*args, **kwargs)
def get_ksp_reason(solver):
r = solver.snes.getKSP().getConvergedReason()
return KSPReasons[r]
rank, size = COMM_WORLD.rank, COMM_WORLD.size
opts = PETSc.Options()
N = opts.getInt('N', 32*size)
test_mesh = RectangleMesh(nx=N, ny=N, Lx=1, Ly=1)
x, y = SpatialCoordinate(test_mesh)
f = sin(pi*x)*sin(pi*y)
V = FunctionSpace(test_mesh, 'CG', degree=1)
u, v = TrialFunction(V), TestFunction(V)
a = inner(grad(u), grad(v))*dx - inner(f, v)*dx
bc = DirichletBC(V, 0, sub_domain='on_boundary')
u_h = Function(V, name='u_h')
problem = LinearVariationalProblem(lhs(a), rhs(a), u_h, bcs=bc)
solver_parameters = {'ksp_type': 'cg',
'ksp_max_it': 4,
'ksp_converged_reason': None,
# 'ksp_error_if_not_converged': None,
'pc_type': 'none'}
solver = LinearVariationalSolver(problem, solver_parameters=solver_parameters, options_prefix='')
for i in range(3):
printf(f"Loop i = {i}")
try:
solver.solve()
except ConvergenceError:
printf(f" Error from Firedrake: solver did not converged: {get_ksp_reason(solver)}")
except PETSc.Error as e:
if e.ierr == 91: # https://petsc.org/release/include/petscerror.h.html
printf(f" Error from PETSc: solver did not converged: {get_ksp_reason(solver)}")
elif e.ierr == 76:
printf(" Error from PETSc:")
printf(f" ksp reason: {get_ksp_reason(solver)}")
printf(" error in library called by PETSc:")
printf(" "*6 + str(e).replace("\n", "\n" + " "*6))
# We should terminate the process when an error occured in petsc
# as suggested by Matt https://lists.mcs.anl.gov/pipermail/petsc-users/2023-March/048146.html
raise
Loop i = 0
Linear solve did not converge due to DIVERGED_ITS iterations 4
Error from Firedrake: solver did not converged: DIVERGED_MAX_IT
Loop i = 1
Linear solve did not converge due to DIVERGED_ITS iterations 4
Error from Firedrake: solver did not converged: DIVERGED_MAX_IT
Loop i = 2
Linear solve did not converge due to DIVERGED_ITS iterations 4
Error from Firedrake: solver did not converged: DIVERGED_MAX_IT
5.4. DMPlex#
DMPlex 是管理非结构化网格的数据结构, 内部使用有向无环图表示网格的拓扑关系 [HKA+21, LMKG16].
DMPlex 中的 Plex 表示 Complex. 并行计算时, 网格会被划分成不同的块, 分配到各个进程.
Firedrake 的常规网格背后都有一个 DMPlex, 可以用 mesh.topology_dm 取到 (VertexOnlyMesh 是例外, 它背后是 DMSwarm). 下面先给出一个直接用 DMPlex 做网格粗化的脚本, 借助 Viewer 观察 DMPlex 的例子放在下一节.
DMPlex is the data structure that manages unstructured meshes; internally it
represents the topological relations of the mesh with a directed acyclic graph
[HKA+21, LMKG16].
The Plex in DMPlex stands for Complex. In a parallel computation the mesh is
divided into pieces that are distributed over the processes.
Every ordinary Firedrake mesh has a DMPlex behind it, which is obtained with
mesh.topology_dm (VertexOnlyMesh is the exception: it is backed by a
DMSwarm). A script that coarsens a mesh directly with DMPlex is given first
below; the example that inspects a DMPlex with a Viewer is left to the next
section.
5.4.1. 网格粗化 Mesh coarsening#
下面的脚本用 PETSc.DMPlex 建一张立方体网格, 分发到各个进程, 再调用 coarsen() 得到一张更粗的网格. 三处 viewFromOptions 分别在初始、分发后和粗化后输出网格, 输出目标由命令行选项给出 (格式见下一节), 因此不加选项时它们什么都不做. coarsen() 依赖 PETSc 的网格适应模块, 用哪个库由 -dm_adaptor 决定 (末尾注释中的命令用的是 parmmg), 需要 PETSc 构建时带入相应的库. 这段代码以独立脚本的形式给出 (可以用 mpiexec 并行运行), 没有在笔记本中执行.
The script below builds a cube mesh with PETSc.DMPlex, distributes it over the
processes, and then calls coarsen() to obtain a coarser mesh. The three
viewFromOptions calls output the mesh initially, after the distribution and after
the coarsening; the output target is given by command-line options (the format is
described in the next section), so without any options they do nothing. coarsen()
relies on PETSc’s mesh adaptation module, and which library is used is decided by
-dm_adaptor (the command in the trailing comment uses parmmg), which requires
PETSc to have been built with the corresponding library. This code is given as a
standalone script (it can be run in parallel with mpiexec) and is not executed in
the notebook.
import sys
import petsc4py
petsc4py.init(sys.argv)
from petsc4py import PETSc
def output_vtk(dmplex, filename):
viewer = PETSc.Viewer().createVTK(filename, 'w')
viewer.view(dmplex)
opts = PETSc.Options()
N = opts.getInt('N', 4)
dim = opts.getInt('dim', 3)
overlap = opts.getInt('overlap', 1)
faces = [N for _ in range(dim)]
plex = PETSc.DMPlex().createBoxMesh(faces, simplex=True)
plex.setName('Init DM')
plex.viewFromOptions('-init_dm_view')
sf = plex.distribute(overlap=overlap)
plex.setName('Distribue DM')
plex.viewFromOptions('-dist_dm_view')
new_plex = plex.coarsen()
new_plex.setName('Coarsen DM')
new_plex.viewFromOptions('-coarsen_dm_view')
# mpiexec -n 2 python test_coarsen.py -dim 3 -overlap 0 -dm_adaptor parmmg -coarsen_dm_view vtk:data/test.vtu
5.5. Viewer#
Viewer 是 PETSc 输出对象的统一接口. 大多数 PETSc 对象都有两个查看方法: view() 直接打印到屏幕, viewFromOptions('-xxx_view') 则把输出目标和格式交给命令行选项决定. 后者的好处是不改代码就能把同一个对象打印到屏幕、写成二进制或 HDF5 文件, 甚至画成图.
Viewer is PETSc’s unified interface for outputting objects. Most PETSc objects
have two viewing methods: view() prints directly to the screen, while
viewFromOptions('-xxx_view') leaves the output target and format to the
command-line options. The advantage of the latter is that the same object can be
printed to the screen, written as a binary or HDF5 file, or even drawn as a
picture, without changing the code.
选项字符串的语法Syntax of the option string (
PetscObjectViewFromOptions): https://petsc.org/main/manualpages/Sys/PetscObjectViewFromOptions/
Viewer 的选项取值:The values the viewer option can take:
If no value is provided ascii:stdout is used
ascii[:[filename][:[format][:append]]] defaults to stdout - format can be one of ascii_info, ascii_info_detail, or ascii_matlab,
for example ascii::ascii_info prints just the information about the object not all details
unless :append is given filename opens in write mode, overwriting what was already there
binary[:[filename][:[format][:append]]] defaults to the file binaryoutput
draw[:drawtype[:filename]] for example, draw:tikz, draw:tikz:figure.tex or draw:x
socket[:port] defaults to the standard output port
saws[:communicatorname] publishes object to the Scientific Application
Webserver (SAWs)
绘图类 viewer 的选项Options of the drawing viewers (
PetscDrawSetFromOptions): https://petsc.org/main/manualpages/Draw/PetscDrawSetFromOptions/
-nox - do not use X graphics (ignore graphics calls, but run program correctly)
-nox_warning - when X Windows support is not installed this prevents the warning message from being printed
-draw_pause <pause amount> -- -1 indicates wait for mouse input,
-2 indicates pause when window is to be destroyed
-draw_marker_type - <x,point>
-draw_save [optional filename] - (X Windows only) saves each image before it is cleared to a file
-draw_save_final_image [optional filename] - (X Windows only) saves the final image displayed in a window
-draw_save_movie - converts image files to a movie at the end of the run. See PetscDrawSetSave()
-draw_save_single_file - saves each new image in the same file, normally each new image is saved in a new file with 'filename/filename_%d.ext'
-draw_save_on_clear - saves an image on each clear, mainly for debugging
-draw_save_on_flush - saves an image on each flush, mainly for debugging
5.5.1. 用 petsc4py 读取并查看网格文件 Reading and viewing a mesh file with petsc4py#
下面不经过 Firedrake, 直接用 petsc4py 把一个 gmsh 网格读成 DMPlex, 用 dm.view() 打印它的基本信息, 再用不同的 Viewer 写出几种文件: createHDF5 写成 HDF5 便于以后重新载入; 换成 HDF5_XDMF 格式再写一份, 可以用 PETSc 自带的 petsc_gen_xdmf.py 生成 .xdmf 文件供 ParaView 可视化; createVTK 则直接写成 VTK 文件.
dm.view() 输出中的 Number of 0-cells、1-cells、2-cells 分别是顶点、边和单元的个数, Labels 一节列出网格上的标签, 其中 depth 和 celltype 是 DMPlex 自己维护的拓扑信息.
Below a gmsh mesh is read into a DMPlex directly with petsc4py, without going
through Firedrake; dm.view() prints its basic information, and several kinds of
files are then written with different Viewers: createHDF5 writes HDF5, which is
convenient for reloading later; writing another copy in the HDF5_XDMF format
allows PETSc’s own petsc_gen_xdmf.py to generate an .xdmf file for
visualization in ParaView; createVTK writes a VTK file directly.
In the output of dm.view(), Number of 0-cells, 1-cells and 2-cells are the
numbers of vertices, edges and cells respectively, and the Labels section lists
the labels on the mesh, among which depth and celltype are the topological
information that DMPlex maintains itself.
import os
import sys
import petsc4py
petsc4py.init(sys.argv)
from petsc4py import PETSc
import numpy as np
dm = PETSc.DMPlex().createFromFile('gmsh/Lshape.msh', plexname='test')
dm.view()
# make sure dir "data" exists
os.makedirs("data", exist_ok=True)
# hdf5 for load
viewer = PETSc.Viewer().createHDF5('data/Lshape.h5', mode='w')
viewer(dm)
# hdf5 for visualization:
# You can generate xdmf file from this file by
# `petsc/lib/petsc/bin/petsc_gen_xdmf.py`
# Then load the xdmf file to paraview to visualize the mesh.
viewer = PETSc.Viewer().createHDF5('data/Lshape_xdmf.h5', mode='w')
viewer.pushFormat(viewer.Format.HDF5_XDMF)
viewer(dm)
viewer.popFormat()
# vtk file
viewer = PETSc.Viewer().createVTK('data/Lshape.vtk', mode='w')
viewer(dm)
# draw on X window
# viewer = PETSc.Viewer().createDraw()
# viewer(dm)
DM Object: test 1 MPI process
type: plex
test in 2 dimensions:
Number of 0-cells per rank: 274
Number of 1-cells per rank: 755
Number of 2-cells per rank: 482
Labels:
celltype: 3 strata with value/size (0 (274), 1 (755), 3 (482))
depth: 3 strata with value/size (0 (274), 1 (755), 2 (482))
petsc4py 没有把 PetscDraw 对象暴露出来, 它的 Viewer().createDraw() 也无法指定 tikz 这类输出格式和目标文件名, 因此想用 draw 类型的 viewer 只能走选项数据库: 先用 insertString 把 -dm_view 写进 PETSc.Options(), 再调用 viewFromOptions('-dm_view') 让对象按该选项输出. 这里用 draw:tikz:data/Lshape.tex 把网格画成 TikZ 代码存成 .tex 文件, 可以直接插进 LaTeX 文档. 注意 insertString 写入的是全局的选项数据库, 之后同一进程中所有 viewFromOptions('-dm_view') 都会受它影响.
petsc4py does not expose the PetscDraw object, and its Viewer().createDraw()
cannot specify an output format such as tikz or a target file name either, so the
only way to use a viewer of draw type is through the options database:
insertString first writes -dm_view into PETSc.Options(), and
viewFromOptions('-dm_view') then makes the object output according to that
option. Here draw:tikz:data/Lshape.tex draws the mesh as TikZ code saved in a
.tex file, which can be inserted into a LaTeX document directly. Note that
insertString writes into the global options database, so every
viewFromOptions('-dm_view') in the same process afterwards is affected by it.
# since the petsc_draw is not in petsc4py, we use options to save the images
opts = PETSc.Options()
opts_old = opts.getAll()
opts.insertString('-dm_view draw:tikz:data/Lshape.tex')
dm.viewFromOptions('-dm_view')
5.5.2. 用 DMPlex 查看 Firedrake 网格 Viewing a Firedrake mesh with DMPlex#
Firedrake 网格底层的 DMPlex 用 mesh.topology_dm 取到. 网格分区的信息只有在多进程下才看得出来, 因此下面借助 ipyparallel 启动 2 个 MPI 进程, 之后带 %%px 的单元都在这两个进程上执行.
The DMPlex underlying a Firedrake mesh is obtained with mesh.topology_dm. The
information about the mesh partitioning is only visible with more than one process,
so ipyparallel is used below to start 2 MPI processes; from then on all the cells
carrying %%px are executed on these two processes.
import ipyparallel as ipp
cluster = ipp.Cluster(engines="mpi", n=2)
client = cluster.start_and_connect_sync()
Starting 2 engines with <class 'ipyparallel.cluster.launcher.MPIEngineSetLauncher'>
下面在 2 个进程上建同一张 8×8 的网格并打印它的 DMPlex. 输出中 Number of 0-cells per rank 这几行后面各有两个数字, 分别是两个进程本地 DMPlex 上的顶点、边、单元个数, 它们之和比串行时的总数大, 因为分区界面附近的实体在相邻进程上各有一份. 要注意这是本地的数量 (含 halo), 不是 “本进程持有” 的数量: 真正持有的数量求和应当正好等于串行时的总数. pyop2_core、pyop2_owned、pyop2_ghost 三个标签是 Firedrake 加上去的, 把网格点分成三类: ghost 是从别的进程借来的一层, owned 是本进程持有并且需要发送给别人的部分, core 是本进程持有且不需要发送的部分, 因此 core 上的计算可以在通信进行的同时先做.
Below the same 8×8 mesh is built on 2 processes and its DMPlex is printed. The Number of 0-cells per rank line and the ones like it are each followed by two numbers,
the numbers of vertices, edges and cells on the local DMPlex of each of the two
processes; their sums are larger than the totals of the serial run, because the
entities near the partition interface have one copy on each of the neighboring
processes. Note that these are the local counts (including the halo), not the
counts “owned by this process”: the sums of the counts actually owned should be
exactly equal to the totals of the serial run. The three labels pyop2_core,
pyop2_owned and pyop2_ghost are added by Firedrake and split the mesh points
into three classes: ghost is the layer borrowed from the other processes, owned
is the part owned by this process that has to be sent to the others, and core is
the part owned by this process that does not have to be sent, so the computation on
core can be done while the communication is going on.
%%px --block
from firedrake import *
mesh = RectangleMesh(8, 8, 1, 1)
mesh.topology_dm.view()
[stdout:0] DM Object: DM_0x30024de0_0 2 MPI processes
type: plex
DM_0x30024de0_0 in 2 dimensions:
Number of 0-cells per rank: 61 71
Number of 1-cells per rank: 142 153
Number of 2-cells per rank: 82 83
Labels:
depth: 3 strata with value/size (0 (61), 1 (142), 2 (82))
celltype: 3 strata with value/size (0 (61), 1 (142), 3 (82))
marker: 1 strata with value/size (1 (18))
Face Sets: 3 strata with value/size (2 (5), 3 (4), 4 (9))
exterior_facets: 1 strata with value/size (1 (18))
interior_facets: 1 strata with value/size (1 (199))
pyop2_core: 1 strata with value/size (1 (72))
pyop2_owned: 1 strata with value/size (1 (104))
pyop2_ghost: 1 strata with value/size (1 (109))
5.6. Star Forest 结构体 PetscSF The star forest PetscSF#
PetscSF 是 PETSc 中用于进程间数据交换的数据结构.
它存储了根节点(当前进程上)和叶子节点(任意进程上)的对应关系, 可以把数据从根节点发送到叶子节点, 也可以把数据从叶子节点收集到根节点 [ZBB+22].
下面的例子在 rank 0 上建一张单位正方形的三角形网格 (9 个顶点, 8 个单元), 在顶点上定义一个每点 1 个自由度的 Section, 再把网格分发到 2 个进程: distribute() 返回的 SF 描述网格点的对应关系, 用它调用 distributeSection 可以把 Section 一并分发, 得到叶子进程上的 Section 与偏移, 最后由 createSectionSF 得到自由度层面的 SF. 函数里反复调用 distributeSection, 是为了验证其中几种调用写法 (是否预先创建 leafSection) 给出的结果一致.
PetscSF is the data structure PETSc uses to exchange data between processes.
It stores the correspondence between the root nodes (on the current process) and
the leaf nodes (on any process), and can send data from the roots to the leaves as
well as gather data from the leaves back to the roots
[ZBB+22].
The example below builds a triangular mesh of the unit square on rank 0 (9
vertices, 8 cells), defines on the vertices a Section with 1 degree of freedom
per point, and then distributes the mesh over 2 processes: the SF returned by
distribute() describes the correspondence of the mesh points, and calling
distributeSection with it distributes the Section as well, giving the Section
and the offsets on the leaf processes; createSectionSF finally gives the SF at
the level of the degrees of freedom. distributeSection is called repeatedly
inside the function in order to check that the several calling conventions
(whether or not a leafSection is created beforehand) give the same result.
%%px --block
from firedrake import *
from firedrake.petsc import PETSc
from petsc4py import PETSc
import numpy as np
# 6--------7--------8
# | | |
# 3--------4--------5
# | | |
# 0--------1--------2
def test_SFDistributeSection():
comm = COMM_WORLD
if comm.rank == 0:
cells = np.asarray(
[[0, 1, 3],
[1, 2, 4],
[1, 4, 3],
[2, 5, 4],
[3, 4, 6],
[4, 5, 7],
[4, 7, 6],
[5, 8, 7]], dtype=np.int32)
coords = np.asarray(
[[0. , 0. ],
[0.5, 0. ],
[1. , 0. ],
[0. , 0.5],
[0.5, 0.5],
[1.0, 0.5],
[0. , 1. ],
[0.5, 1. ],
[1. , 1. ]], dtype=np.double)
else:
cells = np.zeros([0, 3], dtype=np.int32)
coords = np.zeros([0, 2], dtype=np.double)
dim = 2
plex = PETSc.DMPlex().createFromCellList(dim, cells, coords, comm=comm)
rootSection = PETSc.Section().create(comm=comm)
pStart, pEnd = plex.getHeightStratum(2)
rootSection.setChart(*plex.getChart())
for p in range(pStart, pEnd):
rootSection.setDof(p, 1)
rootSection.setUp()
rootSection.viewFromOptions('-section_view')
dplex = plex.clone()
msf = dplex.distribute()
if msf is None:
PETSc.Sys.Print("Warning: plex has not been distributed!")
return
dplex.viewFromOptions('-dm_view')
def isEqualSF(ssf0, ssf1):
nroots0, local0, remote0 = ssf0.getGraph()
nroots1, local1, remote1 = ssf1.getGraph()
return (nroots0 == nroots1) \
and np.array_equal(local0, local1) \
and np.array_equal(remote0, remote1)
remoteOffsets0, leafSection0 = msf.distributeSection(rootSection)
ssf0 = msf.createSectionSF(rootSection, remoteOffsets0, leafSection0)
remoteOffsets1, leafSection1 = msf.distributeSection(rootSection, None)
ssf1 = msf.createSectionSF(rootSection, remoteOffsets1, leafSection1)
leafSection2 = PETSc.Section()
remoteOffsets2, leafSection2 = msf.distributeSection(rootSection, leafSection2)
ssf2 = msf.createSectionSF(rootSection, remoteOffsets2, leafSection2)
leafSection3 = PETSc.Section()
remoteOffsets3, _ = msf.distributeSection(rootSection, leafSection3)
ssf3 = msf.createSectionSF(rootSection, remoteOffsets3, leafSection3)
leafSection4 = PETSc.Section().create(dplex.getComm())
remoteOffsets4, leafSection4 = msf.distributeSection(rootSection, leafSection4)
ssf4 = msf.createSectionSF(rootSection, remoteOffsets4, leafSection4)
leafSection5 = PETSc.Section().create(dplex.getComm())
remoteOffsets5, _ = msf.distributeSection(rootSection, leafSection5)
ssf5 = msf.createSectionSF(rootSection, remoteOffsets5, leafSection5)
assert isEqualSF(ssf0, ssf1)
assert isEqualSF(ssf0, ssf2)
assert isEqualSF(ssf0, ssf3)
assert isEqualSF(ssf0, ssf4)
ssf0.view()
上一个单元只是定义函数, 下面真正调用它. 输出的是分发之后的自由度 SF: rank 0 上有 9 个根节点 (原网格的 9 个顶点各 1 个自由度, 分发前整张网格都在 rank 0 上, 所以 rank 1 的根节点数是 0), 两个进程各有 6 个叶子节点; 0 <- (0,3) 表示本进程的第 0 个叶子对应 rank 0 上的第 3 个根. 两边叶子加起来是 12 而不是 9, 多出来的正是分区界面上被两个进程共享的顶点.
The previous cell only defines the function; it is actually called below. The
output is the degree-of-freedom SF after the distribution: rank 0 has 9 roots (1
degree of freedom on each of the 9 vertices of the original mesh; before the
distribution the whole mesh is on rank 0, so the number of roots on rank 1 is 0),
and each of the two processes has 6 leaves; 0 <- (0,3) means that leaf 0 of this
process corresponds to root 3 on rank 0. The leaves add up to 12 rather than 9, and
the extra ones are exactly the vertices on the partition interface that are shared
by the two processes.
%%px --block
test_SFDistributeSection()
[stdout:0] PetscSF Object: 2 MPI processes
type: basic
[0] Number of roots=9, leaves=6, remote ranks=1
[0] 0 <- (0,1)
[0] 1 <- (0,2)
[0] 2 <- (0,4)
[0] 3 <- (0,5)
[0] 4 <- (0,7)
[0] 5 <- (0,8)
[1] Number of roots=0, leaves=6, remote ranks=1
[1] 0 <- (0,0)
[1] 1 <- (0,1)
[1] 2 <- (0,3)
[1] 3 <- (0,4)
[1] 4 <- (0,6)
[1] 5 <- (0,7)
MultiSF sort=rank-order
5.7. PCPatch#
PCPatch 是在单元片 (patch) 上构造的预条件子: 把整体问题拆成许多个小的局部问题, 分别求解再拼起来, 常用作多重网格的光滑子. 用户可以使用 PETSc 提供的单元片类型 (star, vanka, pardecomp), 也可以自行构造.
它根据用户输入的关于解向量的 dm 和 sf 信息, 构造各个单元片上的自由度和全局自由度的对应关系. 下面记录的是 PCPatch 的内部变量, 只有在需要自行构造单元片, 或者要读懂 PETSc 相关源码时才需要了解.
PCPatch is a preconditioner built on patches of cells: the global problem is
split into many small local problems that are solved separately and then put back
together, and it is commonly used as a multigrid smoother. One can use the patch
types provided by PETSc (star, vanka, pardecomp) or construct one’s own.
From the dm and sf information about the solution vector supplied by the user,
it constructs the correspondence between the degrees of freedom on each patch and
the global degrees of freedom. What is recorded below are the internal variables of
PCPatch; they only need to be understood when patches have to be constructed by
hand, or when the relevant PETSc source code has to be read.
/* Topology */
PetscInt dim, codim; /* Dimension or codimension of mesh points to loop over; only one of them can be set */
PetscSection cellCounts; /* Maps patch -> # cells in patch */
IS cells; /* [patch][cell in patch]: Cell number */
PetscSection pointCounts; /* Maps patch -> # points with dofs in patch */
IS points; /* [patch][point in patch]: Point number */
PetscSection intFacetCounts;
PetscSection extFacetCounts;
PetscSection cellNumbering; /* Plex: NULL Firedrake: Numbering of cells in DM */
/* Dof layout */
PetscInt nsubspaces; /* Number of fields */
PetscSF sectionSF; /* Combined SF mapping process local to global */
PetscSection *dofSection; /* ?? For each field, patch -> # dofs in patch */
PetscInt *subspaceOffsets; /* Plex: NULL Firedrake: offset of each field in concatenated process local numbering for mixed spaces */
PetscInt **cellNodeMap; /* [field][cell][dof in cell]: global dofs in cell TODO Free this after its use in PCPatchCreateCellPatchDiscretisationInfo() */
IS dofs; /* [patch][cell in patch][dof in cell]: patch local dof */
IS offs; /* [patch][point in patch]: patch local offset (same layout as 'points', used for filling up patchSection) */
PetscSection gtolCounts; /* ?? Indices to extract from local to patch vectors */
IS gtol;
gtol 是从本进程自由度编号到单个 patch 上局部自由度的映射. 本进程自由度相对整个解是局部编号, 但相对单个 patch 则是全局编号.
dofs 是 patch 上局部的自由度映射关系 (可以看作 patch 上的 cell_node_map, 和传入的 map 一致)
gtol is the map from the degree-of-freedom numbering of this process to the local
degrees of freedom on a single patch. The degrees of freedom of this process are a
local numbering relative to the whole solution, but a global numbering relative to
a single patch.
dofs is the local degree-of-freedom map on a patch (it can be seen as the
cell_node_map of the patch, and agrees with the map that was passed in)
References
Vaclav Hapla, Matthew G. Knepley, Michael Afanasiev, Christian Boehm, Martin van Driel, Lion Krischer, and Andreas Fichtner. Fully parallel mesh I/O using PETSc DMPlex with an application to waveform modeling. SIAM Journal on Scientific Computing, 43(2):C127–C153, jan 2021. doi:10.1137/20m1332748.
Michael Lange, Lawrence Mitchell, Matthew G. Knepley, and Gerard J. Gorman. Efficient mesh management in Firedrake using PETSc DMPlex. SIAM Journal on Scientific Computing, 38(5):S143–S155, jan 2016. doi:10.1137/15m1026092.
Junchao Zhang, Jed Brown, Satish Balay, Jacob Faibussowitsch, Matthew Knepley, Oana Marin, Richard Tran Mills, Todd Munson, Barry F. Smith, and Stefano Zampini. The PetscSF scalable communication layer. IEEE Transactions on Parallel and Distributed Systems, 33(4):842–853, apr 2022. doi:10.1109/tpds.2021.3084070.