OpenFOAM中的源项模型 fvOptions 解析
OpenFOAM 中输运方程中的源项的实现是通过 fvOptions
来完成的. 例如在速度方程中( 例如文件 /applications/solvers/incompressible/simpleFoam/UEqn.H
)
tmp<fvVectorMatrix> tUEqn
(
fvm::div(phi, U)
+ MRF.DDt(U)
+ turbulence->divDevReff(U)
==
fvOptions(U)
);
而这个fvOptions
对象定义在文件createFvOptions.H
中的:
fv::options& fvOptions(fv::options::New(mesh));
非常简单, 通过 mesh
初始化生成了一个对象, 该对象属于类 fv::options
. 根据我们往期关于湍流模型的内容, 可以推测类 fv::options
只是一个基类. 实际运算过程中的对象 fvOptions
应该是属于某个信息更加具体的派生类的.
接下来我们先看类 fv::options
( 文件 /src/finiteVolume/cfdTools/general/fvOptions/fvOptions.H
)
class options
:
public IOdictionary,
public optionList
{...}
这里 IOdictionary
是一个 IO
字典, 更进一步的考察得知该字典要从 mesh
文件里读取一个名为 fvOptions
的文件.而 optionList
是一个 option
列表. 有该列表的存在,使得对象 fvOptions
可以以不同的源项形式适用于多个输运方程.
我们来看类 options
的构造函数 ( 文件src/finiteVolume/cfdTools/general/fvOptions/fvOptions.C
):
Foam::fv::options::options
(
const fvMesh& mesh
)
:
IOdictionary(createIOobject(mesh)),
optionList(mesh, *this)
{}
这里 IOdictionary(createIOobject(mesh))
是将IO
对象 createIOobject(mesh)
对 IOdictionary
进行初始化. createIOobject(mesh)
函数定义如下:
Foam::IOobject Foam::fv::options::createIOobject
(
const fvMesh& mesh
) const
{
IOobject io
(
typeName,
mesh.time().constant(),
mesh,
IOobject::MUST_READ,
IOobject::NO_WRITE
);
可以看到创建了一个对象io
, 其内容从 constant
目录下的一个名为 "typeName" 的文件得来. 这个 typeName
在这里就是 fvOptions
. 这部分内容读者可自行推导:
从文件fvOptions.H
中的宏命令
ClassName("fvOptions");
以及fvOptions.C
中的宏命令
defineTypeNameAndDebug(options, 0);
就可以知道typeName
的值是什么.
如果constant
目录没有文件fvOptions
,那么就到目录system
下寻找:
// Check if the fvOptions file is in system
io.instance() = mesh.time().system();
如果再找不到就不读取任何内容而直接返回io
值:
Info<< "No finite volume options present" << nl << endl;
io.readOpt() = IOobject::NO_READ;
return io;
到这里IOdictionary(createIOobject(mesh))
的操作就解释清楚了.
接下来我们来分析optionList(mesh, *this)
这步操作.其中的*this
指针是什么意思呢?
还是先看类optionList
的定义以及构造函数.
class optionList
:
public PtrList<option>
...
Foam::fv::optionList::optionList(const fvMesh& mesh, const dictionary& dict)
:
PtrList<option>(),
mesh_(mesh),
checkTimeIndex_(mesh_.time().startTimeIndex() + 2)
{
reset(optionsDict(dict));
}
到这里我们应该明白 optionList(mesh, *this)
中的 this
指针是一个字典类型,而这个字典已经在之前的
IOdictionary(createIOobject(mesh))
赋值过了.
在optionList
的构造函数中,用到了两个函数:reSet
和 optionsDict
const Foam::dictionary& Foam::fv::optionList::optionsDict
(
const dictionary& dict
) const
{
if (dict.found("options"))
{
return dict.subDict("options");
}
else
{
return dict;
}
}
其中的 optionsDict
函数功能是查询在 fvOptions
文件中是否存在关键字 options
.如果存在就把该关键字引导的字典的值返回给函数.如果不存在关键字 options
,则把fvOptions
文件的内容返还给函数.而 reset
函数就很有意思了:
它首先从参数字典dict
中读取这里面有多少个字典:
forAllConstIter(dictionary, dict, iter)
{
if (iter().isDict())
{
count++;
}
}
forAllConstIter
是一个宏,作用是遍历 dict
里的所有类型为 dictionary
的内容,然后它会按照这些小字典创建不同的option
对象:
const word& name = iter().keyword();
const dictionary& sourceDict = iter().dict();
this->set
(
i++,
option::New(name, sourceDict, mesh_)
);
比如在文件constant/fvOptions
有如下内容:
heatSource
{
type scalarSemiImplicitSource;
active true;
scalarSemiImplicitSourceCoeffs
{
selectionMode cellZone; // all, cellSet, cellZone, points
cellZone porosity;
volumeMode specific; // absolute;
injectionRateSuSp
{
T (0.1 0);
}
}
}
momentumSource
{
type meanVelocityForce;
selectionMode all;
fields (U);
Ubar (0.1335 0 0);
}
那么就根据这些内容创建了两个option
对象,名字分别是 heatSource
和momentumSource
.对于 heatSource
对应的 option
对象来说,sourceDict
就是heatSource{...}
括号里面的信息.这个信息被用于构造函数option::New
(见文件fvOption.C
)
Foam::autoPtr<Foam::fv::option> Foam::fv::option::New
(
const word& name,
const dictionary& coeffs,
const fvMesh& mesh
)
{
word modelType(coeffs.lookup("type"));
Info<< indent
<< "Selecting finite volume options model type " << modelType << endl;
const_cast<Time&>(mesh.time()).libs().open
(
coeffs,
"libs",
dictionaryConstructorTablePtr_
);
dictionaryConstructorTable::iterator cstrIter =
dictionaryConstructorTablePtr_->find(modelType);
if (cstrIter == dictionaryConstructorTablePtr_->end())
{
FatalErrorInFunction
<< "Unknown Model type " << modelType << nl << nl
<< "Valid model types are:" << nl
<< dictionaryConstructorTablePtr_->sortedToc()
<< exit(FatalError);
}
return autoPtr<option>(cstrIter()(name, modelType, coeffs, mesh));
}
在字典 coeffs
( 也就是上面提到的 sourceDict
) 中寻找关键字 type
, 确定modelType
, 创建一个 option 类型的指针 autoPtr<option>,
这样对象就创建完毕了.和 OF 中一般地创建对象的过程一样, autoPtr<option>
是个基类指针. 在程序执行的时候, 其 option
对象是某个派生类对象,例如 scalarSemiImplicitSource
对象, meanVelocityForce
对象等等.
那么问题来了,我们如何知道创建的源项是被加入到指定的输运方程里去的呢? 以类模板 SemiImplicitSource<Type>
为例 ( 生成具体的类使用宏命令 makeFvOption
), 在文件 SemiImplicitSourceIO.C
中, 先提取字典 injectionRateSuSp
中的内容并在函数 setFieldData
中使用setFieldData(coeffs_.subDict("injectionRateSuSp"));
setFieldData
函数在文件 SemiImplicitSource.C
中定义
void Foam::fv::SemiImplicitSource<Type>::setFieldData(const dictionary& dict)
...
forAllConstIter(dictionary, dict, iter)
{
fieldNames_[i] = iter().keyword();
dict.lookup(iter().keyword()) >> injectionRate_[i];
i++;
}
所以这里边的 fieldNames_
就规定了该源项究竟是加入到哪个输运方程中的, 其取值来自字典 injectionRateSuSp{...}
括号里的关键字. 例如在之前的 fvOptions
文件中, 该关键字就是T
.
到这里, 我们就把求解器里源项fvOptions
的实现过程梳理清楚了. 在具体的算例中, 程序应当首先读取文件constant/fvOptions
或者是system/fvOptions
的内容. 根据里边的内容, 创建若干 ( 也可以没有 ) 不同形式的源项. 而对于每种形式的源项, 也要指定其所应用的场方程. 这样 OF 就做到了在求解器里用一个对象fvOptions
, 以不同的形式加入到各个场方程中去.