OpenFOAM开发编程基础00 基本实现和

参考:
https://github.com/UnnamedMoose/BasicOpenFOAMProgrammingTutorials
https://www.topcfd.cn/simulation/solve/openfoam/openfoam-program/
https://www.tfd.chalmers.se/~hani/kurser/OS_CFD/
https://github.com/ParticulateFlow/OSCCARdoc/blob/master/openFoamUserManual_PFM.pdf
https://www.youtube.com/watchv=KB9HhggUi_E&ab_channel=UCLOpenFOAMWorkshop
http://dyfluid.com/#
感谢原作者们的无私引路和宝贵工作。
前置:
https://aerosand.cn/categories/CFD/
强烈建议在学习过计算流体力学基础以及有限体积法之后再开始本系列学习。至少也要同时开始,不然最后可能很难理解 OpenFOAM 求解器标准算法。
OpenFOAM 是什么呢?引用 wiki 解释如下
OpenFOAM (for "Open-source Field Operation And Manipulation") is a C++ toolbox for the development of customized numerical solvers, and pre-/post-processing utilities for the solution of continuum mechanics problems, most prominently including computational fluid dynamics (CFD).
我们从简单的 C++ 程序实现开始,简单了解编译原理,通过 makefile 逐渐掌控我们的项目,过渡到了解 OpenFOAM 的 Make 实现方式,然后认识 OpenFOAM 的基本程序,最后逐渐深入 OpenFOAM 的程序开发。
鉴于 OpenFOAM 的使用环境,我们选择在 ubuntu 22.04 系统环境中,基于 OpenFOAM 2306 版本进行开发,方便起见使用 vscode 工具。
OpenFOAM 的安装,vscode 的安装,快捷命令的自定义等讨论参见 [OpenFOAM 环境准备 | 𝓐𝓮𝓻𝓸𝓼𝓪𝓷𝓭 (aerosand.cn)](https://aerosand.cn/posts/20230331145640/)

原生实现
我们使用 C++ 写一段简单的 “hello world” 程序。
hello world
对于 OpenFOAM 来说,项目(或者称为应用)写在任何一个文件夹都可以,放在 `$FOAM_RUN` 路径下也是为了方便管理。
比如,此学习讨论中,我把项目放在自建文件夹 `/home/aerosand/aerosand/` 之下,并定义了快捷命令 `asd` 。快捷命令的自定义使用请参考 [OpenFOAM 环境准备 | 𝓐𝓮𝓻𝓸𝓼𝓪𝓷𝓭 (aerosand.cn)](https://aerosand.cn/posts/20230331145640/)
例如,新建总项目文件夹(`// terminal` 表示下面命令需要在终端输入执行)
(`ofsp` 即 `OpenFoam sharing programming` 的缩写)
在用户路径下,建立本章节的项目文件夹,例如 `/home/aerosand/aerosand/ofsp/of00_0/` 。
系列讨论中可能用户路径以后简写为 `userApp/`
详细操作演示如下
可以使用 `tree` 命令查看文件树状结构(命令参考 [OpenFOAM 常用指令 | 𝓐𝓮𝓻𝓸𝓼𝓪𝓷𝓭 (aerosand.cn)](https://aerosand.cn/posts/20230331173234/))
最后,我们能看到文件结构为
我们约定在文件表示中,名称后加了 `/` 的表示文件夹,反之表示文件。
我们分别写入示例代码,内容如下
类的声明 `Aerosand.h`
类的实现 `Aerosand.cpp`
主源码 `of00_0.cpp`
虽然我们笼统的把整个过程称为“编译”,实际上,在 Linux 系统下,C++ 程序的编译过程其实分为下面几个过程。我们将逐步拆解并实现各个环节。

预处理
将程序代码中包含的 `#include` 文件插入替换到原文件中,在 Linux 系统下生成 `.i` 文件。
`.i` 代表着 intermediate preprocessor output 中间预处理输出
我们在终端里通过命令执行这一过程
g++ 的 `-E` 标识指定编译器进行预处理
g++ 的 `-o` 标识指定编译器生成文件的名称
结果生成 `Aerosand.i` 和 `of00_0.i` 文件。
编译
编译器对预处理后的 `.i` 文件进行进一步语法分析,在 Linux 系统下生成 `.s` 文件。
`.s` 代表着 source code written in assembly 汇编语言形式的源文件
终端中执行编译过程
g++ 的 `-S` 标识指定编译器进行编译
结果生成 `Aerosand.s` 和 `of00_0.s` 文件。
汇编
编译器把编译后的 `.s` 文件转换成可以执行的机器指令,在 Linux 系统下生成 `.o` 文件。
`.o` 代表着 object file 目标文件
终端中执行汇编过程
g++ 的 `-c` 标识指定编译器进行汇编
结果生成 `Aerosand.o` 和 `of00_0.o` 文件。
动态库
当项目中有大量“类”的时候,我们希望某些“类”能固定下来提供某种“方法”,这种“方法”就形成一个可以重复使用的“库”(library)。当其他项目使用这个库的时候,库本身无需再次“预处理”,“编译”以及“汇编”,仅仅和这个项目链接即可。
因为静态库开销大,浪费空间,更新维护困难,所以 OpenFOAM 大量使用动态库,我们这里也只以动态库为例。
动态库在**程序编译**时并不链接到目标代码,而仅仅在**程序运行**时才被链接载入。不同的程序如果调用相同的库,那么内存里只需要一份该动态库的可分享实例,这样就大大减少了空间浪费。此外,因为动态库仅在程序运行时才被链接载入,库的单独维护更新也十分方便。
编译可以对汇编后的 `.o` 目标文件进行整理成动态库,在 Linux 系统下生成 `.so` 文件。
`.so` 代表着 shared object 共享目标
终端中执行生成动态库命令
g++ 的 `-shared` 标识指定生成动态链接库
g++ 的 `-fPIC` 标识指定创建与地址无关的编译程序,为了使得结果可以共享使用
动态库文件以 `lib` 开头
生成 `Aerosand.so` 文件,该文件就是可链接的动态库。
链接
编译器把所有的目标文件以及库文件都组织成一个可以执行的二进制文件
使用 `echo` 命令查看原本动态库链接路径,可以发现并不是项目本地路径。临时指定动态库路径为当前文件夹(临时指定不影响 OpenFOAM 动态库路径的环境配置)。
终端中将主源码目标文件和动态库链接,生成可执行文件
g++ 的 `-L` 标识紧跟指定的库的路径, 使用 `-L.` 表示动态库在当前路径
g++ 的 `-l` 标识紧跟指定的库的名称,使用时省略 `lib` 字段
最终生成 `of00_0` 文件。
需要知道的是,无论是把新开发的库放在其他特定路径下,或者是本项目下,每个项目都可以链接使用这个动态库,只要指定正确的链接路径。这也是动态库“相对独立”“自由链接”的意义所在。
运行
运行结果为

make 实现
上一节的编译过程虽然清晰,但是一步一步的执行十分繁琐。
我们采用 make 来管理我们的开发项目。目前很多人也会进一步的使用 CMake 来管理项目,CMake 更加简洁高效,不过本质上也是基于 make 的。
我们可以为项目提供 makefile 文件通用的描述所有执行步骤。这样只需要简单执行 makefile 文件就可以编译整个项目,大大方便调试运行。此外,上面项目的所有代码文件都放在一起,十分不方便,所以我们对代码进行分类管理。
代码准备
我们建立新项目 `/home/aerosand/aerosand/ofsp/of00_1/` 。文件结构如下:
在我们早期开发中,库在很多情况下都只是某个项目的特定库。所以,我们仍然把包含众多 `类` 形成的 `开发库` 作为一个独立的文件夹放进开放项目内。开发库文件夹内有库自己的 `库 makefile`。项目根目录下有项目主源码,开发库文件夹,以及 `项目 makefile` 。文件结构清晰,层次明确。
因为头文件路径有变化,主源码头文件包含修改为
其他代码的内容保持不变。
库 makefile
库的 makefile 只负责库的编译,目的是得到动态库,方便主程序链接使用。
makefile 文件将编译过程简化成“编译”和“链接”两部分,不再展开“预处理”,“汇编”的过程。编译过程把源代码编译成 `.o` 目标文件或者库文件等中间文件,“链接”过程把中间文件链接成最后可执行程序。
makefile 文件的基本格式为
基于前文对 C++ 项目编译原理的过程的讨论,库 makefile 可以非常直白的写成
顺序重要。先指定动态库的编译,再指定汇编 `.o` 文件的编译。
`<support>` 部分不用包括 `.h` 文件
进入库文件夹,执行 makefile
清理的时候自行删除 `.o` 文件和 `.so` 文件即可。
可以看到库文件夹下生成了我们需要的 `libAerosand.so` 文件。
当代码文件繁多的时候,这么直白的写 makefile 效率低,不利于维护,我们优化 makefile 写法到通用形式
希望读者不要对 makefile 的写法感到担心,需要知道的都写在本文里了,陌生的语法知道可以这么使用即可,不需要花费时间了解原因和原理。
第一段定义了一些命令的宏指代,方便第二段命令的书写。注意获取当前文件夹名称 `NAME` 的写法,获取当前路径下所有源文件 `SOURCE` 的写法。
第二段使用宏指代写了编译命令,本质上和原生写法没有什么区别。
第三段自定义了 `make clean` 命令。注意 `Linux` 系统下常见的类似写法,例如 `*.so` 指代所有的 `.so` 文件。
动态库编译
结果生成了动态库 `userApp/Aerosand/libAerosand.so` 文件(注意路径)。
项目 makefile
我们直接写 makefile 文件
如果主源码没有先生成目标文件,而是通过 `-c` 标识把编译和链接放在一句指令内写完,编译后的文件将无法执行,这是因为 `-c` 标识指定的生成文件不能直接运行。
程序编译(注意要离开库文件夹, cd 到 `userApp/` 下)
注意,makefile 中的 `make run` 部分临时指定了动态库链接路径。
运行结果如下
vscode 插件
对于一般的 C++ 项目,推荐使用 vscode 的插件 `C/C++ Project Generator`,操作步骤如下
1. 使用 `F1` 打开快捷命令输入,使用 `Create C++ project`
2. 弹出窗口中选择到准备好的空白项目文件夹,并打开
3. 在 `src/main.cpp` 中开发主函数代码
4. 在 `include/` 路径下开发头文件的定义,在 `src/` 路径下开发头文件的实现
5. 终端使用命令 `make` 编译此项目,`make run` 编译并运行,`make clean` 清理项目
6. 也可以使用 `./output/main` 直接运行该程序

wmake 实现
OpenFOAM 并不使用 `make` 管理应用,而是使用 `wmake` 管理应用。
在理解了 C++ 项目的 `make` 实现方式之后,我们现在可以更加容易的明白 OpenFOAM 提供的 `wmake` 实现方式。
可以找到 OpenFOAM 的 `wmake` 文件夹。
大概扫一眼文件构成,总的来说, OpenFOAM 基于原生 的 `make` 发展了自己的 `wmake` 。OpenFOAM 使用 `Make/files` 和 `Make/options` 实现 `wmake` 的编译管理。
OpenFOAM 约定源代码后缀为 `.C`,头文件后缀为 `.H` 。需要理解的是,OpenFOAM “**应用层面**” 的 `.H` 文件更多只是为了对主源码按功能进行拆分,方便代码阅读和维护,所以并不是真实的头文件。这和 C++ 开发层面的头文件以及 OpenFOAM “**源码层面**”的头文件(如 `$FOAM_SRC/OpenFOAM/dimensionSet.H`)不同。
下面讨论的都是“源码层面”的头文件,不讨论“应用层面”的头文件。
代码准备
OpenFOAM 中,约定把一个“项目”称为“应用(application)”
建立应用(application)`/home/aerosand/aerosand/ofsp/of00_2/` 。进入该文件夹,写入源代码。
代码内容如下:
Aerosand 库头文件
Aerosand 库源文件
应用主源码
注意每个代码文件的最后都需要留个一个空白行,否则 OpenFOAM 会警告 `parse error`
类的头文件不再需要相对路径
OpenFOAM 提供了 Make 文件来帮助开发,其中
`/Make/files` 指定编译需要的所有源文件和目标路径以及目标文件名称
`/Make/options` 指定 application 或者库所需要包含的头文件、链接的其他库文件(包含路径)
类似把 makefile 文件换成了 OpenFOAM 的 Make 文件
硬链接
硬链接并不需要单独编译库文件,而是将库文件直接包含在主应用上,所以这种情况不需要准备库自己的 Make 文件。
代码文件结构如下
文件 `../userApp/Make/files` 内容如下
此应用需要“包扩”自定义库 Aerosand。
这里使用“包括”而不是“链接”,为了强调 Aerosand 并没有生成自己的独立库文件(如静态库 `.a` 文件或者动态库 `.so` 文件)
文件 `userApp/Make/options` 内容如下
因为前文临时定义了库的链接路径,这里需要可能重启终端,以恢复 OpenFOAM 的环境配置。
执行编译
终端输出信息有三段,对应着应用编译的三个过程。
第一段是自定义的 Aerosand 库编译得到目标文件 `Aerosand.o` (见输出信息的末尾处)
第二段是主源码编译得到目标文件 `of00_2.o` (见输出信息的末尾处)
第三段是链接的过程,链接自定义库或者其他的动态链接,最终生成可执行文件(见输出信息的末尾处)。
我们可以直接运行这个应用
运行结果如下
动态库
在实际的开发中,我们还是要使用动态库来保证内存和效率。
文件结构如下
注意此时的文件结构,库自己单独拥有一套 `库 Make`
库 Make
为库创建 Make 文件
文件 `userApp/Aerosand/Make/files` 内容如下
注意库使用 `LIB` 而不是 `EXE`
库的目标路径结尾是 `LIBBIN`
因为这个 Aerosand 库的实现不再进一步需要链接其他库,所以 `userApp/Aerosand/Make/options` 直接空置即可。
编译这个库
可以通过命令 `cd $FOAM_USER_LIBBIN` 确认 Aerosand 库的编译结果文件 `libAerosand.so` 的所在位置。
因为库内的类可能会有多个,甚至还有其他子库,所以库编译后会同时在库的目录下生成 `lnInclude` 文件夹,`lnInclude` 包含了该库所有类(/子库)的声明( `.H` 文件)或者实现 `.C文件` 的快捷方式,方便后续链接的时候可以提供统一路径。可以参考 OpenFOAM 的 `$FOAM_SRC/OpenFOAM` 库,可以看到根目录下有 `lnInclude` 文件夹,其中包含了此库内的所有类(/子库)的快捷方式。
在终端使用 `find` 命令搜索OpenFOAM源代码文件的时候往往能看到相近的路径 `lnInclude/` 下也有一个同名文件,这个就是快捷方式。我们一般选择打开阅读本体。
应用 Make
因为 Aerosand 库已经被编译成了动态库,所以我们需要在应用的 `Make/options` 文件中为应用提供动态库链接。
修改 `userApp/Make/files` 文件
修改 `userApp/Make/options` 文件
标识符号
`EXE_INC` 指定需要**包含**的库
`-I` 同样用来指定**包含**“`库.H`”,从 `lnInclude` 文件夹取得路径
`EXE_LIB` 指定需要**链接**的库
`-L` 同样用来指定**链接**“`库文件.so`”的路径
`-l` 同样用来指定**链接**“`库文件.so`”的名称(字母是 link 的 l,不是 include 的 i)
一定要注意路径,路径对于成功编译非常重要。一般指定的路径都是从本地出发的(可以从本地路径出发,定义其他库的绝对路径,见下文演示)。
编译运行
编译应用
此时终端信息分为两段,第一段将主源码编译成目标文件,第二段链接动态库到应用,生成可执行文件。
可以通过 `cd $FOAM_USER_APPBIN` 确认应用的可执行程序被编译到了哪里。
运行编译生成的应用程序
运行结果如下
结果和前面各种方式实现的结果相同。

OpenFOAM 类
OpenFOAM 中有大量的类。我们大概了解一些常见的类型/类:
`Switch`
OpenFOAM 为了用户使用简单方便,大量使用 `Switch` 供用户选择。`Switch` 本质上一种布尔类型。用户可以使用 `false` , `off` , `no` , `n` , `f` 表示否定选项,使用 `true` , `on` , `yes` , `y` , `t` 表示肯定选项
`label`
本质上是 OpenFOAM 定义的具有 `INT_SIZE` 大小的 integer data type
`scalar`
本质上是 OpenFOAM 定义的具有 `FLOAT_SIZE` 或者 `DOUBLE_SIZE` 大小的 floating-point data type
`vector`
矢量数据结构
包含重载后的数学运算方法
`tensor`
张量数据结构
包含重载后的数学运算方法
`dimensionedScalar/*Vector/*Tensor`
具有 OpenFOAM 单位系统的量
包含名称、单位和取值等成员数据,以及相应的成员方法
`scalar*/vector*/tensorField`
基础类型的列表值
描述各种场
包含数据和方法
`dimensionSet`
OpenFOAM 的单位系统
`dimensionSet (const scalar mass, const scalar length, const scalar time, const scalar temperature, const scalar moles, const scalar current=0, const scalar luminousIntensity=0)`
`[质量, 长度, 时间, 温度, 摩尔, 电流, 光强]`
OpenFOAM 也提供很多单位的组合 `$FOAM_SRC/OpenFOAM/dimensionSet/dimensionSets.H`
`tmp<>`
OpenFOAM 经常见到的处理临时数据的特殊类
因为 C++ 很多时候的内存都需要手动管理,所以 OpenFOAM 为开发者提供此类
`IOobject`
提供接入 OpenFOAM 各种数据结构的方法,本身没有成员数据
Vector 类
我们拿出 Vector 类看一下 OpenFOAM 对 Vector 类的实现。
找到 Vector 类,进入文件夹,
阅读 `Vector.H` 的介绍可知,该类是模板化的 3D Vector,继承自 VectorSpace 并添加了 3 分量的构造、分量元素的接口函数、点积和叉积的方法等。具体到 `Vector.H` 的代码,可以看到简单的方法在代码中直接实现,复杂方法则大量使用内联函数(inline funtion)以提高代码性能。OpenFOAM 特别提供 `VectorI.H` 文件来写内联函数的实现。该类没有 `.C` 文件,因为所有方法都是通过内联函数实现的。其他的文件夹 `/bools` , `/complex` , `/floats` ,`/ints` ,`/lists` 都是 Vector 类对不同基本类型的 `typedef` 。
我们大概看一下 `Vector.H` 代码
陌生的代码细节可以忽略,或者网上查询相关语法。
比如,基于 3 分量的构造,其内联函数在 `VectorI.H` 中实现如下
`Vector` 后面加后缀 `I` 用来表示是对内联函数的实现
成员数据 `v_` 和重载的运算符 `[]` 来自父类 `VectorSpace`,查看 `$FOAM_SRC/primitives/VectorSpace/VectorSpace.H`
传入函数的参数为 `vx, vy, vz`,并使用**常引用**提高性能,保证数据安全
函数将 3 分量参数对应传入类的成员数据
模板化的函数,类型为 `Cmpt`
如果我们自己写也可以参考这种类的架构
比如,3 分量的接口函数因为简单,就没有内联而直接跟在 `Vector.H` 对应的声明中直接实现,如下
对常量数据和非常量数据定义了两套函数,提高性能
标识符 `noexcept` 直接告诉编译器该函数不会发生异常,利于编译优化
比如,标量积和矢量积的声明和实现分别是
声明如下:
实现如下:
`const` 约束输入参数,需要使用常量 vector
`const` 约束函数体,函数体内部不能修改类的成员数据
另外声明 `v1` 作为本类的成员数据 `v_` 的引用
返回 `v1` 和 `v2` 的计算结果
根据类型 `Cmpt` 的不同,就实现了针对不同类型的方法。比如 `Vector<scalar>` 就是 `scalar` 类型的类实现。比如,在 `/Vector/ints/labelVector.H` 文件中说明了这一点,如下
`Vector.H` 的代码最后还专门提供了 `Traits` 部分来说明该模板类中一些方法的方案。
比如对连续性的方案
如果 `Cmpt` 是连续的,那么 `Vector<Cmpt>` 也是连续的
上面这些代码介绍主要是为了帮助读者慢慢熟悉 C++ 语言在 OpenFOAM 中的使用,克服对 C++ 语言的陌生和恐惧,便于读者理解下文要写的主源码。暂时不需要花费更多时间去阅读更多 OpenFOAM 的源代码,后续会有专门的系列文章阅读和讨论代码。
我们通过一个应用来使用 OpenFOAM 的自有类型/类。
OpenFOAM 提供快捷命令
`foamNewApp` 快速建立应用模板
文件结构如下
**注意,开发库的文件结构与前文稍有不同。因为开发库里可能由好几个类构成,开发库拥有自己的 Make 文件,集中管理多个类**
开发库
头文件 `myClass.H` 如下
源文件 `myClass.C` 如下
库 Make
为开发库写 Make 文件
文件 `userApp/myLib/Make/files` 如下
本次开发库的实现没有用到其他库,所以文件 `userApp/myLib/Make/options` 空置。
将开发库编译成动态库
主源码
如果我们要使用 tensor 类,不仅要包含它的头文件,还需要包含相关的库。
查找 tensor 库
通过 `ls` 命令我们可以看到 `OpenFOAM` 拥有自己的 Make 文件以及 `lnInclude` ,所以 tensor 类属于 OpenFOAM 库。后续我们需要在应用中包含、链接 OpenFOAM 库。
同样的,我们想要使用 `dimensionedTensor` 和 `tensorField`
可以看到这两个类也属于 `OpenFOAM` 库。
我们包含这些库,也包含自己的开发库
主源码如下
应用 Make
文件 `userApp/Make/files` 如下
原则上来说,我们应该在 `userApp/Make/options` 里面链接 `OpenFOAM` 库。实际上,因为 `OpenFOAM` 库是最基本的库,所以,所有的应用都默认隐含的自动链接 `OpenFOAM` 库,无需再显式的写明。
所以,我们只需要在 `userApp/Make/options` 里面链接开发库。
编译运行
运行结果如下
fvCFD.H
在实际开发中,我们还需要用到更多的和 FVM 相关的类来离散求解偏微分方程。OpenFOAM 提供 `fvCFD.H` ,其中包含了大部分和 FVM 相关的头文件,包括 tensor 类等。使用 `fvCFD.H` 可以减少主源码要写的头文件数量。
我们找一下 `fvCFD.H` 文件
显然 `fvCFD.H` 不在 `OpenFOAM 库` 里,而是在 `finiteVolume 库` 里,所以需要另外在 `Make/options` 中包含、链接。
我们将上述主源码中的非开发库头文件全部替换成 `fvCFD.H` 。
此时的 `Make/options` 文件应修改为(当然开发库链接仍然需要有)
`finiteVolume` 被显式的包含、链接
`meshTools` 补充了 `finiteVolume` 中涉及的其他库
重新编译运行,结果相同。

OpenFOAM 应用
在完全理解了编译过程的种种,我们使用 OpenFOAM 提供的工具,串一个较为完整的应用开发流程。
创建应用
在用户文件夹下创建应用项目,进入该应用项目
接着可以拷贝一个简单算例用于开发过程的测试
准备脚本
为了方便开发,可以创建脚本文件,我们使用下划线前缀表明它是一个脚本,区别于 OpenFOAM 原生文件。下面举个例子。
`_appmake.sh` 脚本主要负责应用的编译,暂时写入如下内容
`_appclean.sh` 脚本主要负责应用的清理,暂时写如下内容
`_caserun.sh` 脚本主要是负责应用编译成功后,调试算例的运行,暂时写入如下内容
`_caseclean.sh` 脚本主要是负责清理应用到到编译前状态,如果应用要修改,那么测试算例也要还原到运行前的状态,所以暂时写入如下内容
库的编译清理也可以类似的创建脚本,这里不多赘述。
我们可以简单的运行这些脚本,如下所示
说明文件
为了方便后续开发和使用,还应该准备说明文件。
写入需要的开发备忘、运行步骤、注意事项等内容。请不要吝啬时间,务必花费一点点时间把问题写清楚,不然日后转交他人,或者自己重看,都会十分痛苦。
项目结构
所以,应用的文件结构如下
主源码
打开主源码 `of00.C`
这里我约定
`/* comments */` 写在代码功能块之前,用于正式说明或者介绍此代码功能块
`// comments` 写在具体代码行附近,用于解释、标记或者想法等
代码解释
OpenFOAM 文件头内容
文中均省略处理
`#include "fvCFD.H"`
包含了所有的 FVM 方法,通常必须
`#include "setRootCase.H"`
建立应用的参数列表的类,检查算例文件结构
使用 `find $FOAM_SRC -name setRootCase.H` 去查阅
`#include "createTime.H"`
创建 `time` 类的 `runTime` 对象,需要算例
`#include "creatMesh.H"`
求解器应用需要该头文件
创建 `fvMesh` 类的 `mehs` 对象
`Info`
OpenFOAM 提供的输出语法,适配 OpenFOAM 多种类型
`nl`
OpenFOAM 提供的换行符,和 `endl` 类似
本系列约定,当存在大段输出的时候,中间换行用 `nl` ,结尾处用 `endl`
`runTime.printExecutionTime(Info)`
打印时间相关信息
主源码开发
我们在主代码中添加简单的内容
开发库 `myLibrary` 的写法与上一节同理。这里不再重复讨论,因为本例用不到,所以我们空置处理。
注意,这里演示了如何链接使用其他开发库。
我们没有使用新开发库 `myLibrary` ,调用的依然是上一节的开发库 `myLib` (见 `#include myClass.H` )。所以,我们需要妥善处理链接问题。
应用 Make
默认生成的 `userApp/Make/files` 基础上添加自开发库
注意书写格式,比如标识符、行尾的 `\` 换行
原则上可以一行写完所有语句,虽然有警告,但不是不行
为了方便维护,使用换行符 `\` 来换行,换行符 `\` 前面多加空格无所谓,后面如果有空格会有警告
`EXE_INC`
以 `-I` 标识开头列出所有需要包含的头文件的路径
`$(LIB_SRC)` 是环境变量
可以等同替换为 `-I$(FOAM_SRC)/finiteVolume/lnInclude`
或者直接写成绝对地址 `-I/usr/lib/openfoam/openfoam2306/src/finiteVolume/lnInclude`
开发库的调用是相对地址 `../of00_3/myLib/lnInclude`
`EXE_LIBS`
小写 `-l` 标识列出库的名字
大写 `-L` 标识列出库的路径
查看默认生成的 `userApp/Make/file`,其内容不需要修改。
编译运行
编译并运行该应用
编译时会警告 mesh 未使用,我们确实没有使用,这不影响
运行结果如下
以后的运行结果不再赘述 OpenFOAM 标准输出信息。

小结
到此为止,我们应该清楚明白了 OpenFOAM 应用开发的逻辑和架构。
希望这样的介绍能让读者感受到“清晰”和“连续”。
在实际开发中,开发库可以是用户自定义边界条件,也可以是一些用户工具等等。后续会逐渐讨论OpenFOAM求解器的各个部分。