🎉 Welcome to Aerosand!.
ofsp2025-02

ofsp2025-02

参考:

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

前置:
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环境准备 | 𝓐𝓮𝓻𝓸𝓼𝓪𝓷𝓭 建立相应文件夹 )

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

打开该算例,查看 constant/transportProperties 文件和 system/controlDict 文件,可以看到每个关键词都对应着用户给定的值,这些关键词后的值在求解过程中,当然也会被程序读入。

阅读 transportProperties 文件和 controlDict 文件的文件头,可以明白,这两个文件其实都是“字典”文件。虽然两者向主程序输入参数的机制不同,但是我们仍然先行简单理解向主程序输入参数的主要思路。这样也可以在入门阶段更好理解字典文件的意义。

字典的格式如下

// transportProperties

nu              0.01;


// controlDict

...

application     icoFoam;

startFrom       startTime;

startTime       0;

stopAt          endTime;

...

计算该算例

// terminal
blockMesh
icoFoam

// use foamCleanTutorials to clean case

可以很快得到计算结果,我们打开最终时间步的速度场(0.5/U),查看输出的格式

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

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

具体看一下怎么使用文件流。

项目准备

建立本文的项目文件夹

// terminal
ofsp
mkdir ofsp_01_IO_cpp

通过 vscode 的 C/C++ Project Generater 打开新项目 ofsp_01_IO_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;


    // may we got the field U
    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;
        }
    }

    // output our field U 
    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;
}

我们同样为该项目提供模仿字典的文件。ofspProperties 文件内容如下(路径为 /ofsp/ofsp_01_IO_cpp/ofspProperties

nu                  0.01
application         laplacianFoam
deltaT              0.005
writeInterval       20
purgeWrite          0

编译运行

终端编译并运行此项目

// terminal
make clean
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

nu              0.01
application     laplacianFoam
deltaT          0.005
writeInterval   20
purgeWrite      0

Executing run: all complete!

我们可以看到每一项外部文件的关键词后的参数都被读入了程序,可以在程序中操作或输出。

同时发现项目根目录下生成了 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 的输出速度场的格式类似。

通过这个项目,我们可以简单理解, OpenFOAM 求解器是如何通过字典文件拿到参数指定的值。求解器输出的计算结果呢,本质上是 OpenFOAM 可以识别,后处理软件可以识别的,有特定排版格式的文件。

重构项目

让我们停下脚步,仔细想一下上面的项目。我们很难称它是按关键词读取,因为代码只是实现了读取。如果我们更改输入文件的关键词顺序,就会导致传入参数的不一致(读者可以尝试更换 ofspProperties 文件中关键词顺序,重新运行代码,查看结果)。

我们来考虑一下如何真正的按关键词读取。

OF 原生读参

我们看一下 OpenFOAM 中一些简单类型的参数的读取语句是什么样子的。

OpenFOAM 提供 lookup() 函数来按关键词读取,类似于如下形式

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 的这个读取功能。

项目准备

建立本项目文件夹

// terminal
ofsp
mkdir ofsp_01_IO_keyword

整体的架构如下

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 的语法表达。简化的形式如下

// 想要的语法效果
IOdictionary ofspProperties // 构造类的实体
(
	"ofspProperties" // 外部文件的名称,根目录路径
);

double viscosity = ofspProperties.lookup("nu"); // 类的实体可以使用方法

其中,ofspPropertiesIOdictionary 类的实例化。

可以看到,这个自定义的 IOdictionary 类需要读取外部文件,所以我们考虑仍然要使用 C++ 原生 fstream 类。

在类的声明的时候,我们也可以定义一个自己的命名空间,避免错误调用。

类的声明 IOdictionary.H 内容如下

#pragma once

#include <iostream>
#include <fstream>
#include <string>

namespace Aerosand // 自定义命名空间,避免命名重复
{

class IOdictionary
{
    
private:
    std::ifstream dictionary_;
    std::string filePath_;

public:

    // Constructor

        IOdictionary(const std::string& filePath);

    // Destructor

        ~IOdictionary();

    // Member Function

        std::string lookup(const std::string& keyword);

};

}

类的定义 IOdictionary.C 内容如下

#include "IOdictionary.H"

// Constructor

Aerosand::IOdictionary::IOdictionary(const std::string& filePath)
{
    filePath_ = filePath;
}

// Destructor

Aerosand::IOdictionary::~IOdictionary()
{
    if (dictionary_.is_open())
    {
        dictionary_.close();
    }
}

// Member Function

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 内容如下

IOdictionary.C

LIB = $(FOAM_USER_LIBBIN)/libIOdictionary

文件 IOdictionary/Make/options 留空。

编译生成动态库

// terminal
wclean IOdictionary
wmake IOdictionary

主源码

重写主源码,内容如下

#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 内容如下

ofsp_01_IO_keyword.C

EXE = $(FOAM_USER_APPBIN)/ofsp_01_IO_keyword

文件 ofsp_01_IO_keyword/Make/options 内容如下

EXE_INC = \
    -IIOdictionary/lnInclude

EXE_LIBS = \
    -L$(FOAM_USER_LIBBIN) \
    -lIOdictionary

编译运行

终端执行

// terminal
wclean
wmake

ofsp_01_IO_keyword

终端输出结果如下

nu              0.01
application     laplacianFoam
deltaT          0.005
writeInterval   20
purgeWrite      0

即使我们更换字典文件中内容的顺序,或者增加新的关键词,都不会影响该项目按关键词读入取值。

重构项目的思路基本正确。但是读取得到的参数类型统一是 string 类型,不符合我们的要求,我们需要进一步开发功能特性。

开发特性

这里有一些作者关于版本存档的建议(暂不使用 git 控制版本)。

当对项目有新的想法增加或者改进的时候,建议拷贝至项目,在新项目上进行修改和开发,保留原始文件以便回滚版本。

项目准备

建立本项目文件夹

// 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

整体的架构如下

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 内容如下

#pragma once

#include <iostream>
#include <fstream>
#include <string>
#include <sstream>

namespace Aerosand // 自定义命名空间,避免命名重复
{

class IOdictionary
{
    
private:
    std::ifstream dictionary_;
    std::string filePath_;

public:

    // Constructor

        IOdictionary(const std::string& filePath);

    // Destructor

        ~IOdictionary();

private:

    // Private Member Function

		// 私有成员函数,仅在类内使用
        template<typename T>
        T convertFromString(const std::string& str);

public:

    // Public Member Function

        template<typename T>
        T lookup(const std::string& keyword);

};


// Private Member Function

template<typename T>
T Aerosand::IOdictionary::convertFromString(const std::string& str)
{
    T value;
    std::istringstream iss(str);
    iss >> value;

    return value;
}

// Public Member Function

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 内容相应的修改为

#include "IOdictionary.H"

// Constructor

Aerosand::IOdictionary::IOdictionary(const std::string& filePath)
{
    filePath_ = filePath;
}

// Destructor

Aerosand::IOdictionary::~IOdictionary() // 其实 C++ 会自动关闭
{
    if (dictionary_.is_open())
    {
        dictionary_.close();
    }
}

由此以来,主源码 ofsp_01_IO_feature.C 的写法如下

#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;
}

测试

编译运行

// terminal
wmake IOdictionary
wmake

ofsp_01_IO_feature

结果如下

nu              0.01
application     laplacianFoam
deltaT          0.005
writeInterval   20
purgeWrite      0

Write time interval = 0.1

可以看到读取功能正常,并且通过计算验证了读取的参数值的类型也是正确的。

风格和注释和报错

有了前文对字符串的处理的思路,我们可以进行自定义类的风格开发设计。

开发设计思路

风格的设计参考 C++,每行末尾加 ; 表示语句结束,如下所示

nu         0.01;

注释的设计参考 C++ ,有以下两种形式

// nu is viscosity
nu                 0.01;

deltaT             0.005; // we can change deltaT

报错的设计简单考虑有以下几种情况

  1. 字典文件不存在或者文件名称错误,应该终止程序并输出对应的报错信息
  2. 字典中的关键词找不到,应该终止程序并输出对应的报错信息
  3. 字典中的关键词存在,但是没有给定值,应该终止程序并输出对应的报错信息

代码改进

自定义类 IOdictionary 需要进一步改进。

类的声明 IOdictionary.H 的内容如下

#pragma once

#include <iostream>
#include <fstream>
#include <string>
#include <sstream>

namespace Aerosand // 自定义命名空间,避免命名重复
{

class IOdictionary
{
    
private:
    std::ifstream dictionary_;
    std::string filePath_;

public:

    // Constructor

        IOdictionary(const std::string& filePath);

    // Destructor

        ~IOdictionary();

private:

    // Private Member Function

		// 私有成员函数,仅在类内使用
        template<typename T>
        T convertFromString(const std::string& str);

public:

    // Public Member Function

        template<typename T>
        T lookup(const std::string& keyword);

};


// Private Member Function

template<typename T>
T Aerosand::IOdictionary::convertFromString(const std::string& str)
{
    T value;
    std::istringstream iss(str);
    iss >> value;

    return value;
}

// Public Member Function

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 内容如下

#include "IOdictionary.H"

// Constructor

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();
}

// Destructor

Aerosand::IOdictionary::~IOdictionary() // 其实 C++ 会自动关闭
{
    if (dictionary_.is_open())
    {
        dictionary_.close();
    }
}

测试

修改本地字典 ofspProperties 内容如下

// 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 的读取和写入更加高级,按关键词进行索引查找的方法直接封装在了相关的类中,直接使用方法即可,暂时不用深究到实现的代码层面。

项目准备

// terminal
ofsp
foamNewApp ofsp_01_IO
cd ofsp_01_IO
cp -r $FOAM_TUTORIALS/incompressible/icoFoam/cavity/cavity debug_case

文件结构如下

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 主要是负责应用编译成功后,调试算例的运行,暂时写入如下内容

#!/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 脚本主要是负责清理应用到到编译前状态,如果应用要修改,那么测试算例也要还原到运行前的状态,所以暂时写入如下内容

#!/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));
    // 如果字典中没有提供这一关键词,则使用此句提供的默认值

    dimensionedScalar alpha("alpha",dimless,myProperties);
    // 常用这种语法读取字典中的参数
    
    dimensionedScalar beta(myProperties.lookup("beta"));
    // 这种写法在老代码中常见,但是在新一些的版本中,编译虽然能过,但是提醒语法过时


    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
        << "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;

    /* 
    * 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;

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);
}

编译运行

终端运行

// terminal
wclean
wmake
./caseclean
./caserun

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

终端显示结果如下

// 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 内容如下

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