OpenFOAM 中的热物理类之接口

本系列来看一下 OpenFOAM 中的热物理类。热物理类比较繁杂,这里先看一下纯物质的热物理模型。本篇先来看看热物理类在求解器中的接口,以2.3.x 版的 twoPhaseEulerFoam 为例。

twoPhaseEulerFoam 中的热物理类的接口在 phaseModel 类中声明:

1
2
3
4
//- Thermophysical properties
autoPtr<rhoThermo> thermo_;

thermo_(rhoThermo::New(fluid.mesh(), name_))

可见,接口是 rhoThermo 类的指针。

接着看 rhoThermo 类的 New 函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//- Selector
static autoPtr<rhoThermo> New
(
const fvMesh&,
const word& phaseName=word::null
);

Foam::autoPtr<Foam::rhoThermo> Foam::rhoThermo::New
(
const fvMesh& mesh,
const word& phaseName
)
{
return basicThermo::New<rhoThermo>(mesh, phaseName);
}

这里调用的是 basicThermo 类的 New 函数。 这里先提一下继承关系,后面再细说:rhoThermo 类继承自 fluidThermofluidThermo 类继承自 basicThermo

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
//- Generic New for each of the related thermodynamics packages
// basicThermo.C

template<class Thermo>
static autoPtr<Thermo> New
(
const fvMesh&,
const word& phaseName=word::null
);

template<class Thermo>
Foam::autoPtr<Thermo> Foam::basicThermo::New
(
const fvMesh& mesh,
const word& phaseName
)
{
IOdictionary thermoDict
(
IOobject
(
phasePropertyName(dictName, phaseName),
mesh.time().constant(),
mesh,
IOobject::MUST_READ_IF_MODIFIED,
IOobject::NO_WRITE,
false
)
);

typename Thermo::fvMeshConstructorTable::iterator cstrIter =
lookupThermo<Thermo, typename Thermo::fvMeshConstructorTable>
(
thermoDict,
Thermo::fvMeshConstructorTablePtr_
);

return autoPtr<Thermo>(cstrIter()(mesh, phaseName));
}

根据 RTS 机制的惯例, New 函数的功能是模型选择(selector),即根据用户指定的关键字来选择对应的模型。 New 函数中先定义了一个 IOdictionary 类的对象, thermoDict,这个对象对应的正是热物理类的配置文件 thermophysicalPropertiesNew 函数里调用了 lookupThermo 函数,这个函数是关键:

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
// basicThermoTemplates.C
template<class Thermo, class Table>
typename Table::iterator Foam::basicThermo::lookupThermo
(
const dictionary& thermoDict,
Table* tablePtr
)
{
word thermoTypeName;

if (thermoDict.isDict("thermoType"))
{
const dictionary& thermoTypeDict(thermoDict.subDict("thermoType"));

Info<< "Selecting thermodynamics package " << thermoTypeDict << endl;

const int nCmpt = 7;
const char* cmptNames[nCmpt] =
{
"type",
"mixture",
"transport",
"thermo",
"equationOfState",
"specie",
"energy"
};

// Construct the name of the thermo package from the components
thermoTypeName =
word(thermoTypeDict.lookup("type")) + '<'
+ word(thermoTypeDict.lookup("mixture")) + '<'
+ word(thermoTypeDict.lookup("transport")) + '<'
+ word(thermoTypeDict.lookup("thermo")) + '<'
+ word(thermoTypeDict.lookup("equationOfState")) + '<'
+ word(thermoTypeDict.lookup("specie")) + ">>,"
+ word(thermoTypeDict.lookup("energy")) + ">>>";

// Lookup the thermo package
typename Table::iterator cstrIter = tablePtr->find(thermoTypeName);

// Print error message if package not found in the table
if (cstrIter == tablePtr->end())
{
FatalErrorIn(Thermo::typeName + "::New")
<< "Unknown " << Thermo::typeName << " type " << nl
<< "thermoType" << thermoTypeDict << nl << nl
<< "Valid " << Thermo::typeName << " types are:" << nl << nl;

// Get the list of all the suitable thermo packages available
wordList validThermoTypeNames
(
tablePtr->sortedToc()
);

// Build a table of the thermo packages constituent parts
// Note: row-0 contains the names of constituent parts
List<wordList> validThermoTypeNameCmpts
(
validThermoTypeNames.size() + 1
);

validThermoTypeNameCmpts[0].setSize(nCmpt);
forAll(validThermoTypeNameCmpts[0], j)
{
validThermoTypeNameCmpts[0][j] = cmptNames[j];
}

// Split the thermo package names into their constituent parts
forAll(validThermoTypeNames, i)
{
validThermoTypeNameCmpts[i+1] =
Thermo::splitThermoName(validThermoTypeNames[i], nCmpt);
}

// Print the table of available packages
// in terms of their constituent parts
printTable(validThermoTypeNameCmpts, FatalError);

FatalError<< exit(FatalError);
}

return cstrIter;
}
else
{
thermoTypeName = word(thermoDict.lookup("thermoType"));

Info<< "Selecting thermodynamics package " << thermoTypeName << endl;

typename Table::iterator cstrIter = tablePtr->find(thermoTypeName);

if (cstrIter == tablePtr->end())
{
FatalErrorIn(Thermo::typeName + "::New")
<< "Unknown " << Thermo::typeName << " type "
<< thermoTypeName << nl << nl
<< "Valid " << Thermo::typeName << " types are:" << nl
<< tablePtr->sortedToc() << nl
<< exit(FatalError);
}

return cstrIter;
}
}

可见, loopupThermo 分两种情况处理,一种是 thermophysicalProperties 文件里有一个名为 thermoTypesubdict,例如

1
2
3
4
5
6
7
8
9
10
thermoType
{
type heRhoThermo;
mixture pureMixture;
transport const;
thermo hConst;
equationOfState perfectGas;
specie specie;
energy sensibleInternalEnergy;
}

这种情况下, subdict 里的 7 个关键字将逐一读入,最终将合并起来,得到一个字符串,并赋值给 thermoTypeName
以上面的那种情况为例,最终得到的thermoTypeName

1
heRhoThermo<pureMixture<const<hConst<perfectGas<specie>>,sensibleInternalEnergy>>>

然后,根据这个关键词,从 hashTable 中找到对应的元素。如果找不到对应的,则报错,并输出所有可选的方案(由 splitThermoNameprintTable 两个函数完成,细节这里暂且不表)。

另一种情况,直接从 thermophysicalProperties 读取 thermoType 对应的字符串并赋值给 thermoTypeName,然后据此来从 hashTable 中找到对应的元素。

OpenFOAM 自带的算例中,thermophysicalProperties 文件绝大部分采用前一种方式,因为更直观。后一种方式我在 OpenFOAM-2.3.x 版中只找到一个例子:tutorials/mesh/foamyQuadMesh/OpenCFD/constant/thermophysicalProperties

1
thermoType      hePsiThermo<pureMixture<const<hConst<perfectGas<specie>>,sensibleEnthalpy>>>;

至此,大致就知道热物理类的接口定义是怎么回事了。但是,这个存储了可选模型的 hashTable 里有哪些内容,又是怎么构建起来的,还有待进一步深入探索。另外,从 thermoType 对应的字符串的样式,能猜到最终热物理类的接口 thermo_ 指向的可能是类似 heRhoThermo 类的对象,而且这些类多半是模板类,并有着复杂的继承派生关系,这部分也还有待深入探索。