参考:

感谢原作者们的无私引路和宝贵工作。

前置:
OpenFOAM开发编程基础01 输入输出 | 𝓐𝓮𝓻𝓸𝓼𝓪𝓷𝓭 (aerosand.cn)

OpenFOAM 初学者很难不注意到求解器总是有几个固定的头文件,包括 setRootCase.HcreateTime.HcreateMesh.H 。网络上找到的代码解析常常只有一句注释介绍功能,也许无法消除困惑感。

本文同样从 C++ 开始,简单介绍主函数参数,在此基础上讨论 setRootCase.H 头文件。

建立本文的项目文件夹并进入

1
2
3
4
// terminal 
cd /home/aerosand/aerosand/ofsp
mkdir 02_args
cd 02_args

C++ 实现

主函数参数

我们在初学 C++ 的时候,主函数的参数列表一般留空,即有如下形式

1
2
3
4
5
...
int main() // 省略参数列表
{
...
}

当进一步深入 C++ 开发时,了解到主函数的更一般写法为

1
2
3
4
...
int main(int argc, char *argv[]) {}
// 或者
int main(int argc, char **argv) {}
  • argcargument count 的缩写,保存程序运行时传递给主函数的参数个数
  • argvargument vector 的缩写,保存程序运行时传递给主函数的具体参数的字符型指针,每个指针都指向一个具体的参数。
    • argv[0] 指向程序运行时的全路径名称
    • argv[1] 指向程序运行时命令行中执行程序名后第一个字符串
    • argv[2] 指向程序运行时命令行中执行程序名后第二个字符串
    • 其他以此类推

项目实现

新建项目 02_arg/02_01_args/

主源码 src/main.cpp 如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>

int main(int argc, char *argv[])
{
std::cout << "Number of arguments = " << argc << std::endl;

for (int i=0; i<argc; ++i)
{
std::cout << "Argument " << i << ": "
<< argv[i] << std::endl;
}

return 0;
}

终端直接编译运行

1
2
// terminal
make run

运行结果如下

1
2
Number of arguments = 1
Argument 0: ./output/main

如果运行时增加参数,例如

1
2
// terminal
./output/main hi hey hello

运行结果如下

1
2
3
4
5
Number of arguments = 4
Argument 0: ./output/main
Argument 1: hi
Argument 2: hey
Argument 3: hello

基于主函数参数列表的了解,下面讨论 OpenFOAM 中的主函数参数等内容。

OpenFOAM 实现

应用准备

1
2
3
4
5
// terminal
foamNewApp 02_02_args
cd 02_02_args
cp -r $FOAM_TUTORIALS/incompressible/icoFoam/cavity/cavity debug_case
code .

文件结构如下

1
2
3
4
5
6
7
8
9
|- 02_02_args/
|- debug_case/
|- 0/
|- constant/
|- system/
|- Make/
|- files
|- options
|- 02_02_args.C

以后非特别情况不再赘述文件结构。

脚本和说明

新建脚本

1
2
// terminal
code _appmake.sh _appclean.sh _caserun.sh _caseclean.sh README.md

脚本内容和说明文档略。

setRootCase.H

先看一下主源码中的头文件 setRootCase.H

1
2
3
// termianl
find $FOAM_SRC -iname setRootCase.H
// $FOAM_SRC/OpenFOAM/include/setRootCase.H

代码具体为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Construct from (int argc, char* argv[]),
// - use argList::argsMandatory() to decide on checking command arguments.
// - check validity of the options

Foam::argList args(argc, argv); // 构造argList类型的args变量
if (!args.checkRootCase()) // 如果检查应用参数(算例路径)错误
{
Foam::FatalError.exit(); // 报错退出
}

// User can also perform checks on otherwise optional arguments.
// Eg,
//
// if (!args.check(true, false))
// {
// Foam::FatalError.exit();
// }

// Force dlOpen of FOAM_DLOPEN_LIBS (principally for Windows applications)
#include "foamDlOpenLibs.H" // 兼容性处理,无需深究

代码讨论

  • 该 H 文件检查了该应用的参数是否正确
  • 构造参数列表的对象 args (所以此 H 文件需要放在所有的参数代码等后面)
  • 如果参数列表检查不通过,则报错退出

argList 类是一个非常基础的类,具有很多的成员数据和成员方法。我们大概挑几处代码作为切入点简单了解一下 argList 类。

1
2
3
// terminal
find $FOAM_SRC -iname argList.H
// /usr/lib/openfoam/openfoam2306/src/OpenFOAM/global/argList/argList.H

打开 argList.H ,内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
...
class argList
{
...
public:
...
// 基于参数的构造函数
argList
(
int& argc, // 主函数参数个数
char**& argv, // 主函数参数的指针
bool checkArgs = argList::argsMandatory(),
bool checkOpts = true,
bool initialise = true
);
...
...

方法的实现较为复杂,对于现阶段来说,基础类没有必要去深入讨论代码的实现部分,目前只需要做到心里有数、看到不那么陌生即可。

在上一篇文章的讨论中,也可以深入看一下 OpenFOAM 的 IOdictionary 类,终端使用命 find $FOAM_SRC -iname IOdictionary.H,可以找到对应的构造函数原型。也可以查找到 IOobject.H 中的构造函数原型。

主源码

帮助信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include "fvCFD.H"

// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //

int main(int argc, char *argv[])
{
argList::addNote
(
"Demonstrates how to handle command line options.\n\n"
"Application arguments:\n"
"----------------------\n"
" mathLib - Eigen/GSL/Armodillo/BLAS\n"
" level - computation speedup\n"
);

#include "setRootCase.H"
#include "createTime.H"

// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //

Info<< nl;
runTime.printExecutionTime(Info);

Info<< "End\n" << endl;

return 0;
}


// ************************************************************************* //

编译后查看帮助信息

1
2
// terminal
02_02_args -help

终端输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Usage: 02_02_args [OPTIONS]
Options:
-case <dir> Case directory (instead of current directory)
-decomposeParDict <file>
Alternative decomposePar dictionary file
-parallel Run in parallel
-doc Display documentation in browser
-help Display short help and exit
-help-full Display full help and exit

Demonstrates how to handle command line options.

Application arguments:
----------------------
mathLib - Eigen/GSL/Armodillo/BLAS
level - computation speedup

Using: OpenFOAM-2306 (2306) - visit www.openfoam.com
Build: _fbf00d6b-20230626
Arch: LSB;label=32;scalar=64

可以看到我们自定义的帮助信息。

应用参数

应用参数(arguments)在执行应用命令时是强制需要的,下一节的应用选项(option)是可选的。

给主源码增加应用参数如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include "fvCFD.H"

// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //

int main(int argc, char *argv[])
{
argList::addNote
(
"Demonstrates how to handle command line options.\n\n"
"Application arguments:\n"
"----------------------\n"
" mathLib - Eigen/GSL/Armodillo/BLAS\n"
" level - computation speedup\n"
);

argList::validArgs.append("mathLib");
argList::validArgs.append("level");
// 将应用参数增补到主函数参数列表里
// 这两个参数是强制必须的

#include "setRootCase.H" // 构造OpenFOAM的argList类型的args对象
#include "createTime.H"

#include "createMesh.H"

// args第0参数是程序名本身
const word args1 = args[1]; // args第1参数就是增补的第1个参数
const scalar args2 = args.get<scalar>(2); // 增补的第2个参数


// 显示参数
Info<< "Solver setup: " << nl
<< " use : " << args1 << nl
<< " speedup : " << args2 << nl
<< nl << endl;

// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //

Info<< nl;
runTime.printExecutionTime(Info);

Info<< "End\n" << endl;

return 0;
}


// ************************************************************************* //

终端运行是必须要提供相应类型的参数

编译后运行

1
2
3
// terminal
blockMesh -case debug_case
02_02_args -case debug_case GSL 2

结果为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Create time

Create mesh for time = 0

Solver setup:
use : GSL
speedup : 2



ExecutionTime = 0 s ClockTime = 0 s

End

应用选项

应用选项在运行时可写可不写。

主源码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
#include "fvCFD.H"

// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //

int main(int argc, char *argv[])
{
argList::addNote
(
"Demonstrates how to handle command line options.\n\n"
"Application arguments:\n"
"----------------------\n"
" mathLib - Eigen/GSL/Armodillo/BLAS\n"
" level - computation speedup\n"
);

// 应用参数要在 setRootCase.H 构造 args 之前
argList::validArgs.append("mathLib");
argList::validArgs.append("level");

// 应用选项也要在 setRootCase.H 构造 args 之前
argList::addOption
(
"dict",
"word",
"Use addtional dictionary (just for example)"
);

argList::addOption
(
"nPrecision",
"label",
"Set the precision level (just for example)"
);

argList::addBoolOption
(
"log",
"output the log"
);

// setRootCase.H 将构造argList类型的args对象
#include "setRootCase.H"
#include "createTime.H"

#include "createMesh.H"

const word args1 = args[1];
const scalar args2 = args.get<scalar>(2);

Info<< "Solver setup: " << nl
<< " use : " << args1 << nl
<< " speedup : " << args2 << nl
<< nl << endl;

// 创建_dict并给默认初始值
fileName _dict("./system/myDict");
if (args.found("dict"))
{
args.readIfPresent("dict",_dict);
// 如果使用了 -dict 选项,则使用 -dict 后的值
// 也许会有同学疑问上面这句代码为什么能读取 -dict 后的值并传给 _dict
// 建议先不要深究,以后熟悉C++了可以翻阅OpenFOAM更底层的源代码
Info<< "Reading myDict " << endl;
}
Info<< "Dictionary from " << _dict << nl << endl;

// 创建 label 类型的对象并给默认初始值
label _nPrecision(6);
args.readIfPresent("nPrecision",_nPrecision);
Info<< "Precision is " << _nPrecision << endl;

const bool _log = args.found("log");
if (_log)
{
Info<< "Output the logs" << endl;
}
// 只要使用 -log 选项,就会执行一些方法,比如这里的输出


// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //

Info<< nl;
runTime.printExecutionTime(Info);

Info<< "End\n" << endl;

return 0;
}


// ************************************************************************* //

编译运行应用

1
2
3
// terminal
blockMesh -case debug_case
02_02_args -case debug_case GSL 2 -dict ../constant/myDict -nPrecision 12

运行结果为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Create time

Create mesh for time = 0

Solver setup:
use : GSL
speedup : 2


Reading myDict
Dictionary from "../constant/myDict"

Precision is 12


ExecutionTime = 0 s ClockTime = 0 s

End

如果运行应用如下

1
02_02_args -case debug_case GSL 2 -dict ../constant/myDict -log

运行结果为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Create time

Create mesh for time = 0

Solver setup:
use : GSL
speedup : 2


Reading myDict
Dictionary from "../constant/myDict"

Precision is 0

Output the logs

ExecutionTime = 0.01 s ClockTime = 0 s

End

应用选项 -dict 也有默认值,可以尝试省略并运行应用。

通过使用不同的应用选项,看到各行源代码的效果。

小结

从 C++ 基础出发, 简单讨论了主函数运行时的帮助、参数和选项。虽然上面的开发都是仅停留在表面做做样子,但目的也是使读者对 OpenFOAM 中运行应用的命令行方法有了一些了解,以后也可以在此基础上深入。更深入的开发见后续讨论。

本文基本搞明白了 OpenFOAM 求解器中必备的 setRootCase.H 到底是什么,那么 createTime.H 头文件到底是什么呢?