Palabos用户手册翻译及学习(二)运行模拟与数据评估

运行模拟与数据评估

Palabos用户文档 的第十章和第十二章

(一)Running A Simulation

原始文档

Time cycles of a Palabos program

The lattice Boltzmann method (or at least, the “classical” lattice Boltzmann method as it is implemented in Palabos) consists of an explicit solver. With a single iteration step, the state of the fluid evolves from a time t to the next time t+dt. The following discussion is led in the units of the lattice, with dt=1. One time iteration of this kind takes the following form in Palabos:

  1. To start with, all fluid variables are defined at time t. The particle populations are in pre-collision state (also called “incoming populations”).
  2. The collision operator is applied to all cells. They are now in post-collision state (also called “outgoing variables”).
  3. The streaming operator is applied to the lattice.
  4. All data processors are executed, to perform non-local operations or couplings between lattices.
  5. The populations are now again in pre-collision state, but at time t+1.



The collision step (Step 2) is executed by invoking the method lattice.collide(), and the streaming step (Step 3) is called with the method lattice.stream(). These two methods can also be combined into a single call to lattice.collideAndStream(). On most hardware platforms, the collideAndStream() version is computationally more efficient, because it is executed by traveling through the memory of the lattice only once.
The data processors can be executed manually by calling the method lattice. executeInternalProcessors(), as explained in Section Executing, integrating, and wrapping up dataprocessing functionals. This is however rarely done, because the data processors are also executed automatically at the end of the function stream() and the function collideAndStream(). If you’d like to call stream() or collideAndStream without having the data processors executed, for example for debugging a program, you can use the domain-version of these functions, for example lattice.collideAndStream(lattice.getBoundingBox()).
In Palabos, data processors are always executed after the collision-streaming cycle. Consequently, non-local operations are always executed after the collision-streaming cycle, at a moment where the populations are in a pre-collision state (incoming populations). This bears no loss of generality, though, because executing an operation before the collision of time t is equivalent to executing it after the streaming of time t-1, right? The only problem arises with the initial condition, as it is most often desired to have the data processors executed exactly once at the very beginning, right after setting up the initial condition. This is achieved by calling the method lattice.initialize() right before starting the first iteration step. This method executes the data processors once, and performs an internal communication step inside the block to guarantee that its internal state is consistent.

At which point do you evaluate data?

To monitor the evolution of a program, it is useful evaluate some hydrodynamic quantities from time to time, such as the average energy:

pcout << computeAverageEnergy(lattice) << endl;

It is recommended to compute hydrodynamic variables always only when the system is in pre-collision state (incoming populations). While this distinction not really matters for the conserved variables density and velocity (they are equal in the pre- and post-collision state), it is important for the non-conserved variables such as the stress tensor. Non-conserved velocity moments can be related to hydrodynamic variables only when they are computed in the precollision state. Note that if you use the method collideAndStream(), there is no risk for doing things wrong, because you have no access to post-collision variables anyway.
Normally, the computation of hydrodynamic variables like the average energy in the example above is performed right after the call to the method collideAndStream(), because at this point all hydrodynamic variables are well defined, and correspond to the same moment in time (between collision and streaming the velocity is well defined, but the strain-rate is not). An exception is made for the internal statistics of lattice. Internal statistics are automatically computed without any impact on performance (at least not in serial program; for the parallel case, see the discussion in Section Controlling the efficiency), as a side-effect of executing the collision step. They are however evaluated for the fluid variables at time t, during the collision-streaming cycle which carries the system from time t to time t+1. It is therefore usual to access the internal statistics after the call to the method collideAndStream(). Computing the average energy as in the example above before collision-and-streaming produces the same result as accessing the average energy from internal statistics, as in the example below, after collision-and-streaming:

pcout << getStoredAverageEnergy(lattice) << endl;
Other important things to do

Numbers are often difficult to interpret. It is therefore useful to regularly produce images in your program, so that you can monitor the state of simulation, identify problems as soon as possible, and re-run the program when needed. Section Producing images in 2D and 3D simulations explains how to do this.
Finally, it is always good to save the state of a simulation from time to time, in order not to loose everything when the computer crashes, and in order to be able to recover the data if you forgot to produce a crucial output file for post-processing. This is explained in Section Checkpointing: saving and loading the state of a simulation.

文档翻译

Palabos程序的时间周期

格尔兹曼方法(或者至少是Palabos中实现的“经典”格尔兹曼方法)包含一个显式求解器。通过一个迭代步骤,流体的状态从时间t演化到下一次时间t+dt。下述讨论中依照的晶格单位中dt=1。这种时间迭代在Palabos中采用如下形式:

  1. 首先,所有的流体变量都是在t时刻定义的,粒子群处于碰撞前的状态(也称为入射粒子群)。
  2. 碰撞操作应用于所有单元格。它们现在处于碰撞后的状态(也称为“输出变量”)。
  3. 流操作应用于晶格。
  4. 所有的数据处理器都被执行,以执行非本地操作或格之间的耦合。
  5. 种群现在又回到了碰撞前的状态,但时间是t+1。

通过调用方法lattice.collision()来执行碰撞步骤(步骤2),然后使用方法lattice.stream()来调用流步骤(步骤3)。这两个方法还可以组合成对lattice.collideandstream()的单个调用。在大多数硬件平台上,collideAndStream()版本的计算效率更高,因为它只在晶格层的内存中运行一次。
可以通过调用方法lattice.executeInternalProcessors()手动执行数据处理器,如执行、集成和封装数据处理函数(Executing, integrating, and wrapping up data processing functionals, 文档第16.3.4节)一节所述。但是这种情况很少发生,因为数据处理器也是在函数stream()和函数collideAndStream()的末尾自动执行的。如果您想在不执行数据处理器的情况下调用stream()或collideAndStream(),例如为了调试一个程序,您可以使用这些函数的域版本,例如lattice.collideAndStream(lattice.getboundingbox())。
在Palabos中,数据处理器总是在碰撞流动演化循环之后执行。因此,非本地操作总是在碰撞流动循环之后执行,此时种群处于碰撞前状态(入射粒子群)。但是,这并不会损失一般性,因为在t时间碰撞之前执行操作等价于在t-1时间流之后执行它,对吧?惟一的问题出现在初始条件上,因为最常见的情况是数据处理器在初始条件设置之后恰好执行一次。这是通过在开始第一个迭代步骤之前调用方法initialize()来实现的。此方法只执行一次数据处理器,并在块内部执行内部通信步骤以确保其内部状态是一致的。

在什么时候评估数据?

为了监测程序的发展,不时地评估一些水动力学量是很有用的,例如平均能量:

pcout << computeAverageEnergy(lattice) << endl;

建议只在系统处于碰撞前状态(入射粒子群)时才计算流体动力学变量。虽然这种区别对于守恒变量密度和速度来说并不重要(它们在碰撞前和碰撞后的状态是相等的),但是对于非守恒变量,比如应力张量来说,这是很重要的。非守恒速度矩只有在碰撞前状态下计算时才能与水动力变量相联系。注意,如果使用collideAndStream()方法,就不会有出错的风险,因为无论如何都无法访问碰撞后的变量。
通常情况下,在调用方法collideAndStream()后计算水动力变量(如上面的示例中的平均能量),因为此时对应于同一时刻的所有水动力变量是定义明确的(碰撞和流动间的速度也是定义明确的,但应变率不是)。格点的内部统计是一个例外,内部统计数据是不会对性能产生任何影响的自动计算(至少在串行程序中不会;对于并行情况,请参阅原文15.2Controlling the efficiency小节中的讨论),这是执行碰撞步骤的副作用。然而,在碰撞流动循环中(从时间t到时间t+1),它们在时间t的流体变量进行评估。因此,通常在调用collideAndStream()方法后访问内部统计信息。上面的例子中,在碰撞与流动之前计算计算平均能量,与下面的例子,在碰撞与流动之后获取内部统计中的平均能量值,有相同的结果:

pcout << getStoredAverageEnergy(lattice) << endl;
其它重要的事

数字通常很难解释。因此,在程序中定期生成图像是很有用的,这样您就可以监视模拟的状态,尽快识别问题,并在需要时重新运行程序。在2D和3D模拟中生成图像(原文11.5节 Producing images in 2D and 3D simulations)的部分解释了如何做到这一点。
最后,经常保存模拟的状态总是好的,这样在计算机崩溃时就不会丢失所有东西,而且如果您忘记为后期处理生成关键的输出文件,也可以恢复数据。这将在检查:保存和加载模拟的状态(原文11.7节 Checkpointing: saving and loading the state of a simulation)中解释。

解释说明

这一部分主要是介绍了程序中碰撞流动循环,写的挺清楚的,collideAndStream()方法集成了数据处理器计算统计步骤,可以直接调用而不用担心错误,数据处理器计算步骤是作用于碰撞和流动之后,但其实在你为你的模拟设定好各种内部及边界条件后,其内部的粒子群并没有被设置好,还是原始状态,只有在调用过initialize()后,一次性调用了一遍数据处理步骤,粒子群才被正式初始化。
我有时候会使用那个原文中说的不包含数据处理步骤的版本,中间写一些代码当中的正确操作而不一定是模拟计算当中的正确操作,最后再由相应的调用数据处理步骤的函数来实现操作,骚操作,得到的数据很好看但不一定对,嘻嘻嘻。

(二)Data Evaluation

原始文档

Overview

The variables simulated in a lattice Boltzmann program, the particle populations, contrast with the quantities a typical fluid engineer is interested in, the macroscopic variables pressure, velocity, and others. Obviously, one needs to somehow convert the data before providing it to a standard post-processing tool. Many functions for conversion, evaluation, or transformation of data are provided in Palabos, as listed in Appendix Non-mutable operations for data analysis and other purposes. In the present section, the function computeVelocity is discussed as an example, as the other functions work in a very similar way. These functions are defined for the 2D and the 3D case, and they work with atomic-blocks and multi-blocks. For the sake of illustration, the following codes refer to the 3D multi-block case.
The function computeVelocity is provided in three versions:

// Version 1
void computeVelocity(MultiBlockLattice3D<T,Descriptor>& lattice,
MultiTensorField3D<T,Descriptor<T>::d>& velocity, Box3D domain);
// Version 2
std::unique_ptr<MultiTensorField3D<T,3> >
computeVelocity(MultiBlockLattice3D<T,Descriptor>& lattice, Box3D domain);
// Version 3
std::unique_ptr<MultiTensorField3D<T,3> >
computeVelocity(MultiBlockLattice3D<T,Descriptor>& lattice);

In the first version, the velocity is computed on a sub-domain of a block-lattice, and the result is written to a corresponding sub-domain of a three-component tensor-field. The block-lattice and the tensor-field don’t need to have the same size, nor do they need to have the same internal block arrangement. If the domain exceeds the dimensions of either the block-lattice or the tensor-field, the domain is trimmed correspondingly.
In the second version, which is much more often used in practice, a tensor-field with the size of domain is automatically created and returned from the function. The auto-pointer keyword used in the return value of the function refers to a class of smart-pointers offered by the C++ standard library. From the user’s point of view, it is practically the same as a pointer. This means that you treat an object of type std::unique_ptr<MultiTensorField3D<T,3> > in the same way as you would treat an object of type MultiTensorField3D<T,3>*. The big difference between the two is that the auto-pointer has an automatic memory management mechanism, and that you never need to call the operator delete on this type of pointers. If the return value of the function were a raw pointer, you wouldn’t be able to pipeline different operators, as shown in the next section, because you’d never get an explicit pointer to them and therefore couldn’t delete them.
The following code shows a typical use case of the function computeVelocity:

pcout << *computeVelocity(lattice, domain) << endl;

Here, the computed velocity values are immediately redirected to the terminal. The star in front of the function call is used to dereference the pointer to the velocity field. At the end of this program line, the memory for the velocity field is automatically deallocated, and does not need to be disposed of explicitly.
The third version is a pure convenience function in which the argument domain is replaced by the full domain of the lattice, lattice.getBoundingBox().

Pipelining data evaluation operators

The return value of a function like computeVelocity can directly be reused as the argument of other data evaluation operators. In this way, it is possible to construct complex expressions. For example, the function computeAverage in the previous section could be replaced by a computation of the velocity field, followed by the computation of the norm-square of each element, a division by two, and finally a computation of the average value:

pcout << "The value "
      << *computeAverageEnergy(lattice) << " is the same as "
      << computeAverage(*multiply(0.5,*computeNormSqr(*computeVelocity(lattice))))
      << endl;

All the functions used in this example are listed in the Appendix Appendix: partial function/class reference. More examples of the evaluation of data, constructions of scalar fields, and combination of data evaluation operators are provided in the directory examples/codesByTopic/scalarField.

文档翻译

综述

在格子玻尔兹曼程序中模拟的变量,粒子的数量,对照于典型的流体工程师感兴趣的量如压力,速度,和其他宏观变量。显然,在将数据提供给标准的后处理工具之前,需要以某种方式转换数据。Palabos中提供了许多用于数据转换、评估或转换的函数,如附录中列出的用于数据分析和其他目的的不可变操作( Appendix: Non-mutable operations for data analysis and other purposes)。在本节中,将以computeVelocity函数为例进行讨论,因为其他函数的工作方式非常类似。这些函数是为2D和3D情况定义的,它们使用原子块和多块。为了便于说明,以下代码参考了3D多块情况。
computeVelocity函数有三个版本:

// Version 1
void computeVelocity(MultiBlockLattice3D<T,Descriptor>& lattice,
MultiTensorField3D<T,Descriptor<T>::d>& velocity, Box3D domain);
// Version 2
std::unique_ptr<MultiTensorField3D<T,3> >
computeVelocity(MultiBlockLattice3D<T,Descriptor>& lattice, Box3D domain);
// Version 3
std::unique_ptr<MultiTensorField3D<T,3> >
computeVelocity(MultiBlockLattice3D<T,Descriptor>& lattice);

在第一个版本中,速度是在块晶格的子域上计算的,并将结果写到有三个分量的张量场的相应子域上。块晶格和张量场不需要有相同的尺寸,也不需要有相同的内部块排列。如果计算域超过了块晶格或张量场的尺寸,计算域就会相应地被修剪。
在第二个版本中(在实践中更常用),将自动创建具有域大小的张量字段并从函数中返回。函数返回值中使用的auto-pointer关键字指的是c++标准库提供的一类智能指针。从用户的角度来看,它实际上和指针是一样的。这意味着处理std::unique_ptr<MultiTensorField3D<T,3> >类型对象的方法与处理MultiTensorField3D<T,3>*类型对象的方法相同。两者之间的主要区别是,自动指针有一个自动的内存管理机制,并且您永远不需要在这种类型的指针上调用操作符delete。如果该函数的返回值是一个原始指针,那么您将无法进行不同的流水线操作(如下一节所示),因为您永远不会得到指向它们的显式指针,因此无法删除它们。
下面的代码展示了computeVelocity函数的一个典型用例:

pcout << *computeVelocity(lattice, domain) << endl;

在这里,计算出的速度值立即被重定向到终端。函数调用前面的星号用于取消指向velocity字段的指针的引用。在这一行程序的末尾,velocity字段的内存被自动释放,不需要显式地释放。
第三个版本是一个纯粹方便的函数,其中参数域被格的完整域替换,即lattice.getboundingbox()。

流水线数据评估操作符

像computeVelocity这样的函数的返回值可以直接作为其他数据求值操作符的参数重用。这样,就可以构造复杂的表达式。例如,前一章中的computeAverage函数可以被对速度场的计算代替,然后是每个元素的法向平方的计算,除以2,最后是平均值的计算:

pcout << "The value "
      << *computeAverageEnergy(lattice) << " is the same as "
      << computeAverage(*multiply(0.5,*computeNormSqr(*computeVelocity(lattice))))
      << endl;

本例中使用的所有函数都在附录Appendix: partial function/class reference中列出。在examples/codesByTopic/scalarField目录中提供了关于数据求值、标量字段构造和数据求值操作符组合的更多示例。

解释说明

所有的数据评估操作,格式与综述里所给的三种方式一样,推荐使用第二种,可以自己规定计算域范围。
数据评估操作,请多多参阅附录Appendix: partial function/class reference中的操作,所有的函数按照附录里面的写法都可以写成复杂的流水线式的数据操作代码,不会出错,但也请注意不要做附录以外的其它复杂操作,我试过,不太可控。
之前给人解决过相应的数据输出的问题,请注意数据输出到你自己需要的文件时的精度问题,如果过小的数据变化,可能你输出的时候,得到的结果基本不变,以为代码写错了,其实实际情况就是,你的变量的结果值差距太小,后处理时基本不显示了,请注意这点。

发布了15 篇原创文章 · 获赞 5 · 访问量 3677

猜你喜欢

转载自blog.csdn.net/qq_28632981/article/details/104119935