mesh 模块详解#

网格数据结构基础知识#

网格(Mesh)是 FEALPy 中最常用的共性模块之一,核心功能是提供一种网格数据结构,为包括有限元、有限差分、有限体积、虚拟单元在内的多种数值计算方法提供支持。一如 FEALPy 的张量化设计理念,Mesh 模块所提供的各类网格数据都完全基于张量表示,以此向上层模块提供区域剖分数据。在深入理解网格的张量表示之前,有必要了解 FEALPy 对网格中各种几何或拓扑关系的命名约定。

观察下面的三角形和四面体,依据不同的拓扑维数,FEALPy 用节点(Node)、边(Edge)、面(Face)、单元(Cell)来指代它们包含的各类实体(Entity):

三角形与四面体的拓扑结构 三角形与四面体的拓扑结构2

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

实体示意图

在数值计算中,网格的拓扑维数一般不超过 3 维,故 FEALPy 仅需指代这四类实体。当最高拓扑维数为 2 时,edgeface 指代同一类实体。

依据最高维实体的形状,可将网格可分为不同类型,如下表所示。对于生成方法、连接关系的区别,可能有进一步细分,比如区间网格和边网格(前者要求一个节点只能有两侧的线段单元,后者无要求)。

FEALPy 中大部分非结构网格都可使用 nodecell 张量来唯一确定,node 张量存储点的几何信息,cell 张量存储单元到点的拓扑关系。

网格张量结构

对于上图中的网格:

  1. 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)
    
  2. 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,构造出表示其他实体的张量,包括 edgeface

边和面编号示意图

例如上图中边上的标号,就是 FEALPy 构造边以后为其全局编号而成的。edgeface 分别存储着组成边和面的顶点的编号,其中 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 的网格,edgeface 引用同一个 Python 对象。

此外,边和面都有定向。每一条边的方向由 edge 张量每一行中的顶点编号顺序决定,例如在上面的例子中,第 0 号边是从顶点 1 指向顶点 0,第 2 号边是从顶点 4 指向顶点 0。而在 2 维拓扑的情形下,每一个面的方向则由 face 张量每一行中的顶点编号顺序决定,按右手定则确定其指向。

以上的拓扑关系都是高维实体到顶点的关系。另一种在 FEALPy 中常用的拓扑关系是面到其两侧单元的关系,称为 face to cell,其变量简称 face2cell。它的形状为 (NF, 4),对每一个面存储以下四个编号:

  • 拓扑 1 维的情形(边):

    1. 左侧相邻单元的全局编号;

    2. 右侧相邻单元的全局编号;

    3. 边在左侧相邻单元中的局部编号;

    4. 边在右侧相邻单元中的局部编号。

    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 维的情形:

    1. 面朝向的相邻单元的全局编号;

    2. 面背向的邻单元的全局编号;

    3. 面在朝向的相邻单元中的局部编号;

    4. 面在背向的相邻单元中的局部编号。

face2cell 示意图

网格模块使用方法#

在网格模块中,可以直接导入所需网格类型:

from fealpy.mesh import QuadrangleMesh

mesh = QuadrangleMesh(node, cell)
boxmesh = QuadrangleMesh.from_box([-1, 1, -1, 1], nx=5, ny=5)

依据上文的介绍,传入 nodecell 张量,FEALPy 就能初始化网格对象。当然,网格类提供了一些类方法,以便捷构造一些特殊区域中的网格。

mesh = QuadrangleMesh(node, cell) # 通过 node 和 cell 初始化
boxmesh = QuadrangleMesh.from_box([-1, 1, -1, 1], nx=5, ny=5) # 通过类方法构造特殊区域的网格

通常,网格对象有以下作用:

  1. 提供形函数,参与构建函数空间的基函数。 用户无需关心如何使用形函数,基于网格的函数空间是依据网格对象来初始化的,它在计算基函数值时会自动调用网格形函数。

    from fealpy.functionspace import LagrangeFESpace
    space = LagrangeFESpace(mesh, p=1)
    
  2. 网格上的积分。 可以计算单个函数 u 的积分,以及一个典型的应用是计算两个函数 u, v 之间的误差:

    mesh.integral(u) # 单个函数的积分
    mesh.error(u, v) # 两个函数的误差
    
  3. 网格的可视化。 基于 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 层继承结构:

网格继承结构图

  1. MeshDS 是所有网格的基类,处理网格实体的拓扑关系。

  2. Mesh 继承自 MeshDS 后加入 node,以及处理几何数据的功能。

  3. HomogeneousMesh(均质网格)具有相同的单元类型,与之对应的是 PolygonMesh(多边形网格)。

  4. 根据形函数构造方式的不同,从均质网格进一步派生出 SimplexMesh(单纯形网格)、TensorMesh(张量网格)、StructuredMesh(结构网格)。

  5. 进一步地,派生用户使用的具体网格类。

常用网格功能接口#

此处列出一些网格常用接口,更详细的信息请查询接口文档。

  1. Mesh.entity(self, etype: int | str) -> TensorLike
    返回所选网格实体数据。

  2. Mesh.boundary_<entity>_flag(self) -> TensorLike
    返回一维布尔张量,指示实体是否处于网格边界。

  3. Mesh.face_to_cell(self) -> TensorLike
    返回 face2cell 数据。

  4. Mesh.barycenter(self, etype: int | str) -> TensorLike
    返回指定类型的实体的重心。

  5. Mesh.measure(self, etype: int | str) -> TensorLike
    返回指定类型实体的度量(如单元面积/体积、边长等)。

  6. Mesh.quadrature_formula() -> TensorLike
    返回适用于当前网格的积分公式对象,积分公式可用于获取积分点和权重。

  7. Mesh.error(u, v, q=3, power=2, celltype=False) -> TensorLike
    在网格上计算两个函数的误差。

  8. Mesh.integral(u, q=3, celltype=False) -> TensorLike
    在网格上积分函数。