上次讲到了Time对象,并介绍了我们的程序如何跟system/controlDict
文件关联。这次我们介绍程序如何读取网格、网格文件在OpenFOAM里面如何表示、以及如何获取网格信息。
程序如何读取网格
上次介绍setTime.H
时提到过,程序读取网格是通过在main函数开头用包含createMesh.H
文件来实现的。 现在我们改动我们的HelloWorld程序,包含这个头文件,如下
createMesh.H
文件位于$FOAM_SRC/OpenFOAM/include/
。 我们可以看看其内容是
可以发现其实是创建了一个fvMesh对象,叫做mesh。 创建这个fvMesh对象的时候程序读取了constant/polyMesh/
目录下面的points
、faces
、owner
、neighbour
和boundary
这5个文件。这5个文件就是OpenFOAM存储网格的文件。它们可以用blockMesh生成,也可以用前处理工具从其他格式的网格文件转换得到(比如用fluentMeshtoFoam、gmshToFoam等等)。 有了这个对象,我们就可以从这个对象获得网格的所有信息(如cell center的坐标、cell的体积等等),这些信息在创建fvMesh对象时,其构造函数就以自动计算好了。
下面举个例子,看看如何获得每个cell 的体积。 简单起见,首先我们准备好一个简单的网格,直接从官方教程的cavity case里面复制blockMeshDict到我们之间创建的HelloWorld case过来:
修改constant/polyMesh/blockMeshDict文件,把cell个数改为为(2 2 1),也就是X、Y方向各两个cell,Z方向一个cell, 并且把convertToMeters
改成1。 如下
|
|
使用blockMesh
生成网格。现在constant/polyMesh目录下面就有那5个表示网格的文件了。
接下来还要准备system/fvSchemes
文件和system/fvSolutions
文件, 即使它们里面是空的,因为fvMesh的构造函数必须读取这两个文件。这里我们用的这两个文件内容(除去头部和尾部)分别是:
和
这已经是程序能初始化mesh所需要的最少的信息了,少了任何一条都不行。
现在我们在HelloWorld程序里面增加如下几行代码,来输出每个cell的体积:
这里的const scalarField& V = mesh.V()
就是获取mesh对象的所有cell体积,给一个scalarFiled类型的引用。scalrField其实是通过typedef Field<scalar>
得到的, 这里初略的理解为一个标量元素的数组,并且可以用[]
操作符取元素就够了。(Field<>只是OpenFOAM众多层次的容器类的一层)。
重新wmake编译,运行输出如下
可以发现每个cell的体积都是(1/2)*(1/2)*0.1 = 0.025
。
类似的,还可以获取cell center坐标、face center坐标、face的法向量和面积等几何信息,如下:
下面这个表格列出了获取网格的一些基本几何信息的函数:
我们暂时先在这里停住,在最后一节再演示如何获取网格的其他信息(数据信息和拓扑信息)。 接下来我们看看任意非结构网格在OpenFOAM内部是如何表示的。
网格在OpenFOAM里面如何表示
我们开始以刚才的2*2*1
的网格为例来说明。 constant/polyMesh
目录下面的5个文件内容分布是:
points
: 记录所有的网格顶点,每个点都是由三个分量组成的(三维空间坐标)。 本例中一共有3*3*2
= 18个点。faces
: 记录所有的face和组成每个face的顶点编号(就是出现在points
里面所有顶点中的次序)。对于本例,一共有20个face,包含了内部face (internal face)和位于边界上的face(boundary face)。boundary
: 记录每个boundary patch的信息,包括patch的名字,类型、所包含的face的个数n,以及这些face在所有face中的开始位置(也就是此patch中第一个face出现在face
记录的face列表里面的次序)。 每个boundary patch中的face编号在faces
中是连续的。faces
中开头记录的face是internal face。下图展示的是之前例子中的boundary和face:
这里有4个internal face和3个boundary patch,分别是, movingWall(2个face), fixedWall(6个face), frontAandBack(8个face)。
在介绍剩下的两个文件owner
和neighbour
之前,有必要介绍OpenFOAM网格里面的face的owner和neighbour的概念。 无论网格多么复杂,有一个基本特点或者说是要求是: 除了boundary face外的每个face (也就是internal face) 被且仅被两个cell共享。这两个cell分别称为这个face的owner cell和neighbour cell。 规定这个这个face的法向量从owner指向neighbour。 boundary face也有owner cell,但是没有neighbour cell,因为boundary face的法向量指向了计算区域外部,那里没有cell了。现在再看owner
和neighbour
文件就简单了:
owner
文件记录了一个整型数组,第i个元素表示第i个face(包括boundary face)的owner cell的在所有cell中的编号(cell ID),数组大小等于所有face的个数(本例是20)。neighbour
文件记录了一个整型数组,第i个元素表示第i个internal face的neighbour cell的在所有cell中的编号(cell ID),数组大小等于所有internal face的个数(本例是4)。
获取网格信息
之前我们介绍了如何通过V()
、C()
、Cf()
等函数获取一些基本的几何信息,现在我们看如何怎样获取一个face的owner cellID和neighbour cellID。直接看代码:
|
|
这段代码输出的是:
现在我们可以验证一下到底是不是这样的, 通过刚刚介绍的C()
、 Cf()
、Sf()
函数输出的位置等信息,我们可以找到每个cell和face的编号以及每个face的法向量如下:
对比刚刚输出的结果可以发现确实是这样。
回到刚才插入的代码,可以发现 fvMesh.owner()
和 fvMesh.neighbour()
函数可以获取与每个internal face的owner cellID和neighbour cellID。注意这里的owner()
返回的数组不包含boundary face的cell ID,这也可以从上例中forAll(owner, facei)
只循环4次(4个internal face)可以看出。
那要怎么知道每个boundary face的owner cellID (也就跟这个bounary face相邻的那个cell的编号)呢,毕竟这个信息在处理边界时必须知道。 下面这段代码就是答案
|
|
这里是打印出了每个boundary patch的每个face的onwer cellID,所以有两层forAll循环。 boundary patch的所有face的owner cellID数组是通过faceCells()函数获取的。
这段代码的输出是:
这里的0号bounary patch是movingWall,1号boundary patch是fixedWall。可以看到这里并没有处理因为计算1D/2D问题而声明的empty类型的boundary patch (frontAndBack),事实上,我在写CFD程序时也确实不需要处理empty patch,因为从一个empty patch的某个face上流入的必然等于从其相对那个face上流出的,其合贡献为0。
除了faceCell()函数外,还可以获取boundary patch的更多信息,比如name,start(该patch第一个face中在所有的face中的位置)。更多信息可以查看$FOAM_SRC/finiteVolume/fvMesh/fvPatches/fvPatch/fvPatch.H
文件。
至于为什么是这个文件,我们可以浏览源代码顺藤摸瓜搞清楚这里面的boundary patch和mesh的关系以及其中涉及到的类和相应的文件。 很多时候浏览源代码也可以获得很多信息。
首先这里的mesh.boundary()返回的对象类类型是fvBoundaryMesh
, 这可以从fvMesh的头文件$FOAM_SRC/finiteVolume/fvMesh/fvMesh.H
中boundary()的函数声明看到:
在去查找fvBoundaryMesh
类的头文件$FOAM_SRC/finiteVolume/fvMesh/fvBoundaryMesh/fvBoundaryMesh.H
发现它是继承自fvPatchList
|
|
而fvPatchList的声明是通过typedef实现的, 在$FOAM_SRC/finiteVolume/fvMesh/fvPatches/fvPatch/fvPatchList.H
可以看到
可以看到fvPatchList就是一个fvPatch的指针数组(PtrList
)。现在去看看fvPatch
类的头文件,在$FOAM_SRC/finiteVolume/fvMesh/fvPatches/fvPatch/fvPatch.H
。
这里的fvPatch指的就是我们说的boundary patch。 其Access类型的函数可以返回诸多我们感兴趣的数据,比如
有时候在写后处理程序时希望通过boundary patch的名字找到boundary patch的编号,这可以通过fvBondaryMesh提供的findPatchID函数实现,
举个例子,我们希望找到HelloWorld case里面的fixedWall对应的boundary patch编号,可以用下面的代码实现:
程序输出是