本节将介绍运行模拟的用户遇到的OpenFOAM的某些部分。

在第3章中设置初始条件和边界条件已经表明,用户具有相当大的灵活性可供他或她使用。无需编写任何附加代码,即可在输入(场或配置)文件中选择现有边界条件、插值和离散格式、粘度模型、状态方程和类似参数。

就底层实现而言,基于来自文件的输入数据选择边界条件相对复杂:在运行时基于用户定义的读取参数(边界条件类型)选择并实例化特定类的对象。通过运行模拟,用户可以直接或间接地接触OpenFOAM的以下部分:可执行应用程序(求解器、预处理实用程序、后处理实用程序)、配置系统(字典文件)、边界条件和数值运算(选择离散格式)。

5.2.1 Applications

可执行应用程序是由用户在命令行或通过GUI运行的程序。它们属于通常所说的客户端代码,它使用各种OpenFOAM库(即客户端)。

OpenFOAM中应用类型

可执行应用程序(简称:应用程序)组织在如图5.1所示的目录结构中。通过在命令行中执行别名app或切换到applications目录,可以轻松访问Applications文件夹:

?>  cd $FOAM_APP

OpenFOAM中的模块化设计和高级抽象使用户可以轻松地构建数学模型。利用OpenFOAM的高级DSL,也可以直接实现耦合偏微分方程组的不同求解算法。因此,随着时间的推移,可供选择的求解器应用程序非常广泛。求解器应用程序按组进行分类,如图5.2所示。

在test子目录中可以找到不同的测试应用程序。例如,directory测试应用程序测试dictionary类的主要功能,而parallel和parallel-nonBlocking应用程序实现用于在OpenFOAM中抽象并行通信的代码。在检查库源代码不足以理解类/算法如何工作的情况下,查看测试应用程序源代码可能非常有用。

5.2.2 配置系统

配置系统由配置(字典)文件组成:以类似于JSON的格式存储的文本文件。字典文件由用于构建称为dictionary的关联数据结构的分类输入数据组成。字典本质上是一个哈希表。

此数据结构在OpenFOAM中经常用来将键关联(映射)到值。

注:test应用程序是关于特定类和算法的非常有用的信息资源,其显示了如何使用类和算法。

在模拟案例目录中找到的各种字典文件包含不同的配置参数:边界条件、插值格式、梯度格式、数值求解器等。选择不同的元素以及正确初始化它们是在启动可执行应用程序后在运行时执行的。在运行时选择类型的过程称为运行时选择(Runtime Selection,RTS),它使用了相当多的软件设计模式和C++语言习惯用法。RTS过程相当复杂,其描述超出了本章的范围。在这一点上,重要的是要记住,它使OpenFOAM非常灵活,易于使用。

注:配置系统本身并不以抽象的形式存在,也不是作为单个类实现的。该系统由dictionary和IOdirectionary类的函数、输入/输出(IO)文件流以及运行时可选类中使用的RTS机制支持。

例如,OpenFOAM中的任何用户定义类都可以在运行时选择。此外,可以在模拟过程中修改类属性,只要修改了字典文件,就可以重新读取该文件,并将类属性设置为新值。

注:字典中定义的参数在修改后不会立即在可执行应用程序中更改。更改将在选中Modified后的下一个时间步开始时启用。

5.2.3 边界条件

边界条件应用于离散场,如第1.3节所述,并作为单独的类结构实现。因为它们是作为类实现的,所以RTS机制允许用户在运行时将边界条件类型和参数配置为字典条目。边界条件将在第10章中介绍,因此本章不会完全介绍它们。

5.2.4 数值操作

数值运算由求解器使用,负责模拟方程离散部分。用户通过修改模拟案例目录中的system/fvSchemes字典文件以选择不同类型的离散格式、插值格式或类似参数时。不同的数值运算有不同的性质,选择它们需要CFD方面的经验,理想情况下还需要数值计算方面的经验。

OpenFOAM中负责FVM数值操作的部分可以在控制台中执行别名foamfv,或者切换到适当的目录:

?>  cd $FOAM_SRC/finiteVolume

数值计算由插值格式组成,然后离散微分算子使用插值格式对模型方程进行离散。CFD中常用的离散微分算子有:散度()、梯度()、拉普拉斯() 、旋度(),它们要么是显式的,要么是隐式的。显式运算符会生成新物理场作为结果;隐式运算符用于装置系数矩阵,其将数学模型的方程项离散化。另一方面,离散微分算子被实现为函数模板,由几何张量场参数实现参数化。微分运算符的这种通用实现使得在相同函数名下编写数学模型变得简单,这些函数名用于区分不同等级的张量场。由于运算符是作为函数实现的,因此组装不同的数学模型很简单:不同的函数调用序列会产生不同的模型。OpenFOAM具有命名相同的隐式和显式离散运算符,如显式散度和隐式散度。为了避免名称查找过程中的歧义,运算符被归类到两个C++名称空间下,并使用完全限定名称(FVC::为显式,FVM::为隐式)进行访问。

离散运算符是以张量参数为模板的通用算法,使用类特征系统来确定结果张量的秩。OpenFOAM中离散化的标准选择是基于散度定理,将算子计算委托给离散格式。那些对开发自己的离散格式感兴趣的人可能希望检查文件fvmDiv.cCurectionScheme.c。这两个文件包含的实现都显示了散度项是如何处理方程离散的。下面列出的源代码包含将散度计算从操作符委托给对流方案的代码的实现。

return fv::convectionScheme<Type> ::New
(
    vf.mesh(),
    flux,
    vf.mesh().divScheme(name)
)().fvmDiv(flux, vf);

可以通过RTS选择格式,但它们必须符合ConversationScheme.H中所描述的接口。这种灵活的设计可以用以下方式总结:

  • 算子将离散化委托给格式
  • 格式构成了运行时可选择的分层结构
  • 要编写新的对流格式,只需继承自对流格式并添加RTS
  • 实现对流格式后,不需要修改任何求解器应用程序代码

将运算符实现为函数模板并将其绑定到格式有很多优点,而且它为轻松扩展准备了框架,而无需对现有代码进行修改。详细描述离散化机制是如何实现的超出了本书的范围。

实现其他离散运算符的源代码可以在fvcfvc子目录下的$foam_src/finiteVolume/finiteVolume中找到。目录fvc储显式离散运算符的实现,这会产生计算字段。目录fvm存储隐式离散运算符的实现,这导致代数方程系统的系数矩阵组装。

注:只要代码中有fvc::运算符,其结果是一个物理场。当遇到fvm::运算符时,其结果是一个系数矩阵。

这些操作由求解器应用程序执行,与求解器代码的交互是在用户修改求解器以使其以不同方式运行时完成的。OpenFOAM支持通过fvOptions和function object修改运行时求解器(参见第12章),而不需要用户修改求解器代码。有关求解器应用程序以及如何编程实现新求解器的更多信息,请参见第9章。通常,当使用标准求解器获取模拟结果时,用户通常不会修改其代码。

OpenFOAM中的高级抽象允许用户非常快速地编写新的求解器和求解算法。OpenFOAM的高抽象层几乎可以用作CFD编程语言(DSL)-也称为方程模拟([2])。例如,以标量属性T的标量传输的数学模型为例: 方程(5.1)描述了由具有速度U的被动对流、具有扩散系数k的扩散以及源项S组成的场T的输运,离散化运算符是模板函数,可以对不同张量场进行运算,因此在标量场上它们在OpenFOAM中产生以下模型方程:

ddt(phi) + fvm::div(phi,T) + fvm::laplacian(k,T) = fvc::Sp(T)

描述模型的代码由离散运算符ddt、div、Laplacian和Sp组成。

插值算法也属于OpenFOAM中的数值运算。最常用的插值格式建立在非结构化网格的owner-neighbour寻址的基础上,插值得到网格面中心的值。在此基础上,构建了插值格式的树状类层次结构,如图5.3所示,其根节点为SurfaceInterpolationScheme。

从软件设计的角度来看,内插格式被封装到形成类层次结构的类中。一些格式共享属性和功能,因此将它们组织为类层次结构是有意义的。作为这种组织的结果,RTS机制允许用户在仿真开始时(或甚至在仿真期间)选择不同的离散化/内插格式,而不需要重新编译程序代码。

5.2.5 后处理

可以在仿真之后或仿真期间执行后处理。在模拟完成后进行后处理时,将使用PostProcess应用程序。OpenFOAM提供了另一种独特的方法,通过调用函数对象在模拟过程中对数据进行后处理。

后处理应用程序与OpenFOAM一起分发,或者由用户自己编写。

在编写后处理应用程序之前,建议检查是否已经存在具有所需功能的应用程序。OpenFOAM提供了大量实用程序可供选择。

目录$FOAM_APP/UTILITIES/PostProcessing中包含与OpenFOAM一起分发的所有后处理应用程序,这些应用程序被归入不同的组。

目录$FOAM_APP/UTILITIES/PostProcessing及其子目录是查找现有后处理应用程序的第一个位置,如图5.4所示。如果需要编写新的应用程序,则现有应用程序的某些部分很可能可以用作开发的起点。后处理应用程序通常用于基于模拟期间存储的场来计算一些整数值,或者对流量域的特定部分中的场值进行采样。然而,这绝不能仅限于此。基本上,您可以使用后处理应用程序在后处理步骤中计算任何内容。

与后处理应用程序不同,function objects是在模拟运行期间调用的。术语function object来自C++语言术语,其中它表示可调用的类,因为它实现了调用运算符:Operator()()。函数对象基本上是封装到类中的函数,这是有利的,例如当函数在执行后需要存储有关其状态的信息时。例如,在模拟中计算平均最大压力的函数对象可以在压力值超过规定值时停止模拟。这样的函数需要访问最大压力值,并且需要存储计算运行平均值所需的数据。因此,这两个属性被封装到函数对象类中。有关OpenFOAM中函数对象的更多信息,请参见第12章。