08_io
0. 前言
通过第一阶段的 8 篇讨论,相信读者已经对 OpenFOAM 的项目架构和编译原理已经有了一定的理解。接下来我们进一步讨论求解器项目必须的一些基础。
Tip
需要重申目前阶段 OpenFOAM 初学者的 C++ 学习的个人建议。
- 需要做的是: 先学习 C++ 面向对象基础,然后不断学习 C++,逐渐深入,在不断的实践中积累 C++ 经验。
- 不需要做: 暂时不需要掌握 C++ 高级特性,暂时不需要学习复杂算法,不能等学完 C++ 再开始 OpenFOAM。
本文主要讨论
- 了解 OpenFOAM 的信息写入写出
- 实现 C++ 方式的信息写入写出
- 编译运行 io 项目
1. 参考 OF 的写入写出
我们知道 C++ 可以通过输入输出流来实现信息的写入写出,下面我们查看一下 OpenFOAM 写入写出的数据格式。
终端输入命令,前往 run
文件夹路径
run
如果终端提示缺少此文件夹
终端输入命令,创建 run
文件夹,并来到此文件夹路径
mkdir -p $WM_PROJECT_USER_DIR/run
run
终端输入命令,拷贝原生算例 cavity
到 run
文件夹路径
cp -r $FOAM_TUTORIALS/incompressible/icoFoam/cavity/cavity .
终端输入命令,通过 vscode 打开算例,查看其中的文件
code cavity
1.1. 写入文件
在 vscode 终端中输入命令,查看算例的文件结构以及初始文件
tree
终端输入如下
.
├── 0
│ ├── p
│ └── U
├── constant
│ └── transportProperties
└── system
├── blockMeshDict
├── controlDict
├── decomposeParDict
├── fvSchemes
├── fvSolution
└── PDRblockMeshDict
文件解释如下
- 文件夹
0/
是算例的初始场文件(即时间为 0 的时间步)- 比如包含此算例的压力场 p 和速度场 U
- 均包含相应的内部场和边界条件
- 文件夹
constant/
是几何、物理等相关的写入字典- 这里的“字典”是 OpenFOAM 的写入文件格式,可以简单理解成 OpenFOAM 的写入文件
- 比如包含
transportProperties
文件,一般指定流体的粘度、扩散系数等物理参数 - 稍后生成的网格信息文件将保存在此文件夹
- 文件夹
system/
是控制计算的写入字典- 比如包含
blockMeshDict
文件,指定稍后网格生成的参数 - 比如包含
controlDict
文件,指定时间步长、起止时间等参数 - 比如包含
fvSchemes
文件,指定空间时间的离散格式等 - 比如包含
fvSolution
文件,指定求解器选择、松弛因子等 - 其他字典文件暂不深究
- 比如包含
使用 vscode 查看 transportProperties
字典
FoamFile // FoamFile{}描述了文件的元信息,帮助OpenFOAM正确读取和识别字典文件
{
version 2.0; // 文件格式版本
format ascii; // 数据存储格式,ascii为可读文件,binary为更快的二进制
class dictionary; // 字典类
object transportProperties; // 对应对象的名字,一般和文件名一致
}
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
nu 0.01;
// 非可压缩流体的运动粘度,单位[m^2.s^-1]
// 如果是可压缩流体,一般使用动力粘度 mu = nu*rho,单位 [kg.m^-1.s^-1]
使用 vscode 查看 controlDict
字典
FoamFile
{
version 2.0;
format ascii;
class dictionary;
object controlDict;
}
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
application icoFoam; // 应用名称
startFrom startTime;
// 以什么开始计算,可选
// - startTime,以startTime指定的时间作为开始
// - latestTime,从最新时间步结果开始计算
startTime 0; // 指定起始时间为0秒,即 0/ 文件夹
stopAt endTime;
// 以什么停止计算,可选
// - endTime,以endTime指定的时间作为停止
// - writeNow, 立即写出结果并停止
// - noWriteNow,立即停止但不写出结果
endTime 0.5; // 指定停止时间为0.5秒
deltaT 0.005; // 时间步长为0.005秒
writeControl timeStep;
// 写出控制,可选
// - timeStep,每隔固定时间步
// - runTime,每个固定物理时间
// - adjustableRunTime,自动调节(多用于并行)
writeInterval 20; // 写出间隔,这里即20个时间步写出一次
purgeWrite 0; // 是否覆盖写出,这里为否
writeFormat ascii;
// 写出格式,可选
// - ascii,文本文件(可读)
// - binary,二进制(更快更小但不可读)
writePrecision 6; // 写出数据的精确度(有效数字位数)
writeCompression off; // 是否压缩写出文件
timeFormat general;
// 写出时间文件夹命名格式,可选
// - general,常规(默认科学记数法)
// - fixed,固定位数(配合timePrecision指定)
timePrecision 6; // 时间输出精度
runTimeModifiable true; // 是否允许在计算中修改字典文件
简单来说,原生算例里的所有文件(包括稍后其中生成的网格信息)都是写入文件,其中的信息将传输进入 OpenFOAM 的应用。
1.2. 写出文件
我们快速计算该算例
终端输入命令,划分网格并计算
blockMesh
icoFoam
终端输入命令,查看计算后的文件结构
tree -L 2
.
├── 0
│ ├── p
│ └── U
├── 0.1
│ ├── p
│ ├── phi
│ ├── U
│ └── uniform
...
├── 0.5
│ ├── p
│ ├── phi
│ ├── U
│ └── uniform
├── constant
│ ├── polyMesh
│ └── transportProperties
└── system
├── blockMeshDict
├── controlDict
├── decomposeParDict
├── fvSchemes
├── fvSolution
└── PDRblockMeshDict
可以看到计算后,结果以字典中指定的方式 0.005*20=0.1s
,每隔 0.1 秒存储一次,直到 0.5 秒停止。保存的内容也由求解器确定,比如 phi
等,暂不深究。另外也能看到网格划分后保存的网格信息 polyMesh
文件夹。
我们以 0.5/
文件夹中的速度场结果为例,查看其数据格式
FoamFile
{
version 2.0;
format ascii;
arch "LSB;label=32;scalar=64"; // 生成计算结果的机器依赖信息
class volVectorField; // volVectorField 类
location "0.5";
object U;
}
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
dimensions [0 1 -1 0 0 0 0]; // 速度场的单位 [m.s^-1]
internalField nonuniform List<vector> // 内部场信息,内部场数据格式
400 // 内部场的数据点的总数量
(
(0.000253405 -0.000250456 0) // 三个方向的速度值构成该点的速度矢量
(0.000141206 0.000111424 0)
(-0.00117704 0.000564623 0)
(-0.00348128 0.00088502 0)
...
)
;
boundaryField // 边界场
{
movingWall
{
type fixedValue;
value uniform (1 0 0);
}
fixedWalls
{
type noSlip;
}
frontAndBack
{
type empty;
}
}
Tip
整个过程即:
- 从外部文件向项目写入信息
- 项目运行(计算)
- 从项目向外部文件写出信息
2. C++ 方式实现写入写出
在初学的时候我们就知道 C++ 提供 iostream
标准库,包含 cin
和 cout
方法,用于从标准输入中读取信息流,或者从标准输出中写入信息流。除此之外,C++ 还提供 fstream
标准库,用于外部文件和信息流之间的交互。
ofstream
表示输出文件流,用于创建文件并向文件写入信息ifstream
表示输入文件流,用于从文件读取信息fstream
表示通用文件流,同时具有写入写出的方法
我们使用 C++ 的方式去实现写入写出。
2.1. 项目准备
终端输入命令,建立本文项目
ofsp
通过 vscode 的 C/C++ Project Generater
打开此项目。
终端输入命令,测试项目模板
make run
可以看到项目运行结果,说明项目初始模板没有问题。
2.2. 主源码
我们通过该项目模拟求解器读取算例字典文件的参数。
主源码 ofsp_09_io/src/main.cpp
内容如下
#include <iostream> // IO标准库
#include <string> // 字符串库
#include <fstream> // 文件流库
#include <cassert> // 异常判断库
int main(int argc, char *argv[])
{
// 给外部参数准备变量
double viscosity;
std::string application;
double deltaT;
int writeInterval;
bool purgeWrite;
std::string var[5];
std::fstream infile; // 定义一个文件流
infile.open("ofspProperties", std::fstream::in); // 打开文件并准备读取
if (!infile) { // 异常判断,如果打开失败,则执行下面
std::cout << "# WARNING: NO input!" << std::endl;
assert(infile); // 终止程序并抛出错误信息(频繁调用影响性能)
}
infile >> var[0] >> viscosity;
infile >> var[1] >> application;
infile >> var[2] >> deltaT;
infile >> var[3] >> writeInterval;
infile >> var[4] >> purgeWrite;
infile.close(); // 必须关闭文件
std::cout << std::endl
<< var[0] << "\t\t" << viscosity << std::endl
<< var[1] << "\t" << application << std::endl
<< var[2] << "\t\t" << deltaT << std::endl
<< var[3] << "\t" << writeInterval << std::endl
<< var[4] << "\t" << purgeWrite << std::endl
<< std::endl;
// 模拟速度场的计算
int nCells = 4;
int dimension = 3;
double initVelocity = 0.1;
double fieldU[nCells][dimension] = {0};
for (int i = 0; i < nCells; ++i)
{
for (int j = 0; j < dimension; ++j)
{
fieldU[i][j] = initVelocity * i * deltaT;
}
}
// 输出计算得到的速度场
std::fstream outfile;
outfile.open("U", std::fstream::out); // 打开文件并准备写入
outfile << "internalField\tnonuniform List<vector>" << std::endl;
outfile << nCells << "\n(" << std::endl;
for (int i = 0; i < nCells; ++i)
{
outfile << "( ";
for (int j = 0; j < dimension; ++j)
{
outfile << fieldU[i][j] << " ";
}
outfile << ")\n";
}
outfile << ")\n;";
outfile.close(); // 必须关闭文件
// 现代 C++ 可以不写 return 0;
}
2.3. 说明文件
因为目前的项目较为简单,读者可以根据自己的需要准备说明文件。不再赘述。
2.4. 编译运行
终端输入命令,检查编译
make
终端输出命令
g++ -std=c++17 -Wall -Wextra -g -Iinclude -c -MMD src/main.cpp -o src/main.o
src/main.cpp: In function ‘int main(int, char**)’:
src/main.cpp:6:14: warning: unused parameter ‘argc’ [-Wunused-parameter]
6 | int main(int argc, char *argv[])
| ~~~~^~~~
src/main.cpp:6:26: warning: unused parameter ‘argv’ [-Wunused-parameter]
6 | int main(int argc, char *argv[])
| ~~~~~~^~~~~~
g++ -std=c++17 -Wall -Wextra -g -Iinclude -o output/main src/main.o -Llib
Executing all complete!
可以看到终端输出的信息提醒 warning: unused parameter ‘argc’
,我们确实没有使用 argc
,所以不用在意。最后提醒 Executing all complete!
表示编译成功,没有问题。
我们在该项目的根目录下提供类似 OpenFOAM 字典的文件。ofspProperties
文件内容如下(路径为 /ofsp/ofsp_09_io/ofspProperties
)
nu 0.01
application laplacianFoam
deltaT 0.005
writeInterval 20
purgeWrite 0
终端输入命令,运行此项目
./output/main
或者重新编译并运行
make clean
make run
终端输出信息如下
nu 0.01
application laplacianFoam
deltaT 0.005
writeInterval 20
purgeWrite 0
我们可以看到每一项外部文件的关键词后的参数都被读入了程序,意味着这些参数都可以参与到项目的计算中,项目最终将结果写出。
写出的文件 U
同样在项目根目录下,内容如下
internalField nonuniform List<vector>
4
(
( 0 0 0 )
( 0.0005 0.0005 0.0005 )
( 0.001 0.001 0.001 )
( 0.0015 0.0015 0.0015 )
)
;
格式和 OpenFOAM 的输出速度场的格式类似。
3. 小结
通过这个项目,我们可以简单理解, OpenFOAM 求解器是如何通过字典文件拿到参数指定的值。这些参数参与项目计算。之后,项目将计算结果写出到外部文件。写出的文件本质上是符合 OpenFOAM 格式要求,符合后处理软件格式要求,等待进一步分析的数据文件。
本文完成讨论
- 了解 OpenFOAM 的信息写入写出
- 实现 C++ 方式的信息写入写出
- 编译运行 io 项目