CFD 模拟需要的前处理和后处理,通过需要涉及到对某些指定区域进行操作。比如,在指定区域设置初始浓度,在指定区域加入一个源项,给指定区域加上一个旋转速度,计算指定区域内的统计值,计算某个截面上的通量,甚至对某些特定的网格使用特殊的离散格式等等。
OpenFOAM 中有一个专门的工具来帮助用户管理 “指定区域”,叫 topoSet
。topoSet
含义是 topology set(集 合),该工具作用是将一些点、面、或者体网格提取出来存放在指定名字的 set 里,用以辅助前后处理。topoSet
可以生成的“集 合”包括 pointSet,faceSet,cellSet,pointZone,faceZone,cellZone。set 和 zone 在概念上基本上是一致的,都是包含一系列点(或者面,或者体)的集 合,只是由于历史原因,目前 set 和 zone 并存于 OpenFOAM 中,有的前后处理工具可能同时支持使用 set 或 zone,但也有的只支持其中一种。下面来看看 topoSet 工具的使用方法,以及一些应用的例子。
topoSet
的基本使用分两步,第一步是编写一个 topoSetDict
放在算例的 system
目录下,其中包含需要提取的 set 的信息,然后运行 topoSet
命令,正常的话,将在 constant/polyMesh
目录下生成需要的 set 或者 zone 文件。下面是一个 topoSetDict 的示例:
actions
(
{
name porousCells; // 需要生成的集 合的名字
type cellSet; // 集 合类型,pointSet, pointZoneSet,faceSet, faceZoneSet, pointSet, pointZoneSet, cellSet, cellZoneSet,
action new; // 操作类型,new, add, delete, subset(交集)
source boxToCell; // 操作对象,boxToCell 含义是将某个box内的所有cell作为本次 action 的操作对象
sourceInfo // 操作对象的信息,这个取决于上一条的设置
{
box (2.05 0.4 -1) (2.1 0.85 1); // 一个 box 的左下和右上角点的坐标
}
}
{
name porousZone; // 需要生成的集 合的名字
type cellZoneSet; // 表示需要生成一个cellZone
action new; // 新建
source setToCellZone; // 表示是要将某个 cellSet 中的所有网格作为操作对象
sourceInfo
{
set porousCells; // 指定这个将集 合作的 cellSet 的名字
}
}
);
所有的操作,都囊括在 actions ( );
这一层括号里,里面的每一个被 { }
包围起来的便是一个 action。每个 action 需要包含的基本元素的含义见上述注释。运行 topoSet
命令时,actions 里面的每一个 action 将按顺序执行。注释中已将 type 和 action 的种类都列出来了,source 的种类比较多,这里不便一一列出,OpenFOAM 提供了一个示例 topoSetDict,位于 applications/utilities/mesh/manipulation/topoSet/topoSetDict
,里面列出了所有支持的 source 类型,供用户参考。此外,还有一种方式可以获取到所有支持的 source 类型:在编写 topoSetDict 时,source 字段后面随便填写一串字符(比如 banana),然后运行 topoSet
命令,这时候,命令会报错,因为随便填入的字符串不是合法的 source,但同时,topoSet
命令也会将所有支持的 source 类型输出到屏幕上,示例如下:
--> FOAM FATAL ERROR:
Unknown topoSetSource type banana
Valid topoSetSource types :
43
(
boundaryToFace
boxToCell
boxToFace
boxToPoint
cellToCell
cellToFace
cellToPoint
cylinderAnnulusToCell
cylinderToCell
faceToCell
faceToFace
faceToPoint
......
这种小技巧在 OpenFOAM 中几乎所有的 dict 中都可以使用,以下简称为 “banana 方法”。
对于上述示例中的 topoSetDict , topoSet
命令运行结束后,将在 constant/polyMesh
目录下生成一个 cellZones
文件,里面存放的是 cellZone 信息,
1 // 表示 cellZone 的数量
(
porousZone // 这是上述 topoSetDict 中指定的 cellZone 的名字
{
type cellZone;
cellLabels List<label>
// 下面的是网格信息,10表示当前 cellZone 包含的网格数目,括号里的10个整数是网格编号(是的,假如你知道你需要提取的网格编号,你也可以手动填入这些数字)
10 (
12750 12751 12752 12753 12754 12755 12756 12757 12758 12759
);
}
)
同时,在 constant/polyMesh
会生成一个目录 sets
,里面存放的是 set 的信息,对于上述示例,sets 目录项将会有一个 porousCells
文件,里面存放的是名字为 porousCells 这个 cellSet 的信息(格式相信一目了然,不需要额外解释)
FoamFile
{
version 2.0;
format ascii;
class cellSet;
location "constant/polyMesh/sets";
object porousCells;
}
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
10
(
12750
12751
12752
12753
12754
12755
12756
12757
12758
12759
)
接下来举一些使用 set 或者 zone 的前后处理示例。
1.setFields
setFields 的作用是设置非均匀初始场,可以在一个指定区域内对某个场设置指定的初始值。典型的 setFieldsDict 如下:
defaultFieldValues
(
volScalarFieldValue alpha.water 0.0
);
regions
(
boxToCell
{
box (0 0 -1) (0.1461 0.292 1);
fieldValues
(
volScalarFieldValue alpha.water 1.0
);
}
);
需要注意的是,这里的 boxToCell
不是唯一选项,用户可以使用“banana 方法”来获取所有可选项。其中就包含 cellToCell,这个选项的作用是让 setFields 将某个 cellSet 中的所有网格的初始值设置为指定值,假设事先用 topoSet
将需要的网格提取到了 waterCells 这个 cellSet中,那么 regions 可以按如下方式写:
regions
(
cellToCell
{
set waterCells;
fieldValues
(
volScalarFieldValue alpha.water $speValue
);
}
);
2.源项
OpenFOAM 中设置源项需要使用 fvOptions,其中指定区域的方式也通常是 cellSet 或者 cellZone,下面以设置多孔介质区域的源相为例:
porosity1
{
type explicitPorositySource;
active true;
explicitPorositySourceCoeffs
{
type DarcyForchheimer;
selectionMode cellZone; // 选择 cellZone 作为多孔介质区域
cellZone porousZone; // cellZone 的名字
DarcyForchheimerCoeffs
{
d d [0 -2 0 0 0 0 0] (5e7 -1000 -1000);
f f [0 -1 0 0 0 0 0] (0 0 0);
coordinateSystem
{
type cartesian;
origin (0 0 0);
coordinateRotation
{
type axesRotation;
e1 (1 0 0);
e2 (0 1 0);
}
}
}
}
}
3.后处理
OpenFOAM 中后处理建议的方式是在设置算例时就考虑好需要做哪些后处理,然后,在 controlDict 的 functions 部分加上需要的 functionObject 来实现一边计算一边后处理。这样可以减少需要保存的计算数据,这在算例很大(比如数千万网格)时很有意义(不仅是节省硬盘空间,也节省时间,因为往硬盘上频繁写入大量数据是非常耗时的)。这里介绍 surfaceFieldValue和 volFieldValue 两个 functionObject,主要关注其如何使用 set 或者 zone 来实现后处理。
比如,需要统计某些面上的某个场相对某相体积分率的加权和,那么可以用如下方式编写一段添加到 controlDict中的 functions 部分:
weightdSum1
{
type surfaceFieldValue;
libs ("libfieldFunctionObjects.so");
log true;
writeControl writeTime;
writeFields true;
surfaceFormat none;
regionType faceZone; // 选择一个 faceZone 中的所有面网格作为操作对象
name f0; // faceZone 的名字
operation sum;
weightField alpha.gas;
fields
(
phi
U
);
}
volFieldValue 也是类似,只不过它处理的是体网格相关的,例如
volWeightedAverage1
{
type volFieldValue;
libs ("libfieldFunctionObjects.so");
log true;
writeControl writeTime;
writeFields true;
regionType cellZone; // 选择一个 cellZone 中的所有体网格作为操作对象
name c0; // cellZone 的名字
operation volAverage;
weightField alpha.gas;
fields
(
U
);
}
如果 OpenFOAM 中提供的工具还满足不了需求,你需要在程序中来读取某些 set 或者 zone 进来,然后对其中的面或者网格进行特殊操作,这也是可以做到的,这里提供读取 cellZone 和 cellSet 的示例代码如下:
读取 cellZone
word zoneName = "zone1"; // 需要读取的 cellZone 的名字
label zoneID = mesh.cellZones().findZoneID(zoneName);
if (zoneID == -1)
{
FatalErrorIn("cellZones") << "Cannot find cellZone " << zoneName << exit(FatalError);
}
labelList cells = mesh.cellZones()[zoneID]; // 获取到 zone1 中包含的所有的网格 id 列表
获取到网格 id 列表,接下来怎么玩就随你了。读取 faceZone 或者 pointZone,方法也是类似的。
读取 cellSet 和 faceSet
读取 set 的方法略微麻烦一些
#include "faceSet.H"
#include "cellSet.H"
// 读取 faceSet
word faceSetName_("faceSet1");
faceSet face1
(
mesh_,
faceSetName_,
IOobject::READ_IF_PRESENT,
IOobject::NO_WRITE
);
forAllConstIter(faceSet ,meshQualityFaces, faceI) // 遍历 faceSet
{
Info << faceI.key() << endl; // faceI.key() 返回的就是 face 的编号
}
// 读取 cellSet
word cellSetName_ = "cellSet1";
cellSet cellset1
(
mesh,
cellSetName_,
IOobject::READ_IF_PRESENT,
IOobject::NO_WRITE
);
forAllConstIter(cellSet ,cellset1, cellI) // 遍历 cellSet
{
Info << cellI.key() << endl; // cellI.key() 返回的就是 cell 的编号
}
最后想说的是,当你的需求中涉及对一些指定的区域进行操作时,几乎肯定会用到 topoSet
,这个工具值得大家花点时间去掌握。