参考:
感谢原作者们的无私引路和宝贵工作。
前置:
OpenFOAM Sharing Programming 开发编程基础00 基本实现和开发 | 𝓐𝓮𝓻𝓸𝓼𝓪𝓷𝓭
这里需要重申 OpenFOAM 初学者的 C++ 学习的个人建议:目前阶段,需要系统学习 C++ 面向对象基础,暂时不需要掌握 C++ 高级特性,暂时不需要学习编程算法,不需要等到学完 C++ 再开始 OpenFOAM。需要不断学习 C++,需要慢慢深入学习 C++,需要在不断的实践中积累 C++ 经验。
在做计算或者其他交互工作的时候,信息流的写入写出是不可避免的。比如从文件中读取计算参数、读取材料性质,又或者是向文件写入计算后的物理场。
所以,在了解应用的基本实现和开发后,我们紧接着讨论一下程序的输出输出。
本文依然基于 ubuntu 24.04 系统,OpenFOAM 2406 版本(和 2212 版本,2206 版本几乎没有什么差别,不用担心)。此系列以后不再赘述。
C++ 实现
我们已经知道 C++ 可以通过输入输出流来实现从文件中读取或者向文件中写入。
拷贝标准算例到 run
文件夹下。(如果使用 run
指令无效的,参考 OpenFOAM环境准备 | 𝓐𝓮𝓻𝓸𝓼𝓪𝓷𝓭 建立相应文件夹 )
1 2 3 // terminal run cp -r $FOAM_TUTORIALS/incompressible/icoFoam/cavity/cavity .
打开该算例,查看 constant/transportProperties
文件和 system/controlDict
文件,可以看到每个关键词都对应着用户给定的值,这些关键词后的值在求解过程中,当然也会被程序读入。
阅读 transportProperties
文件和 controlDict
文件的文件头,可以明白,这两个文件其实都是“字典”文件。虽然两者向主程序输入参数的机制不同,但是我们仍然先行简单理解向主程序输入参数的主要思路。这样也可以在入门阶段更好理解字典文件的意义。
字典的格式如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 // transportProperties nu 0.01; // controlDict ... application icoFoam; startFrom startTime; startTime 0; stopAt endTime; ...
计算该算例
1 2 3 4 5 // terminal blockMesh icoFoam // use foamCleanTutorials to clean case
可以很快得到计算结果,我们打开最终时间步的速度场(0.5/U
),查看输出的格式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 dimensions [0 1 -1 0 0 0 0 ]; 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 ) ... ) ; ...
下面,我们尝试讨论和理解这个过程,即
从外部文件向主程序输入参数
从主程序向外部文件输出结果
文件流
在初学的时候我们就知道 C++ 提供 iostream
标准库,包含 cin
和 cout
方法,用于从标准输入中读取信息流,或者从标准输出中写入信息流。除此之外,C++ 还提供 fstream
标准库,用于外部文件和信息流之间的交互。
ofstream
表示输出文件流,用于创建文件并向文件写入信息
ifstream
表示输入文件流,用于从文件读取信息
fstream
表示通用文件流,同时具有写入写出的方法
具体看一下怎么使用文件流。
项目准备
建立本文的项目文件夹
1 2 3 // terminal ofsp mkdir ofsp_01_IO_cpp
通过 vscode 的 C/C++ Project Generater
打开新项目 ofsp_01_IO_cpp
。
项目实现
我们通过该项目模拟求解器读取算例的字典文件的参数。
主源码 src/main.cpp
如下所示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 #include <iostream> #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 (); }
我们同样为该项目提供模仿字典的文件。ofspProperties
文件内容如下(路径为 /ofsp/ofsp_01_IO_cpp/ofspProperties
)
1 2 3 4 5 nu 0.01 application laplacianFoam deltaT 0.005 writeInterval 20 purgeWrite 0
编译运行
终端编译并运行此项目
1 2 3 // terminal make clean make run
运行结果如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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 nu 0.01 application laplacianFoam deltaT 0.005 writeInterval 20 purgeWrite 0 Executing run: all complete!
我们可以看到每一项外部文件的关键词后的参数都被读入了程序,可以在程序中操作或输出。
同时发现项目根目录下生成了 U
文件,打开看到其中内容为
1 2 3 4 5 6 7 8 9 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 的输出速度场的格式类似。
通过这个项目,我们可以简单理解, OpenFOAM 求解器是如何通过字典文件拿到参数指定的值。求解器输出的计算结果呢,本质上是 OpenFOAM 可以识别,后处理软件可以识别的,有特定排版格式的文件。
重构项目
让我们停下脚步,仔细想一下上面的项目。我们很难称它是按关键词读取,因为代码只是实现了读取。如果我们更改输入文件的关键词顺序,就会导致传入参数的不一致(读者可以尝试更换 ofspProperties 文件中关键词顺序,重新运行代码,查看结果)。
我们来考虑一下如何真正的按关键词读取。
OF 原生读参
我们看一下 OpenFOAM 中一些简单类型的参数的读取语句是什么样子的。
OpenFOAM 提供 lookup()
函数来按关键词读取,类似于如下形式
1 2 3 4 5 6 7 8 9 10 11 12 13 IOdictionary transportProperties ( IOobject ( "transportProperties" , runTime.constant(), mesh, IOobject::MUST_READ_IF_MODIFIED, IOobject::NO_WRITE ) ) ;double viscosity = transportProperties.lookup ("nu" );
可以看到 OpenFOAM 的代码,基于 IOobject
实体,构造了 IOdictionary
类的 transportProperties
实体,而 IOobject
则是基于文件 "transportProperties"
和其他参数构造的。而 transportProperties
使用属于的 IOdictionary
类的方法 lookup()
指定关键词 "nu"
来读取此关键词后的参数值。虽然这个语法在 OpenFOAM 中有点过时,但是相对来说非常直观。
现在更多的使用 dimensionedScalar viscosity("nu", transportProperties)
这种写法,更加的面向对象,而非面向过程。
简单概括如下,
IOdictionary
是和文件读取相关的类
IOdictionary
基于外部文件 transportProperties
构造实体 transportProperties
实体 transportProperties
具有 IOdictionary
类的方法
也就是说,实体 transportProperties
可以使用 lookup()
方法
我们尝试自己动手模仿,实现 OpenFOAM 的这个读取功能。
项目准备
建立本项目文件夹
1 2 3 // terminal ofsp mkdir ofsp_01_IO_keyword
整体的架构如下
1 2 3 4 5 6 7 8 9 10 11 12 ofsp_01_IO_keyword/ ├── IOdictionary/ │ ├── IOdictionary.C │ ├── IOdictionary.H │ └── Make/ │ ├── files │ └── options ├── Make/ │ ├── files │ └── options ├── ofsp_01_IO_keyword.C └── ofspProperties
自定义字典文件 ofspProperties
中提供和上节项目相同的内容。
自定义 IOdictionary
我们设想,自定义的 IOdictionary
类也可以有类似 OpenFOAM 的语法表达。简化的形式如下
1 2 3 4 5 6 7 IOdictionary ofspProperties ( "ofspProperties" ); double viscosity = ofspProperties.lookup ("nu" );
其中,ofspProperties
是 IOdictionary
类的实例化。
可以看到,这个自定义的 IOdictionary
类需要读取外部文件,所以我们考虑仍然要使用 C++ 原生 fstream
类。
在类的声明的时候,我们也可以定义一个自己的命名空间,避免错误调用。
类的声明 IOdictionary.H
内容如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #pragma once #include <iostream> #include <fstream> #include <string> namespace Aerosand { class IOdictionary { private : std::ifstream dictionary_; std::string filePath_; public : IOdictionary (const std::string& filePath); ~IOdictionary (); std::string lookup (const std::string& keyword) ; }; }
类的定义 IOdictionary.C
内容如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 #include "IOdictionary.H" Aerosand::IOdictionary::IOdictionary (const std::string& filePath) { filePath_ = filePath; } Aerosand::IOdictionary::~IOdictionary () { if (dictionary_.is_open ()) { dictionary_.close (); } } std::string Aerosand::IOdictionary::lookup (const std::string& keyword) { dictionary_.open (filePath_); std::string returnValue; std::string line; while (std::getline (dictionary_, line)) { size_t keywordPos = line.find (keyword); if (keywordPos != std::string::npos) { returnValue = line.substr (keywordPos + keyword.length ()); } size_t spacePos = returnValue.find (" " ); while (spacePos != std::string::npos) { returnValue.erase (spacePos, 1 ); spacePos = returnValue.find (" " ); } } dictionary_.close (); return returnValue; }
库 Make
文件 IOdictionary/Make/files
内容如下
1 2 3 IOdictionary.C LIB = $(FOAM_USER_LIBBIN)/libIOdictionary
文件 IOdictionary/Make/options
留空。
编译生成动态库
1 2 3 // terminal wclean IOdictionary wmake IOdictionary
主源码
重写主源码,内容如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <iostream> #include <fstream> #include "IOdictionary.H" int main (int argc, char const *argv[]) { Aerosand::IOdictionary ofspProperties ("ofspProperties" ) ; std::string viscosity = ofspProperties.lookup ("nu" ); std::string application = ofspProperties.lookup ("application" ); std::string deltaT = ofspProperties.lookup ("deltaT" ); std::string writeInterval = ofspProperties.lookup ("writeInterval" ); std::string purgeWrite = ofspProperties.lookup ("purgeWrite" ); std::cout << "nu\t\t" << viscosity << std::endl; std::cout << "application\t" << application << std::endl; std::cout << "deltaT\t\t" << deltaT << std::endl; std::cout << "writeInterval\t" << writeInterval << std::endl; std::cout << "purgeWrite\t" << purgeWrite << std::endl; return 0 ; }
项目 Make
文件 ofsp_01_IO_keyword/Make/files
内容如下
1 2 3 4 ofsp_01_IO_keyword.C EXE = $(FOAM_USER_APPBIN)/ofsp_01_IO_keyword
文件 ofsp_01_IO_keyword/Make/options
内容如下
1 2 3 4 5 6 7 EXE_INC = \ -IIOdictionary/lnInclude EXE_LIBS = \ -L$(FOAM_USER_LIBBIN) \ -lIOdictionary
编译运行
终端执行
1 2 3 4 5 // terminal wclean wmake ofsp_01_IO_keyword
终端输出结果如下
1 2 3 4 5 nu 0.01 application laplacianFoam deltaT 0.005 writeInterval 20 purgeWrite 0
即使我们更换字典文件中内容的顺序,或者增加新的关键词,都不会影响该项目按关键词读入取值。
重构项目的思路基本正确。但是读取得到的参数类型统一是 string
类型,不符合我们的要求,我们需要进一步开发功能特性。
开发特性
这里有一些作者关于版本存档的建议(暂不使用 git 控制版本)。
当对项目有新的想法增加或者改进的时候,建议拷贝至项目,在新项目上进行修改和开发,保留原始文件以便回滚版本。
项目准备
建立本项目文件夹
1 2 3 4 5 6 7 8 9 // terminal ofsp cp -r ofsp_01_IO_keyword ofsp_01_IO_feature cd ofsp_01_IO_feature wclean IOdictionary wclean mv ofsp_01_IO_keyword.C ofsp_01_IO_feature.C sed -i s/"IO_keyword"/"IO_feature"/g Make/files
整体的架构如下
1 2 3 4 5 6 7 8 9 10 11 12 ofsp_01_IO_feature/ ├── IOdictionary/ │ ├── IOdictionary.C │ ├── IOdictionary.H │ └── Make/ │ ├── files │ └── options ├── Make/ │ ├── files │ └── options ├── ofsp_01_IO_feature.C └── ofspProperties
开发库和主程序的 Make
参考上节项目进行修改。
自定义字典文件 ofspProperties
中提供和上节项目相同的内容。
区分参数类型
当我们拿到 string
类型的返回值后,我们需要把该返回值转换成我们需要的类型。
开发设计思路
考虑函数 lookup()
需要读入各种数据类型,以及要返回给主程序各种数据类型,我们尝试使用 C++ 的模板 template
进行开发。
代码改进
改进自定义开发类 IOdictionary
,声明和定义如下
类的声明 IOdictionary.H
内容如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 #pragma once #include <iostream> #include <fstream> #include <string> #include <sstream> namespace Aerosand { class IOdictionary { private : std::ifstream dictionary_; std::string filePath_; public : IOdictionary (const std::string& filePath); ~IOdictionary (); private : template <typename T> T convertFromString (const std::string& str) ;public : template <typename T> T lookup (const std::string& keyword) ;}; template <typename T>T Aerosand::IOdictionary::convertFromString (const std::string& str) { T value; std::istringstream iss (str) ; iss >> value; return value; } template <typename T>T Aerosand::IOdictionary::lookup (const std::string& keyword) { dictionary_.open (filePath_); T returnValue; std::string line; std::string valueLine; while (std::getline (dictionary_, line)) { size_t keywordPos = line.find (keyword); if (keywordPos != std::string::npos) { valueLine = line.substr (keywordPos + keyword.length ()); size_t spacePos = valueLine.find (" " ); while (spacePos != std::string::npos) { valueLine.erase (spacePos, 1 ); spacePos = valueLine.find (" " ); } } } dictionary_.close (); returnValue = convertFromString <T>(valueLine); return returnValue; } }
这里需要注意,因为模板的编译机制不同,我们把类内模板函数的定义和声明放在了同一个文件,分开会导致主程序找不到函数的实例化对象。因为涉及到了更多的 C++ 知识,这里暂不深究,知道这么处理就可以了。
类的定义 IOdictionary.C
内容相应的修改为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include "IOdictionary.H" Aerosand::IOdictionary::IOdictionary (const std::string& filePath) { filePath_ = filePath; } Aerosand::IOdictionary::~IOdictionary () { if (dictionary_.is_open ()) { dictionary_.close (); } }
由此以来,主源码 ofsp_01_IO_feature.C
的写法如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <iostream> #include <fstream> #include <string> #include "IOdictionary.H" int main (int argc, char const *argv[]) { Aerosand::IOdictionary ofspProperties ("ofspProperties" ) ; double viscosity = ofspProperties.lookup <double >("nu" ); std::string application = ofspProperties.lookup <std::string>("application" ); double deltaT = ofspProperties.lookup <double >("deltaT" ); int writeInterval = ofspProperties.lookup <int >("writeInterval" ); bool purgeWrite = ofspProperties.lookup <bool >("purgeWrite" ); std::cout << "nu\t\t" << viscosity << std::endl; std::cout << "application\t" << application << std::endl; std::cout << "deltaT\t\t" << deltaT << std::endl; std::cout << "writeInterval\t" << writeInterval << std::endl; std::cout << "purgeWrite\t" << purgeWrite << std::endl; std::cout << "\nWrite time interval = " << deltaT * writeInterval << std::endl; return 0 ; }
测试
编译运行
1 2 3 4 5 // terminal wmake IOdictionary wmake ofsp_01_IO_feature
结果如下
1 2 3 4 5 6 7 nu 0.01 application laplacianFoam deltaT 0.005 writeInterval 20 purgeWrite 0 Write time interval = 0.1
可以看到读取功能正常,并且通过计算验证了读取的参数值的类型也是正确的。
风格和注释和报错
有了前文对字符串的处理的思路,我们可以进行自定义类的风格开发设计。
开发设计思路
风格的设计参考 C++,每行末尾加 ;
表示语句结束,如下所示
注释的设计参考 C++ ,有以下两种形式
1 2 3 4 nu 0.01 ; deltaT 0.005 ;
报错的设计简单考虑有以下几种情况
字典文件不存在或者文件名称错误,应该终止程序并输出对应的报错信息
字典中的关键词找不到,应该终止程序并输出对应的报错信息
字典中的关键词存在,但是没有给定值,应该终止程序并输出对应的报错信息
代码改进
自定义类 IOdictionary
需要进一步改进。
类的声明 IOdictionary.H
的内容如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 #pragma once #include <iostream> #include <fstream> #include <string> #include <sstream> namespace Aerosand { class IOdictionary { private : std::ifstream dictionary_; std::string filePath_; public : IOdictionary (const std::string& filePath); ~IOdictionary (); private : template <typename T> T convertFromString (const std::string& str) ;public : template <typename T> T lookup (const std::string& keyword) ;}; template <typename T>T Aerosand::IOdictionary::convertFromString (const std::string& str) { T value; std::istringstream iss (str) ; iss >> value; return value; } template <typename T>T Aerosand::IOdictionary::lookup (const std::string& keyword) { dictionary_.open (filePath_); T returnValue; bool foundKeyword = false ; std::string line; std::string valueLine; while (std::getline (dictionary_, line)) { if (line.find ("//" ) == 0 ) { continue ; } size_t keywordPos = line.find (keyword); if (keywordPos != std::string::npos) { foundKeyword = true ; valueLine = line.substr (keywordPos + keyword.length ()); if (valueLine.find (";" )) { valueLine = valueLine.substr (0 ,valueLine.find (";" )); } if (valueLine.find ("//" ) != std::string::npos) { valueLine = valueLine.substr (0 , valueLine.find ("//" )); } size_t spacePos = valueLine.find (" " ); while (spacePos != std::string::npos) { valueLine.erase (spacePos, 1 ); spacePos = valueLine.find (" " ); } if (valueLine.empty ()){ std::cerr << "\n--> AEROSAND FATAL ERROR: " << std::endl << "cannot find value of " << keyword << std::endl << std::endl; exit (1 ); } } } if (!foundKeyword) { std::cerr << "\n--> AEROSAND FATAL ERROR: " << std::endl << "cannot find keyword " << keyword << std::endl << std::endl; exit (1 ); } dictionary_.close (); returnValue = convertFromString <T>(valueLine); return returnValue; } }
类的定义 IOdictionary.C
内容如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include "IOdictionary.H" Aerosand::IOdictionary::IOdictionary (const std::string& filePath) { filePath_ = filePath; dictionary_.open (filePath_); if (!dictionary_.is_open ()) { std::cerr << "\n--> AEROSAND FATAL ERROR: " << std::endl << "cannot find file \"" << filePath_ << "\"\n" << std::endl << std::endl; exit (1 ); } dictionary_.close (); } Aerosand::IOdictionary::~IOdictionary () { if (dictionary_.is_open ()) { dictionary_.close (); } }
测试
修改本地字典 ofspProperties
内容如下
1 2 3 4 5 6 7 8 9 10 11 // This is ofspProperties. nu 0.02; // see the difference // test deltaT 0.005; writeInterval 20; purgeWrite 0; // test application laplacianFoam;
重新编译,读者可以测试字典文件可能出现的多种情况,可以发现结果满足设计要求。
开发小结
在开发特性的过程,我们可以意识到读取操作需要面对的数据类型还有很多,特别是 OpenFOAM 自己的类型。而且,需要处理的 OpenFOAM 输入文件格式和异常处理也有很多。这些功能特性毫无疑问需要更好的程序架构和更多的程序开发。
值的高兴的是,通过我们自己动手开发输入输出功能,相信现在对 OpenFOAM 的字典文件已经有了一定的概念。
下面,我们看一看 OpenFOAM 为我们提供的输入输出方法。
OpenFOAM 实现
OpenFOAM 的应用一般需要从 case 中读取字典,向 case 中输出计算结果等等。
OpenFOAM 是怎么实现从文件夹读取和写入的呢?OpenFOAM 的读取和写入更加高级,按关键词进行索引查找的方法直接封装在了相关的类中,直接使用方法即可,暂时不用深究到实现的代码层面。
项目准备
1 2 3 4 5 // terminal ofsp foamNewApp ofsp_01_IO cd ofsp_01_IO cp -r $FOAM_TUTORIALS/incompressible/icoFoam/cavity/cavity debug_case
文件结构如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 ofsp_01_IO/ ├── caseclean ├── caserun ├── debug_case/ │ ├── 0/ │ │ ├── p │ │ └── U │ ├── constant/ │ │ └── transportProperties │ └── system/ │ ├── blockMeshDict │ ├── controlDict │ ├── decomposeParDict │ ├── fvSchemes │ ├── fvSolution │ └── PDRblockMeshDict ├── Make/ │ ├── files │ └── options ├── ofsp_01_IO.C └── README.md
脚本和说明
脚本和上一篇讨论的类似,修改脚本内的求解器名称即可。
脚本 caserun
主要是负责应用编译成功后,调试算例的运行,暂时写入如下内容
1 2 3 4 5 6 #!/bin/bash blockMesh -case debug_case | tee debug_case/log.mesh echo "Meshing done." ofsp_01_IO -case debug_case | tee debug_case/log.run
caseclean
脚本主要是负责清理应用到到编译前状态,如果应用要修改,那么测试算例也要还原到运行前的状态,所以暂时写入如下内容
1 2 3 4 5 6 #!/bin/bash wclean rm -rf debug_case/log.* foamCleanTutorials debug_case echo "Cleaning done."
README.md
写入需要说明的内容。
以后除非有特别情况,不再赘述脚本和说明
主源码
主源码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 #include "fvCFD.H" int main (int argc, char *argv[]) { #include "setRootCase.H" #include "createTime.H" #include "createMesh.H" const word dictName ("customProperties" ) ; IOobject dictIO ( dictName, runTime.constant (), mesh, IOobject::MUST_READ, IOobject::NO_WRITE ); if (!dictIO.typeHeaderOk <dictionary>(true )) { FatalErrorIn (args.executable ()) << "Cannot open specified dictionary" << dictName << exit (FatalError); } dictionary myDictionary; myDictionary = IOdictionary (dictIO); Info<< "Reading myProperties\n" << endl; IOdictionary myProperties ( IOobject ( "myProperties" , runTime.constant (), mesh, IOobject::MUST_READ, IOobject::NO_WRITE ) ); word solver; myProperties.lookup ("application" ) >> solver; word format (myProperties.lookup("writeFormat" )) ; scalar timeStep (myProperties.lookupOrDefault("deltaT" , scalar(0.01 ))) ; dimensionedScalar alpha ("alpha" ,dimless,myProperties) ; dimensionedScalar beta (myProperties.lookup("beta" )) ; bool ifPurgeWrite (myProperties.lookupOrDefault<Switch>("purgeWrite" ,0 )) ; 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 << "alpha: " << alpha << nl << nl << "beta: " << beta << nl << nl << "purgeWrite: " << ifPurgeWrite << nl << nl << "point: " << pointList << nl << nl << "source: " << sourceField << nl << nl << "myVec: " << myVec << nl << nl << endl; fileName outputDir = runTime.path ()/"processing" ; 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
,该字典没有读取写入操作,只需要写上正确的文件头,内容留空处理。
1 2 3 4 5 6 7 8 9 FoamFile { version 2.0 ; format ascii; class dictionary ; location "constant" ; object customProperties; }
字典文件 debug_case/constant/myProperties
,内容如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 FoamFile { version 2.0 ; format ascii; class dictionary ; location "constant" ; object myProperties; } application icoFoam; writeFormat ascii; purgeWrite 1 ; alpha 0.2 ; beta beta [0 0 0 0 0 0 0 ] 0.5 ; point ( 0 1 2 ); source ( U1 (0 0 0 ) U2 (1 0 0 ) ); subDict { myVec (0.0 0.0 1.0 ); }
编译运行
终端运行
1 2 3 4 5 // terminal wclean wmake ./caseclean ./caserun
以后不再赘述简单的执行命令
终端显示结果如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 // terminal Create time Create mesh for time = 0 Reading myProperties application: icoFoam writeFormat: ascii deltaT: 0.01 alpha: alpha [0 0 0 0 0 0 0] 0.2 beta: beta [0 0 0 0 0 0 0] 0.5 purgeWrite: 1 point: 3(0 1 2) source: 2 ( U1 (0 0 0) U2 (1 0 0) ) myVec: (0 0 1) ExecutionTime = 0 s ClockTime = 0 s End
另外算例文件夹下有了一个新建文件夹 debug_case/processing/
,路径下的 myOutPut.dat
内容如下
1 2 3 4 5 6 7 8 9 10 processing/myOutPut.dat 0 1 2 3 ...3 ( U1 (0 0 0 )U3 (1 0 0 )U2 (1 0 0 ))
小结
我们同样从最一般的 C++ 基础情况切入,了解了基于文件流的输入输出,也讨论了 OpenFOAM 设计的文件流输入输出方法,以后会不断地使用文件流输入输出。