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中的操作,所有的函數按照附錄裏面的寫法都可以寫成複雜的流水線式的數據操作代碼,不會出錯,但也請注意不要做附錄以外的其它複雜操作,我試過,不太可控。
之前給人解決過相應的數據輸出的問題,請注意數據輸出到你自己需要的文件時的精度問題,如果過小的數據變化,可能你輸出的時候,得到的結果基本不變,以爲代碼寫錯了,其實實際情況就是,你的變量的結果值差距太小,後處理時基本不顯示了,請注意這點。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章