基础数据类型
Palabos用户文档 的第六章
(一)The BlockXD data structures
原始文档
The fundamental data structures which hold the variables of a simulation are of type Block2D for 2D simulations, and of type Block3D for simulation (in the following, we use the generic name BlockXD to keep the discussion short, although there exists no type BlockXD in Palabos). From the standpoint of a user, a BlockXD construct represents a regular 2D or 3D array (or matrix) of data. Behind the scenes, they are sometimes really implemented as regular arrays and sometimes as more complicated constructs, which allows for example memory savings through sparse memory implementations, or parallel program executions based on a data-parallel model.
The BlockXD structures are specializes for different areas of application. One type of specializations is used to specify the type of data stored in the blocks. To store the particle populations of a lattice Boltzmann simulation, and potentially other variables such as external forces, you’ll use a specialization in which the name Block is replaced by BlockLattice. To store a spatially extended scalar variable, the data type to use is a variant of the ScalarFieldXD, whereas vector- or tensor-valued fields are stored in a TensorFieldXD or similar. A second type of specialization is applied to specify the nature of the underlying data structure. The AtomicBlockXD data structure stands essentially for a regular data array, whereas the MultiBlockXD is a complex construct in which the space corresponding to a BlockXD is partially or entirely covered by smaller blocks of type AtomicBlockXD. The MultiBlockXD and the AtomicBlockXD have practically the same user interface, and you are urged to systematically use the more general MultiBlockXD in end-user applications. It is almost as efficient as the AtomicBlockXD for regular problems, it can be used to represent irregular domains, and it is automatically parallelizable.
The following figure illustrates the C++ inheritance hierarchy between the various specializations of the BlockXD :
The figure shows, as an example, a few functions implemented at each level of the inheritance hierarchy. For example, all blocks of type “block-lattice” have a method collideAndStream(), no matter if they are implemented as a multi-block or an atomic-block. In the same way, all multi-blocks have a method getComponent(), no matter if they are of type block-lattice, scalar-field, or tensor-field. The situation shown on this figure is referred to as “multiple inheritance”, because end-user classes inherit from BlockXD in two ways: once through the atomic- vs. multi-block path, and once through the block-lattice vs. scalar- vs. tensor-field path. Note that multiple inheritance is often considered as bad practice because it can lead to error-prone code; in the present case however, we’ve had positive experiences so far with the inheritance diagram shown above, because it is easy to use and represents the two facets of a BlockXD in a natural way.
文档翻译
保存用于模拟的变量值的基本数据结构有两种,用于2D仿真的Block2D类型和用于仿真的Block3D类型(在下面,我们使用通用名称BlockXD来保持简短的讨论,尽管Palabos中不存在类型BlockXD)。从用户的角度来看,BlockXD结构表示一个常规的2D或3D数组(或矩阵)的数据。在幕后,它们有时作为常规数组实现,有时作为更复杂的构造实现,比如通过稀疏内存实现来节省内存,或者基于数据并行模型执行并行程序。
BlockXD结构有专门针对不同作用域的应用,第一种特化类型用于指定存储在块中的数据的类型。存储格玻尔兹曼模拟的粒子群,以及可能的其他变量,如外力,您将使用一种专门化的BlockXD结构,其中名称Block将被BlockLattice替换。要存储空间扩展的标量变量,使用的数据类型是ScalarFieldXD的变体,而向量或张量值字段存储在TensorFieldXD或类似的数据类型中。第二种特化类型用于指定底层数据结构的性质。AtomicBlockXD数据结构本质上代表一个常规的数据数组,而MultiBlockXD是一个复杂的结构,其中与BlockXD对应的空间由类型为AtomicBlockXD的较小块部分或全部覆盖。MultiBlockXD和AtomicBlockXD实际上具有相同的用户界面,我们敦促您在最终用户应用程序中系统地使用更通用的MultiBlockXD。对于常规问题,它几乎与AtomicBlockXD一样有效,它可以用来表示不规则域,并且它是自动并行的。
下图展示了BlockXD不同特化版本之间的C++继承层次图:
作为一个例子,图中显示了在继承层次结构的每一层实现的几个函数。例如,所有类型为“block-lattice”的块都有一个collideAndStream()方法,不管它们是作为多块还是原子块实现的。同样,所有多块都有一个getComponent()方法,不管它们是块格、标量字段还是张量字段类型。该图中显示的情况被称为“多重继承”,因为最终用户类以两种方式从BlockXD继承:一次通过原子与多块路径,一次通过块格与标量与张量字段路径。注意,多重继承通常被认为是不好的做法,因为它会导致容易出错的代码;然而,在目前的情况下,我们对上面所示的继承关系图已经有了积极的体验,因为它易于使用,并且以自然的方式表示了BlockXD的两个方面。
解释说明
每一个案例都需要新建BlockXD下属的Block-Lattice类,其中BlockLatticeXD为单块格的计算格子域,无法并行,并行版本为MultiBlockLatticeXD类,并行由计算域分块来实现,类中按照程序设定的并行核数,会在每次声明对象时在类内部生成相应的BlockLatticeXD指针列表。一般情况下,在使用MultiBlockLatticeXD类时,你只需要,为它提供计算域尺寸和背景动态类(也就是默认动态类),为了使动态类可以共享,减少内存消耗,所以在类内部是以指针的方式存储动态类的一个引用地址,在声明时初始化MultiBlockLatticeXD对象时,应在堆区新建一个动态类对象,也就是new一个动态类对象,把指针地址传入。
以下为声明对象的一个语法,Nx,Ny分别为计算域的尺寸,堆区新建了一个BGKdynamics类,并将指针作为实参交给lattice对象。<T, DESCRIPTOR>为模板参数,将在下一节予以介绍。
MultiBlockLattice2D<T, DESCRIPTOR> lattice(Nx, Ny, new BGKdynamics<T,DESCRIPTOR>);
类继承图上的类只有标颜色的终端类可以使用,其他的类都不可以使用,都是属于抽象类,无法实例化。
(二) Lattice descriptors
原始文档
All BlockXD constructs are templatized with respect to the underlying data type. In practice, this feature is used to switch between single-precision and double-precision arithmetics by changing just one word in the end-user program:
// Construct a 100x100 scalar-field with double-precision floating point values. MultiScalarField2D<double> a(100,100); // Construct a 100x100 scalar-field with single-precision floating point values. MultiScalarField2D<float> b(100,100);
Block-lattices additionally have a template parameter, the lattice descriptor, which specifies a few topological properties of the lattice (the number of particle populations, the discrete velocities, the weights of the directions, and other lattice constants). It is therefore easy to try out different lattices in an application:
// Construct a 100x100 block-lattice using the D3Q19 structure. MultiBlockLattice2D<double, D3Q19Descriptor> lattice1(100,100); // Construct a 100x100 block-lattice using the D3Q27 structure. MultiBlockLattice2D<double, D3Q27Descriptor> lattice2(100,100);
It is also easy to write a new lattice descriptor (this is currently not documented, but you can check out the files in the directory src/latticeBoltzmann/nearestNeighborLattices2D.h to see how it works). This is extremely useful, because it means that you don’t need to re-write the lengthy code parts for the implementation of a BlockLatticeXD when you switch to a new type of lattice. This argument is part of a general concept described in the section Non-intrusive program development with Palabos.
文档翻译
所有BlockXD构造都是根据底层数据类型进行模板化的。在实践中,这个功能是用来切换单精度和双精度算术,只需在最终用户程序中改变一个单词:
// 构建一个 100x100 的双精度浮点值的标量场
MultiScalarField2D<double> a(100,100);
// 构建一个 100x100 的单精度浮点值的标量场
MultiScalarField2D<float> b(100,100);
Block-Lattice还有一个模板参数,格子描述符descriptor,它指定了格的一些拓扑属性(粒子的数量,离散速度,方向的权重,和其他格子常数)。在一个应用程序中尝试不同的格是很容易的:
// 构建一个 100x100 块格结构使用 D3Q19 描述符
MultiBlockLattice2D<double, D3Q19Descriptor> lattice1(100,100);
// 构建一个 100x100 块格结构使用 D3Q27 描述符
MultiBlockLattice2D<double, D3Q27Descriptor> lattice2(100,100);
编写一个新的晶格描述符也很容易(目前还没有文档说明,但是您可以查看src/latticeBoltzmann/nearestNeighborLattices2D.h目录中的文件,了解它是如何工作的)。这是非常有用的,因为这意味着当您切换到一种新类型的格时,您不需要为BlockLatticeXD的实现重写冗长的代码部分。这个是Palabos的非介入式程序开发一节中描述的一般概念的一部分。
解释说明
这部分就是说格子描述符,你需要在声明计算域块格结构对象时为你的指定格子描述符,格子描述符,是规定CELL也就是元胞里的数据结构的描述符,里面包含了,元胞所包含的数据量的数量以及不同数据的偏移值,一般在应用当中都是通过偏移值来选定所需的元胞数据值。描述符其实就是规定了元胞中一个连续表结构,以表的方式来存储数据值。
一般情况下,基础的,如上面的那两个可以随便改着玩,但更高级的模型都是有自己的格子描述符的,在开新模型时,要看看格子描述符,以便于大致了解你所计算的块格数据结构,大致由哪几部分组成,用现成的简单模型,只要按照原有方式匹配格子描述符,动态类以及数据处理器,一般不会有问题。
这个格子描述符可以自己写,但是,嗯,我还没有时间尝试,我大致看了一下,一般都是浸入式开发的三个一块写,也就是格子描述符、动态类以及数据处理器。
(三)The dynamics classes
原始文档
During a time iteration of a lattice Boltzmann simulation, all cells of a block-lattice perform a local collision step, followed by a streaming step. The streaming step is hardcoded and can be influenced only by defining the discrete velocities in a lattice descriptor. The collision step on the other hand can be fully customized and can be different from one cell to another. In this way, the nature of the physics simulated on the lattice can be adjusted locally, and specific areas such as the boundaries can get an individual treatment.
Each cell of a block-lattice holds, additionally to the variables of the simulation, a pointer to an object of type Dynamics.
To learn how to define a new dynamics class, it is easiest to look at one of the classes defined in Palabos. For example, the BGK dynamics is defined in the file src/basicDynamics/isoThermalDynamics.hh. A good example for composite dynamics (a class which modifies the behavior of another, existing dynamics class) is the Smagorinsky dynamics, defined in src/complexDynamics/smagorinskyDynamics.hh.
For the implementation of the collision, the dynamics object gets a reference to a single cell; the collision step is therefore necessarily local. Non-local ingredients of a simulation are implemented with data processors, as shown in the next section.
Like the block-lattice, a dynamics object is dependent on two template parameters, one for the floating-point representation, and one for the lattice descriptor. By using the information provided in the lattice descriptor, the collision step should be written in a generic, lattice-independent way. There is obviously an efficiency trade-off in writing the algorithms in a generic way, because it is possible to formulate optimizations for specific lattices which the compiler fails to find. This problem can be circumvented by using template specializations for a given lattice. As an example, take again the implementation of the class BGKdynamics. The implementation of collision step refers to a generic object dynamicsTemplates, which is defined in the file src/latticeBoltzmann/dynamicsTemplates.h. Efficient specializations of this class for various 2D and 3D lattices are found in the files dynamicsTemplates2D.h and dynamicsTemplates3D.h.
文档翻译
在格子Boltzmann模拟的时间迭代中,block-lattice的所有单元执行局部碰撞步骤,然后执行流动步骤。流动步骤是硬编码的,只能通过在格子描述符中定义离散速度来影响。另一方面,碰撞步骤可以完全定制,并且可以根据不同的单元格而有所不同。这样,在格子上模拟的物理性质可以局部调整,特定的区域如边界可以得到单独的处理。
除了模拟的变量数据值外,块格的每个单元格还持有一个指向类型为Dynamics的对象的指针。
要了解如何定义一个新的动态类,最简单的方法是查看Palabos中定义的类之一。例如,BGK动态在src/basicDynamics/isoThermalDynamics.hh文件中定义。复合动力学(修改另一个现有动力学类的行为的类)的一个很好的例子是Smagorinsky dynamics,它在src/complexDynamics/smagorinskyDynamics.hh中定义。
对于碰撞的实现,dynamics对象获取对单个单元的引用;因此,冲突步骤必然是局部的。模拟的非本地成分是用数据处理器实现的,如下一节所示。
与block-lattice一样,动态对象依赖于两个模板参数,一个用于浮点表示,另一个用于格子描述符。通过使用格子描述符中提供的信息,碰撞步骤应该以一种通用的、与晶格无关的方式编写。显然,以通用的方式编写算法是一种效率权衡,因为可以对编译器无法找到的特定格点进行优化。这个问题可以通过使用给定晶格的模板专门化来解决。以BGKdynamics类的实现为例。冲突步骤的实现涉及到一个通用对象dynamicsTemplates,它在文件src/latticeBoltzmann/dynamicsTemplates.h中定义。在dynamicsTemplates2D.h和dynamicsTemplates3D.h文件中可以找到该类对各种2D和3D格子的有效团版本。
解释说明
这个动态类是作用于碰撞迁移步骤当中的,用于规范格点上的碰撞与迁移过程,所有的与碰撞迁移相关的动态类,包裹边界节点上所施加的反弹之类的碰撞,皆来自于这个类,一般在初始化阶段通过指针的形式引入块格计算域结构(在上一节解释中有说明),可以重复设置,后一次设置会将前一次设置的设定清除,只要最后计算时,能确定所有的格点都有相应的动态类,这部分的计算就没有问题。
动态类只适用于本地操作,也就是说,在格子点本地进行操作,并不能引用除当前格点以外的值来作为本格点碰撞迁移计算的引用值,如果想对格点进行外推之类的操作,应该在后一部分的数据处理器部分进行编写。(这段存疑,记得应该是这样,以后我可能会修改)
同样,只有在继承类末端的类,才是用户使用类,其他的父类,都是数据抽象类或半抽象类,无法实例化。
(四)Data processors
原始文档
Data processors define an operation to be performed on the entire domain, or on parts of a block. On a block-lattice, they are used to implement all operation which cannot be formulated in terms of dynamics objects. These consist most notably of non-local operations, such as the evaluation of finite difference stencils for the implementation of boundary conditions. On scalar-fields and tensor-fields, data processors provide the only (sufficiently efficient and parallelizable) way to perform an operation on a spatially extended domain.
Furthermore, data processors have the ability to exchange information between several blocks, either of the same type or of different type (example: coupling between a block-lattice and scalar-field). This is for example used for the implementation of physical couplings (multi-component fluids, thermal fluids with Boussinesq approximation), for the setup of initial conditions (initialization of the velocity from the values in a vector-field), or the execution of classical array-based operations (element-wise addition of two scalar-fields).
Finally, data processors are used to perform reduction operations, on the entire block or on sub-domains. Examples range from the computation of the average kinetic energy in a simulation to the computation of the drag force acting on an obstacle.
Two different point of views are adopted for the definition and for the application of a data processor. At the application level, the user specifies an area (which can be rectangular or irregular) of a given block, on which to execute the data processor. If the block has internally a multi-block structure, the data processor is subdivided into several more specific data processors, one for each atomic-block inside the multi-block which intersects with the specified area. At the execution level, a data processor therefore always acts on an atomic-block, on an area which was previously determined by intersecting the original area with the domain of the atomic-block.
It should be mentioned that while the raw data processors are somewhat awkward to use, you are likely to never be in contact with them. Instead, Palabos offers a simplified interface through so-called data processing functionals, which hide technical details and let you concentrate on the essential parts. The rest of the user guides concentrates exclusively on these functionals, which will be called data processors for short.
In the end, it is quite easy to define new data processors. All you need to do is write a function which receives an atomic-block and the coordinates of a sub-domain as parameters, and executes an algorithm on this sub-domain. All complex operations, like the sub-division of the operations in presence of a multi-block, or the parallelization of the code, are automatic.
An educative example of a data processor is found in the example file examples/codeByTopics/couplings. It shows how to initialize a block-lattice with a velocity from a vector field by writing a data processor for block-lattice vs. 2D tensor-field coupling.
More definitions of data-processors acting on block-lattices can be found in the files src/simulationSetup/latticeInitializerXD.h and .hh, and data-processors acting on scalar- or tensor-fields are defined in the files src/simulationSetup/dataFieldInitializerXD.h and .hh. Examples for the evaluation of reduction operations are provided in the files src/core/dataAnalysisXD.h and .hh.
All these data-processors are wrapped up in convenience functions, which are summarized in the Appendix: partial function/class reference.
文档翻译
数据处理器是定义在整个域或块的部分上执行的操作。在block-lattice上,它们用于实现所有不能用动态对象表示的操作。这些包括最显著的非局部操作,如边界条件实现的有限差分模板的评估。在标量域和张量域上,数据处理器提供了在空间扩展域上执行操作的惟一可行的方法(足够有效且可并行化)。
此外,数据处理器能够在几个块之间交换信息,这些块可以是同一类型的,也可以是不同类型的(例如:块格和标量场之间的耦合)。这是例如用于物理耦合的实现(与Boussinesq近似多组分的流体、热流体),初始条件的设置(初始化的速度值向量场),或古典的执行基于数组的操作(元素方式添加两个标量场)。
最后,使用数据处理器对整个块或子域执行约简操作。例子范围涵括从模拟中平均动能的计算到作用于障碍物上的阻力的计算。
数据处理器的定义和应用采用了两种不同的观点。在应用程序级别,用户指定给定块的一个区域(可以是矩形或不规则的),在该区域上执行数据处理器。如果数据块内部有一个多块结构,那么数据处理器就被细分为几个更具体的数据处理器,每个数据处理器对应一个与指定区域相交的多块内部的原子块。因此,在执行级别上,数据处理器总是作用于一个原子块,即先前通过将原始区域与原子块的域相交确定的区域。
应该提到的是,虽然原始数据处理器使用起来有些笨拙,但是您可能永远不会与它们接触。相反,Palabos通过所谓的数据处理函数提供了一个简化的接口,这些函数隐藏了技术细节,让您可以集中精力处理关键部分。其余的用户指南专门关注这些函数,简称为数据处理器。
最后,定义新的数据处理器非常容易。您所需要做的就是编写一个函数,该函数接收一个原子块和一个子域的坐标作为参数,并在这个子域上执行一个算法。所有复杂的操作,比如存在多块的操作的细分,或者代码的并行化,都是自动的。
在示例文件examples/codeByTopics/couplings中可以找到一个数据处理器的可教育性示例。它通过编写块晶格与二维张量场耦合的数据处理器,展示了如何用向量场的速度初始化块晶格。
在src/simulationSetup/latticeInitializerXD.h和.hh文件中可以找到更多作用于块格的数据处理器的定义,而作用于标量或张量字段的数据处理器定义在src/simulationSetup/dataFieldInitializerXD.h和.hh文件中。src/core/ dataAnalysisx.h和.hh文件中提供了评估还原操作的示例。
所有这些数据处理器都封装在方便的函数中,这些函数在手册文档附录:partial function/class reference部分中进行了总结。
解释说明
此处为数据处理器,作用于每次碰撞与迁移之后的,宏观量统计部分,用的比较多,传入的数据为块格数据,每一个数据点,都由一个信封来包裹,信封内为想要操作数据点及周围对应信封大小的数据包,我自己还没有完全改写过数据处理器,但是大致看过,需要改写由数据处理器父类继承而来的execute函数,来实现自己所需要的对块格数据进行改写以及统计的相关操作。
(五)总结
以上四种,为Palabos中最基础的数据吧类型,其中改写Descriptor,Dynamics class,Data processors为高阶,通过改写这三个,可以做出Palabos中不存在的模型以及边界条件,新手不推荐改写,还是在了解的基础上,尽可能的调用原有类来实现自己的模拟计算目的。