作者的话:本人在学习palabos时,发现国内中文资料甚少,恰好网上可以直接搜到palabos user guide这种英文资料,加之时间充裕,便打算开始翻译,翻了一节后发现这可能算侵权,就比较伤脑筋,突然想到自己写中文解读即可,便有了下面的博客。

Palabos User Guide
Release 1.0 r1
Copyright © 2019 University of Geneva
Jul 05, 2019

Chapter Sixteen

Palabos里的Dynamics类和数据处理器不可或缺。

且不说理论上的细节,数据处理器往往在标量场或张量场中发挥重要作用,比如TensorField3D<T,3>表示一个速度场,其中每一个元素都是Array<T,3>的速度向量。
在Palabos里这种场可以通过computeVelocity函数得到。

下面会一步步循循善诱地带你理解这些概念。

16.1 Using helper functions to avoid explicitly writing data processors

数据处理器可被分为3类,按照用途来分的话,第一类用于设置模拟,为子域分配Dynamics对象,初始化粒子群等待。第二类用于实现物理模型。第三类用于数据评估。

在8.2节的初始化密度和速度和第九章定义边界条件就属于第一类。附录19.1节 模拟设置中参数变化操作与其他 里列出了一些函数,可用于覆盖写入block-lattice里数据。

第七章Palabos的模型就属于第二类。

第12章的数据评估就属于第三类。附录19.2节 参数不变操作的数据评估和其他 可以查找到其他函数。

16.2 局部操作的简易包装器

与数据处理器相比,这些操作并不更普适性,因为它们无法用于非局部的操作。

比如说你需要在一块block-lattice上进行初始化,即可使用继承OneCellFunctionalXD的一个类来实现这个virtual方法(这里是2D的例子)

virtual void execute(Cell<T,Descriptor>& cell) const;

在这句代码里,只是简单地使用了cell语句,如果涉及到空间位置,那么可以用继承 OneCellIndexedFunctionalXD的类。(还是2D的例子)

virtual void execute(plint iX, plint iY, Cell<T,Descriptor>& cell) const;

lattice上被执行的这种单个cell函数如下:

// Case of a plain one-cell functional.
applyIndexed(lattice, domain, new MyOneCellFunctional<T,Descriptor>);
// Case of an indexed one-cell functional.
applyIndexed(lattice, domain, new MyOneCellIndexedFunctional<T,Descriptor>);

在目录examples/showCases/multiComponent2d可以找到运用这种方法的算例。这里是以一种自定义的初始化方法来访问外部lattice的标量以实现热模拟。

16.3 编写数据处理器(确切来说,数据处理函数)

下一节会列出许多数据处理用的函数,为了方便描述,下面的例子是把一块lattice的f[1]的值与f[5]的值互换,这可以通过16.2节的单格子函数来实现,但下面的例子是用数据的函数来实现的:

template<typename T, template<typename U> class Descriptor>
class Invert_1_5_Functional2D : public BoxProcessingFunctional2D_L<T,Descriptor> {
public:
// ... implement other important methods here.
void process(Box2D domain, BlockLattice2D<T,Descriptor>& lattice)
{
for (plint iX=domain.x0; iX<=domain.x1; ++iX) {
for (plint iY=domain.y0; iY<=domain.y1; ++iY) {
Cell<T,Descriptor>& cell = lattice.get(iX,iY);
// Use the function swap from the C++ STL to swap the two values.
std::swap(cell[1], cell[5]);
} } }
};

数据处理用到的函数继承BoxProcessingFunctional2D_L(L表示数据处理器作用在一块lattice上),运行了 process 方法。

process对应着在原始域分割后的域的计算。两个for的循环定义出了域的位置。这种方法总是用于atomic-block上(从来不是multi-block)。因为通过process,palabos已经分割了原始block,访问其内部的子atomic-block数据。

对比16.2节的局部操作的封装器,你会发现你的工作量增多了,尤其是你写很多数据处理器的时候,容易出错。

但,这是为了效率呀。

另一个优点如下面的例子所示,可以执行非局部的操作,将f[1]的值与右侧格子f[5]的值互换。

void process(Box2D domain, BlockLattice2D<T,Descriptor>& lattice)
{
for (plint iX=domain.x0; iX<=domain.x1; ++iX) {
for (plint iY=domain.y0; iY<=domain.y1; ++iY) {
Cell<T,Descriptor>& cell = lattice.get(iX,iY);
Cell<T,Descriptor>& partner = lattice.get(iX+1,iY);
std::swap(cell[1], partner[5]);
} }

有两点需要注意,确保自己没有访问到范围外的格子。
1)邻格操作的时候(D2Q9,D3Q19等),非局部操作至多可以访问1个格子距离的格子。(lattice.get(iX+1,iY) 而不是(lattice.get(iX+2,iY))。
如果是扩展的邻域,你也可以扩长距离在数据处理器里访问邻域格子。
允许的非局部量由常数Descriptor<T>::vicinity决定 。
2)数据处理器里不允许进行非局部操作,因为它会在"communication envelope"里作用。(16.3.2节 你需要override的方法 里会有解释)

总结一下你能在数据处理器做什么,可以在一定范围内(包括邻域)读取格子或修改格子。使用process的时候空间依赖必须是普适性的并且不依赖于一定的domain语句坐标,这非常重要正如数据处理器的第零法则:

Rule 0 of data processors:
A data processor must always be written in such a way that executing the data processor on a given domain has the same effect as splitting the domain into two sub-domains, and then executing the data processor consecutively on each of these sub-domains.

大意为数据处理器在指定域执行时,就像把一个域分成两个子域,接着数据处理器在两个域上连续执行。

实践上,这意味着你将不能通过domain的x0, x1, y0,
和 y1,或iX、iY指数来做任何决定。

这些局部的指数必须先转换成全局的,独立于子域的数据处理器,在16.3.3节 绝对速度和相对速度 有解释。

16.3.1 数据处理函数的种类

取决于何种block何种数据处理器,不同类型的处理函数如下:

Class BoxProcessingFunctionalXD< T> void processGenericBlocks(BoxXD domain,
std::vector<AtomicBlockXD< T>*> atomicBlocks);
This class is practically never used. It is the fall-back option when everything else fails. It handles an arbitrary number of blocks of arbitrary type, which were casted to the generic type AtomicBlockXD. Before use, you need to cast them back to their real type.

Class LatticeBoxProcessingFunctionalXD<T,Descriptor> void process(BoxXD domain,
std::vector<BlockLatticeXD<T,Descriptor>*> lattices);
Use this class to process an arbitrary number of block-lattices, and potentially create a coupling between them. This data-processing functional is for example used to define a coupling between an arbitrary number of lattice for the Shan/Chen multi-component model defined in the files src/multiPhysics/shanChenProcessorsXD.h and .hh. This type of data-processing functional is not very frequently used either, as the two-block versions listed below are more appropriate in most cases.

Class ScalarFieldBoxProcessingFunctionalXD< T> void process(BoxXD domain,
std::vector<ScalarFieldXD< T>*> fields);
Same as above, applied to scalar-fields.

Class TensorFieldBoxProcessingFunctionalXD<T,nDim> void process(BoxXD domain,
std::vector<TensorFieldXD<T,nDim>*> fields);
Same as above, applied to tensor-fields.

Class BoxProcessingFunctionalXD_L<T,Descriptor> void process(BoxXD domain,BlockLatticeXD<T,Descriptor>& lattice);
Data processor acting on a single lattice.

Class BoxProcessingFunctionalXD_S< T> void process(BoxXD domain,
ScalarFieldXD< T>& field);
Data processor acting on a single scalar-field.

Class BoxProcessingFunctionalXD_T<T,nDim> void process(BoxXD domain,
TensorFieldXD<T,nDim>& field);
Data processor acting on a single tensor-field.

Class BoxProcessingFunctionalXD_LL<T,Descriptor1,Descriptor2> void process(BoxXD
domain, BlockLatticeXD<T,Descriptor1>& lattice1, BlockLatticeXD<T,
Descriptor2>& lattice2);
Data processor for processing and/or coupling two lattices with potentially different descriptors.
Similarly, there is an SS version for two scalar-fields, and a TT version for two tensor-fields with
potentially different dimensionality nDim.

Class BoxProcessingFunctionalXD_LS<T,Descriptor> void process(BoxXD domain,
BlockLatticeXD<T,Descriptor>& lattice, ScalarFieldXD& field);
Data processor for processing and/or coupling a lattice and a scalar-field. Similarly, there is an LT and an ST
version for the lattice-tensor and the scalar-tensor case.

这些处理函数,都有一个减化的版本(比如ReductiveBoxProcessingFuncionalXD_L)以应对数据处理器在“reduction operation”中运行和得到值。

16.3.2 你需要override的方法

除了process,一个数据处理函数必须override三个方法。这三个方法的使用在类 Invert_1_5_Functional2D中已介绍:

BlockDomain::DomainT appliesTo() const
{
return BlockDomain::bulk;
}
void getModificationPattern(std::vector& isWritten) const
{
isWritten[0] = true; }
Invert_1_5_Functional2D<T,Descriptor>* clone() const
{
return new Invert_1_5_Functional2D<T,Descriptor>(*this);
}

首先你需要用palabos里典范的clone(),(在第五章有介绍)。接下来你需要让palabos知道哪些block被数据处理器修改中。例子里只有一个block。通常,向量isWritten的尺寸和涉及的block数量一致,而且你必须要给每一个设定true/false。除此以外,这个信息会被palabos用于决定是否要进行数据处理器运行后的block内部处理操作。

下面的内容太长不看。

16.3.3 绝对和相对位置

iX和iY仅仅在数据处理器的循环中使用,因为他们仅仅代表着atomic-block的局部变量,在整体的multi-block中这atomic-block也是位于随机的一个位置。若是需要参考空间位置,局部的坐标需要被转换成全局的:

// Access the position of the atomic-block inside the multi-block.
Dot2D relativePosition = lattice.getLocation();
// Convert local coordinates to global ones.
plint globalX = iX + relativePosition.x;
plint globaly = iY + relativePosition.x;

examples/showCases/boussinesqThermal2d/路径里有一个算例,因为这种转换有点尴尬,所以推荐使用单格子函数(16.2节)来自动帮你实现功能。

下面的内容太长不看

16.3.4 执行、合并和封装数据处理函数
16.3.4 内部数据处理器的执行顺序
感觉是时候跳过这些手册内容,去看examples里面的代码,等到需要理解这些内容的时候再回头填坑。
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐