🎉 Welcome to Aerosand!.
ofsp2024-01

ofsp2024-01

参考:

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

前置: OpenFOAM开发编程基础00 基本实现和开发 | 𝓐𝓮𝓻𝓸𝓼𝓪𝓷𝓭 (aerosand.cn)

这里需要重申 OpenFOAM 初学者的 C++ 学习的个人建议:目前阶段,需要系统学习 C++,需要对 C++ 有基本和完整的认知,暂时不需要深入学习 C++,暂时不需要学习编程算法,不需要等到学完再开始 OpenFOAM。需要不断学习 C++,需要慢慢深入学习 C++,需要在不断的实践中积累 C++ 经验。

在做计算或者其他交互工作的时候,信息流的写入写出是不可避免的。比如从文件中读取计算参数、读取材料性质,又或者是向文件写入计算后的物理场。

所以,在了解应用的基本实现和开发后,我们讨论一下应用的输出输出方法。

本文依然基于 ubuntu22.04 系统,OpenFOAM 2306 版本(和 2212 版本,2206 版本几乎没有什么差别,不用担心)。此系列以后不再赘述。

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

// terminal
cd /home/aerosand/aerosand/ofsp
mkdir 01_IO
cd 01_IO

C++ 实现

C++ 通过输入输出流来实现从文件中读取或者向文件中写入。

文件流

在初学的时候我们就知道 C++ 提供 iostream 标准库,包含 cincout 方法,用于从标准输入中读取信息流,或者从标准输出中写入信息流。除此之外,C++ 还提供 fstream 标准库,用于外部文件和信息流之间的交互。

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

项目实现

通过 vscode 的 C/C++ Project Generater01_IO/ 路径下新建项目 01_01_IO/

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

#include <iostream> // IO标准库
#include <string> // 字符串库
#include <fstream> // 文件流库
#include <cassert> // 异常判断库

int main(int argc, char *argv[])
{
    std::string name;
    int year;
    std::string var[3];

    std::fstream infile; // 定义一个文件流
    infile.open("input.dat",std::fstream::in); // 打开文件并准备读取
    if (!infile) { // 异常判断,如果打开失败,则执行下面
        std::cout << "# WARNING: NO input!" << std::endl;
        assert(infile); // 终止程序并抛出错误信息(频繁调用影响性能)
    }
    infile >> var[0] >> name;
    infile >> var[1] >> year;
    infile.close(); // 必须关闭文件

    std::cout << var[0] << "\t\t@\t" << var[1] << std::endl;
    std::cout << name << "\t@\t" << year << std::endl;


    std::fstream outfile;
    outfile.open("output.dat",std::fstream::out); // 打开文件并准备写入
    outfile << var[0] <<"\t" << name << std::endl;
    outfile << var[1] << "\t" << year << std::endl;
    outfile.close(); // 必须关闭文件

	// 现代 C++ 可以不写 return 0;
}

为了保证文件读取正常,我们需要提供相应的 input.dat 文件,放在项目根目录下即可。文件内容如下

arg0    Aerosand
arg1    2023

终端编译并运行此项目

// terminal
make run

运行结果如下

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!
./output/main
arg0		@	arg1
Aerosand	@	2023
Executing run: all complete!

我们找到输出结果(除了编译语句,还有其他信息提醒有未使用变量,我们确实没有使用,不用在意,以后不再赘述),

arg0		@	arg1
Aerosand	@	2023

同时发现项目根目录下生成了 output.dat 文件,打开看到其中内容为

arg0	Aerosand
arg1	2023

该项目满足我们的要求。

OpenFOAM 实现

OpenFOAM 的应用一般需要从 case 中读取字典、从边界条件库中读取边界数据,向 case 中输出计算结果等等。

OpenFOAM 是怎么实现从文件夹读取和写入的呢?OpenFOAM 的读取和写入更加高级,按关键词进行索引查找的方法直接封装在了相关的类中,直接使用方法即可,暂时不用深究到实现的代码层面。

应用准备

// terminal
foamNewApp 01_02_IO
cd 01_02_IO
cp -r $FOAM_TUTORIALS/incompressible/icoFoam/cavity/cavity debug_case
code .

文件结构如下

|- 01_02_IO/
	|- debug_case/
		|- 0/
		|- constant/
		|- system/
	|- Make/ 
		|- files
		|- options
	|- 01_02_IO.C

脚本和说明

新建脚本

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

_appmake.sh 脚本主要负责应用的编译,暂时写入如下内容

#!/bin/bash

wmake

_appclean.sh 脚本主要负责应用的清理,暂时写如下内容

#!/bin/bash

wclean

_caserun.sh 脚本主要是负责应用编译成功后,调试算例的运行,暂时写入如下内容

#!/bin/bash

blockMesh -case debug_case | tee debug_case/log.mesh
echo "Meshing done."

01_02_IO -case debug_case | tee debug_case/log.run

_caseclean.sh 脚本主要是负责清理应用到到编译前状态,如果应用要修改,那么测试算例也要还原到运行前的状态,所以暂时写入如下内容

#!/bin/bash

wclean
rm -rf debug_case/log.*
foamCleanTutorials debug_case
echo "Cleaning done."

README.md 写入需要说明的内容。

以后除非有特别情况,不再赘述脚本和说明

主源码

主源码如下

#include "fvCFD.H"

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

int main(int argc, char *argv[])
{
    #include "setRootCase.H"
    #include "createTime.H"

    #include "createMesh.H"


    /* 
    * Reading from the dictionary
    */
    const word dictName("customProperties"); // 创建word类型变量保存字典名称
    IOobject dictIO // 创建IOobject类型变量,从参数列表初始化
    (
        dictName, // 字典名称
        runTime.constant(), // 字典位置
        mesh, // 和mesh相关
        IOobject::MUST_READ, // 必须从字典读取
        IOobject::NO_WRITE // 不向字典写入
    );

    if (!dictIO.typeHeaderOk<dictionary>(true))
    {
        FatalErrorIn(args.executable()) << "Cannot open specified dictionary"
            << dictName << exit(FatalError);
    }
    // 如果字典文件在文件头中指定的不是 dictionary 类型,则报错

    dictionary myDictionary;
    myDictionary = IOdictionary(dictIO);
    // 从IOobject变量中创建字典对象

    // 一般使用下面这种紧凑写法
    Info<< "Reading myProperties\n" << endl; 
    IOdictionary myProperties // 字典变量名和字典文件名取相同
    (
        IOobject
        (
            "myProperties",
            runTime.constant(),
            mesh,
            IOobject::MUST_READ,
            IOobject::NO_WRITE
        )
    );

    word solver; // 创建word类型变量
    myProperties.lookup("application") >> solver;
    // 从myProperties文件中查找到关键词,并取值赋给solver

    word format(myProperties.lookup("writeFormat"));
    // 或者写成更紧凑的形式

    scalar timeStep(myProperties.lookupOrDefault("deltaT", scalar(0.01)));
    // 也可以写成
    // scalar timeStep(myProperties.lookupOrDefault<scalar>("deltaT", 0.01));
    // 如果字典中没有提供这一关键词,则使用此句提供的默认值

    bool ifPurgeWrite(myProperties.lookupOrDefault<Switch>("purgeWrite",0));
    // bool类型也可以按关键词查找并读取

    List<scalar> pointList(myProperties.lookup("point"));
    // 列表也可以读取

    HashTable<vector,word> sourceField(myProperties.lookup("source"));
    // 哈希表也可以读取

	vector myVec = vector(myProperties.subDict("subDict").lookup("myVec"));
	// 也可以在字典文件中再使用子字典
	// 注意后文字典文件中的子字典写法

	// 输出读取的内容
    Info<< nl
        << "application: " << solver << nl << nl
        << "writeFormat: " << format << nl << nl
        << "deltaT: " << timeStep << nl << nl
        << "purgeWrite: " << ifPurgeWrite << nl << nl
        << "point: " << pointList << nl << nl
        << "source: " << sourceField << nl << nl
	    << "myVec: " << myVec << nl << nl
        << endl;


    /* 
    * Writing to files
    */
    fileName outputDir = runTime.path()/"processing"; // 创建outputDir变量并赋值路径
    mkDir(outputDir); // 创建上句路径的文件夹

    autoPtr<OFstream> outputFilePtr; // 输出文件流的指针
    outputFilePtr.reset(new OFstream(outputDir/"myOutPut.dat")); // 给指针定向

	// 通过指针给输出文件写入信息
    outputFilePtr() << "processing/myOutPut.dat" << endl;
    outputFilePtr() << "0 1 2 3 ..." << endl;
    sourceField.insert("U3", vector(1, 0.0, 0.0));
    outputFilePtr() << sourceField << endl;

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

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

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

    return 0;
}


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

字典文件

提供字典文件 debug_case/constant/customProperties,该字典没有读取写入操作,只需要写上正确的文件头,内容留空处理。

FoamFile
{
    version     2.0;
    format      ascii;
    class       dictionary;
    location    "constant";
    object      customProperties;
}

字典文件 debug_case/constant/myProperties ,内容如下

FoamFile
{
    version     2.0;
    format      ascii;
    class       dictionary;
    location    "constant";
    object      myProperties;
}

application     icoFoam;

writeFormat     ascii;

purgeWrite      1;

point
(
    0
    1
    2
);

source
(
    U1 (0 0 0)
    U2 (1 0 0)
);

subDict
{
    myVec (0.0 0.0 1.0);
}

编译运行

终端运行

// terminal
sh _appmake.sh
sh _caserun.sh

以后不再赘述简单的执行命令

终端显示结果如下

// terminal

Create time

Create mesh for time = 0

Reading myProperties


application: icoFoam

writeFormat: ascii

deltaT: 0.01

purgeWrite: 1

point: 3(0 1 2)

source: 
2
(
U1 (0 0 0)
U2 (1 0 0)
)

myVec: (0 0 1)


ExecutionTime = 0.01 s  ClockTime = 0 s

End

另外算例文件夹下有了一个新建文件夹 debug_case/processing/,路径下的 myOutPut.dat 内容如下

processing/myOutPut.dat
0 1 2 3 ...

3
(
U1 (0 0 0)
U3 (1 0 0)
U2 (1 0 0)
)

小结

我们同样从最一般的 C++ 基础情况切入,了解了基于文件流的输入输出,也讨论了 OpenFOAM 设计的文件流输入输出方法,以后会不断地使用文件流输入输出。

Last updated on