大多数求解器定制涉及以一种或另一种方式修改数学模型方程。可能会引入全新的模型方程,方程项可能会因为它们对解的影响被忽略而被删除,并且可以添加新项,例如作用于流体的新力。虽然不可能涵盖自定义求解器过程中可能出现的所有复杂性和困难,但本章涵盖的一些示例应该有助于理解与求解器自定义相关的问题以及如何克服这些问题。求解器的一个典型修改是添加被动传输场和材料属性。求解器应用程序必须从模拟案例中读取新的场变量及材料属性。模拟案例中有各种可用的文件,从中读取不同类型的数据。让我们从涉及的一个更基本的操作开始:在字典中查找一个值。 OpenFOAM 配置(字典)文件和它们所基于的数据结构都在第 5 章中进行了详细描述。不过,本章还提供了使用字典的基本信息,以使求解器自定义描述更加自我持续。

9.2.1 使用字典

OpenFOAM 配置文件称为字典(字典文件),用于为求解器应用程序提供配置参数。在各种字典(例如 transportProperties、controlDict 或 fvSolution)之间,用户可以完全控制求解器、材料属性、时间步长等。在本节中,将介绍访问现有字典和新字典以及查找值并将其加载到求解器范围内。检查 icoFoam 求解器如何从 constant/transportProperties 字典中查找材料属性将作为如何处理字典的简单示例。首先,打开位于 FOAM_SOLVERS/incompressible/icoFoam/createFields.H 的 createFields.H 文件。在这个头文件的开头,有一个 IOdictionary 对象的实例,如下所示。IOobject 声明被注释掉以表明每个参数的用途。本节稍后将提供有关这些论点的更多详细信息。清单 33 的代码将字典初始化为全局变量,因此从中检索值仍然很简单。

Info<< "Reading transportProperties\n" << endl;
IOdictionary transportProperties
(
    IOobject
    (
        // Name of the file
        "transportProperties",
        // File location in the case directory
        runTime.constant(),
        // Object registry to to which the dict is registered
        mesh,
        // Read strategy flag: read if the file is modified
        IOobject::MUST_READ_IF_MODIFIED,
        // Write strategy flag: do not re-write the file
        IOobject::NO_WRITE
    )
);

createFields.H 文件的接下来几行包含使用 transportProperties 字典中包含的值初始化粘度 (nu) 的代码:

nu                0.01;

预定义尺寸集dimViscosity用于设置运动粘度nu的单位量纲。

dimensionedScalar nu
(
    "nu",
    dimViscosity,
    transportProperties
);

量纲标量的构造由构造函数完成:

dimensioned<Type> ::dimensioned<Type> 
(
    const word&,
    const dimensionedSet&,
    const dictionary&
)

有关 OpenFOAM 中量纲系统如何工作的更多信息,请参阅第 5 章。以下示例显示了 interFoam 求解器如何查找输运属性,这有点复杂。重要的源代码位于 FOAM_APP/solvers/multiphase/interFoam/ 在 createFields.H 文件的开头,实例化了 immiscibleIncompressibleTwoPhaseMixture 类(参见下面的代码片段)。有两个成员函数(rho1()、rho2()),用于查找twoPhaseMixture类的材质属性。检查类源代码是必要的,以便找到执行实际字典访问的代码。

Info<< "Reading transportProperties\n" << endl;
immiscibleIncompressibleTwoPhaseMixture mixture(U, phi);
volScalarField& alpha1(mixture.alpha1());
volScalarField& alpha2(mixture.alpha2());
const dimensionedScalar& rho1 = mixture.rho1();
const dimensionedScalar& rho2 = mixture.rho2();

9.2.2 对象注册及regIOobjects

由于物理场和网格在 OpenFOAM 求解器应用程序中以全局变量的形式使用,因此跟踪所有物理场和显式分配对其成员函数的调用将涉及大量不必要的代码重复。这种集群调用调度的一个示例是求解器请求将所有物理场写入硬盘。在直接使用对象的情况下,对象的名称将被硬编码,并且更改名称会在应用程序代码中引入一连串的更改。此外,实现此类调用的代码需要复制到多个位置。例如,负责物理场输出的相同代码随后将被复制到依赖于具有相同变量名称的同一组物理场的每个应用程序。对于依赖相同物理场变量的求解器系列,可以使用第 9.1 节中介绍的包含头文件。但是更改单个物理场变量会导致这样的头文件无法用于整个求解器系列。因此,这种方法代表了僵硬的软件设计,或者不能很好地扩展的软件设计。僵硬或不可扩展的软件设计是一种设计,其中单个扩展需要修改现有代码,此外,修改经常发生在现有代码库的多个位置。

由于这个问题,多个对象的操作协调背后的逻辑被封装到一个类中,然后可以在 OpenFOAM 代码的许多地方重用。为此,已经实现了一个对象注册表:一个对象将其他对象注册到自己,然后将对其成员函数的调用分派(转发)到已注册的对象。对象注册是 OOD 中观察者模式的一个实现,它在 8.4 节中有更详细的描述。此外,在 OpenFOAM Wiki 页面 1 上对对象注册表以及已注册的对象类进行了很好的审查。

使用对象注册表的一个例子是边界条件实现,其中在一个字段上操作的边界条件需要访问另一个字段。总压边界条件 (totalPressureFvPatchScalarField) 就是这样一个边界条件,需要访问多个字段才能更新分配给它的字段。

void Foam::totalPressureFvPatchScalarField::updateCoeffs()
{
    updateCoeffs
    (
        p0(),
        patch().lookupPatchField<volVectorField, vector> (UName())
    );
}

练习:找出要使用的 totalPressureFvPatchScalarField 边界条件是什么。哪个成员函数执行实际计算?如何实施替代计算?您能想到替代计算的替代运行时可选实现吗?

总压力边界条件使用清单 36 中所示的 updateCoeffs 成员函数更新已分配给它的场,就像 OpenFOAM 中的所有其他边界条件一样。但是,updateCoeffs() 将计算分派给重载的 updateCoeffs。清单 36 中显示的 updateCoeffs 调用的第二个参数使用 patch() 成员函数访问边界字段的 fvPatch 常量引用属性。另一方面,fvPatch 类存储对有限体积网格的常量引用,它继承自 objectRegistry,因此是一个对象注册表。 lookupPatchField 被定义为 fvPatch 类模板的模板成员函数,在文件 fvPatchFvMeshTemplates.H 中,如清单 37 所示。函数的返回类型声明取决于模板参数 GeometricField,因此需要 typename 关键字,为了让编译器知道 PatchFieldType 确实是一种类型。成员函数模板中更重要的部分是显然利用了对象注册功能的返回语句,它是从对象注册类objectRegistry继承到fvMesh的。由于 fvPatch 是一个类模板,因此对基类成员函数的调用有点复杂。 lookupObject 是 objectRegistry 基类的成员函数模板,必须在成员函数调用站点使用 template 关键字指定。回顾所有 C++ 模板代码,有限体积网格边界补丁访问整个边界网格,然后访问相应的体积网格,并要求体积网格查找具有特定名称(名称)的字段。此示例中描述的通过所涉及类的调用路径允许总压力边界条件根据字段名称参数从网格访问字段。

template<class GeometricField, class Type> 
const typename GeometricField::PatchFieldType&
Foam::fvPatch::lookupPatchField
(
    const word& name,
    const GeometricField*,
    const Type*
    ) const
    {
        return patchField<GeometricField, Type> 
        (
            boundaryMesh().mesh().objectRegistry::template
            lookupObject<GeometricField> (name)
        );
    }