mesh 模块详解#
网格数据结构基础知识#
网格(Mesh)是 FEALPy 中最常用的共性模块之一,核心功能是提供一种网格数据结构,为包括有限元、有限差分、有限体积、虚拟单元在内的多种数值计算方法提供支持。一如 FEALPy 的张量化设计理念,Mesh 模块所提供的各类网格数据都完全基于张量表示,以此向上层模块提供区域剖分数据。在深入理解网格的张量表示之前,有必要了解 FEALPy 对网格中各种几何或拓扑关系的命名约定。
观察下面的三角形和四面体,依据不同的拓扑维数,FEALPy 用节点(Node)、边(Edge)、面(Face)、单元(Cell)来指代它们包含的各类实体(Entity):

节点是 0 维的实体,它本身不具有测度或内部空间,仅代表一个位置。两个点可在 1 维空间内连接成线段,构成边,它是 1 维实体。以此类推,在 n 维空间内组合拓扑维数为 n 的几何体,称 n 维实体。一个网格通常由大量的不同种类的实体构成。

在数值计算中,网格的拓扑维数一般不超过 3 维,故 FEALPy 仅需指代这四类实体。当最高拓扑维数为 2 时,edge 和 face 指代同一类实体。
依据最高维实体的形状,可将网格可分为不同类型,如下表所示。对于生成方法、连接关系的区别,可能有进一步细分,比如区间网格和边网格(前者要求一个节点只能有两侧的线段单元,后者无要求)。
FEALPy 中大部分非结构网格都可使用 node 和 cell 张量来唯一确定,node 张量存储点的几何信息,cell 张量存储单元到点的拓扑关系。

对于上图中的网格:
node数组形状为(NN, GD),存储每个节点的坐标位置,例如:node = bm.array([ [0.0, 0.0], #0 [0.0, 0.5], #1 [0.0, 1.0], #2 [0.5, 0.0], #3 [0.5, 0.5], #4 [0.5, 1.0], #5 [1.0, 0.0], #6 [1.0, 0.5], #7 [1.0, 1.0], #8 ], dtype=bm.float64)
cell存储着组成单元的顶点编号,形状是(NC, NVC),即 [单元数, 每个单元的顶点数]。cell = bm.array([ [3, 4, 0], #0 [6, 7, 3], #1 [4, 5, 1], #2 [7, 8, 4], #3 [1, 0, 4], #4 [4, 3, 7], #5 [2, 1, 5], #6 [5, 4, 8], #7 ], dtype=bm.int32)
注意:上述这种表达单元实体的方式只能适用于单元类型相同的网格,不适用于多边形网格。
FEALPy 网格对象在初始化时,会依据网格类型和 cell,构造出表示其他实体的张量,包括 edge 和 face。

例如上图中边上的标号,就是 FEALPy 构造边以后为其全局编号而成的。edge 和 face 分别存储着组成边和面的顶点的编号,其中 face 的形状是 (NF, NVF),edge 的形状是 (NE, 2)。例如:
edge = bm.array([
[1, 0], #0
[0, 3], #1
[4, 0], #2
[2, 1], #3
[1, 4], #4
[5, 1], #5
[5, 2], #6
[3, 4], #7
[3, 6], #8
[7, 3], #9
[4, 5], #10
[4, 7], #11
[8, 4], #12
[8, 5], #13
[6, 7], #14
[7, 8], #15
], dtype=bm.int32)
对于拓扑维数为 2 的网格,edge 和 face 引用同一个 Python 对象。
此外,边和面都有定向。每一条边的方向由 edge 张量每一行中的顶点编号顺序决定,例如在上面的例子中,第 0 号边是从顶点 1 指向顶点 0,第 2 号边是从顶点 4 指向顶点 0。而在 2 维拓扑的情形下,每一个面的方向则由 face 张量每一行中的顶点编号顺序决定,按右手定则确定其指向。
以上的拓扑关系都是高维实体到顶点的关系。另一种在 FEALPy 中常用的拓扑关系是面到其两侧单元的关系,称为 face to cell,其变量简称 face2cell。它的形状为 (NF, 4),对每一个面存储以下四个编号:
拓扑 1 维的情形(边):
左侧相邻单元的全局编号;
右侧相邻单元的全局编号;
边在左侧相邻单元中的局部编号;
边在右侧相邻单元中的局部编号。
face2cell = bm.array([ [4, 4, 2, 2], #0 [0, 0, 1, 1], #1 [0, 4, 0, 0], #2 [6, 6, 2, 2], #3 [2, 4, 1, 1], #4 [2, 6, 0, 0], #5 [6, 6, 1, 1], #6 [0, 5, 2, 2], #7 [1, 1, 1, 1], #8 [1, 5, 0, 0], #9 [2, 7, 2, 2], #10 [3, 5, 1, 1], #11 [3, 7, 0, 0], #12 [7, 7, 1, 1], #13 [1, 1, 2, 2], #14 [3, 3, 2, 2], #15 ], dtype=bm.int32)
拓扑 2 维的情形:
面朝向的相邻单元的全局编号;
面背向的邻单元的全局编号;
面在朝向的相邻单元中的局部编号;
面在背向的相邻单元中的局部编号。

网格模块使用方法#
在网格模块中,可以直接导入所需网格类型:
from fealpy.mesh import QuadrangleMesh
mesh = QuadrangleMesh(node, cell)
boxmesh = QuadrangleMesh.from_box([-1, 1, -1, 1], nx=5, ny=5)
依据上文的介绍,传入 node 和 cell 张量,FEALPy 就能初始化网格对象。当然,网格类提供了一些类方法,以便捷构造一些特殊区域中的网格。
mesh = QuadrangleMesh(node, cell) # 通过 node 和 cell 初始化
boxmesh = QuadrangleMesh.from_box([-1, 1, -1, 1], nx=5, ny=5) # 通过类方法构造特殊区域的网格
通常,网格对象有以下作用:
提供形函数,参与构建函数空间的基函数。 用户无需关心如何使用形函数,基于网格的函数空间是依据网格对象来初始化的,它在计算基函数值时会自动调用网格形函数。
from fealpy.functionspace import LagrangeFESpace space = LagrangeFESpace(mesh, p=1)
网格上的积分。 可以计算单个函数
u的积分,以及一个典型的应用是计算两个函数u,v之间的误差:mesh.integral(u) # 单个函数的积分 mesh.error(u, v) # 两个函数的误差
网格的可视化。 基于
matplotlib的可视化分为可选的两个方面,一是绘制整个网格的背景图(add_plot),二是在图中点出网格实体的重心所在(find_<entity>)。from matplotlib import pyplot as plt fig = plt.figure() axes = fig.add_subplot(111) boxmesh.add_plot(axes) # 画出网格背景 boxmesh.find_cell(axes, showindex=True) # 找到单元重心 plt.show()

拓展:继承关系与模块设计#
FEALPy 中的网格模块同样采取面向对象的设计,且目前存在 5 层继承结构:

MeshDS是所有网格的基类,处理网格实体的拓扑关系。Mesh继承自MeshDS后加入node,以及处理几何数据的功能。HomogeneousMesh(均质网格)具有相同的单元类型,与之对应的是PolygonMesh(多边形网格)。根据形函数构造方式的不同,从均质网格进一步派生出
SimplexMesh(单纯形网格)、TensorMesh(张量网格)、StructuredMesh(结构网格)。进一步地,派生用户使用的具体网格类。
常用网格功能接口#
此处列出一些网格常用接口,更详细的信息请查询接口文档。
Mesh.entity(self, etype: int | str) -> TensorLike
返回所选网格实体数据。Mesh.boundary_<entity>_flag(self) -> TensorLike
返回一维布尔张量,指示实体是否处于网格边界。Mesh.face_to_cell(self) -> TensorLike
返回face2cell数据。Mesh.barycenter(self, etype: int | str) -> TensorLike
返回指定类型的实体的重心。Mesh.measure(self, etype: int | str) -> TensorLike
返回指定类型实体的度量(如单元面积/体积、边长等)。Mesh.quadrature_formula() -> TensorLike
返回适用于当前网格的积分公式对象,积分公式可用于获取积分点和权重。Mesh.error(u, v, q=3, power=2, celltype=False) -> TensorLike
在网格上计算两个函数的误差。Mesh.integral(u, q=3, celltype=False) -> TensorLike
在网格上积分函数。