本篇简单介绍 OpenFOAM 中的 fvOptions。按照官方的介绍,fvOptions 是一个可以在指定区域内添加源项或者其他约束(比如固定温度,或者多孔介质等)的框架。本篇对 fvOptions 框架的源码做一个浅析。
先来了解一下 fvOptions 框架的结构,以及,在求解器中是怎么调用 fvOptions 的。为了避免问题复杂化,先从一个简单的求解器开始:scalarTransportFoam
。OpenFOAM-2.3.1 中的 scalarTransportFoam
中已经引入了 fvOptions
,而更早的 OpenFOAM-2.1.1 中,则没有使用 fvOptions
。对比之下,很容易发现引入 fvOptions
其实就只涉及到两处代码修改:1. 增加了一个头文件 createFvOptions.H
;2. 在 T 方程中增加了一项 fvOptions(T)
。 根据 OpenFOAM 的习惯,可以猜测这里增加的 fvOptions(T)
,从C++的角度来看,多半是一个基类的对象 fvOptions
在调用类中重载过的 ()
操作符。以上便是从这段简单的求解器代码中产生的对 fvOptions
的第一印象,下面来仔细看看 fvOptions
这个框架的结构。
1. createFvOptions.H
既然求解器里只增加了这一个头文件,那就先从这个看起。这个文件位于 src/fvOptions/include
,内容很简单,就一句话:1
fv::IOoptionList fvOptions(mesh);
从这里就很清楚地可以看出, scalarTransportFoam
中 T 方程中增加的 fvOptions
是 IOoptionList
类的对象。
2. IOoptionList 类
接下来看 IOoptionList
类。IOoptionList 类由两个文件组成: fvIOoptionList.H 和 fvIOoptionList.C。这里的目的在于了解 fvOptions 这个框架的结构,所以重点看头文件以及构造函数的定义。
- fvIOoptionList.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#ifndef IOoptionList_H
#define IOoptionList_H
#include "fvOptionList.H"
#include "IOdictionary.H"
#include "autoPtr.H"
namespace Foam
{
namespace fv
{
class IOoptionList
:
public IOdictionary,
public optionList
{
private:
// Private Member Functions
//- Create IO object if dictionary is present
IOobject createIOobject(const fvMesh& mesh) const;
//- Disallow default bitwise copy construct
IOoptionList(const IOoptionList&);
//- Disallow default bitwise assignment
void operator=(const IOoptionList&);
public:
// Constructors
//- Construct from components with list of field names
IOoptionList(const fvMesh& mesh);
//- Destructor
virtual ~IOoptionList()
{}
// Member Functions
//- Read dictionary
virtual bool read();
};
} // End namespace fv
} // End namespace Foam
#endif
从头文件可以看出, IOoptionList
类继承自 IOdictionary
和 optionList
类。根据对 OpenFOAM 的了解,IOdictionary
类是处理跟 IO 有关的,所以这里这个类很可能是用来处理 fvOptions 相关的字典文件的。真正涉及到具体的 fvOptions 源项的内容,应该是在 optionList
中有相关定义。
- 构造函数
1
2
3
4
5
6
7
8Foam::fv::IOoptionList::IOoptionList
(
const fvMesh& mesh
)
:
IOdictionary(createIOobject(mesh)), //构造函数里创建fvOptions字典文件
optionList(mesh, *this)
{}
构造函数中调用 createIOobject
函数来对父类 IOdictionary
进行初始化,并将自己(*this
) 作为参数传递给了父类 optionList
。
- createIOobject 函数
构造函数中调用了createIOobject
函数,而且这里的调用显然至关重要,所以需要看一下这个函数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
46Foam::IOobject Foam::fv::IOoptionList::createIOobject
(
const fvMesh& mesh
) const
{
IOobject io
(
"fvOptions",
mesh.time().constant(),
mesh,
IOobject::MUST_READ,
IOobject::NO_WRITE
);
if (io.headerOk())
{
Info<< "Creating finite volume options from "
<< io.instance()/io.name() << nl
<< endl;
io.readOpt() = IOobject::MUST_READ_IF_MODIFIED;
return io;
}
else
{
// Check if the fvOptions file is in system
io.instance() = mesh.time().system();
if (io.headerOk())
{
Info<< "Creating finite volume options from "
<< io.instance()/io.name() << nl
<< endl;
io.readOpt() = IOobject::MUST_READ_IF_MODIFIED;
return io;
}
else
{
Info<< "No finite volume options present" << nl << endl;
io.readOpt() = IOobject::NO_READ;
return io;
}
}
}
这个函数的意图就很明显了:创建了一个 IOobject
对象,并从 system
中读入文件 fvOptions
的内容来初始化该对象。并且,如果 system
目录下不存在 fvOptions
文件,那就尝试在 constant
下寻找。如果仍找不到,那就放弃读取。
3. optionList 类
这个类由三个源文件定义: fvOptionList.H , fvOptionList.C 和 fvOptionListTemplates.C。这里依然是重点关注头文件。
- fvOptionList.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#ifndef optionList_H
#define optionList_H
#include "PtrList.H"
#include "GeometricField.H"
#include "fvPatchField.H"
#include "fvOption.H"
namespace Foam
{
namespace fv
{
class optionList
:
public PtrList<option>
{
protected:
......
const dictionary& optionsDict(const dictionary& dict) const;
bool readOptions(const dictionary& dict);
public:
// Constructors
optionList(const fvMesh& mesh);
optionList(const fvMesh& mesh, const dictionary& dict);
void reset(const dictionary& dict);
......
//- Return source for equation
// 重载的括号操作符,在求解器里,方程的构造过程中调用的就是这些括号操作符。
template<class Type>
tmp<fvMatrix<Type> > operator()
(
const volScalarField& rho,
GeometricField<Type, fvPatchField, volMesh>& fld
);
......
};
} // End namespace fv
} // End namespace Foam
#ifdef NoRepository
#include "fvOptionListTemplates.C"
#endif
#endif
这里注意两点,一是 optionList
类继承自 PtrList<option>
,另一个是,这里出现了重载的运算符 ()
。继承自 PtrList<option>
意味着很可能 fvOptions 类是支持同时定义多个源项的。
- 构造函数
1
2
3
4
5
6
7
8Foam::fv::optionList::optionList(const fvMesh& mesh, const dictionary& dict)
:
PtrList<option>(),
mesh_(mesh),
checkTimeIndex_(mesh_.time().startTimeIndex() + 2)
{
reset(optionsDict(dict));
}
构造函数里,调用了 reSet
和 optionsDict
函数。下面看一下这两个函数的作用:
- optionsDict 函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14const Foam::dictionary& Foam::fv::optionList::optionsDict
(
const dictionary& dict
) const
{
if (dict.found("options"))
{
return dict.subDict("options");
}
else
{
return dict;
}
}
这个函数需要一个参数 const dictionary& dict
,这个参数,显然是从构造函数的 const dictionary& dict
,再回顾一下上文中,IOoptionList
中将自己本身(*this)传递给父类 optionsDict
,而且, IOoptionList
同时也继承自 IOdictionary
类,并且将从文件 fvOptions
类中读取的内容来对其父类 IOdictionary
类。所以这里不难理解,参数 const dictionary& dict
其实就是文件 fvOptions
的内容。这里需要从文件中 fvOptions
寻找关键字 options
,如果找到,那就返回 options
所指定的 subDict
,否则就直接返回 fvOptions
本身。
- reset 函数
再来看 reset 函数: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
29void Foam::fv::optionList::reset(const dictionary& dict)
{
// Count number of active fvOptions
label count = 0;
forAllConstIter(dictionary, dict, iter)
{
if (iter().isDict())
{
count++;
}
}
this->setSize(count);
label i = 0;
forAllConstIter(dictionary, dict, iter)
{
if (iter().isDict())
{
const word& name = iter().keyword();
const dictionary& sourceDict = iter().dict();
this->set
(
i++,
option::New(name, sourceDict, mesh_)
);
}
}
}
注意在构造函数里,这个函数的参数是 optionsDict
类的返回值。这里,函数是在统计 fvOptions
中有效的 options
的个数,并根据每一个有效的 options
,调用 option
类的 New
函数(看到 New 函数,很自然就会想到 Run Time Selection 吧!)来构造对象指针,且将该指针存到 PtrList
类定义的 List 里。 这里更是直接能看出来,在文件 fvOptions
中,是可以同时定义多个源项的。
4. option 类
再继续看 option
类。option
类由四个源文件定义: fvOption.H
, fvOption.C
, fvOptionI.H
和 fvOptionIO.C
。
fvOption.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#ifndef fvOption_H
#define fvOption_H
#include "fvMatricesFwd.H"
#include "volFieldsFwd.H"
#include "cellSet.H"
#include "autoPtr.H"
#include "meshToMesh.H"
#include "runTimeSelectionTables.H"
namespace Foam
{
class fvMesh;
namespace fv
{
class option
{
public:
// Public data
//- Enumeration for selection mode types
enum selectionModeType
{
smPoints,
smCellSet,
smCellZone,
smMapRegion,
smAll
};
......option
类终于不再继承自其他类了。而且,包含的头文件中有#include "runTimeSelectionTables.H"
可以猜想,这个类肯定是作为接口的基类来使用的。
经验证,具体的源项类,如semiImplicitSource
,都是继承自option
类的。当然,这里会用到 RTS 机制来提供灵活地源项选择。和其他的作为接口使用的基类类似,option
类中定义了所有的具体源项类可能用到的控制选项,比如,区域的选择(Points,cellSet,cellZone,mapRegion,all),开始时间(timeStart),持续时间(duration)激活开关(active)以及各个可能调用到的函数。
至此,fvOptions 框架的结构就大体理清了,总结如下图: