实现新的函数对象需要定义functionObject类的类接口,并准备编译函数对象库所需的文件。当实现不同的函数对象时,这些任务会重复执行。为了节省开发时间,OpenFOAM中提供了一个shell脚本,用于生成新函数对象库所需的文件结构。该脚本名为foamNewFunctionObject,它生成目录结构、函数对象类文件(.C和.H)以及包含单个函数对象的函数对象库的基本构建配置。

12.3.1 函数对象生成器

要使用函数对象生成器,请确保已设置OpenFOAM环境。

以下命令使用foamNewFunctionObject脚本创建新的函数对象库:

?>  foamNewFunctionObject myFuncObject
?>  cd myFuncObject

在OpenFOAM-v2012中,模板文件中存在语法错误,该错误已在主分支上修复。由于本书描述了发布版本,因此应该对生成的函数对象的构造函数进行一个小的修改。行中有括号错误:

Foam::functionObjects::myFuncObject::myFuncObject
(
    const word& name,
    const Time& runTime,
    const dictionary& dict
)
:
fvMeshFunctionObject(name, runTime, dict),
// Bracket error.
boolData_(dict.getOrDefault<bool> ("boolData"), true),

应修改为:

Foam::functionObjects::myFuncObject::myFuncObject
(
    const word&amp; name,
    const Time&amp; runTime,
    const dictionary&amp; dict
)
:
fvMeshFunctionObject(name, runTime, dict),
// Bracket error fixed.
boolData_(dict.getOrDefault<bool> ("boolData",true)),

在这个小的语法修正之后,新的库可以用下面的命令进行编译:

myFuncObject >  wmake libso

foamNewFunctionObject脚本为每个函数对象生成一个OpenFOAM库,如myFunctObject/Make/files中所定义:

myFuncObject >  cat Make/files
myFuncObject.C
LIB = $(FOAM_USER_LIBBIN)/libmyFuncObjectFunctionObject

该库自动命名为myFuncObjectFunctionObject,并存储在包含用户定义的库二进制文件的OpenFOAM文件夹中。

为我们引入的每个新函数对象生成一个不同的函数对象库是不切实际的,因为这需要在system/controlDict文件中为用foamNewFunctionObject生成的每个函数对象添加一个库条目。可以分类到一个组中的函数对象属于同一个库。为了将foamNewFunctionObject生成的函数对象组织成组,可以使用foamNewFunctionObject生成多个函数对象文件夹结构,并将其编译成单个库。例如,

?>  mkdir myFunctionObjects &amp;&amp; cd myFunctionObjects
?>  foamNewFunctionObject myFuncObjectA
?>  foamNewFunctionObject myFuncObjectB

创建两个函数对象myFuncObjecta和,yFuncObjectb,它们应该编译到同一个库myFunctionObjects中。foamNewFunctionObject生成的任何函数对象的make/files选项可以用作库构建配置的开始。 例如,使用来自myFuncObjectB的构建配置,

myFunctionObjects >  mv myFuncObjectB/Make .
myFunctionObjects >  rm -rf myFuncObjectA/Make

它的make文件夹现在包含需要修改的MyFunctionObjects库的构建配置。 特别地,MyFunctionObject/make/文件应该如下所示:

myFuncObjectA/myFuncObjectA.C
myFuncObjectB/myFuncObjectB.C
LIB = $(FOAM_USER_LIBBIN)/libmyFunctionObjects

它基本上列出了MyFunctionObjects库中两个函数对象容器的两个实现(.C)文件,并指定了新函数对象库的位置(FOAM_RUN && cd ! ?> mkdir myFunctionObjectsTest && cd myFunctionObjectsTest ?> cp -r FOAM_TUTORIALS/incompressible/icoFoam/cavity/cavity . ?> cd cavity


通过system/controlDict文件中的以下条目激活新库myFunctionObjects中的新函数对象myFuncObjectA:

```c++
libs ("libmyFunctionObjects.so");
functions
{
    funcA
    {
        type myFuncObjectA;
    }
}

第一行指定应该动态加载的新库。OpenFOAM自动搜索$FOAM_LIBBIN和$FOAM_USER_ LIBBIN以查找可用的库。函数条目是一个字典,可以包含任意多个字典条目,每个条目指定一个不同的函数对象。在上面的代码片段中,funcA子字典配置了myFuncObjectA函数对象。funcA的名称是任意的:任何用户定义名称都是可接受的。

使用blockMesh生成网格并启动icoFoam求解器会导致错误:

-->  FOAM FATAL IO ERROR: (openfoam-2012)
Entry 'labelData' not found in dictionary
"path/to/cavity/system/controlDict.functions.funcA"

这是预料之中的:函数对象生成器脚本foamNewFunctionObject准备了一个骨架实现,除了初始化一些伪数据成员(布尔、标签和标量)之外,它什么也未做。如果system/controlDict中未提供这些数据成员的数据,则函数对象将发出抱怨。

另一方面,如果我们错误地命名了函数对象,OpenFOAM会发出警告:

-->  FOAM Warning :
Unknown function type myFuncObjectAA
Valid function types :
2(myFuncObjectA myFuncObjectB)

因此,使用函数对象有两种方式可能出错:我们错误地命名了函数对象,或者我们没有提供初始化它所需的数据。 如果我们错误地命名了一个函数对象,求解器将在没有它的情况下运行。 如果缺少初始化函数对象所需的数据项,求解器将不运行,用户将在system/controlDict中输入所需的数据。system/controlDict中funcA子指令的完整定义如下:

functions
{
    funcA
    {
        type myFuncObjectA;
        boolData false;
        labelData 0;
        scalarData 0;
    }
}

一旦模板函数对象实现被编译到库中并且可以与求解器一起使用(测试),则可以扩展默认实现。

FoamNewFunctionObject生成的虚拟数据成员:

class myFuncObjectA
:
    public fvMeshFunctionObject
    {
        // Private Data
        //- bool
        bool boolData_;
        //- label
        label labelData_;
        //- word
        word wordData_;
        //- scalar
        scalar scalarData_;

将取而代之的是数据成员支持新函数的计算对象。成员函数生成的foamNewFunctionObject几乎什么都不做:

bool Foam::functionObjects::myFuncObjectA::read(const dictionary&amp; dict)
{
    dict.readEntry("boolData", boolData_);
    dict.readEntry("labelData", labelData_);
    dict.readIfPresent("wordData", wordData_);
    dict.readEntry("scalarData", scalarData_);
    return true;
}
bool Foam::functionObjects::myFuncObjectA::execute()
{
    return true;
}
bool Foam::functionObjects::myFuncObjectA::end()
{
    return true;
}
bool Foam::functionObjects::myFuncObjectA::write()
{
    return true;
}

而且应该实现。

12.3.2 实现Function对象

当函数对象实现一个通常有用的计算时,将函数对象的计算封装在一个单独的类中并在函数对象内重用它是有意义的。有时,这种计算与运行时处理能力的分离会导致具有独立关注点的更干净的实现:进行计算的类与处理运行时执行的函数对象类是分开的。当编写新的函数对象时,应该考虑在函数对象中重用OpenFOAM中的现有类,而不是从头开始编写所有的东西,因为这可能会缩短开发时间。在本节的示例中,函数对象类实现计算并从functionObject继承运行时操作。

如上所述,函数对象的成员函数在OpenFOAM中的模拟循环内的特定位置被调用:

  • start:在时间循环开始时执行
  • execute:当时间增加时执行
  • end:在时间循环结束时执行(时间已达到endTime的值)。

在计算在时间循环的开始和结束没有不同的情况下,成员函数开始和结束调用成员函数执行。本节中介绍的函数对象跟踪多相模拟期间包含特定相的所有网格单元。此示例不是特别有用,其唯一目的是演示通常由功能对象执行的计算,包括网格,场与模拟时间控制。

函数对象示例的代码可在的示例代码库中找到:

src/ofPrimerFunctionObjects/phaseCellsFunctionObject

在文本编辑器中查看代码可能有助于理解下面描述的示例。

要开始对库进行编程,请为新函数对象库创建一个名为ofPrimerFunctionObjects的目录,并使用foamNewFunctionObject从模板生成函数对象:

?>  mkdir ofPrimerFunctionObjects &amp;&amp; cd ofPrimerFunctionObjects
?>  foamNewFunctionObject phaseCellsFunctionObject
?>  mv phaseCellsFunctionObject/Make .

编辑Make/files,以便将函数对象编译到新库中:

phaseCellsFunctionObject/phaseCellsFunctionObject.C
LIB = $(FOAM_USER_LIBBIN)/libofPrimerFunctionObjects

如果使用了git标签OpenFOAM-v2012,请更正构造函数行中的括号错误:

// Fixed bracket error.
boolData_(dict.getOrDefault<bool> ("boolData", true)),

编译库:

ofPrimerFunctionObjects >  wmake libso

然后在下落液滴测试案例上进行测试:

cases/chapter11/falling-droplet-2D

通过将以下条目添加到system/controlDict:

markPhaseCells
{
    type phaseCellsFunctionObject;
    libs ("libofPrimerFunctionObjects.so");
    boolData false;
    labelData 0;
    scalarData 0;
}

Allrun脚本生成网格并启动模拟,而从模板生成的函数对象最初不执行任何操作。

注:函数对象的实现如下所述,它假定具有C++编程语言的一些先验知识:类/封装、声明与定义、虚函数、继承等。

用于OpenFOAM中的运行时类型选择(RTS)的函数对象的名称由foamNewFunctionObject生成为“name”+“FunctionObject”,其中“name”是给予foamNewFunctionObject的参数。生成的函数对象的类型名可以在phaseCellsFunctionObject. H中更改,从:

//- Runtime type information
TypeName("phaseCellsFunctionObject");

修改为:

//- Runtime type information
TypeName("phaseCells");

此更改需要重新编译库并修改system/controlDict中函数对象子字典中的type属性。

在模拟过程中,计算包含特定相的单元需要以下数据:相指示符的名称(例如,体积分数)、用于确定网格是否包含特定相的容差以及用于标记包含相的网格的indicator字段(作为phaseCellsFunctionObject的私有属性实现),如清单77所示。

// List77
// Private data
//- Time.
const Time&amp; time_;
//- Mesh const reference.
const fvMesh&amp; mesh_;
//- Name of the phase indicator field.
word fieldName_;
//- Reference to the phase indicator.
const volScalarField&amp; alpha1_;
//- Phase cells field.
volScalarField phaseCells_;
//- Phase cell tolerance.
scalar phaseTolerance_;
//- Percent of cells that have contained the phase.
scalar phaseDomainPercent_;

一旦声明了私有属性,就需要由类构造函数初始化它们,如清单78所示。

//List78
phaseCellsFunctionObject::phaseCellsFunctionObject
(
    const word&amp; name,
    const Time&amp; time,
    const dictionary&amp; dict
)
:
    functionObject(name),
    time_(time),
    mesh_(time.lookupObject<fvMesh> (polyMesh::defaultRegion)),
    fieldName_(dict.lookup("phaseIndicator")),
    alpha1_
    (
        mesh_.lookupObject<volScalarField> 
        (
            fieldName_
        )
    ),
    phaseCells_
    (
        IOobject
        (
            "phaseCells",
            time.timeName(),
            time,
            IOobject::NO_READ,
            IOobject::AUTO_WRITE
        ),
        mesh_,
        dimensionedScalar
        (
            "zero",
            dimless,
            0.0
        )
    ),
    phaseTolerance_(dict.get<scalar> ("phaseTolerance")),
    phaseDomainPercent_(0)
{}

当然,此定义需要在phaseCellsFunctionObject. H中进行相应的声明。phaseCellsFunctionObject的运行时类型选择(RTS)使用上述类型名称:

//- Runtime type information
TypeName("phaseCells");

foamNewFunctionObject应用运行时类型选择(RTS)所需的OpenFOAM宏:唯一可以修改的是上述函数对象的类型名。通过在相关子字典系统/controlDict字典中提供类型条目来选择函数对象。

属性初始化后,实际计算在phaseCellsFunctionObject::execute中实现

bool phaseCellsFunctionObject::execute()
{
    calcWettedCells();
    calcWettedDomainPercent();
    report();
    return true;
}

在这里,execute()成员函数的子计算已经被分开。在这个小示例中,这不是必需的。但是,在软件工程中,当函数计算量很大时,这是一种很好的做法,很难理解函数实际上做了什么。将子计算组织成子函数使得实现模块化:可以实现子计算的变化,并且可以利用继承来扩展该类,而不修改现有的实现。此外,将较大的算法组织成子算法,并将它们实现为子函数,可以提高主算法的可读性,如上面的execute()成员函数所示。

calcWettedCells成员函数标记“wetted”的网格:

void phaseCellsFunctionObject::calcWettedCells()
{
    forAll (alpha1_, cellI)
    {
        if (isWetted(alpha1_[cellI]))
        {
            phaseCells_[cellI] = 1;
        }
    }
}

在这种情况下:

bool isWetted(scalar s)
{
    return (s >  phaseTolerance_);
}

被特定相“润湿”的网格百分比计算为:

void phaseCellsFunctionObject::calcWettedDomainPercent()
{
    scalar phaseCellsSum = 0;
    forAll (phaseCells_, cellI)
    {
        if (phaseCells_[cellI] == 1)
        {
            phaseCellsSum += 1;
        }
    }
    phaseDomainPercent_ = 100. * phaseCellsSum / alpha1_.size();
}

该报告将生成到标准输出流,其中:

void phaseCellsFunctionObject::report()
{
    Info << "Phase " << fieldName_ << " covers " << phaseDomainPercent_
        << " % of the solution domain." << endl << endl;
}

由于函数对象包含可以在模拟期间写入以供以后检查的数据成员,因此write()成员函数实现为:

bool phaseCellsFunctionObject::write()
{
    if (time_.writeTime())
    {
        phaseCells_.write();
    }
    return true;
}

这确保了phaseCells场仅以由模拟用户定义并由Foam::Time控制的写入频率写入磁盘。

system/controlDict中函数字典的以下子字典启用函数对象:

markPhaseCells
{
    type phaseCells;
    libs ("libofPrimerFunctionObjects.so");
    phaseIndicator alpha.water;
    phaseTolerance 1e-06;
}

这定义了构造函数的字典条目(请参见清单78),以及函数对象的动态链接库。在cases/chapter 11/falling-droplet-2D中执行Allrun会将phaseCells写入磁盘,并在求解程序输出中添加以下内容:

Phase alpha.water covers 5.4 % of the solution domain.
Phase alpha.water covers 5.4 % of the solution domain.
Phase alpha.water covers 5.4 % of the solution domain.
Phase alpha.water covers 5.48 % of the solution domain.
Phase alpha.water covers 5.48 % of the solution domain.