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, 以不同的形式加入到各个场方程中去.