泊松方程¶
examples/poisson/ 中的三个脚本借助泊松问题展示了 TensorMesh 的主要特性:基础的二维求解、同一求解器的三维版本,以及在 L 形区域上的 h-自适应加密——该区域的凹角会产生梯度奇异性。
若需对众多源项进行批量右端项求解(即机器学习数据生成模式),请参见 机器学习数据集——该脚本位于 examples/dataset/poisson/ 下,使所有批量右端项工作流集中于一处。
强形式为:
对应的弱形式为
下面每个示例都用 LaplaceElementAssembler 实现刚度,用 NodeAssembler 实现载荷,用 Condenser 处理齐次狄利克雷边界条件。真值源场来自 PoissonMultiFrequency,它生成一个随机截断的傅里叶级数,其解析解同样可用。
基础二维泊松——poisson.py¶
仓库中最短的端到端驱动脚本。完整流程仅需 25 行:
import torch
from tensormesh import (LaplaceElementAssembler, Mesh,
Condenser, NodeAssembler)
from tensormesh.dataset import PoissonMultiFrequency
device = "cuda" if torch.cuda.is_available() else "cpu"
mesh = Mesh.gen_rectangle(chara_length=0.02).to(device=device)
assembler = LaplaceElementAssembler.from_mesh(mesh)
equation = PoissonMultiFrequency(K=16)
boundary_value = torch.zeros(mesh.boundary_mask.shape).to(device=device)
condenser = Condenser(mesh.boundary_mask, boundary_value)
f = equation.source_term(mesh.points, domain="rectangle")
K = assembler(mesh.points)
class FAssembler(NodeAssembler):
def forward(self, v, f):
return v * f
F_asm = FAssembler.from_mesh(mesh)
b = F_asm(mesh.points, point_data={"f": f})
K_, b_ = condenser(K, b)
u_ = K_.solve(b_, verbose=True)
u = condenser.recover(u_)
u_analytical = equation.solution(mesh.points)
mesh.plot({"f": f, "u_fem": u, "u_analytical": u_analytical},
save_path="poisson.png")
有几处细节值得指出:
assembler(mesh.points)返回一个由梯度双线性形式构建的SparseMatrix。纯 Python 弱形式,无需自定义核。FAssembler通过名称将源项f传入被积函数(point_data={"f": f}会成为forward(v, f)中的f关键字参数)。完整的参数分派约定参见 形式。Condenser的简写K_, b_ = condenser(K, b)会施加狄利克雷边界掩码,并将给定值并入右端项。recover(...)在求解后将边界值重新拼接回去。K_.solve(b_)继承自torch-sla的SparseTensor;它会检查矩阵并自动分派到可用的最佳后端。传入verbose=True会打印该决策,例如:[torch-sla] solve: n=2814, nnz=19300, dtype=float64, device=cuda, symmetric=True, spd=False, backend=cudss, method=ldlt这里 torch-sla 在 CUDA 上检测到一个对称(但非对称正定 SPD)系统,并通过 cuDSS 以 \(LDL^{T}\) 分解进行求解。在 CPU 上,或未安装 cuDSS 时,它会回退到其他后端;
backend=字段总会告诉你实际运行的是哪个后端。
图 38 poisson.py 的输出:单位正方形上的源项 f(左)、有限元解 u_fem(中)和解析参考解 u_analytical(右)。在该分辨率下,两幅解的图在视觉上无法区分——这正是收敛网格所应有的表现。¶
三维扩展——poisson_3d.py¶
机制完全相同,只是换用了四面体剖分的立方体:
mesh = Mesh.gen_cube(chara_length=0.05, element_type="tet")
K = LaplaceElementAssembler.from_mesh(mesh)()
同一个 LaplaceElementAssembler 之所以适用,是因为 ∇u·∇v 与维度无关——被积函数没有硬编码空间维度。输出写为 poisson_3d.vtu 以便在 ParaView 中打开;脚本还会生成一张半区域剖切的 PNG,供快速查看。
图 39 poisson_3d.py 的输出:在 \(x=0.5\) 处剖切的半区域,展示单位立方体上的有限元解。在 chara_length=0.05 下,相对于解析傅里叶解的质量加权相对 \(L^2\) 误差为 \(\mathrm{rel\_L2} = 1.059\times10^{-2}\)。¶
L 形区域上的 h-自适应加密 — poisson_h_adaptivity.py¶
这是本页中唯一一个明显超出弱形式本身的示例。L 形区域在\((0.5, 0.5)\)处有一个凹角;在该处,精确解\(u = r^{2/3}\sin(2\theta/3)\)具有梯度奇异性。由于全局误差受该角点奇异性主导,均匀 h-加密收敛较慢。
脚本实现了经典的求解 -> 估计 -> 标记 -> 重剖分循环。对于 P1 三角形,u_fem在每个单元上都是仿射函数,因此在这个 Laplace 问题中,每个单元内部都有\(\Delta u_h = 0\)。因此,残差估计器来自内部边上法向梯度的跳跃:
在直的 P1 边上,跳跃量是常数;实现中使用等价形式\(\sum_e |e|^2 [[\nabla u_h \cdot n_e]]^2\)。TensorMesh 通过Transformation提供单元梯度,并通过mesh.element_adjacency("triangle")提供内部相邻单元对。
mesh = Mesh.gen_L(chara_length=0.08, element_type="tri")
for level in range(max_levels):
u_fem = solve_laplace(mesh)
u_exact = singular_solution(mesh.points)
rel_err = global_l2_error(mesh, u_fem, u_exact)
if rel_err < target_error:
break
centroids, eta, h = element_error_and_sizes(mesh, u_fem)
h_new = doerfler_sizes(h, eta, theta=0.5) # halve where eta > theta*max(eta)
mesh = remesh_L(centroids, h_new) # gmsh remesh
有三处值得细看:
非齐次狄利克雷边界条件。 精确解在边界上非零,因此脚本构建了一个
Condenser,其dirichlet_value=g由奇异公式在每个边界节点上求值得到。静态凝聚将边界值并入右端项;参见 边界条件。质量加权的报告误差。
MassElementAssembler提供\(L^2\)范数算子:e^T M e。相对误差除以\(\sqrt{u_\text{exact}^T M u_\text{exact}}\)以获得尺度不变性。这个诊断量只使用制造的奇异解来衡量收敛性;它不驱动加密。Dörfler 标记。 标记所有误差指示子超过 \(\theta\, \max(\eta)\)(默认 \(\theta=0.5\))的单元并将其尺寸减半;对(几乎)无误差的单元则进行粗化。新的尺寸场被传入
gmsh.model.mesh.setSizeCallback,并通过 OpenCASCADE 布尔运算对 L 形几何进行重剖分。
备注
重剖分步骤通过 Python API 直接调用 gmsh,并且 setSizeCallback 需要 gmsh >= 4.8。请使用 pip install gmsh 安装。
图 40 poisson_h_adaptivity.py采用法向梯度跳跃估计器重新生成的输出。左:相对\(L^2\)误差随自由度数的变化。中:最终自适应网格,单元在凹角附近聚集。右:有限元解本身。¶
运行示例¶
cd examples/poisson
python poisson.py # 2D, writes poisson.png
python poisson_3d.py # 3D, writes poisson_3d.vtu
python poisson_h_adaptivity.py # writes poisson_h_adaptivity.png