OpenFOAM 中的 function object 是其提供的比较灵活的数据处理功能,可以在数值仿真的同时进行数据处理,OpenFOAM 将其称为运行时数据处理

在 OpenFOAM 5.0 以后,function object 的整个框架进行了代码重构。本文以 OpenFOAM 6 对象,分析 function object 框架的实现原理。`

Time

OpenFOAM 通过 Time 类实现 function object。

Time 是 OpenFOAM 中描述时间的基本类,通常用 while (runTime.run())while (runTime.loop()) 来实现时间步的循环。这两种方法的区别是:runTime.run() 需要手动增加时间步,而 runTime.loop() 自动增加时间步。这可以从 Time::run() 的定义中看出来:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
bool Foam::Time::loop()
{
    bool running = run();

    if (running)
    {
        operator++();
    }

    return running;
}

loop() 函数先调用 run(),然后执行 operator++() ,将时间步加一。

Time 类的头文件中也给出了这两个函数的使用示例,在 runTime.run() 循环中需要手动执行 runTime++

  • Time::run()
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
//- Return true if run should continue,
//  also invokes the functionObjectList::end() method
//  when the time goes out of range
//  \note
//  For correct behaviour, the following style of time-loop
//  is recommended:
//  \code
//      while (runTime.run())
//      {
//          runTime++;
//          solve;
//          runTime.write();
//      }
//  \endcode
  • Time::loop()
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
//- Return true if run should continue and if so increment time
//  also invokes the functionObjectList::end() method
//  when the time goes out of range
//  \note
//  For correct behaviour, the following style of time-loop
//  is recommended:
//  \code
//      while (runTime.loop())
//      {
//          solve;
//          runTime.write();
//      }
//  \endcode

function object 功能的实现

Time 类中有一个 functionObjectList 类型的成员变量 functionObjects_,这个变量中存储了从 controlDict 文件中读取的所有 function objects.

每次执行 runTime.run() 时,functionObjects_ 的一些成员函数将被执行,函数执行的流程图如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
st=>start: 开始
e=>end: 结束
funcObjsStart=>operation: functionObjects_.start():>https://github.com/OpenFOAM/OpenFOAM-6/blob/master/src/OpenFOAM/db/Time/Time.C#L820[blank]
funcObjsExec=>operation: functionObjects_.execute():>https://github.com/OpenFOAM/OpenFOAM-6/blob/master/src/OpenFOAM/db/Time/Time.C#L824[blank]
funcObjsEnd=>operation: functionObjects_.end():>https://github.com/OpenFOAM/OpenFOAM-6/blob/master/src/OpenFOAM/db/Time/Time.C#L808[blank]
runTimeLoop=>operation: 进入runTime.loop():>https://github.com/OpenFOAM/OpenFOAM-6/blob/master/src/OpenFOAM/db/Time/Time.C#L836[blank]
runTimeWrite=>inputoutput: 执行runTime.write()
(实际调用regIOobject::write())
cond1=>condition: 是否是第一个时间步?:>https://github.com/OpenFOAM/OpenFOAM-6/blob/master/src/OpenFOAM/db/Time/Time.C#L818[blank]
cond2=>condition: 是否结束计算?:>https://github.com/OpenFOAM/OpenFOAM-6/blob/master/src/OpenFOAM/db/Time/Time.C#L805[blank]

st->runTimeLoop->cond1
cond1(yes,right)->funcObjsStart->cond2
cond1(no)->funcObjsExec->cond2
cond2(yes,down)->funcObjsEnd->e
cond2(no)->runTimeWrite(right)->runTimeLoop

调用流程的具体步骤归纳如下:

  1. 在第一个时间步内,执行 functionObjects_.start()
  2. 在随后的时间步内,执行 functionObjects_.execute()
  3. 在最后一个时间步内,依次执行 functionObjects_.execute()functionObjects_.end()

这些涉及到的成员函数主要作用如下:

  • functionObjectList::start(): 调用 read(),从 controlDict 中读取关键词 functions对应的条目,并初始化自身(functionObjectList 继承自 PtrList<functionObject>,是一个由多个 functionObject 组成的列表)。
  • functionObjectList::execute(): 遍历所有 function objects 对象,并调用每个对象的 execute()write() 方法。
  • functionObjectList::end(): 遍历所有 function objects 对象,并调用每个对象的 end() 方法。

functionObjectList::read()

先看 functionObjectList::read() 中构造 function objects 的相关代码:

 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
        if (objPtr)
        {
            ...
        }
        else if (enabled)
        {
            autoPtr<functionObject> foPtr;

            FatalError.throwExceptions();
            FatalIOError.throwExceptions();
            try
            {
                if
                (
                    dict.found("writeControl")
                 || dict.found("outputControl")
                )
                {
                    foPtr.set
                    (
                        new functionObjects::timeControl(key, time_, dict)
                    );
                }
                else
                {
                    foPtr = functionObject::New(key, time_, dict);
                }
            }

            ...
        }

如果字典文件中有 writeControloutputControloutputControl 是旧版本用的关键词,为了兼容性而保留的),则构造 functionObjects::timeControl 对象(注意这里的 functionObjects命名空间);否则,调用 functionObjectNew 函数,利用 RTS 构造其派生类对象。

timeControl 是一个在 functionObjects 命名空间 下,派生自 functionObject 的类。该类提供了与时间相关的操作,类中还封装了另一个 functionObject 对象 foPtr_foPtr_为实际要执行的 function object。

以时间平均 fieldAverage 这个 function object 为例说明。下面是其在 controlDict 中的配置:

1
2
3
4
5
6
7
    fieldAverage1
    {
        type                fieldAverage;
        libs                ("libfieldFunctionObjects.so");

        writeControl    writeTime;
        ...

当读取到关键词 writeControl 后,将构造一个 functionObject::timeControl 对象。在构造这个对象时,成员变量 foPtr_ 也会被初始化,利用 RTS 构造 foPtr_

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Foam::functionObjects::timeControl::timeControl
(
    const word& name,
    const Time& t,
    const dictionary& dict
)
:
    functionObject(name),
    time_(t),
    dict_(dict),
    startTime_(-vGreat),
    endTime_(vGreat),
    nStepsToStartTimeChange_
    (
        dict.lookupOrDefault("nStepsToStartTimeChange", 3)
    ),
    executeControl_(t, dict, "execute"),
    writeControl_(t, dict, "write"),
    foPtr_(functionObject::New(name, t, dict_))    // 利用 RTS 构造 foPtr_
{
    readControls();
}

functionObject 的主要成员函数

我们再来看 functionObject 的三个成员函数 execute()write()end()

前两个是纯虚函数,必须在派生类中重新实现:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
        //- Called at each ++ or += of the time-loop.
        //  postProcess overrides the usual executeControl behaviour and
        //  forces execution (used in post-processing mode)
        virtual bool execute() = 0;

        //- Called at each ++ or += of the time-loop.
        //  postProcess overrides the usual writeControl behaviour and
        //  forces writing always (used in post-processing mode)
        virtual bool write() = 0;

        //- Called when Time::run() determines that the time-loop exits.
        //  By default it simply calls execute().
        virtual bool end();

最后一个 end() 函数注释中写的默认调用 execute() 是错误的,end() 默认是一个什么都不做的空函数:

1
2
3
4
5
bool Foam::functionObject::end()
{
    return true;
}

上面注释中提到在 post-processing 模式中 execute()write() 将分别被 executeControlwriteControl 覆盖。由于所有的 postProcess function object 都有 writeControl 关键词(参考 etc/caseDicts/postProcessing 目录下的示例),这些 function object 对象均为 functionObjects::timeControl 类型,该类重写了 execute()write(),使其通过字典文件中的 executeControlwriteControl 关键词来控制 function object 执行和写入的行为和频率。如只有 executeControl_.execute() 为真时才会执行实际 function object 的成员函数 foPtr_->execute()

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
bool Foam::functionObjects::timeControl::execute()
{
    if (active() && (postProcess || executeControl_.execute()))
    {
        foPtr_->execute();
    }

    return true;
}


bool Foam::functionObjects::timeControl::write()
{
    if (active() && (postProcess || writeControl_.execute()))
    {
        foPtr_->write();
    }

    return true;
}