🎉 Welcome to Aerosand!.

08_io

0. 前言

通过第一阶段的 8 篇讨论,相信读者已经对 OpenFOAM 的项目架构和编译原理已经有了一定的理解。接下来我们进一步讨论求解器项目必须的一些基础。

Tip

需要重申目前阶段 OpenFOAM 初学者的 C++ 学习的个人建议。

  • 需要做的是: 先学习 C++ 面向对象基础,然后不断学习 C++,逐渐深入,在不断的实践中积累 C++ 经验。
  • 不需要做: 暂时不需要掌握 C++ 高级特性,暂时不需要学习复杂算法,不能等学完 C++ 再开始 OpenFOAM。

本文主要讨论

  • 了解 OpenFOAM 的信息写入写出
  • 实现 C++ 方式的信息写入写出
  • 编译运行 io 项目

1. 参考 OF 的写入写出

我们知道 C++ 可以通过输入输出流来实现信息的写入写出,下面我们查看一下 OpenFOAM 写入写出的数据格式。

终端输入命令,前往 run 文件夹路径

terminal
run

如果终端提示缺少此文件夹

终端输入命令,创建 run 文件夹,并来到此文件夹路径

terminal
mkdir -p $WM_PROJECT_USER_DIR/run
run

终端输入命令,拷贝原生算例 cavityrun 文件夹路径

terminal
cp -r $FOAM_TUTORIALS/incompressible/icoFoam/cavity/cavity .

终端输入命令,通过 vscode 打开算例,查看其中的文件

terminal
code cavity

1.1. 写入文件

在 vscode 终端中输入命令,查看算例的文件结构以及初始文件

terminal
tree

终端输入如下

terminal
.
├── 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 字典

cavity/constant/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 字典

cavity/system/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. 写出文件

我们快速计算该算例

终端输入命令,划分网格并计算

terminal
blockMesh
icoFoam

终端输入命令,查看计算后的文件结构

terminal
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/ 文件夹中的速度场结果为例,查看其数据格式

cavity/0.5/U
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 标准库,包含 cincout 方法,用于从标准输入中读取信息流,或者从标准输出中写入信息流。除此之外,C++ 还提供 fstream 标准库,用于外部文件和信息流之间的交互。

  • ofstream 表示输出文件流,用于创建文件并向文件写入信息
  • ifstream 表示输入文件流,用于从文件读取信息
  • fstream 表示通用文件流,同时具有写入写出的方法

我们使用 C++ 的方式去实现写入写出。

2.1. 项目准备

终端输入命令,建立本文项目

terminal
ofsp

通过 vscode 的 C/C++ Project Generater 打开此项目。

终端输入命令,测试项目模板

terminal
make run

可以看到项目运行结果,说明项目初始模板没有问题。

2.2. 主源码

我们通过该项目模拟求解器读取算例字典文件的参数。

主源码 ofsp_09_io/src/main.cpp 内容如下

/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. 编译运行

终端输入命令,检查编译

terminal
make

终端输出命令

terminal
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

/ofspProperties
nu                  0.01
application         laplacianFoam
deltaT              0.005
writeInterval       20
purgeWrite          0

终端输入命令,运行此项目

terminal
./output/main

或者重新编译并运行

terminal
make clean
make run

终端输出信息如下

terminal
nu              0.01
application     laplacianFoam
deltaT          0.005
writeInterval   20
purgeWrite      0

我们可以看到每一项外部文件的关键词后的参数都被读入了程序,意味着这些参数都可以参与到项目的计算中,项目最终将结果写出。

写出的文件 U 同样在项目根目录下,内容如下

/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 项目
Last updated on