fvOptions 浅析

本篇简单介绍 OpenFOAM 中的 fvOptions。按照官方的介绍,fvOptions 是一个可以在指定区域内添加源项或者其他约束(比如固定温度,或者多孔介质等)的框架。本篇对 fvOptions 框架的源码做一个浅析。

先来了解一下 fvOptions 框架的结构,以及,在求解器中是怎么调用 fvOptions 的。为了避免问题复杂化,先从一个简单的求解器开始:scalarTransportFoam。OpenFOAM-2.3.1 中的 scalarTransportFoam 中已经引入了 fvOptions ,而更早的 OpenFOAM-2.1.1 中,则没有使用 fvOptions 。对比之下,很容易发现引入 fvOptions 其实就只涉及到两处代码修改:1. 增加了一个头文件 createFvOptions.H;2. 在 T 方程中增加了一项 fvOptions(T) 。 根据 OpenFOAM 的习惯,可以猜测这里增加的 fvOptions(T),从C++的角度来看,多半是一个基类的对象 fvOptions 在调用类中重载过的 () 操作符。以上便是从这段简单的求解器代码中产生的对 fvOptions 的第一印象,下面来仔细看看 fvOptions 这个框架的结构。

1. createFvOptions.H

既然求解器里只增加了这一个头文件,那就先从这个看起。这个文件位于 src/fvOptions/include,内容很简单,就一句话:

1
fv::IOoptionList fvOptions(mesh);

从这里就很清楚地可以看出, scalarTransportFoam 中 T 方程中增加的 fvOptionsIOoptionList 类的对象。

2. IOoptionList 类

接下来看 IOoptionList 类。IOoptionList 类由两个文件组成: fvIOoptionList.H 和 fvIOoptionList.C。这里的目的在于了解 fvOptions 这个框架的结构,所以重点看头文件以及构造函数的定义。

  • fvIOoptionList.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
    #ifndef IOoptionList_H
    #define IOoptionList_H

    #include "fvOptionList.H"
    #include "IOdictionary.H"
    #include "autoPtr.H"

    namespace Foam
    {
    namespace fv
    {

    class IOoptionList
    :
    public IOdictionary,
    public optionList
    {
    private:

    // Private Member Functions

    //- Create IO object if dictionary is present
    IOobject createIOobject(const fvMesh& mesh) const;

    //- Disallow default bitwise copy construct
    IOoptionList(const IOoptionList&);

    //- Disallow default bitwise assignment
    void operator=(const IOoptionList&);

    public:

    // Constructors

    //- Construct from components with list of field names
    IOoptionList(const fvMesh& mesh);

    //- Destructor
    virtual ~IOoptionList()
    {}

    // Member Functions

    //- Read dictionary
    virtual bool read();
    };

    } // End namespace fv
    } // End namespace Foam
    #endif

从头文件可以看出, IOoptionList 类继承自 IOdictionaryoptionList 类。根据对 OpenFOAM 的了解,IOdictionary 类是处理跟 IO 有关的,所以这里这个类很可能是用来处理 fvOptions 相关的字典文件的。真正涉及到具体的 fvOptions 源项的内容,应该是在 optionList 中有相关定义。

  • 构造函数
    1
    2
    3
    4
    5
    6
    7
    8
    Foam::fv::IOoptionList::IOoptionList
    (
    const fvMesh& mesh
    )
    :
    IOdictionary(createIOobject(mesh)), //构造函数里创建fvOptions字典文件
    optionList(mesh, *this)
    {}

构造函数中调用 createIOobject 函数来对父类 IOdictionary 进行初始化,并将自己(*this) 作为参数传递给了父类 optionList

  • createIOobject 函数
    构造函数中调用了 createIOobject 函数,而且这里的调用显然至关重要,所以需要看一下这个函数
    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
    Foam::IOobject Foam::fv::IOoptionList::createIOobject
    (
    const fvMesh& mesh
    ) const
    {
    IOobject io
    (
    "fvOptions",
    mesh.time().constant(),
    mesh,
    IOobject::MUST_READ,
    IOobject::NO_WRITE
    );

    if (io.headerOk())
    {
    Info<< "Creating finite volume options from "
    << io.instance()/io.name() << nl
    << endl;

    io.readOpt() = IOobject::MUST_READ_IF_MODIFIED;
    return io;
    }
    else
    {
    // Check if the fvOptions file is in system
    io.instance() = mesh.time().system();

    if (io.headerOk())
    {
    Info<< "Creating finite volume options from "
    << io.instance()/io.name() << nl
    << endl;

    io.readOpt() = IOobject::MUST_READ_IF_MODIFIED;
    return io;
    }
    else
    {
    Info<< "No finite volume options present" << nl << endl;

    io.readOpt() = IOobject::NO_READ;
    return io;
    }
    }
    }

这个函数的意图就很明显了:创建了一个 IOobject 对象,并从 system 中读入文件 fvOptions 的内容来初始化该对象。并且,如果 system 目录下不存在 fvOptions 文件,那就尝试在 constant 下寻找。如果仍找不到,那就放弃读取。

3. optionList 类

这个类由三个源文件定义: fvOptionList.H , fvOptionList.C 和 fvOptionListTemplates.C。这里依然是重点关注头文件。

  • fvOptionList.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
    #ifndef optionList_H
    #define optionList_H

    #include "PtrList.H"
    #include "GeometricField.H"
    #include "fvPatchField.H"
    #include "fvOption.H"

    namespace Foam
    {
    namespace fv
    {

    class optionList
    :
    public PtrList<option>
    {
    protected:

    ......

    const dictionary& optionsDict(const dictionary& dict) const;

    bool readOptions(const dictionary& dict);

    public:

    // Constructors
    optionList(const fvMesh& mesh);

    optionList(const fvMesh& mesh, const dictionary& dict);


    void reset(const dictionary& dict);

    ......

    //- Return source for equation
    // 重载的括号操作符,在求解器里,方程的构造过程中调用的就是这些括号操作符。
    template<class Type>
    tmp<fvMatrix<Type> > operator()
    (
    const volScalarField& rho,
    GeometricField<Type, fvPatchField, volMesh>& fld
    );

    ......
    };

    } // End namespace fv
    } // End namespace Foam

    #ifdef NoRepository
    #include "fvOptionListTemplates.C"
    #endif

    #endif

这里注意两点,一是 optionList 类继承自 PtrList<option> ,另一个是,这里出现了重载的运算符 () 。继承自 PtrList<option> 意味着很可能 fvOptions 类是支持同时定义多个源项的。

  • 构造函数
    1
    2
    3
    4
    5
    6
    7
    8
    Foam::fv::optionList::optionList(const fvMesh& mesh, const dictionary& dict)
    :
    PtrList<option>(),
    mesh_(mesh),
    checkTimeIndex_(mesh_.time().startTimeIndex() + 2)
    {
    reset(optionsDict(dict))
    ;

    }

构造函数里,调用了 reSetoptionsDict 函数。下面看一下这两个函数的作用:

  • optionsDict 函数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    const Foam::dictionary& Foam::fv::optionList::optionsDict
    (
    const dictionary& dict
    ) const
    {
    if (dict.found("options"))
    {
    return dict.subDict("options");
    }
    else
    {
    return dict;
    }
    }

这个函数需要一个参数 const dictionary& dict,这个参数,显然是从构造函数的 const dictionary& dict ,再回顾一下上文中,IOoptionList 中将自己本身(*this)传递给父类 optionsDict ,而且, IOoptionList 同时也继承自 IOdictionary 类,并且将从文件 fvOptions 类中读取的内容来对其父类 IOdictionary 类。所以这里不难理解,参数 const dictionary& dict 其实就是文件 fvOptions 的内容。这里需要从文件中 fvOptions 寻找关键字 options ,如果找到,那就返回 options 所指定的 subDict ,否则就直接返回 fvOptions 本身。

  • reset 函数
    再来看 reset 函数:
    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
    void Foam::fv::optionList::reset(const dictionary& dict)
    {
    // Count number of active fvOptions
    label count = 0;
    forAllConstIter(dictionary, dict, iter)
    {
    if (iter().isDict())
    {
    count++;
    }
    }

    this->setSize(count);
    label i = 0;
    forAllConstIter(dictionary, dict, iter)
    {
    if (iter().isDict())
    {
    const word& name = iter().keyword();
    const dictionary& sourceDict = iter().dict();

    this->set
    (
    i++,
    option::New(name, sourceDict, mesh_)
    );
    }
    }
    }

注意在构造函数里,这个函数的参数是 optionsDict 类的返回值。这里,函数是在统计 fvOptions 中有效的 options 的个数,并根据每一个有效的 options ,调用 option 类的 New 函数(看到 New 函数,很自然就会想到 Run Time Selection 吧!)来构造对象指针,且将该指针存到 PtrList 类定义的 List 里。 这里更是直接能看出来,在文件 fvOptions 中,是可以同时定义多个源项的。

4. option 类

再继续看 option 类。option 类由四个源文件定义: fvOption.HfvOption.CfvOptionI.HfvOptionIO.C

  • fvOption.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
    #ifndef fvOption_H
    #define fvOption_H

    #include "fvMatricesFwd.H"
    #include "volFieldsFwd.H"
    #include "cellSet.H"
    #include "autoPtr.H"
    #include "meshToMesh.H"

    #include "runTimeSelectionTables.H"

    namespace Foam
    {
    class fvMesh;
    namespace fv
    {

    class option
    {
    public:

    // Public data

    //- Enumeration for selection mode types
    enum selectionModeType
    {
    smPoints,
    smCellSet,
    smCellZone,
    smMapRegion,
    smAll
    };

    ......

    option 类终于不再继承自其他类了。而且,包含的头文件中有 #include "runTimeSelectionTables.H" 可以猜想,这个类肯定是作为接口的基类来使用的。
    经验证,具体的源项类,如 semiImplicitSource,都是继承自 option 类的。当然,这里会用到 RTS 机制来提供灵活地源项选择。和其他的作为接口使用的基类类似, option 类中定义了所有的具体源项类可能用到的控制选项,比如,区域的选择(Points,cellSet,cellZone,mapRegion,all),开始时间(timeStart),持续时间(duration)激活开关(active)以及各个可能调用到的函数。

至此,fvOptions 框架的结构就大体理清了,总结如下图:

fvOptions 框架的结构

参考
  1. http://www.sourceflux.de/blog/series/fvoptions/
  2. http://www.openfoam.org/version2.2.0/fvOptions.php