内容如题,在阅读完大量的基础代码之后,我们现在可以尝试解读一下chemFOAM中的程序流程。因为内容很多,所以会分好几个子标题讲完。
我们使用openFOAM的过程中,通常是从tutorials
中找到对应算例的配置文件,复制到run
的文件夹中,然后使用编译好程序运行,而控制则通过配置文件中的参数设置。那么,首先我们需要理解这些控制文件中的参数是怎样读入到程序中。
本文以chemFOAM
中的氢气燃烧算例为例,对应的文件夹为tutorials/combustion/chemFoam/h2
,内容有:
Allclean Allrun chemkin/ constant/ system/ validation/
其中文件夹chemkin
为程序CHEMKIN
中的源码和计算结果,而validation
则是将当前求解器和CHEMKIN
中结果进行对比的。真正的配置文件位于constant/ system/
中,
constant/
chemistryProperties initialConditions thermophysicalProperties
燃烧的求解器和参数 燃烧初值 组分等性质的配置
system/
controlDict fvSchemes fvSolution
时间参数 pde方程设置 各个部分的求解器
其中,我们比较关注的时间参数位于controlDict
中,内容如下:
/*--------------------------------*- C++ -*----------------------------------*\
========= |
\\ / F ield | OpenFOAM: The Open Source CFD Toolbox
\\ / O peration | Website: https://openfoam.org
\\ / A nd | Version: 7
\\/ M anipulation |
\*---------------------------------------------------------------------------*/
FoamFile
{
version 2.0;
format ascii;
class dictionary;
location "system";
object controlDict;
}
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
application chemFoam;
startFrom startTime;
startTime 0;
stopAt endTime;
endTime 0.001;
deltaT 1e-05;
maxDeltaT 1;
adjustTimeStep on;
writeControl adjustableRunTime;
writeInterval 5e-04;
purgeWrite 0;
writeFormat ascii;
writeCompression off;
timeFormat general;
timePrecision 6;
runTimeModifiable yes;
DebugSwitches
{
SolverPerformance 0;
}
// ************************************************************************* //
内容分别为文件抬头,FoamFile
的文件版本(这个不能删除,程序中会检查版本),以及真正的控制参数设置。每个参数的设置形式为key parameter
,即关键字加内容的组合。而具体的含义基本上看关键字就可以看明白,比如说startFrom
就是指从哪个时间点开始计算,如果是0就是从头开始,如果非零就是续算的。
首先我们需要解释一下,文件的配置文件的范例位于turtorial\
中,而编译好的求解器的顶层源码在application
文件夹中,比如当前这个求解器的路径位于applications/solvers/combustion/chemFoam
,其内容包括:
dyfluid@dyfluid:~/OpenFOAM/OpenFOAM-7/applications/solvers/combustion/chemFoam ls
chemFoam.C hEqn.H setDeltaT.H
createBaseFields.H Make/ solveChemistry.H
createControls.H output.H thermoTypeFunctions.H
createFieldRefs.H pEqn.H YEqn.H
createFields.H readControls.H
createSingleCellMesh.H readInitialConditions.H
其中Make
给定了当前这个求解器依赖的基础源码,这个在之前的帖子中有介绍:
https://blog.csdn.net/qq_40583925/article/details/106639854
其他代码中,chemFoam.C
为最顶层,其他代码也为执行代码,只不过通过.H
的形式添加到代码中,增加可读性,chemFoam.C
的源码这里大概对框架进行说明:
注释文件头
一系列基础类的.H文件添加
//********************************************//
int main(int argc, char *argv[])
{
argList::noParallel();
类对象的创建
以及计算参数的读取(Time的参数就是在这个位置被读取的)
//******************************************//
Info<< "\nStarting time loop\n" << endl;
while (runTime.run())
{ 可以在计算过程中在这个位置控制时间步长参数
runTime++;
Info<< "Time = " << runTime.timeName() << nl << endl;
燃烧的求解,Y,h,p这一系列热学参数的更新
#include "output.H"
Info<< "ExecutionTime = " << runTime.elapsedCpuTime() << " s"
<< " ClockTime = " << runTime.elapsedClockTime() << " s"
<< nl << endl;
}
Info << "Number of steps = " << runTime.timeIndex() << endl;
Info << "End" << nl << endl;
return 0;
}
具体是类对象创建的哪个文件看名字就可以猜到,是createTime.H
,其内容如下:
Foam::Info<< "Create time\n" << Foam::endl;
Foam::Time runTime(Foam::Time::controlDictName, args);
嗯…就两行,第一行输出到终端告诉用户选择要创建时间,然后使用了类Foam::Time
的构造函数Time(Foam::Time::controlDictName, args);
,而创建的类对象的是runTime
。
两个输入值的内容这里大概描述一下,第一个其实可以猜到,是要读取的数据所在的Dictionary
的名称。而第二个则是用户在终端键入的配置参数,是int main(int argc, char *argv[])
的两个输入参数写进一个类之后得到的args
,其中argc
代表终端键入的选项个数,例如chemFOAM
就是args=1
,后者是键入的选项的字符串存储。
这两个输入参数的详细内容,会在后面解释,这里先了解到通过这个输入,程序可以找到正确的配置文件中正确的{ }
中的内容读取。
没错,在计算过程中,如果修改了时间的配置参数,比如修改了更大的步长,程序也可以实时更新对应的时间步长,具体的实现在while
循环中的第一个文件readControls.H
,内容如下:
runTime.controlDict().lookup("adjustTimeStep") >> adjustTimeStep;
maxDeltaT = readScalar(runTime.controlDict().lookup("maxDeltaT"));
嗯…又只有两行,runTime
就是前面创建的Foam::Time
类的对象,对象中内置了函数controlDict()
,他返回了Foam::Time
类中内置的另外一个类对象unwatcedIOdictionary controlDict_
,这个类对象又具有lookup()
这个函数(套娃现场)。
而lookup
的具体实现后面再介绍,这里可以很容易的猜到,就是检索配置文件中的adjustTimeStep
关键字,然后读取其中的内容,然后将文件流导入adjustTimeStep
变量中。注意这里的>>
其实是经过运算符重定义的,OpenFOAM的代码中几乎对所有的类的IO输入都进行了与运算符重定义。
第二个则使用了readScalar
这个函数,它位于src/OpenFOAM/primitives/Scalar/Scalar.C
中,输入为一个文件流,函数创建了一个Scalar rs
对象,然后将文件流>>
进rs
,然后返回rs
,就是说这个函数的返回值是一个Scalar
对象,然后通过=
赋值给maxDeltaT
,这里的=
也是经过运算符重定义的。
总之就是,读取的核心句式为runTime.controlDict().lookup("keyWord")
,通过输入关键字,让程序读取对应关键字的内容,返回一个文件流供操作。
我们可以看到如果只读turtorial\
和application\
这两个文件夹,虽然不难猜出代码的具体含义,但是细节的实现并不了解。这就需要我们阅读src\
这个文件夹中的内容,不过我们也并不需要读取其中全部的内容。只要通过Make\
中的配置文件,找到依赖的源码阅读即可。嘛,但是这也有上千个源码。
这篇到这里为止~尽量一篇不要超过5000字,之前阅读Essential C++一次写太多自己都看不过来。更详细的实现会在后续更新。