OpenFOAM中的DSMC求解器dsmcFoam分析

dsmcFoam solver分析

dsmcFoam 的架构:

主要的源文件:

dsmcFoma 源码:

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
int main(int argc, char *argv[])
{
#include "setRootCase.H"
#include "createTime.H"
#include "createMesh.H"
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
Info<< nl << "Constructing dsmcCloud " << endl;
dsmcCloud dsmc("dsmc", mesh);
Info<< "\nStarting time loop\n" << endl;
while (runTime.loop())
{
Info<< "Time = " << runTime.timeName() << nl << endl;
dsmc.evolve(); //<<------ 这里是演化步
dsmc.info(); //<<------ dump到屏幕一些基本信息
runTime.write(); //<<------- 这里很关键,到底写的是什么
Info<< nl << "ExecutionTime = " << runTime.elapsedCpuTime() << " s"
<< " ClockTime = " << runTime.elapsedClockTime() << " s"
<< nl << endl;
}
Info<< "End\n" << endl;
return(0);
}

DsmcCloud 关键函数分析

DsmcCloud::evolve() 主要演化过程在这里

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
template<class ParcelType>
void Foam::DsmcCloud<ParcelType>::evolve()
{
typename ParcelType::trackingData td(*this);
// Reset the data collection fields
resetFields(); //<<------ 每个演化步都需要重置 rhoN_ rhoM_ linearKE_ ... 等等这些场为0;
if (debug)
{
this->dumpParticlePositions();
}
// Insert new particles from the inflow boundary
this->inflowBoundary().inflow(); //<<------ 处理边界
// Move the particles ballistically with their current velocities
Cloud<ParcelType>::move(td, mesh_.time().deltaTValue()); //<<------ 自由迁移,得到particle新的位置
// Update cell occupancy
buildCellOccupancy(); //<<-------
// Calculate new velocities via stochastic collisions
collisions(); //<<------ 碰撞处理,得到particle新的速度
// Calculate the volume field data
calculateFields(); //<<------每个演化步都需要统计 rhoN_ rhoM_ linearKE_ ... 等等这些场为;
}

DsmcCloud::resetFields() 每个演化步都需要重置这些场为0,为开始统计做准备

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
template<class ParcelType>
void Foam::DsmcCloud<ParcelType>::resetFields()
{
q_ = dimensionedScalar("zero", dimensionSet(1, 0, -3, 0, 0), 0.0);
fD_ = dimensionedVector
(
"zero",
dimensionSet(1, -1, -2, 0, 0),
vector::zero
);
rhoN_ = dimensionedScalar("zero", dimensionSet(0, -3, 0, 0, 0), VSMALL);
rhoM_ = dimensionedScalar("zero", dimensionSet(1, -3, 0, 0, 0), VSMALL);
dsmcRhoN_ = dimensionedScalar("zero", dimensionSet(0, -3, 0, 0, 0), 0.0);
linearKE_ = dimensionedScalar("zero", dimensionSet(1, -1, -2, 0, 0), 0.0);
internalE_ = dimensionedScalar("zero", dimensionSet(1, -1, -2, 0, 0), 0.0);
iDof_ = dimensionedScalar("zero", dimensionSet(0, -3, 0, 0, 0), VSMALL);
momentum_ = dimensionedVector
(
"zero",
dimensionSet(1, -2, -1, 0, 0),
vector::zero
);
}

DsmcCloud::calculateFields(): 统计场

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
template<class ParcelType>
void Foam::DsmcCloud<ParcelType>::calculateFields()
{
scalarField& rhoN = rhoN_.internalField();
scalarField& rhoM = rhoM_.internalField();
scalarField& dsmcRhoN = dsmcRhoN_.internalField();
scalarField& linearKE = linearKE_.internalField();
scalarField& internalE = internalE_.internalField();
scalarField& iDof = iDof_.internalField();
vectorField& momentum = momentum_.internalField();
forAllConstIter(typename DsmcCloud<ParcelType>, *this, iter)
{
const ParcelType& p = iter();
const label cellI = p.cell();
rhoN[cellI]++;
rhoM[cellI] += constProps(p.typeId()).mass();
dsmcRhoN[cellI]++;
linearKE[cellI] += 0.5*constProps(p.typeId()).mass()*(p.U() & p.U());
internalE[cellI] += p.Ei();
iDof[cellI] += constProps(p.typeId()).internalDegreesOfFreedom();
momentum[cellI] += constProps(p.typeId()).mass()*p.U();
}
rhoN *= nParticle_/mesh().cellVolumes();
rhoN_.correctBoundaryConditions();
rhoM *= nParticle_/mesh().cellVolumes();
rhoM_.correctBoundaryConditions();
dsmcRhoN_.correctBoundaryConditions();
linearKE *= nParticle_/mesh().cellVolumes();
linearKE_.correctBoundaryConditions();
internalE *= nParticle_/mesh().cellVolumes();
internalE_.correctBoundaryConditions();
iDof *= nParticle_/mesh().cellVolumes();
iDof_.correctBoundaryConditions();
momentum *= nParticle_/mesh().cellVolumes();
momentum_.correctBoundaryConditions();
}

看看DsmcCloud的构造函数,可以发现它主要存储哪些东西:

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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
// * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
template<class ParcelType>
Foam::DsmcCloud<ParcelType>::DsmcCloud
(
const word& cloudName,
const fvMesh& mesh,
bool readFields
)
:
Cloud<ParcelType>(mesh, cloudName, false),
DsmcBaseCloud(),
cloudName_(cloudName),
mesh_(mesh),
particleProperties_
(
IOobject
(
cloudName + "Properties",
mesh_.time().constant(),
mesh_,
IOobject::MUST_READ_IF_MODIFIED,
IOobject::NO_WRITE
)
),
typeIdList_(particleProperties_.lookup("typeIdList")),
nParticle_(readScalar(particleProperties_.lookup("nEquivalentParticles"))),
cellOccupancy_(mesh_.nCells()),
sigmaTcRMax_ //<<------ 这个主要是作为计算的时候用,输出并没有意义。
(
IOobject
(
this->name() + "SigmaTcRMax",
mesh_.time().timeName(),
mesh_,
IOobject::MUST_READ, //<<------ 这里都是MUST_READ
IOobject::AUTO_WRITE
),
mesh_
),
collisionSelectionRemainder_(mesh_.nCells(), 0),
q_
(
IOobject
(
"q",
mesh_.time().timeName(),
mesh_,
IOobject::MUST_READ,
IOobject::AUTO_WRITE
),
mesh_
),
fD_
(
IOobject
(
"fD",
mesh_.time().timeName(),
mesh_,
IOobject::MUST_READ,
IOobject::AUTO_WRITE
),
mesh_
),
rhoN_
(
IOobject
(
"rhoN",
mesh_.time().timeName(),
mesh_,
IOobject::MUST_READ,
IOobject::AUTO_WRITE
),
mesh_
),
rhoM_
(
IOobject
(
"rhoM",
mesh_.time().timeName(),
mesh_,
IOobject::MUST_READ,
IOobject::AUTO_WRITE
),
mesh_
),
dsmcRhoN_
(
IOobject
(
"dsmcRhoN",
mesh_.time().timeName(),
mesh_,
IOobject::MUST_READ,
IOobject::AUTO_WRITE
),
mesh_
),
linearKE_
(
IOobject
(
"linearKE",
mesh_.time().timeName(),
mesh_,
IOobject::MUST_READ,
IOobject::AUTO_WRITE
),
mesh_
),
internalE_
(
IOobject
(
"internalE",
mesh_.time().timeName(),
mesh_,
IOobject::MUST_READ,
IOobject::AUTO_WRITE
),
mesh_
),
iDof_
(
IOobject
(
"iDof",
mesh_.time().timeName(),
mesh_,
IOobject::MUST_READ,
IOobject::AUTO_WRITE
),
mesh_
),
momentum_
(
IOobject
(
"momentum",
mesh_.time().timeName(),
mesh_,
IOobject::MUST_READ,
IOobject::AUTO_WRITE
),
mesh_
),
constProps_(),
rndGen_(label(149382906) + 7183*Pstream::myProcNo()),
boundaryT_
(
volScalarField
(
IOobject
(
"boundaryT",
mesh_.time().timeName(),
mesh_,
IOobject::MUST_READ,
IOobject::AUTO_WRITE
),
mesh_
)
),
boundaryU_
(
volVectorField
(
IOobject
(
"boundaryU",
mesh_.time().timeName(),
mesh_,
IOobject::MUST_READ,
IOobject::AUTO_WRITE
),
mesh_
)
),
binaryCollisionModel_
(
BinaryCollisionModel<DsmcCloud<ParcelType> >::New
(
particleProperties_,
*this
)
),
wallInteractionModel_
(
WallInteractionModel<DsmcCloud<ParcelType> >::New
(
particleProperties_,
*this
)
),
inflowBoundaryModel_
(
InflowBoundaryModel<DsmcCloud<ParcelType> >::New
(
particleProperties_,
*this
)
)
{
buildConstProps();
buildCellOccupancy();
// Initialise the collision selection remainder to a random value between 0
// and 1.
forAll(collisionSelectionRemainder_, i)
{
collisionSelectionRemainder_[i] = rndGen_.scalar01();
}
if (readFields)
{
ParcelType::readFields(*this);
}
}

可以发现DsmcCloud的演化过程十分清晰明了。就是内部记录了一些extensive fields,和 particle的位置、速度。然后每个演化步统计这些 extensive fields,然后执行迁移和碰撞,边界处理。这些extensive fields完全由新时刻的particle速度计算出来。

fieldAverage 分析

在一个dsmcFoam的case里面,controlDict 文件里面要设置在求解过程中输出一些时间平均的场, 这由OpenFOAM本身提供的fieldAverage 和dsmcFOAM提供的dsmcField 这两个Function object来完成。

NOTE: 关于fieldAverage Function object到底是怎样进行时间平均的, 这篇博文有很好的分析。

下面这个例子是一个dsmcFoam case里controlDict文件的functions部分:

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
functions // 这里面是设置了两个时间平均计算,一个是fieldAverage1,另一个是dsmcFields1
{
fieldAverage1
{
type fieldAverage; //<<------ 这个是OpenFOAM本身提供的。
functionObjectLibs ( "libfieldFunctionObjects.so" );
outputControl outputTime; //<<------ 随着每次dsmcFoam输出AUTO_WRITE属性的
//场(由controlDict里面的writeControl控制)
//的时候都会输出这些时均场。
resetOnOutput false;
fields
(
rhoN
{
mean on;
prime2Mean off;
base time; // 如果base设为time的话,每个演化步都会计算一次时均
//windows 2; //窗口长度为2,就是相对于没有windows,统计频率会降低一倍。
}
rhoM
{
mean on;
prime2Mean off;
base time;
}
... //省略
fD
{
mean on;
prime2Mean off;
base time;
}
);
}
dsmcFields1
{
type dsmcFields;
functionObjectLibs ( "libutilityFunctionObjects.so" );
enabled true;
outputControl outputTime;
}
}

这里面设置每个需要计算时均值的场的设置。包括mean,prome2Mean,base,window。时均场输出的文件名后面会多一个Mean。 如momentum的时均场为momentumMean。

上面的设置中basetime,发现计算过程中每个演化步(DsmcCloud::evolve())都计算了时均场,log里面每个演化都会出现:

1
2
fieldAverage fieldAverage1 output:
Calculating averages
  • 如果不设置window : 每次计算的时均场都是从求解开始到目前所有步的时均场。
  • 如果设置了window :

    base用来指定作时间平均的基础,是基于时间步数(ITER)还是物理时间(time); window用来作平均的时间段的长度,如果不设定,则求的是从开始到当前时间这个时间段的平均值。window的数值的实际含义依base而定,如果base是ITER,则window=20表示当前步及其前 19 个时间步从 20 个时间步内的平均,而如果base是time,则表示的是 20s 内的平均。

在输出数据文件时(每evolve 100次)还会输出fieldAverage1设置的时均场(writing average fields):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fieldAverage fieldAverage1 output:
Calculating averages
writing average fields //<------ 表示输出时均场
Calculating dsmcFields.
Calculating UMean field.
Calculating translationalT field.
Calculating internalT field.
Calculating overallT field.
Calculating pressure field.
mag(UMean) max/min : 76.50092418551631 1.282028354585403
translationalT max/min : 436.5786449656766 219.0809500397843
internalT max/min : 0 0
overallT max/min : 436.5786449656766 219.0809500397843
p max/min : 67296.44015303567 42358.60866053814
dsmcFields written.

NOTE: 可以发现在writing average fields的时候dsmcFields function object被触发了,它计算UMean,translationalT,internalT,overallT,pressure等场。

可以定义两个开关分别是resetOnOutputresetOnRestart, 其意义是:

resetOnRestart的值决定当solver继续运行时,是否要读取最近一个时间步的meanField的值来计算接下来时刻的时均值;>resetOnOutput,顾名思义,是否要在每一次输出到文件以后重置meanField的值。这两个开关的默认值都是false。”

这里可能有个疑问,如果不做这两个时均计算会怎样?会不会影响计算过程?毕竟dsmc的计算过程不依赖于这些宏观量场以及它们的时均场。这个疑问是很自然的。可以通过一个例子分析一下:
如果在controlDict文件里面如下设置

1
2
3
4
5
6
startTime 0;
stopAt endTime;
endTime 1e-8;
deltaT 1e-11;
writeControl timeStep;
writeInterval 100;

表示计算1000步,每100步输出。

  1. 如果我们再注释掉functions部分, 也就是不求平均。运行dsmcFoam,也没有问题,不会报错。运行完会产生10个数据文件目录(1000/100=10)。
1
2
[lhzhu@ws3 dsmc1]$ ls
0 1e-08 1e-09 2e-09 3e-09 4e-09 5e-09 6e-09 7e-09 8e-09 9e-09 constant log PyFoamHistory system

每个目录里面只有当前步的统计出来的场(非时均):

1
2
[lhzhu@ws3 dsmc1]$ ls 1e-09
boundaryT boundaryU dsmcRhoN dsmcSigmaTcRMax fD iDof internalE lagrangian linearKE momentum q rhoM rhoN uniform
  1. 但是如果我们如果只注释掉functions里面的fieldAverage1部分,或者只去掉 fieldAverage1里面的任何一个时均场,则会报错。比如去掉fD的时均计算,会有Fatal Error如下:
1
2
--> FOAM FATAL ERROR:
request for volVectorField fDMean from objectRegistry region0 failed

这个错误应该是在执行dsmcFields function object 时产生的。下面我们分析dsmcFields

dsmcFields分析

源文件位置: https://github.com/OpenFOAM/OpenFOAM-2.3.x/tree/master/src/postProcessing/functionObjects/utilities/dsmcFields

dsmcFields.H

1
2
3
4
5
6
7
8
9
10
Description
Calculate intensive fields:
- UMean
- translationalT
- internalT
- overallT
from averaged extensive fields from a DSMC calculation.
SourceFiles
dsmcFields.C
IOdsmcFields.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
class dsmcFields
{
// Private data
//- Name of this set of dsmcFields objects
word name_;
const objectRegistry& obr_;
//- on/off switch
bool active_;
// Private Member Functions
//- Disallow default bitwise copy construct
dsmcFields(const dsmcFields&);
//- Disallow default bitwise assignment
void operator=(const dsmcFields&);
public:
//- Runtime type information
TypeName("dsmcFields");
// Constructors
//- Construct for given objectRegistry and dictionary.
// Allow the possibility to load fields from files
dsmcFields
(
const word& name,
const objectRegistry&,
const dictionary&,
const bool loadFromFiles = false
);
//- Destructor
virtual ~dsmcFields();
// Member Functions
//- Return name of the set of dsmcFields
virtual const word& name() const
{
return name_;
}
//- Read the dsmcFields data
virtual void read(const dictionary&);
//- Execute, currently does nothing
virtual void execute();
//- Execute at the final time-loop, currently does nothing
virtual void end();
//- Called when time was set at the end of the Time::operator++
virtual void timeSet();
//- Calculate the dsmcFields and write
virtual void write(); //<------ 最重要的是这个函数
//- Update for changes of mesh
virtual void updateMesh(const mapPolyMesh&)
{}
//- Update for changes of mesh
virtual void movePoints(const polyMesh&)
{}
};

dsmcFields::write()

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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
void Foam::dsmcFields::write()
{
if (active_)
{
word rhoNMeanName = "rhoNMean";
word rhoMMeanName = "rhoMMean";
word momentumMeanName = "momentumMean";
word linearKEMeanName = "linearKEMean";
word internalEMeanName = "internalEMean";
word iDofMeanName = "iDofMean";
word fDMeanName = "fDMean";
const volScalarField& rhoNMean = obr_.lookupObject<volScalarField>
(
rhoNMeanName
);
const volScalarField& rhoMMean = obr_.lookupObject<volScalarField>
(
rhoMMeanName
);
const volVectorField& momentumMean = obr_.lookupObject<volVectorField>
(
momentumMeanName
);
const volScalarField& linearKEMean = obr_.lookupObject<volScalarField>
(
linearKEMeanName
);
const volScalarField& internalEMean = obr_.lookupObject<volScalarField>
(
internalEMeanName
);
const volScalarField& iDofMean = obr_.lookupObject<volScalarField>
(
iDofMeanName
);
const volVectorField& fDMean = obr_.lookupObject<volVectorField>
(
fDMeanName
);
if (min(mag(rhoNMean)).value() > VSMALL)
{
Info<< "Calculating dsmcFields." << endl;
Info<< " Calculating UMean field." << endl;
volVectorField UMean
(
IOobject
(
"UMean",
obr_.time().timeName(),
obr_,
IOobject::NO_READ
),
momentumMean/rhoMMean
);
Info<< " Calculating translationalT field." << endl;
volScalarField translationalT
(
IOobject
(
"translationalT",
obr_.time().timeName(),
obr_,
IOobject::NO_READ
),
2.0/(3.0*physicoChemical::k.value()*rhoNMean)
*(linearKEMean - 0.5*rhoMMean*(UMean & UMean))
);
Info<< " Calculating internalT field." << endl;
volScalarField internalT
(
IOobject
(
"internalT",
obr_.time().timeName(),
obr_,
IOobject::NO_READ
),
(2.0/physicoChemical::k.value())*(internalEMean/iDofMean)
);
Info<< " Calculating overallT field." << endl;
volScalarField overallT
(
IOobject
(
"overallT",
obr_.time().timeName(),
obr_,
IOobject::NO_READ
),
2.0/(physicoChemical::k.value()*(3.0*rhoNMean + iDofMean))
*(linearKEMean - 0.5*rhoMMean*(UMean & UMean) + internalEMean)
);
Info<< " Calculating pressure field." << endl;
volScalarField p
(
IOobject
(
"p",
obr_.time().timeName(),
obr_,
IOobject::NO_READ
),
physicoChemical::k.value()*rhoNMean*translationalT
);
const fvMesh& mesh = fDMean.mesh();
forAll(mesh.boundaryMesh(), i)
{
const polyPatch& patch = mesh.boundaryMesh()[i];
if (isA<wallPolyPatch>(patch))
{
p.boundaryField()[i] =
fDMean.boundaryField()[i]
& (patch.faceAreas()/mag(patch.faceAreas()));
}
}
Info<< " mag(UMean) max/min : "
<< max(mag(UMean)).value() << " "
<< min(mag(UMean)).value() << endl;
Info<< " translationalT max/min : "
<< max(translationalT).value() << " "
<< min(translationalT).value() << endl;
Info<< " internalT max/min : "
<< max(internalT).value() << " "
<< min(internalT).value() << endl;
Info<< " overallT max/min : "
<< max(overallT).value() << " "
<< min(overallT).value() << endl;
Info<< " p max/min : "
<< max(p).value() << " "
<< min(p).value() << endl;
UMean.write();
translationalT.write();
internalT.write();
overallT.write();
p.write();
Info<< "dsmcFields written." << nl << endl;
}
else
{
Info<< "Small value (" << min(mag(rhoNMean))
<< ") found in rhoNMean field. "
<< "Not calculating dsmcFields to avoid division by zero."
<< endl;
}
}
}

明显可以发现dsmcFields所做的只是根据fieldAverage1输出的时均场来求出其他时均场,例如根据时均密度场和时均动量场来求出时均速度场。这个工作也可以由后处理工具dsmcFieldsCalc完成,而不是写成controlDict里面的function object。

总结

综上分析,有以下总结:

  • fieldAverage对宏观量场求时均是dsmcFoam里面输出宏观量场的最重要步骤。
  • window控制对那一段时间求平均。如果不设置window就是从开始到当前所有步的平均。
    如果设置window, 结合base (ITER/time),可以设置 从当前时间/步 往前多少s/步 求时均值。
  • dsmcFields 辅助计算其他时均场如 UMean, overallT等等,它只是对fieldAverage输出的时均场做简单的加减乘除计算。

OpenFOAM里面的网格对象(fvMesh)

上次讲到了Time对象,并介绍了我们的程序如何跟system/controlDict文件关联。这次我们介绍程序如何读取网格、网格文件在OpenFOAM里面如何表示、以及如何获取网格信息。

程序如何读取网格

上次介绍setTime.H时提到过,程序读取网格是通过在main函数开头用包含createMesh.H文件来实现的。 现在我们改动我们的HelloWorld程序,包含这个头文件,如下

1
2
3
4
5
6
int main(int argc, char* argv[])
{
#include "setRootCase.H"
#include "createTime.H"
#include "createMesh.H"
...

createMesh.H文件位于$FOAM_SRC/OpenFOAM/include/。 我们可以看看其内容是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// createMesh.H
// ~~~~~~~~~~~~
Foam::Info
<< "Create mesh for time = "
<< runTime.timeName() << Foam::nl << Foam::endl;
Foam::fvMesh mesh
(
Foam::IOobject
(
Foam::fvMesh::defaultRegion,
runTime.timeName(),
runTime,
Foam::IOobject::MUST_READ
)
);

可以发现其实是创建了一个fvMesh对象,叫做mesh。 创建这个fvMesh对象的时候程序读取了constant/polyMesh/目录下面的
pointsfacesownerneighbourboundary这5个文件。这5个文件就是OpenFOAM存储网格的文件。它们可以用blockMesh生成,也可以用前处理工具从其他格式的网格文件转换得到(比如用fluentMeshtoFoam、gmshToFoam等等)。 有了这个对象,我们就可以从这个对象获得网格的所有信息(如cell center的坐标、cell的体积等等),这些信息在创建fvMesh对象时,其构造函数就以自动计算好了。

下面举个例子,看看如何获得每个cell 的体积。 简单起见,首先我们准备好一个简单的网格,直接从官方教程的cavity case里面复制blockMeshDict到我们之间创建的HelloWorld case过来:

1
2
3
cd $FOAM_RUN/HelloWorld #进入之前我们创建的helloWorld case目录
mkdir -p constant/polyMesh #先创建这个目录
cp $FOAM_TUTORIALS/incompressible/icoFoam/cavity/constant/polyMesh/blockMeshDict constant/polyMesh/blockMeshDict #复制blockMeshDict

修改constant/polyMesh/blockMeshDict文件,把cell个数改为为(2 2 1),也就是X、Y方向各两个cell,Z方向一个cell, 并且把convertToMeters改成1。 如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
...
convertToMeters 1; //改动
vertices
(
(0 0 0)
(1 0 0)
(1 1 0)
(0 1 0)
(0 0 0.1)
(1 0 0.1)
(1 1 0.1)
(0 1 0.1)
);
blocks
(
hex (0 1 2 3 4 5 6 7) (2 2 1) simpleGrading (1 1 1) //改动
);
...

使用blockMesh生成网格。现在constant/polyMesh目录下面就有那5个表示网格的文件了。

接下来还要准备system/fvSchemes文件和system/fvSolutions文件, 即使它们里面是空的,因为fvMesh的构造函数必须读取这两个文件。这里我们用的这两个文件内容(除去头部和尾部)分别是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ cat system/fvSchemes
...
gradSchemes
{
default none;
}
divSchemes
{
default none;
}
laplacianSchemes
{
default none;
}
...


1
2
3
solvers
{
}

这已经是程序能初始化mesh所需要的最少的信息了,少了任何一条都不行。

现在我们在HelloWorld程序里面增加如下几行代码,来输出每个cell的体积:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include "fvCFD.H"
#include "MyLib.H"
int main(int argc, char* argv[])
{
#include "setRootCase.H"
#include "createTime.H"
#include "createMesh.H"
...
// mesh info
const scalarField& V = mesh.V(); // cell volume
forAll(V, i) // forAll是OpenFOAM提供的对容器类循环的宏
{
Info << "Volume of cell # " << i << ": " << V[i] << endl;
}
...
return 0;
}

这里的const scalarField& V = mesh.V()就是获取mesh对象的所有cell体积,给一个scalarFiled类型的引用。scalrField其实是通过typedef Field<scalar>得到的, 这里初略的理解为一个标量元素的数组,并且可以用[]操作符取元素就够了。(Field<>只是OpenFOAM众多层次的容器类的一层)。

重新wmake编译,运行输出如下

1
2
3
4
5
...
Volume of cell # 0: 0.025
Volume of cell # 1: 0.025
Volume of cell # 2: 0.025
Volume of cell # 3: 0.025

可以发现每个cell的体积都是(1/2)*(1/2)*0.1 = 0.025

类似的,还可以获取cell center坐标、face center坐标、face的法向量和面积等几何信息,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
...
const vectorField& C = mesh.C(); // cell center coordinate
forAll(C, i)
{
Info << "Center of cell # " << i << ": " << C[i] << endl;
}
const vectorField& Cf = mesh.Cf(); // face center coordinate
forAll(Cf, i)
{
Info << "Center of face # " << i << ": " << Cf[i] << endl;
}
const vectorField& Sf = mesh.Sf(); // face normal
forAll(Sf, i)
{
Info << "Normal of internal face # " << i << ": " << Sf[i] << endl;
}
...

下面这个表格列出了获取网格的一些基本几何信息的函数:

我们暂时先在这里停住,在最后一节再演示如何获取网格的其他信息(数据信息和拓扑信息)。 接下来我们看看任意非结构网格在OpenFOAM内部是如何表示的。

网格在OpenFOAM里面如何表示

我们开始以刚才的2*2*1的网格为例来说明。 constant/polyMesh目录下面的5个文件内容分布是:

  • points : 记录所有的网格顶点,每个点都是由三个分量组成的(三维空间坐标)。 本例中一共有3*3*2 = 18个点。
  • faces : 记录所有的face和组成每个face的顶点编号(就是出现在points里面所有顶点中的次序)。对于本例,一共有20个face,包含了内部face (internal face)和位于边界上的face(boundary face)。
  • boundary: 记录每个boundary patch的信息,包括patch的名字,类型、所包含的face的个数n,以及这些face在所有face中的开始位置(也就是此patch中第一个face出现在face记录的face列表里面的次序)。 每个boundary patch中的face编号在faces中是连续的。 faces 中开头记录的face是internal face。下图展示的是之前例子中的boundary和face:


这里有4个internal face和3个boundary patch,分别是, movingWall(2个face), fixedWall(6个face), frontAandBack(8个face)。

在介绍剩下的两个文件ownerneighbour之前,有必要介绍OpenFOAM网格里面的face的owner和neighbour的概念。 无论网格多么复杂,有一个基本特点或者说是要求是: 除了boundary face外的每个face (也就是internal face) 被且仅被两个cell共享。这两个cell分别称为这个face的owner cell和neighbour cell。 规定这个这个face的法向量从owner指向neighbour。 boundary face也有owner cell,但是没有neighbour cell,因为boundary face的法向量指向了计算区域外部,那里没有cell了。现在再看ownerneighbour文件就简单了:

  • owner文件记录了一个整型数组,第i个元素表示第i个face(包括boundary face)的owner cell的在所有cell中的编号(cell ID),数组大小等于所有face的个数(本例是20)。
  • neighbour文件记录了一个整型数组,第i个元素表示第i个internal face的neighbour cell的在所有cell中的编号(cell ID),数组大小等于所有internal face的个数(本例是4)。

获取网格信息

之前我们介绍了如何通过V()C()Cf()等函数获取一些基本的几何信息,现在我们看如何怎样获取一个face的owner cellID和neighbour cellID。直接看代码:

1
2
3
4
5
6
7
8
9
10
11
// find owner cell and neighbour cell of internal faces
const labelUList& owner = mesh.owner(); // labelUList是一个存贮label的容器。
const labelUList& neighbour = mesh.neighbour();
// loop over ONLY internal face to find owner and neighbour
forAll(owner, facei) //注意这里的owner只存了internal face的owner cellID
{
const label own = owner[facei];
const label nei = neighbour[facei];
Info << "face # " << facei << "'s own: " << own
<< ", nei: " << nei << endl;
}

这段代码输出的是:

1
2
3
4
face # 0's own: 0, nei: 1
face # 1's own: 0, nei: 2
face # 2's own: 1, nei: 3
face # 3's own: 2, nei: 3

现在我们可以验证一下到底是不是这样的, 通过刚刚介绍的C()Cf()Sf()函数输出的位置等信息,我们可以找到每个cell和face的编号以及每个face的法向量如下:


对比刚刚输出的结果可以发现确实是这样。

回到刚才插入的代码,可以发现 fvMesh.owner()fvMesh.neighbour()函数可以获取与每个internal face的owner cellID和neighbour cellID。注意这里的owner()返回的数组不包含boundary face的cell ID,这也可以从上例中forAll(owner, facei)只循环4次(4个internal face)可以看出。

那要怎么知道每个boundary face的owner cellID (也就跟这个bounary face相邻的那个cell的编号)呢,毕竟这个信息在处理边界时必须知道。 下面这段代码就是答案

1
2
3
4
5
6
7
8
9
10
11
// loop over boundary faces, except empty boundaries
forAll(mesh.boundary(), patchi) //这里对每个bounary patch循环,mesh.boundary()返回boundary patch的数组,不包含empty类型的boundary patch。
{
const labelUList& pOwner = mesh.boundary()[patchi].faceCells(); // faceCell函数
forAll(pOwner, facei)
{
Info << "Boundary patch #" << patchi
<< "'s face " << "#" << facei << "'s owner cell : "
<< pOwner[facei] << endl;
}
}

这里是打印出了每个boundary patch的每个face的onwer cellID,所以有两层forAll循环。 boundary patch的所有face的owner cellID数组是通过faceCells()函数获取的。
这段代码的输出是:

1
2
3
4
5
6
7
8
Boundary patch #0's face #0's owner cell : 2
Boundary patch #0's face #1's owner cell : 3
Boundary patch #1's face #0's owner cell : 0
Boundary patch #1's face #1's owner cell : 2
Boundary patch #1's face #2's owner cell : 1
Boundary patch #1's face #3's owner cell : 3
Boundary patch #1's face #4's owner cell : 0
Boundary patch #1's face #5's owner cell : 1

这里的0号bounary patch是movingWall,1号boundary patch是fixedWall。可以看到这里并没有处理因为计算1D/2D问题而声明的empty类型的boundary patch (frontAndBack),事实上,我在写CFD程序时也确实不需要处理empty patch,因为从一个empty patch的某个face上流入的必然等于从其相对那个face上流出的,其合贡献为0。

除了faceCell()函数外,还可以获取boundary patch的更多信息,比如name,start(该patch第一个face中在所有的face中的位置)。更多信息可以查看
$FOAM_SRC/finiteVolume/fvMesh/fvPatches/fvPatch/fvPatch.H文件。

至于为什么是这个文件,我们可以浏览源代码顺藤摸瓜搞清楚这里面的boundary patch和mesh的关系以及其中涉及到的类和相应的文件。 很多时候浏览源代码也可以获得很多信息。

首先这里的mesh.boundary()返回的对象类类型是fvBoundaryMesh, 这可以从fvMesh的头文件
$FOAM_SRC/finiteVolume/fvMesh/fvMesh.H中boundary()的函数声明看到:

1
2
//- Return reference to boundary mesh
const fvBoundaryMesh& boundary() const;

在去查找fvBoundaryMesh类的头文件$FOAM_SRC/finiteVolume/fvMesh/fvBoundaryMesh/fvBoundaryMesh.H发现它是继承自fvPatchList

1
2
3
4
5
6
7
...
class fvBoundaryMesh
:
public fvPatchList
{
// Private data
...

而fvPatchList的声明是通过typedef实现的, 在$FOAM_SRC/finiteVolume/fvMesh/fvPatches/fvPatch/fvPatchList.H可以看到

1
typedef PtrList<fvPatch> fvPatchList;

可以看到fvPatchList就是一个fvPatch的指针数组(PtrList)。现在去看看fvPatch类的头文件,在$FOAM_SRC/finiteVolume/fvMesh/fvPatches/fvPatch/fvPatch.H
这里的fvPatch指的就是我们说的boundary patch。 其Access类型的函数可以返回诸多我们感兴趣的数据,比如

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
...
//- Return name
const word& name() const
{
return polyPatch_.name();
}
...
//- Return size
virtual label size() const
{
return polyPatch_.size();
}
...
// Access functions for geometrical data
//- Return face centres
const vectorField& Cf() const;
//- Return neighbour cell centres
tmp<vectorField> Cn() const;
//- Return face area vectors
const vectorField& Sf() const;
...

有时候在写后处理程序时希望通过boundary patch的名字找到boundary patch的编号,这可以通过fvBondaryMesh提供的findPatchID函数实现,

1
2
//- Find patch index given a name
label findPatchID(const word& patchName) const;

举个例子,我们希望找到HelloWorld case里面的fixedWall对应的boundary patch编号,可以用下面的代码实现:

1
2
// find patch ID by name
Info << "Patch fixedWalls's patchID = " << mesh.boundary().findPatchID("fixedWalls") << endl;

程序输出是

1
Patch fixedWalls's patchID = 1

OpenFOAM 使用Time 控制程序主循环

接着上一个post OpenFOAM创建应用程序讲。上次的应用程序出了Info输出流是OpenFOAM提供的,其他的东西就是平白的C语言代码,跟OpenFOAM没有啥关系。这次来试试case目录里面的system/controlDict文件是如何跟我们的应用程序关联起来的。

先看一个例子$FOAM_SOLVERS/basic/laplacianFoam这个求解Laplace方程的标准求解器里面的是怎么弄的。

可以发现laplacianFoam.C 开头是这样的(其实大多数求解器都是这样开头的):

1
2
3
4
5
6
7
8
9
10
int main(int argc, char *argv[])
{
#include "setRootCase.H"
#include "createTime.H"
#include "createMesh.H"
#include "createFields.H"
...
}

后面的两行应该是创建网格和物理场,这里我们暂时不讨论。主要看setRootCase.HcreateTime.H
这两个文件在当前目录$FOAM_SOLVERS/basic/laplacianFoam目录里面找不到、去Make/options里面看看头文件位置发现应该是在$FOAM_SRC/finiteVoluem/lnInclude下面,或者直接使用文件查找命令find在$FOAM_SRC目录按文件名查找它们。比如这个setRootCase.H文件

1
2
3
$ find $FOAM_SRC -name setRootCase.H
$FOAM_SRC/OpenFOAM/include/setRootCase.H
$FOAM_SRC/OpenFOAM/lnInclude/setRootCase.H

我这里的输出把用$FOAM_SRC替换了路径前缀/home/lhzhu/OpenFOAM/OpenFOAM-2.4.0/src。这里输出有两行,其实第二个文件只是第一个文件的链接(对应Windows的快捷方式)。下面这个命令可以验证。

1
$ ls -l $FOAM_SRC/OpenFOAM/lnInclude/setRootCase.H

我们可以看看这个setRootCase.HcreateTime.H的内容:

1
$ cat $FOAM_SRC/OpenFOAM/lnInclude/setRootCase.H

1
2
3
4
5
6
7
8
9
//
// setRootCase.H
// ~~~~~~~~~~~~~
Foam::argList args(argc, argv);
if (!args.checkRootCase())
{
Foam::FatalError.exit();
}
1
$ cat $FOAM_SRC/OpenFOAM/lnInclude/createTime.H
1
2
3
4
5
6
7
//
// createTime.H
// ~~~~~~~~~~~~
Foam::Info<< "Create time\n" << Foam::endl;
Foam::Time runTime(Foam::Time::controlDictName, args);

可以发现这两个文件非常简单,总共加起来就是几行。OpenFOAM把这两个文件放在这里是因为基本所有的求解器都要来这么几行。其实我们也完全可以手动把这几行手动加到main函数开头。#include XXX.H这种语句原本就是告诉编译器预处理时把XXX.H文件的内容贴到此处

#include "createTime.H" 创建一个Time对象。我们通过这个对象和system/controlDict文件来让程序输出10句话。这跟让显式求解器程序往前推进10个时间步概念上类似。写显式求解器,去参考典型的可压缩求解器rhoCentralFoam, 看看里面是怎么推进时间步的。这个求解器在$FOAM_SOLVERS/compressible/rhoCentralFoam。可以发现是在main函数里面用while(runTime.run()){runTime++; ...} 这段代码来循环推进时间步的。

现在我们接着改我们的HelloWorld程序。在HelloWorld.C的基础上加上前面讨论的这些行,让程序每一个都输出”Running…” 和当前的物理时间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
...
int main(int argc, char* argv[])
{
#include "setRootCase.H"
#include "createTime.H"
...
// use runTime
Info << "Starting time looping" << endl;
while(runTime.run())
{
runTime++;
Info << "Running... " << "Time = " << runTime.timeName() << endl;
}
Info << "end!" << endl;
return 0;
}

物理时间可以用runTime.timeName()得到。

再次wmake编译HelloWorld。

接着我们来准备一个case目录来运行这个HelloWorld。前面开发程序的终端先不管, 另外开个ssh终端(命令窗口),

1
2
3
4
5
$ of240
$ run
$ mkdir -p HelloWorld/system
$ cd HelloWorld
$ cp $FOAM_TUTORIALS/basic/laplacianFoam/flange/system/controlDict system

这里我们直接从一个教程算例中复制了controDict文件过来。接下来修改里面里面的endTime为1,deltaTime为0.1,表示时间步长为0.1,到时间为1时结束运行,这样就可以让程序主循环跑10次了。

然后就可以运行HelloWorld看看结果了:

1
$ HelloWorld

程序输出的主要部分如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Create time
Hello world!
Dear FOAMers,
I come from Wuhan, China
I'm from MyLib, funA()
I'm from MyLib, funB()
Starting time looping
Running... Time = 0.1
Running... Time = 0.2
Running... Time = 0.3
Running... Time = 0.4
Running... Time = 0.5
Running... Time = 0.6
Running... Time = 0.7
Running... Time = 0.8
Running... Time = 0.9
Running... Time = 1
end!

现在可以用controlDict控制我们的HelloWorld的主循环了。注意这里用到的时固定时间步长循环的方法,后面我们会讲到利用CFL数(Conant数)控制可变时间步长的方法。

访问当前步的时间步长可以用Time.deltaValue()函数, 如

1
const scalar dt = runTime.deltaValue();

我们如果想在system/controlDict自定义新的控制参数,可以使用Time.controlDict()访问这些参数。下面我们分别在controlDict里面增加一个浮点数(testParameter)和一个整数参数(labelTp):

1
2
3
4
5
6
7
8
9
10
11
$ tail system/controlDict
timePrecision 6;
runTimeModifiable true;
testParameter 1.4;
labelTp 2;
// ************************************************************************* //

在HelloWorld程序里面通过Time.controlDict()对象可以访问这些参数,

1
2
3
4
5
// self-defined control parameter
scalar tp = runTime.controlDict().lookupOrDefault<scalar>("testParameter", 2.0);
Info << "testParameter = " << tp << endl;
label ltp = runTime.controlDict().lookupOrDefault<label>("labelTp", 2.0);
Info << "labelTp = " << ltp << endl;

注意: OpenFOAM里面使用label表示正整数、使用scalar表示浮点数,而scalar默认就是double类型的。

当然Time.controlDict也可以访问其他通用的控制参数。 另外,虽然这个Time.controlDict()机制可以往程序里面传递参数,但是我们一般不把时间步长控制以外的参数比如类似物理粘性放到这个system/controlDict里面,而是单独弄一个输出输出字典文件(IOdictionary)来读参数。本身system/controlDict在OpenFOAM内部也表示为IOdictionary对象。

DONE!!!

OpenFOAM 创建程序和使用动态链接库

创建应用程序

创建用户applications目录, 创建HelloWorld目录作为应用程序的源代码目录

1
2
3
4
$ mkdir –p $FOAM_RUN/../applications
$ cd $FOAM_RUN/../applications
$ mkdir HelloWorld
$ cd HelloWorld

源程序文件

1
$ cat HelloWorld.C

1
2
3
4
5
6
7
8
9
#include "fvCFD.H"
int main(int argc, char* argv[])
{
Info << "Hello world!" << endl;
Info << "Dear FOAMers," << nl
<< "I come from Wuhan, China" << endl;
return 0;
}

编写Make文件,包括Make/files和Make/options。

1
$ cat Make/files

1
2
3
HelloWorld.C
EXE = $(FOAM_USER_APPBIN)/HelloWorld
1
$ cat Make/options
1
2
3
4
5
6
EXE_INC = \
-I$(LIB_SRC)/finiteVolume/lnInclude \
-I$(LIB_SRC)/meshTools/lnInclude
EXE_LIBS = -lfiniteVolume \
-lmeshTools

使用wmake编译

1
$ wmake

运行程序

1
$ HelloWorld

这里可以直接使用HelloWorld命令,而不给全它的路径,是因为其所在的$FOAM_USER_APPBIN目录已经在PATH环境变量里面了。

创建并链接动态库

下面我们开始创建一个叫MyLib的动态链接库,里面有两个简单函数。之后在HelloWorld程序里面调用这两个库函数。

MyLib库的源代码就在HelloWorld目录里面创建:

1
$ mkdir MyLib

接着在MyLib目录里面创建库的源程序和头文件。首先看MyLib库的头文件,

1
$ cat MyLib/MyLib.H

1
2
3
4
5
6
7
8
#ifndef MyLib_H
#define MyLib_H
#include "fvCFD.H"
void funA();
void funB();
#endif

再看库的源文件

1
$ cat MyLib/MyLib.C

1
2
3
4
5
6
7
8
9
10
11
#include "MyLib.H"
void funA()
{
Info << "I'm from MyLib, funA()" << endl;
}
void funB()
{
Info << "I'm from MyLib, funB()" << endl;
}

最后为这个库的写Make文件如下

1
$ cat MyLib/Make/files

1
2
3
MyLib.C
LIB = $(FOAM_USER_LIBBIN)/libMyLib
1
$ cat MyLib/Make/options
1
2
3
4
5
6
EXE_INC = \
-I$(LIB_SRC)/finiteVolume/lnInclude \
-I$(LIB_SRC)/meshTools/lnInclude
EXE_LIBS = -lfiniteVolume \
-lmeshTools

现在可以用wmake编译这个生成动态链接库了,在HelloWorld目录执行

1
wmake libso MyLib

或者进入MyLib目录执行wmake libso

1
2
$ cd
$ wmake libso

都可以。编译成功后会提示生成的动态链接库文件会存放在$FOAM_USER_APPBIN目录。

接下来我们在HelloWorld里面使用MyLib这个动态链接库的funA()funB()函数。当然先要在HelloWorld.C里面包含头文件”MyLib.H”,

1
2
3
4
5
6
7
8
9
10
#include "fvCFD.H"
#include "MyLib.H"
int main(int argc, char* argv[])
{
...
// use dynamic library
funA();
funB();
...

在HelloWorld的Make/options里面添加MyLib库的位置(-L选项)、库头文件位置(-I)、库文件(-l)

1
$ cat Make/options

1
2
3
4
5
6
7
8
9
10
EXE_INC = \
-I$(LIB_SRC)/finiteVolume/lnInclude \
-I$(LIB_SRC)/meshTools/lnInclude \
-IMyLib/lnInclude
EXE_LIBS = \
-L$(FOAM_USER_LIBBIN) \
-lfiniteVolume \
-lmeshTools \
-lMyLib

重新编译HelloWorld

1
$ wmake

最后运行

1
$ HelloWorld

输出如下结果

1
2
3
4
Dear FOAMers,
I come from Wuhan, China
I'm from MyLib, funA()
I'm from MyLib, funB()

DONE!!!

OpenFOAM 学习资料

这里罗列我学习和使用OpenFOAM过程中参考到的一些资料,包括一些官方文档、幻灯片、网址等等。大多数都提供了文件连接或者下载最新版本的地址。

官方资料

上面的大多数资料会随着OpenFOAM的发布持续更新。注意除了www.openfoam.orgwww.openfoam.com也算是官方网站。区别是前者是OpenFOAM基金会的网站,而后者是OpenCFD(ESI集团)公司的网站。开源软件的商标、版权、运作都比较复杂。关于这两者的关系可以参考这里

MSc/PhD course in CFD with OpenSource software 幻灯片

论文

书籍

  • F. Moukalled, L. Mangali, The Finite Volume Method in Computational Fluid Dynamics, An Advanced Introduction with OpenFOAM and Matlab, Springer, 2015
  • Jens Höpken, The OpenFOAM Technology Primer

Instructional workshop on OpenFOAM programming 系列幻灯片

这里面讲的是如何开发一个求解器。并且里面也提前介绍了很多开发solver需要用到的概念。

WIKKI公司的一些幻灯片

OpenFOAM Workshop上的课程材料

还有历年举办的OpenFOAM Workshop的幻灯片资料都可以在这里找到

其他幻灯片

网站、博客、论坛

OpenFOAM 自带的test目录 $FOAM_APP/test

这个目录里面有很多示例性质的小程序源代码,每个小程序只演示到OpenFOAM诸多概念中的一个。

在Windows平台的CMAKE里面使用Boost

主要CMAKE FindBoost模块的官方文档

这里假设boost安装位置为D:\Boost_1_61_0

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
cmake_minimum_required(VERSION 2.8)
project (test_boost)
# if on windows
IF(WIN32)
set(BOOST_ROOT D:/boost_1_61_0)
#如果设置了BOOST_ROOT, 下面两行可以注释掉
#set(BOOST_INCLUDEDIR D:/boost_1_61_0)
#set(BOOST_LIBRARYDIR D:/boost_1_61_0/lib64-msvc-14.0)
set(Boost_USE_STATIC_LIBS ON)
set(Boost_USE_MULTITHREADED ON)
set(Boost_USE_STATIC_RUNTIME OFF)
find_package(Boost 1.61.0 REQUIRED COMPONENTS program_options regex)
if(Boost_FOUND)
include_directories(${Boost_INCLUDE_DIRS})
endif()
# A test project that use Boost.ProgramOptions and Boost.Regex。
aux_source_directory(test_boost TEST_BOOST)
add_executable(test_boost ${TEST_BOOST})
target_link_libraries(test_boost ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_REGEX_LIBRARY})

在Windows上安装Boost

官方文档:Getting Started on Windows

Boost里面有很多库,分为需要编译的和不需要编译的(header file only)。

如果只需要使用不需要编译的库(如Boost.PropertyTree)则只需要从官方网站下载源代码解压缩即可使用。Boost官网上的Downloads->Latest Rease->Version x.xx.x 就是下载链接。解压后放到一个目录,比如D:\boost盘。这里以1.61.0版本为例,则安装目录为D:\boost\boost_1_61_0。在Visual Studio 的项目Properties里面的VC++ Directories->Include Directories 里面添加条目D:\boost\boost_1_61_0即可完成编译。可以发现D:\boost\boost_1_61_0\目录是空的,因为官方发布的source pack里面是没有预编译的。

如果需要使用需要编译的库,如 Boost.FileSystem或者Boost.ProgramOptions,则还需要编译这些库。Boost目录(D:\boost\boost_1_61_0)里面的booststrap.bat脚本就是编译脚本。这里我们参照这篇博文使用Visual Studio 2015来编译boost_1_61_0。

  1. D:\boost目录里面创建脚本build_boost_1_61_0_vs2015.bat,内容如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    call "%VS140COMNTOOLS%..\..\VC\vcvarsall.bat" x86
    set cores=%NUMBER_OF_PROCESSORS%
    echo Building boost with %cores% cores
    cd boost_1_61_0
    call bootstrap.bat
    rem Most libraries can be static libs
    b2 -j%cores% toolset=msvc-14.0 address-model=64 architecture=x86 link=static threading=multi runtime-link=shared --build-type=minimal stage --stagedir=stage/x64
    b2 -j%cores% toolset=msvc-14.0 address-model=32 architecture=x86 link=static threading=multi runtime-link=shared --build-type=minimal stage --stagedir=stage/win32
    pause

    这里是设置使用Visual Studio (msvc-14.0) toolset编译32位和64位的静态库文件,并且设置了生成的库文件位置为stage目录。更详细的编译选项可以参考官方文档。

  2. 打开cmd,进入D:\boost目录,执行builde_boost_1_61_0_vs2015.bat脚本,等待编译完成。编译完成后回提示 Press any key to continue。编译生成的库文件在stage目录下面。

  3. 为了区分不同编译器生成的库文件,在D:\boost\boost_1_61_0目录下面新建lib64-msvc-14.0目录,表示是Visual Studio 2015编译的。再把stage/x64/*.lib 拷贝到这个新建的目录下面。

因为编译过程比较耗时,可以考虑直接下载Boost Binaries Installer For Windows。下载安装之后,里面直接提供了lib64-msvc-14.0等目录。