OpenFOAM开发编程基础01 输入输出
和参考:
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/OSCCAR-doc/blob/master/openFoamUserManual_PFM.pdf https://www.youtube.com/watch?v=KB9HhggUi_E&ab_channel=UCLOpenFOAMWorkshop http://dyfluid.com/# 感谢原作者们的无私引路和宝贵工作。
前置:OpenFOAM开发编程基础00 基本实现和开发 | 𝓐𝓮𝓻𝓸𝓼𝓪𝓷𝓭 (aerosand.cn)
为了消除间断感保持学习连续性,上一节的内容有所更新和勘误。微信无法大量修正,详情移步 aerosand.cn
这一次我们依然从C++开始,了解最基本的文件流输出输出。
这里需要重申OpenFOAM初学者的C++学习的个人建议:目前阶段,需要系统学习C++,需要对C++有基本和完整的认知,暂时不需要深入学习C++,暂时不需要学习编程算法,不需要等到学完再开始OpenFOAM。需要长期学习C++,需要慢慢深入学习C++,需要在不断的实践中积累C++经验。
在做计算或者其他交互工作的时候,信息流的写入写出是不可避免的。比如从文件中读取计算参数、读取材料性质,又或者是向文件写入计算后的物理场。
所以,在了解应用的基本实现和开发后,我们讨论一下应用的输出输出方法。
本文依然基于 ubuntu22.04 系统,OpenFOAM 2306 版本(和 2212 版本,2206 版本几乎没有什么差别,不用担心)。OpenFOAM 11 版本改动较大,会再其他系列讨论。此系列以后不再赘述。
建立本文的项目文件夹并进入
// terminalcd /home/aerosand/aerosand/ofspmkdir 01_IOcd 01_IO
ofsp: OpenFOAM Sharing Programming
本文的后续更新和勘误参见 aerosand.cn,或点击最后的【阅读原文】。
C++ 实现
C++ 通过输入输出流来实现从文件中读取或者向文件中写入。
文件流
在初学的时候我们就知道 C++ 提供 iostream
标准库,包含 cin
和 cout
方法,用于从标准输入中读取信息流,或者从标准输出中写入信息流。除此之外,C++ 还提供 fstream
标准库,用于外部文件和信息流之间的交互。
-
ofstream
表示输出文件流,用于创建文件并向文件写入信息 -
ifstream
表示输入文件流,用于从文件读取信息 -
fstream
表示通用文件流,同时具有写入写出的方法
项目实现
通过 vscode 的 C/C++ Project Generater
在 01_IO/
路径下新建项目 01_01_IO/
。
此插件的简单介绍已更新到上一文章,详见 aerosand.cn
主代码 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 Aerosandarg1 2023
终端编译并运行此项目
// terminalmake run
运行结果如下
g++ -std=c++17 -Wall -Wextra -g -Iinclude -c -MMD src/main.cpp -o src/main.osrc/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 -LlibExecuting all complete!./output/mainarg0 @ arg1Aerosand @ 2023Executing run: all complete!
我们找到输出结果(除了编译语句,还有其他信息提醒有未使用变量,我们确实没有使用,不用在意,以后不再赘述),
arg0 @ arg1Aerosand @ 2023
同时发现项目根目录下生成了 output.dat
文件,打开看到其中内容为
arg0 Aerosand
arg1 2023
该项目满足我们的要求。
OpenFOAM 实现
OpenFOAM 的应用一般需要从 case 中读取字典、从边界条件库中读取边界数据,向 case 中输出计算结果等等。
OpenFOAM 是怎么实现从文件夹读取和写入的呢?OpenFOAM 的读取和写入更加抽象和高级,按关键词进行索引查找的方法直接封装在了相关的类中,直接使用方法即可,暂时不用深究到实现的代码层面。
应用准备
// terminalcd /home/aerosand/aerosand/ofsp/01_IO/foamNewApp 01_02_IOcd 01_02_IOcp -r $FOAM_TUTORIALS/incompressible/icoFoam/cavity/cavity debug_casecode .
文件结构如下
|- 01_02_IO/ |- debug_case/ |- 0/ |- constant/ |- system/ |- Make/ |- files |- options |- 01_02_IO.C
脚本和说明
新建脚本
// terminalcode _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"));
// 哈希表也可以读取(无所谓什么是哈希表,没有关系)
// 输出读取的内容
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
<< 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)
);
编译运行
终端运行
// terminalsh _appmake.shsh _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)
)
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 设计的文件流输入输出方法,以后会不断地使用文件流输入输出。
文章来源: Aerosand