OpenFOAM 中的初始化设置
CFD 模拟中初始条件和边界条件的设置至关重要。多相流动中通常涉及到非均匀初始场的设置,比如,需要设置满足一定分布的分散相等。
OpenFOAM 提供了一个设置非均匀初始场的工具叫 setFields ,这个工具可以给指定区域内某个场的值设置为指定值,这个区域可以是指定的一个box,也可以是某个 cellSet,结合 topoSet 工具可以实现对非规则区域设置指定的值。但是,如果需要根据不同网格位置设置不同的值,就需要用到 swak4foam 工具包中提供的另一个工具 funkySetFields ,这个工具可以自定义一个表达式来计算指定区域网格内的某个场的值。
这两种方法各有优缺点,setFields 是 OpenFOAM 自带的,使用也比较方便,但是要实现对非规则区域设置初始场,需要一些技巧。funkySetFields 灵活度大得多,可以比较容易设置非均匀的初始场,但是需要用户额外编译 swak4foam 工具包,对初学者有时候会有点麻烦。边界条件的设置绝大部分可以使用 OpenFOAM 自带的数十种边界条件类型来完成,不过总会有需要自定义边界条件的需求。自定义边界条件需要编写一段代码编译成库文件,然后在算例里面调用,用户可以在src/finiteVolume/fields/fvPatchFields 目录下,找一种跟自己的需求相近的一种边界条件,然后在此基础上修改。这个要求用户对边界条件的代码结构以及基本的类继承关系有一些了解。
本篇介绍另一种不太常用的设置初始条件和边界条件的方法:通过写一段小程序来直接设定初始和边界条件。先看具体做法,后面再说明局限性。
初始条件的本质,其实就是给某个场在每一个体网格内指定一个值,具体体现在场文件(比如U,p这种),就是 internalField 关键字后面对应的部分,常见的是uniform (0 0 0) 这种均匀场。但是其实如果打开模拟过程中某个中间时刻的场文件来看,会发现通常是这种样子:
internalField nonuniform List<vector>
400
(
(-0.00022816 0.000230859 0)
(-0.000765141 0.000434129 0)
(-0.00164784 0.000519204 0)
(-0.00252484 0.000515177 0)
(-0.00335472 0.0004698 0)
(-0.00409915 0.000408054 0)
(-0.00473634 0.000338688 0)
.....
);
也就是说,每一个网格有一个值。因此,如果说可以在一段程序内获取到每个网格的信息,那么我就可以自由地对每个网格进行赋值了。所以归根结底就是在网格生成好的情况下,如何去获取每个网格的信息,下面是一段在圆柱区域被生成一个有径向分布的颗粒体积分率的程序示例:
Info<< "Reading field alpha\n" << endl;
volScalarField alpha // 读取场
(
IOobject
(
"alpha.particles", // 场的名字为 “alpha.particles”
runTime.timeName(),
mesh,
IOobject::MUST_READ,
IOobject::AUTO_WRITE
),
mesh
);
scalar radius = 0.205; // 圆柱半径
scalar center_x = 0.0; // 圆柱截面的中心坐标
scalar center_y = 0.0;
const volVectorField& center = mesh.C(); // 获取网格的中心坐标
forAll(center, cellID) //遍历网格
{
scalar x = center[cellID].component(0); // 圆柱坐标的各个分量
scalar y = center[cellID].component(1); // 需要注意的是,任何一个矢量都可以
scalar z = center[cellID].component(2); // 用component函数来获取分量
scalar rByR = Foam::sqrt( (x-center_x)*(x-center_x)+(y-center_y)*(x-center_x))/radius; // z是轴向,计算网格中心到圆柱中心的距离
alpha[cellID] = radialDis(rByR, z); // radialDis 是一个自定义的函数,用来计算每个网格内的alpha值
}
alpha.write(); // 将alpha的值写入到文件
需要注意的是,对于同一套网格,每个网格都有中心,每个网格都对应一个场的值,网格中心和场都可以理解为是以数组的形式存储的,而且编号顺序是一致的,所以,遍历网格中心场时,可以用相同的编号来获取到该网格的场值,这也就是为什么可以直接写 alpha[cellID] = radialDis(rByR, z);
这样来赋值了。
边界条件也是类似,只要能获取到每个指定边界的场以及网格坐标等信息,就可以来对其进行修改。下面给一个设置抛物型速度分布的小例子:
const word patchName = "inlet"; // 指定需要设置的边界名
vector center = vector(0,0,0);
scalar radius = 1.0;
scalar Umax = 1.0;;
Info<< "Reading field U\n" << endl;
volVectorField U
(
IOobject
(
"U", // 读取名字为U的场
runTime.timeName(),
mesh,
IOobject::MUST_READ,
IOobject::AUTO_WRITE
),
mesh
);
const fvBoundaryMesh& bMesh = mesh.boundary(); // 获取网格所有的边界
const fvPatch& inletPatch = bMesh[patchName]; // 根据边界名来获取对应的边界
// boundaryFieldRef() 返回的是可编辑(即非const)的边界场,通过指定边界的index,可以获取到指定边界对应的场
vectorField& inletField = U.boundaryFieldRef()[inletPatch.index()];
const vectorField& inletCenter = inletPatch.Cf(); // 获取边界面的面网格中心
const vectorField& inletNormal = inletPatch.nf(); // 获取边界面的单位法向量
forAll(inletCenter, faceI) // 遍历指定边界的每一个面网格
{
vector faceCenter = inletCenter[faceI]; // 获取面网格中心坐标
scalar Umag = calcVelocity( faceCenter, center, radius, Umax); // 自定义函数计算速度的模
inletField[faceI] = -Umag * inletNormal[faceI]; // 速度的模乘以单位法向量得到矢量速度
}
U.write();
上述方法的优点是轻量级,使用相对简单,只要简单写数行代码就可以实现非均匀的初始场,即用即扔。但局限性也很明显,那就是几乎没有考虑通用性,这个通过引入选项和命令行参数可以部分解决。另一个是,对于边界条件,这种方法只能针对使用第一类边界条件的边界,如果是第二类边界或者更复杂的情形,还是需要通过编写边界条件代码来解决。以上只是提供一种选择,具体使用读者可以尽情发挥