Code of the Week #17: RTS (Run-Time Selection) 机制

适用版本: OpenFOAM-6.0

:mega:高能预警:以下内容需要一定的时间和功力才能深入理解。若感觉难度过高,可以暂时跳过。另外,若是有没有讲清楚的地方,请跟帖,我会尽量修正得容易懂一些。也不要期望看一次就明白,你可以多来几次,:joy:

RTS (Run-Time Selection) 机制是 OpenFOAM 的一大新特点,为代码提供了简洁与便利。其工作过程很简单:通过运行时从 case 的字典文件 (或配置文件) 的关键字构造对象,相同关键字的对象的类都派生自同一父类,并具有一组相同的接口 (函数)。在 C++ 中,叫“虚构造函数” (Virtual Constructor) 或“初始化的工厂模式” (Factory Method of Initialization)。由于其实现是由宏完成的,因此,大多数同学深入学习还有些困难。主要是宏编写得很复杂。

实际上,如果不需要编写 solver 的话,这一机制的理解和运用还是可以先放一放。待对 OpenFOAM 和 C++ 有一定的深入了解之后,再来看看这个,可能会有更好的认识。

OpenFOAM 广泛使用 RTS 机制的目的是使代码实现高度灵活性。RTS 可实现以下功能,包括:

  • 函数对象
  • 边界条件
  • 离散格式
  • 粘性、湍流、两相流等物理模型

当有包含相同接口的一类对象,或者为已经建立 RTS 机制的基类添加新的派生类时都需要使用此机制,可以说,RTS 机制在 OpenFOAM 中无处不在。

以 pisoFoam 为例,其 createFields.H

singlePhaseTransportModel laminarTransport(U, phi);

上面示例中,实现 transportModel 定义,在 singlePhaseTransportModel.C, 如

Foam::singlePhaseTransportModel::singlePhaseTransportModel
(
    const volVectorField& U,
    const surfaceScalarField& phi
)
:
    IOdictionary
    (
        IOobject
        (
            "transportProperties", // 流体流动属性的字典文件
            U.time().constant(),
            U.db(),
            IOobject::MUST_READ_IF_MODIFIED,
            IOobject::NO_WRITE
        )
    ),
    viscosityModelPtr_(viscosityModel::New("nu", *this, U, phi))
    // 粘度模型
{}

接下来,在 createFields.H 中有代码

autoPtr<incompressible::turbulenceModel> turbulence
(
    incompressible::turbulenceModel::New(U, phi, laminarTransport)
);

这一段代码就是对湍流模型的创建,incompressible::turbulenceModel::New() 代码就在 IncompressibleTurbulenceModel.C 文件里

// * * * * * * * * * * * * * * * * * Selectors * * * * * * * * * * * * * * * //

template<class TransportModel>
Foam::autoPtr<Foam::IncompressibleTurbulenceModel<TransportModel>>
Foam::IncompressibleTurbulenceModel<TransportModel>::New
(
    const volVectorField& U,
    const surfaceScalarField& phi,
    const TransportModel& transport, // 对应 pisoFoam 的 laminarTransport
    const word& propertiesName
)
{
    return autoPtr<IncompressibleTurbulenceModel>
    (
        static_cast<IncompressibleTurbulenceModel*>(
        TurbulenceModel
        <
            geometricOneField,
            geometricOneField,
            incompressibleTurbulenceModel,
            TransportModel
        >::New
        (
            geometricOneField(),
            geometricOneField(),
            U,
            phi,
            phi,
            transport,
            propertiesName
        ).ptr())
    );
}

接下来,就来看看 TurbulenceModel.H,其中一部分为

public:

    // Declare run-time constructor selection table
    // 声明 RTS 构造函数选择表
        declareRunTimeNewSelectionTable
        (
            autoPtr,         // 宏的 autoPtr
            TurbulenceModel, // 宏的 baseType
            dictionary,      // 宏的 argNames
            (
                const alphaField& alpha,
                const rhoField& rho,
                const volVectorField& U,
                const surfaceScalarField& alphaRhoPhi,
                const surfaceScalarField& phi,
                const transportModel& transport,
                const word& propertiesName
            ),  // 宏的 argList,函数指针哈希表中对应的函数New()的参数
            (alpha, rho, U, alphaRhoPhi, phi, transport, propertiesName) 
            // 宏的 parList,派生类中的构造函数在被 New() 调用时的参数表, 应与 argList 一致
        );

    // Constructors 

        //- Construct
        TurbulenceModel
        (
            const alphaField& alpha,
            const rhoField& rho,
            const volVectorField& U,
            const surfaceScalarField& alphaRhoPhi,
            const surfaceScalarField& phi,
            const transportModel& transport,
            const word& propertiesName
        );

    // Selectors
        //- 返回 (选择) 创建的湍流模型的指针
        //- Return a reference to the selected turbulence model
        static autoPtr<TurbulenceModel> New 
        (
            const alphaField& alpha,
            const rhoField& rho,
            const volVectorField& U,
            const surfaceScalarField& alphaRhoPhi,
            const surfaceScalarField& phi,
            const transportModel& transport,
            const word& propertiesName = turbulenceModel::propertiesName
        );

对应的 TurbulenceModel.C 的部分代码如下:

// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //

template
<
    class Alpha,
    class Rho,
    class BasicTurbulenceModel,
    class TransportModel
>
Foam::TurbulenceModel<Alpha, Rho, BasicTurbulenceModel, TransportModel>::
TurbulenceModel
(
    const alphaField& alpha,
    const rhoField& rho,
    const volVectorField& U,
    const surfaceScalarField& alphaRhoPhi,
    const surfaceScalarField& phi,
    const transportModel& transport,
    const word& propertiesName
)
:
    BasicTurbulenceModel
    (
        rho,
        U,
        alphaRhoPhi,
        phi,
        propertiesName
    ),
    alpha_(alpha),
    transport_(transport)
{}
// * * * * * * * * * * * * * * * * * Selectors * * * * * * * * * * * * * * * //

template
<
    class Alpha,
    class Rho,
    class BasicTurbulenceModel,
    class TransportModel
>
Foam::autoPtr
<
    Foam::TurbulenceModel<Alpha, Rho, BasicTurbulenceModel, TransportModel>
>
Foam::TurbulenceModel<Alpha, Rho, BasicTurbulenceModel, TransportModel>::New
(
    const alphaField& alpha,
    const rhoField& rho,
    const volVectorField& U,
    const surfaceScalarField& alphaRhoPhi,
    const surfaceScalarField& phi,
    const transportModel& transport,
    const word& propertiesName
)
{
    // get model name, but do not register the dictionary
    // otherwise it is registered in the database twice
    const word modelType
    (
        IOdictionary
        (
            IOobject
            (
                IOobject::groupName(propertiesName, alphaRhoPhi.group()),
                U.time().constant(),
                U.db(),
                IOobject::MUST_READ_IF_MODIFIED,
                IOobject::NO_WRITE,
                false
            )
        ).lookup("simulationType") // 字典文件里 simulationType 字的值 --> modelType
    );

    Info<< "Selecting turbulence model type " << modelType << endl;

    typename dictionaryConstructorTable::iterator cstrIter =
        dictionaryConstructorTablePtr_->find(modelType);
    // 在 dictionaryConstructorTablePtr_ 里面找 modelType

    if (cstrIter == dictionaryConstructorTablePtr_->end())
    {   // 如果没有找到 modelType, 就输出信息
        FatalErrorInFunction
            << "Unknown TurbulenceModel type "
            << modelType << nl << nl
            << "Valid TurbulenceModel types:" << endl
            << dictionaryConstructorTablePtr_->sortedToc()
            << exit(FatalError);
    }

    return autoPtr<TurbulenceModel>
    (
        cstrIter()(alpha, rho, U, alphaRhoPhi, phi, transport, propertiesName)
    ); // 返回湍流模型的指针
}

宏的定义在 runTimeSelectionTables.H,由于 turbulenceModel 里面使用的是 declareRunTimeNewSelectionTable,所以,以下为 declareRunTimeNewSelectionTable 的定义:

//- Declare a run-time selection for derived classes
#define declareRunTimeNewSelectionTable(                                       \
    autoPtr,baseType,argNames,argList,parList)                                 \
                                                                               \
    /* 从 argList 函数指针类型构建 */                                             \
    /* Construct from argList function pointer type */                         \
    typedef autoPtr<baseType> (*argNames##ConstructorPtr)argList;              \
                                                                               \
    /* 构造函数指针的 Hash 表类的类型别名 */                                        \
    /* Construct from argList function table type */                           \
    typedef HashTable<argNames##ConstructorPtr, word, string::hash>            \
        argNames##ConstructorTable;                                            \
                                                                               \
    /* 静态 Hash 表,用来存储构造函数指针,须在 .C 文件中定义, */                     \
    /* 在宏 definRunTimeSelectionTable(baseType, argNames) 中实现*/              \
    /* Hash 表中的 key 为相应派生类的运行时类型信息 typeName */                      \
    /* Construct from argList function pointer table pointer */                \
    static argNames##ConstructorTable* argNames##ConstructorTablePtr_;         \
                                                                               \
    /* Table constructor called from the table add function */                 \
    static void construct##argNames##ConstructorTables();                      \
                                                                               \
    /* Table destructor called from the table add function destructor */       \
    static void destroy##argNames##ConstructorTables();                        \
                                                                               \
    /* 添加构造函数指针到表中的模板类, 模板参数在使用中应被实例化为派生类的类名。*/        \
    /* 实际上, 并不是通过直接存储派生类的构造函数指针来实现派生类的构造函数的调用的,  */    \
    /* 而是采用了间接的手段: 函数指针及哈希表中存储的函数指针是指向成员模板类 */           \
    /* add##argNames##ConstructorToTable<baseType##Type> 的模板函数成员  */       \
    /* New()的指针, 而 New() 调用了派生类 baseType##Type(模板参数)         */       \
    /* 的构造函数 (通过new表达式), 因此可以实现对派生类的构造函数的间接调用。 */         \ 
    /* 在模板类的构造函数中, 又将New()函数指针"注册"到静态哈希表对象中, */             \
    /* 因此伴随着该模板类的每一次实例化, 均可添加派生类(恰为模板参数)的 */              \
    /* 构造函数调用到哈希表中。而实例化实在派生类的                   */             \
    /* addToRunTimeSelectionTable(baseType,thisType,argNames) 宏中实现的。*/    \
    /* Class to add constructor from argList to table */                       \
    template<class baseType##Type>                                             \
    class add##argNames##ConstructorToTable                                    \
    {                                                                          \
    public:                                                                    \
                                                                               \
        /* New() 函数, 调用派生类的构造函数并返回派生类实例的指针(类型为基类指针)  */    \
        static autoPtr<baseType> New##baseType argList                         \
        {                                                                      \
            return autoPtr<baseType>(baseType##Type::New parList.ptr());       \
        }                                                                      \
                                                                               \
        add##argNames##ConstructorToTable                                      \
        (                                                                      \
            const word& lookup = baseType##Type::typeName                      \
        )                                                                      \
        {                                                                      \
            construct##argNames##ConstructorTables();                          \
            if                                                                 \
            (                                                                  \
               !argNames##ConstructorTablePtr_->insert                         \
                (                                                              \
                    lookup,                                                    \
                    New##baseType                                              \
                )                                                              \
            )                                                                  \
            {                                                                  \
                std::cerr<< "Duplicate entry " << lookup                       \
                    << " in runtime selection table " << #baseType             \
                    << std::endl;                                              \
                error::safePrintStack(std::cerr);                              \
            }                                                                  \
        }                                                                      \
                                                                               \
        ~add##argNames##ConstructorToTable()                                   \
        {                                                                      \
            destroy##argNames##ConstructorTables();                            \
        }                                                                      \
    };                                                                         \
                                                                               \
    /* Class to add constructor from argList to table */                       \
    template<class baseType##Type>                                             \
    class addRemovable##argNames##ConstructorToTable                           \
    {                                                                          \
        /* retain lookup name for later removal */                             \
        const word& lookup_;                                                   \
                                                                               \
    public:                                                                    \
                                                                               \
        static autoPtr<baseType> New##baseType argList                         \
        {                                                                      \
            return autoPtr<baseType>(baseType##Type::New parList.ptr());       \
        }                                                                      \
                                                                               \
        addRemovable##argNames##ConstructorToTable                             \
        (                                                                      \
            const word& lookup = baseType##Type::typeName                      \
        )                                                                      \
        :                                                                      \
            lookup_(lookup)                                                    \
        {                                                                      \
            construct##argNames##ConstructorTables();                          \
            argNames##ConstructorTablePtr_->set                                \
            (                                                                  \
                lookup,                                                        \
                New##baseType                                                  \
            );                                                                 \
        }                                                                      \
                                                                               \
        ~addRemovable##argNames##ConstructorToTable()                          \
        {                                                                      \
            if (argNames##ConstructorTablePtr_)                                \
            {                                                                  \
                argNames##ConstructorTablePtr_->erase(lookup_);                \
            }                                                                  \
        }                                                                      \
    };

在宏的定义中,大量使用了 ##,其表示连接符号,把前后参数连接在一起,其中任意有一个是变量,就替换。比如

    /* 从 argList 函数指针类型构建 */                                             \
    /* Construct from argList function pointer type */                         \
    typedef autoPtr<baseType> (*argNames##ConstructorPtr)argList;              \

就换成了

autoPtr<TurbulenceModel> (*dictionaryConstructorPtr)
(
    const alphaField& alpha,
    const rhoField& rho,
    const volVectorField& U,
    const surfaceScalarField& alphaRhoPhi,
    const surfaceScalarField& phi,
    const transportModel& transport,
    const word& propertiesName
);

大家可以尝试把这个宏解开,写到下面的帖子里。

参考文献

  1. http://sourceflux.de/blog/run-time-type-selection-openfoam-implementation/
  2. http://openfoamwiki.net/index.php/OpenFOAM_guide/runTimeSelection_mechanism
  3. https://www.cfd-online.com/Forums/openfoam-programming-development/141687-turbulence-models-run-time-selection-tables.html

推荐阅读

  1. Implement turbulence model. https://pingpong.chalmers.se/public/courseId/8331/lang-en/publicPage.do?item=3855255
  2. How to add a turbulence model in OpenFOAM-3.0.0. http://hassankassem.me/posts/newturbulencemodel/
  3. 替换文本宏. https://zh.cppreference.com/w/c/preprocessor/replace