simpop

普通会员

【原创】OpenFOAM 中的源项模型 fvOptions 解析

多相流在线 本期继续 推出 OpenFOAM 系列专题文章 「 OpenFOAM 中的源项模型fvOptions 解析 」。如您对 OpenFOAM 有更进一步的学习兴趣,欢迎您参加由我们举办的 “ OpenFOAM 多相流基础课程 ”  !


OpenFOAM中的源项模型 fvOptions 解析

1. 求解器中 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 应该是属于某个信息更加具体的派生类的.

2. 类 fv:options 的定义和构造函数

接下来我们先看类 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))
的操作就解释清楚了.

3. 类 fv:optionList 的定义和构造函数

接下来我们来分析
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的构造函数中,用到了两个函数:reSetoptionsDict

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 对象,名字分别是 heatSourcemomentumSource.对于 heatSource 对应的 option 对象来说,sourceDict 就是heatSource{...} 括号里面的信息.这个信息被用于构造函数option::New(见文件fvOption.C)

4. 类 fv::option 的 New 函数

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 对象等等.

5. fvOptions 对接输运方程的实现

那么问题来了,我们如何知道创建的源项是被加入到指定的输运方程里去的呢? 以类模板 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.

6. 小结

到这里, 我们就把求解器里源项fvOptions的实现过程梳理清楚了.  在具体的算例中,  程序应当首先读取文件constant/fvOptions或者是system/fvOptions的内容.  根据里边的内容, 创建若干 ( 也可以没有 ) 不同形式的源项. 而对于每种形式的源项, 也要指定其所应用的场方程. 这样 OF 就做到了在求解器里用一个对象fvOptions,  以不同的形式加入到各个场方程中去.