Palabos用戶手冊翻譯及學習(三)設置中的問題與定義邊界條件

設置中的問題與定義邊界條件

Palabos用戶文檔 的第八章和第九章

(一)Setting Up A Problem

原始文檔

Attributing dynamics objects

When constructing a new block-lattice, you must decide what type of collision is going to be executed on each of its cells, by assigning them a dynamics object. To avoid bugs related to cells without dynamics objects, the constructor of a block-lattice assigns a default-alue for the dynamics to all cells, the so-called background dynamics:

// Construct a new block-lattice with a background-dynamics of type BGK.
MultiBlockLattice3D<T,DESCRIPTOR> lattice( nx, ny, nz, new BGKdynamics<T,DESCRIPTOR>(omega) );

After this, the dynamics of each cell can be redefined in order to adjust the behavior of the cells locally. The background dynamics differs from usual dynamics objects in an important way. To obtain memory savings, the constructor of the block-lattice creates only one instance of the dynamics object, and all cells refer to the same instance. If you modify a dynamics object on one cell, it changes everywhere. As an example, consider the relaxation parameter omega, which is stored in the dynamics, not in the cell. A function call like

lattice.get(0,0,0).getDynamics().setOmega(newOmega); 

would affect the value of the relaxation parameter on each cell of the lattice. Note that this particular behavior of the background dynamics, having several cells referring to the same object, cannot be reproduced by other means than constructing a new block-lattice. All other functions which override the background-dynamics on given cells with a new dynamics object are defined in such a way as to create an independent copy of the dynamics object for each cell.
If the reference semantics of the background dynamics is contrary to your needs, you can re-define all dynamics objects right after constructing a block-lattice, to guarantee that all cells have an independent copy:

// Override background-dynamics to guarantee and independent per-cell
// copy of the dynamics object.
defineDynamics( lattice, lattice.getBoundingBox(), new BGKdynamics<T,DESCRIPTOR> );

There exist different variants of the function defineDynamics which you can use to adjust the dynamics of a group of cells (their syntax can be found in the Appendix Operations on the block-lattice):

  • One-cell version: Assign a new dynamics to just one cell.
    Example: tutorial/tutorial2/tutorial2_3.cpp.
  • BoxXD version: Assign a new dynamics to all cells within a rectangular domain.
    Example: showCases/multiComponent2d/rayleighTaylor2D.cpp, or the 3d example.
  • DotListXD version: Assign a new dynamics to several cells, listed individually in a dot-list structure.
    Example: codesByTopic/dotList/cylinder2d.cpp
  • Domain-functional version: Provide an analytical function which indicates the coordinates of cells which get a new dynamics object.
    Example: showCases/cylinder2d/cylinder2d.cpp
  • Bool-mask version: Specify the location of cells which get a new dynamics object through a Boolean mask, represented through a scalar-field.
    Example: codesByTopic/io/loadGeometry.cpp

Initial values of density and velocity

It is quite common to assign an initial value of velocity and density to a lattice by initializing all cells to an equilibrium distribution with the chosen density and velocity value. While this approach is not sufficient for time-dependent benchmark problems which depend critically on the initial state, it is most often sufficient to get a simulation started with reasonable initial values. The function initializeAtEquilibrium (see Appendix Operations on the blocklattice) is provided to initialize the cells within a rectangular-shaped domain at an equilibrium distribution. It comes in two flavors: one with a constant density and velocity within the domain (see the example examples/showCases/cavity2d or 3d), and one with a space-dependent value of these macroscopic values, specified through a userdefined function (see the example examples/showCases/poiseuille).
To initialize cells in a different way, it is simplest to apply a custom operator to the cells with the help of one-cell functionals and indexed one-cell functionals introduced in Section Convenience wrappers for local operations.

文檔翻譯

設置動態對象

當構造一個新的塊格時,你必須通過分配一個動態對象來決定在每個單元格上執行什麼類型的碰撞。爲了避免與沒有動態對象的單元格相關的bug,塊格的構造器將動態的默認值賦給所有單元格,即所謂的後臺動態:

// 以BGK動力學類作爲背景動力學構造一個新的塊格。
MultiBlockLattice3D<T,DESCRIPTOR> lattice( nx, ny, nz, new BGKdynamics<T,DESCRIPTOR>(omega) );

在這之後,每個單元格的動態可以重新定義,以調整單元格的局部行爲。背景動力學與一般的動力學對象有重要的區別。爲了節省內存,塊格的構造函數只創建一個動態類對象的實例,所有單元格都引用同一個實例。如果您在任一個單元格上修改動態類對象,它將到處更改。例如,考慮鬆弛參數,它存儲在動態類對象中,而不是在單元格中。如以下函數調用

lattice.get(0,0,0).getDynamics().setOmega(newOmega); 

會影響晶格中每個單元的弛豫參數的值。請注意,這是背景動力學的特定行爲,幾個單元格指向同一個對象,不能通過其他方法重製,只能通過構造一個新的塊晶格來實現。使用新的動態類對象覆蓋給定單元上的背景動態的所有其他函數的定義方式是,爲每個單元創建動態類對象的獨立副本。
如果背景動態的引用語義與你的需要相反,你可以在構建一個塊格之後重新定義所有的動態對象,以保證所有的單元都有一個獨立的副本:

// 覆蓋背景動態以保證每個單元都有動態對象的獨立副本。
defineDynamics( lattice, lattice.getBoundingBox(), new BGKdynamics<T,DESCRIPTOR> );

你可以使用defineDynamics函數的不同變體來調整一組細胞的動態(它們的語法可以在block-lattice的附錄操作中找到):

  • One-cell版本:將一個新的動態分配給一個細胞。
    例子:tutorial/tutorial2/tutorial2_3.cpp
  • BoxXD版本:將一個新的動態分配給矩形域中的所有單元格。
    例子:showCases/multiComponent2d/rayleighTaylor2D.cpp或 3d 案例
  • DotListXD版本:將一個新的動態分配給幾個單元,以點列表結構單獨列出。
    例子:codesByTopic/dotList/cylinder2d.cpp
  • Domain-functional版本:提供一個分析功能,指出細胞的座標,得到一個新的動態對象。
    例子:showCases/cylinder2d/cylinder2d.cpp
  • Bool-mask版本:指定單元格的位置,這些單元格通過一個布爾掩碼得到一個新的動態對象,通過一個標量場表示。
    例子:codesByTopic/io/loadGeometry.cpp
密度和速度的初始值

通過用選定的密度和速度值初始化所有的單元格使其達到平衡分佈,爲晶格分配速度和密度的初值是很常見的。雖然這種方法不適用於依賴於初始狀態的時變基準測試問題,但它通常足以使仿真從合理的初始值開始。函數initializeAtEquilibrium(參見塊格上的附錄操作)被用來初始化矩形區域內的單元格,使其處於平衡分佈。它有兩種使用方式:一種是在域內具有恆定的密度和速度(參見示例examples/showCases/cavity2d或3d),另一種是通過用戶定義的函數指定的這些宏觀值的空間依賴性值(參見示例examples/showCases/poiseuille)。

解釋說明

在構建塊格時生成的動態類對象只有一個,在堆區分配對象空間,然後給塊格中的每一個單元傳遞一個動態類對象的指針,所以只有一個對象,所有單元格共享。爲特定單元格單獨設置動態類,是通過在兌取新建一個相應的動態類對象並將其指針傳遞給對應單元格來覆蓋只想背景動態類對象的指針,從而達到重新設置類對象的效果。One-cell、BoxXD、DotListXD、Domain-functional、Bool-mask都是指定更改所限定作用範圍的參數,你可以把它們都想象成stl標準模板庫中的iterator迭代器,不僅在動態類的設定中有使用,基本上所有其他的設置,都有他們的身影。
密度和速度設置,整個塊格的密度和速度的初始化,方腔流是將整個流區初始化爲相同的密度和速度,也就是穩態模擬最有可能達到的平衡態附近,同理,泊肅葉流是將整個流區初始化爲泊肅葉分佈形式的密度和速度,也是對應的穩態模擬最可能達到的平衡態的附近,此種設置,都是爲了能讓模擬更快達到穩態結果,如果不這樣設置,可能會導致模擬失敗,非穩態的計算,我認爲這個初始值並不是特別重要,主要的是在邊界以及特定流動區域不要出現計算的死點,纔是初始值和對應模擬參數需要關注的重點。

(二)Defining Boundary Condition

原始文檔

Overview

The library Palabos currently implements a bunch of different boundary conditions for straight, grid-aligned boundaries. For general boundaries, the available options include stair-cased walls through bounce-back nodes, and higherorder accurate curved boundaries.
IMPORTANT: Curved boundaries are available as of version 1.0, and are part of a fully-featured parallel stack, including parallel voxelization and I/O extensions. Curved boundaries are not yet documented. You can however find a full-featured example in showCases/aneurysm.

Grid-aligned boundaries

The class OnLatticeBoundaryConditionXD
Some of the implemented boundary conditions are local (and are therefore implemented as dynamics classes), some are non-local (and are therefore implemented as data processors), and some have both local and nonlocal components. To offer a uniform interface to the various possibilities, Palabos offers the interface OnLatticeBoundaryConditionXD which is responsible for instantiating dynamics objects, adding data processors, or both, depending on the chosen boundary condition. The following line for example is used to chose a Zou/He boundary condition:

OnLatticeBoundaryCondition3D<T,DESCRIPTOR>* boundaryCondition = createZouHeBoundaryCondition3D<T,DESCRIPTOR>();

The four possibilities currently offered are (see palabos.org-bc for details):

model class
Regularized BC createLocalBoundaryConditionXD
Skordos BC createInterpBoundaryConditionXD
Zou/He BC createZouHeBoundaryConditionXD
Inamuro BC createInamuroBoundaryConditionXD

With each of these boundary conditions, it is possible to implement velocity Dirichlet boundaries (all velocity components have an imposed value) and pressure boundaries (the pressure is imposed, and the tangential velocity components are zero). Furthermore, various types of Neumann boundary conditions are proposed. In these case, a zero-gradient for a given variable is imposed with first-order accuracy by copying the value of this variable from a neighboring cell.

Boundaries on a regular block domain
The usage of these boundary conditions is somewhat tricky, because Palabos needs to know if a given wall is a straight wall or a corner (2D), or a flat wall, an edge or a corner (3D), and it needs to know the wall orientation. To simplify this, all OnLatticeBoundaryConditionXD objects have a method setVelocityConditionOnBlockBoundaries and a method setPressureConditionOnBlockBoundaries, to set up boundaries which are located on a rectangular domain. If all nodes on the boundary of a given 2D box boundaryBox implement a Dirichlet condition, use the following command:

Box2D boundaryBox(x0,x1, y0,y1);
boundaryCondition->setVelocityConditionOnBlockBoundaries ( lattice, boundaryBox, locationOfBoundaryNodes );

The first argument indicates the boxed shape on which the boundary is located, and the second argument specifies on which node a velocity condition is to be instantiated. If all boundary nodes are located on the exterior bounding box of the lattice, the above command simply becomes:

boundaryCondition->setVelocityConditionOnBlockBoundaries(lattice, locationOfBoundaryNodes);

Finally, if simply all nodes of the exterior bounding box shall implement a velocity condition, this simplifies to:

boundaryCondition->setVelocityConditionOnBlockBoundaries(lattice);

Now, suppose that the upper and lower walls, including corners, in a nx -by- ny lattice implement a free-slip condition (zero-gradient for tangential velocity components), the left wall a Dirichlet condition, and the right wall and outflow condition (zero-gradient for all velocity components). This is achieved by adding one more parameter to the function call:

Box2D inlet(0, 0, 2, ny-2);
Box2D outlet(nx-1, nx-1, 2, ny-2);
Box2D bottomWall(0, nx-1, 0, 0);
Box2D topWall(0, nx-1, ny-1, ny-1);
boundaryCondition->setVelocityConditionOnBlockBoundaries ( lattice, inlet );
boundaryCondition->setVelocityConditionOnBlockBoundaries ( lattice, outlet, boundary::outflow );
boundaryCondition->setVelocityConditionOnBlockBoundaries ( lattice, bottomWall, boundary::freeslip );
boundaryCondition->setVelocityConditionOnBlockBoundaries ( lattice, topWall, boundary::freeslip );

Remember that if the boundary is not located on the outer bounds of the lattice, you must use an additional Box3D argument to say where the boundaries are located, additionally to saying which boundary is going to be added. This is necessary, because Palabos needs to know the orientation and the nature (flat wall, corner, etc.) of the boundary nodes.

Zero-gradient conditions
The optional arguments like boundary::outflow are of type boundary::BcType, and can have the following values for velocity boundaries:

object values explain
boundary::dirichlet (default value) Dirichlet condition: imposed velocity value
boundary::outflow or boundary::neumann Zero-gradient for all velocity components
boundary::freeslip Zero-gradient for tangential velocity components, zero value for the others
boundary::normalOutflow Zero-gradient for normal velocity components, zero value for the others

For pressure boundaries, you have the following choice:

object values explain
boundary::dirichlet (default value) Dirichlet condition: imposed pressure value, zero value for the tangential velocity components
boundary::neumann Zero-gradient for the pressure, zero value for the tangential velocity components

Boundary conditions cannot be overriden
It is not possible to override the type of a boundary. Once a boundary node is a Dirichlet velocity node, it stays a Dirichlet velocity node. The result of re-defining it as, say, a pressure boundary node, is undefined. Therefore, boundary conditions must be defined carefully, and piece-wise, as shown in the example above. On the other hand, the value imposed on the boundary (i.e. the velocity value on a Dirichlet velocity boundary) can be changed as often as needed.

Setting the velocity or pressure value on Dirichlet boundaries
A constant velocity value is imposed with the function setBoundaryVelocity. The following line sets the velocity values to zero on all nodes of a 2D domain which have previously been defined to be Dirichlet velocity nodes:

setBoundaryVelocity(lattice, lattice.getBoundingBox(), Array<T,2>(0.,0.) );

On all other nodes, this command has no effect. To set a constant pressure value (or equivalently, density value), use the command setBoundaryDensity:

setBoundaryDensity(lattice, lattice.getBoundingBox(), 1. );

A non-constant, space dependent velocity resp. pressure profile can be defined by replacing the velocity resp. pressure argument by either a function or by a function object (an instance of a class which overrides the function call operator) which yields a value of the velocity resp. density as a function of space position. An example is provided in the file examples/showCases/poiseuille/poiseuille.cpp.

Interior and exterior boundaries
Sometimes, the boundary is not located on the surface of a regular, block-shaped domain. In this case, the functions like setVelocityConditionOnBlockBoundaries are useless, and the boundary must be manually constructed. As an example, here’s how to construct manually a free-slip condition along the top-wall, including the corner nodes:

// The automatic creation of a top-wall ...
Box2D topWall(0, nx-1, ny-1, ny-1);
boundaryCondition->setVelocityConditionOnBlockBoundaries ( lattice, topWall, boundary::freeslip );

// ... can be replaced by a manual construction as follows:
Box2D topLid(1, nx-2, ny-1, ny-1);
boundaryCondition->addVelocityBoundary1P( topLid, lattice, boundary::freeslip );
boundaryCondition->addExternalVelocityCornerNP( 0, ny-1, lattice, boundary::freeslip );
boundaryCondition->addExternalVelocityCornerPP( nx-1, ny-1, lattice, boundary::freeslip );

A distinction is made between external and internal corners, depending on whether the corner is convex or concave. On the following example geometry, you’ll find five external and one internal corner:
the picture of boundary direction
The extensions like 1P, NP, and PP at the end of the methods of the boundary-condition object are used to indicate the orientation of the wall normal, pointing outside the fluid domain, as shown on the figure above. On a straight wall, the code 1P means: “the wall normal points into positive y-direction”. Likewise, the inlet would be labeled with the code 0N as in “negative x-direction”. On a corner, the code NP means “negative x-direction and positive y-direction”. It is important to mention that this wall normal is purely geometrical and does not depend on whether a given wall has the function of an inlet or an outlet. In both cases, the wall normal points away from the fluid, into the non-fluid area.
In 3D, you’ll use the following function for plane walls:

addVelocityBoundaryDO

where the direction D can be 0 for x, 1 for y, or 2 for z, and the orientation O has the value P for positive and N for negative. Edges can be internal or external. For example:

addInternalVelocityEdge0NP
addExternalVelocityEdge1PN

where the first digit of the code indicates the axis to which the edge is parallel, and the two subsequent digits indicate the orientation of the edge inside the plane normal to the edge, in the same way as the corner nodes in 2D. The axes are counted periodically: 0NP means x-plane, negative y-value, and positive z-value, whereas 1PN means y-plane, positive z-value, and negative x-value. Finally, 3D corners are constructed in the same way as in 2D, through a function call like the following:

addInternalVelocityCornerPNP
addExternalVelocityCornerNNN

Periodic boundaries

The concept of periodicity in Palabos is different from the approach chosen for the other types of boundary conditions. Periodicity can only be implemented on opposite, outer boundaries of an atomic-block or a multi-block. This behavior works also if the multi-block has a sparse memory implementation. In this case, all fluid nodes which are in contact with an outer boundary of the multi-block can be periodic, if the corresponding node on the opposite wall is a fluid node.
In the case of an atomic-block (remember that this case is uninteresting, because you should always work with multiblocks anyway), periodicity is simply a property of the streaming operator. It has no effect for scalar-fields and tensor-fields. In the case of a multi-block however, periodicity can also affect scalar-fields and tensor-fields. Being periodic in this case means that if you define a non-local data processor which accesses nearest neighbor nodes, the value of these neighbor nodes is determined by periodicity along an outer boundary of the multi-block.
By default, all blocks are non-periodic: as mentioned previously, the behavior of outer boundary nodes defaults to one of the versions of bounce-back algorithm. To turn on periodicity along, say, the x-direction, write a command like

lattice.periodicity().toggle(0, true); // Periodic block-lattice.
scalarField.periodicity().toggle(0, true); // Periodic scalar-field.

You can also define all boundaries to be periodic through a single command:

lattice.periodicity().toggleAll(true);

Please note that the periodicity or non-periodicity of a block should be defined as soon as possible after the creation of the block, because Palabos needs to know if boundaries are periodic or not when adding or executing data processors.
As a last remark, you should be aware that boundaries of Neumann type (outflow, free-slip, adiabatic thermal wall, etc.) should never be defined to be periodic. This wouldn’t make sense anyway, and it leads to an undefined behavior (aka program crash).

Bounce-back

In Palabos, all outer boundaries of a lattice which are not periodic automatically implement a version of the bounceback boundary algorithm, according to the following algorithm. In pre-collision state, all unknown populations are assigned the post-collision value on the same node at the end of the previous iteration, from the opposite location. We will call this algorithm Bounce-Back 1.
A bounce-back condition can also be used elsewhere in the domain, by explicitly assigning a dynamics object of type BounceBack<T,Descriptor> to chosen cells. On these newly assigned nodes, the collision step is replaced by a bounce-back procedure, during which each population is replaced by the population corresponding to the opposite direction. Such a node can not be considered any more as a fluid node. Computing velocity-moments of the populations on a bounce-back node yields for example arbitrary results, because some of the populations (those which are not incoming from the fluid) have arbitrary values. Consequently, you cannot compute macroscopic quantities like density and velocity on these nodes. Instead, these nodes should be considered as pure algorithmic entities which have the purpose to re-inject into the fluid all populations that leave the domain. Consider fluid nodes adjacent to this type of bounce-back cells. If you think about it, you’ll realize that these nodes behave exactly as if they were cells of type Bounce-Back 1, with a small difference: unknown populations at time t get the post-collision value from opposite populations from time t-2 (and not time t-1 as in Bounce-Back 1. We will refer to this version of bounce-back as Bounce-Back 2.
The effect of both versions of bounce-back is to produce a no-slip wall located half-way between nodes. In the case of Bounce-Back 1, this is half a cell-width beyond the bounce-back fluid cell, whereas in the case of Bounce-Back 2 it is half-way between the last fluid cell and the non-fluid bounce-back node. Obviously, Bounce-Back 2 wastes numerical precision over Bounce-Back 1 by leaping over a time step, and therefore leads to a lower-order accuracy in time of the numerical method. Furthermore, both versions of bounce-back represent the wall location through a staircase approximation, which is low-order accurate (“Order-h”) in space. On the other hand, Bounce-Back 2 offers a huge advantage: its implementation is independent of the wall orientation. If you decide that a node is a bounce-back node, you simply say so; no need to know if it is a corner, a left wall, or a right wall. Constructing a geometry is then as easy as filling areas with bounce-back nodes. In many cases, this ease of use makes up, to a large extent, for the low-order accuracy of the wall representation. When setting up a new simulation, it is always good to try Bounce-Back 2 as a first attempt, as the quality of the obtained results is often surprising. In highly complex geometry such as porous media, bounce-back is often even considered superior to other approaches.

Bounce-back domain from analytical description
The Rule 0 in Palabos can be stated often enough: don’t write loops over space in end-user code. This also true, of course, when assigning bounce-back dynamics to lattice cells. To guarantee reasonable performance, this is task is performed inside a data processor, or even better, by using the wrapper function defineDynamics. This process is illustrated in the example program examples/codesByTopic/bounceBack/instantiateCylinder.cpp. First, a class is written which defines the location of the bounce-back nodes as a function of the cell coordinates iX and iY. In the present case, the nodes are located inside a 2D circular domain:

template<typename T>
class CylinderShapeDomain2D : public DomainFunctional2D {
public:
	CylinderShapeDomain2D(plint cx_, plint cy_, plint radius) 
						: cx(cx_), cy(cy_), radiusSqr(util::sqr(radius)) { }
	// The function-call operator is overridden to specify the location
	// of bounce-back nodes.
	virtual bool operator() (plint iX, plint iY) const {
		return util::sqr(iX-cx) + util::sqr(iY-cy) <= radiusSqr;
	}
	virtual CylinderShapeDomain2D<T>* clone() const {
		return new CylinderShapeDomain2D<T>(*this);
	}
private:
	plint cx, cy;
	plint radiusSqr;
};

An instance of this class is then provided as an argument to defineDynamics:

defineDynamics( lattice, lattice.getBoundingBox(),
				new CylinderShapeDomain2D<T>(cx, cy, radius),
				new BounceBack<T,DESCRIPTOR> );

The second argument is of type Box2D, and it can be used to improve the efficiency of this function call: the bounce-back nodes are attributed on cells on the intersection between this box and the domain specified by the domain-functional.
If your domain is specified as the union of a collection of simpler domains, you can use defineDynamics iteratively on each of the simpler domains. If the domain is the intersection or any other geometric operation of simpler domains, you’ll need to play with boolean operators inside the definition of your domain-functional.

Bounce-back domain from boolean mask
In this section, it is assumed that the geometry of the domain is prescribed from an external source. This is for example the case when you simulate a porous media and possess data from a scan of the porous material in question. This easiest approach consists in storing this data as an ASCII file which distinguishes fluid nodes from solid nodes by zeros and ones, separated with spaces, tabs, or newline characters. The file represents the content of a regular array and stores only raw data, no information on the matrix dimensions. The data layout must be the same as in Palabos: an increment of the z-coordinate represents a continuous progress in the file (in 3D), followed by the y-coordinate, and then the x-coordinate. This data is simply flushed from the file into a scalar-field. The following code is taken from the example examples/codesByTopic/io/loadGeometry.cpp:

MultiScalarField2D<T> boolMask(nx, ny);
plb_ifstream ifile("geometry.dat");
ifile >> boolMask;

Note that currently, no error-checking is implemented for such I/O operations. It is your responsibility to ensure that the dimensions of the scalar-field correspond to the size of the data in the geometry file. As a next step, the bounce-back nodes are instantiated using one of the versions of the function defineDynamics:

defineDynamics(lattice, boolMask, new BounceBack<T,DESCRIPTOR>, true);

This function is currently only defined for multi-blocks using the same data type (T in this case), which explains why the scalar-field boolMask is of type T and not bool, as one could have expected. The scalar-field is not bool, as one could have expected, but the real type T.

文檔翻譯

綜述

Palabos庫目前爲網格對齊的直線邊界實現了許多不同的邊界條件。對於一般的邊界,可用的選項包括通過反彈節點的階梯式牆壁,以及更高階的精確彎曲邊界。
重要提示:從1.0版開始就可以使用曲線邊界,它是一個功能齊全的並行堆棧的一部分,包括並行體素化和I/O擴展。彎曲的邊界還沒有記錄。然而,你可以在showCases/aneurysm中找到一個全功能的例子。

網格對齊邊界

OnLatticeBoundaryConditionXD類
一些實現的邊界條件是本地的(因此被實現爲動態類),一些是非本地的(因此被實現爲數據處理器),一些同時具有本地和非本地組件。爲了提供各種可能性的統一接口,Palabos提供OnLatticeBoundaryConditionXD接口,該接口負責實例化動態對象、添加數據處理器,或兩者兼得,這取決於所選擇的邊界條件。以下面這行爲例,選擇一個鄒/河邊界條件:

OnLatticeBoundaryCondition3D<T,DESCRIPTOR>* boundaryCondition = createZouHeBoundaryCondition3D<T,DESCRIPTOR>();

目前提供的四種可能性是:

model class
Regularized BC createLocalBoundaryConditionXD
Skordos BC createInterpBoundaryConditionXD
Zou/He BC createZouHeBoundaryConditionXD
Inamuro BC createInamuroBoundaryConditionXD

對於每一個邊界條件,都可以實現速度Dirichlet邊界(所有速度分量都有一個施加的值)和壓力邊界(施加壓力,切向速度分量爲零)。此外,還提出了各種類型的Neumann邊界條件。在這種情況下,通過從鄰近單元格複製該變量的值,以一階精度對給定變量施加零梯度。

常規塊域上的邊界
這些邊界條件的使用有些棘手,因爲Palabos需要知道給定的牆是直牆還是角(2D),還是平牆、邊或角(3D),並且需要知道牆的方向。爲了簡化這一點,所有OnLatticeBoundaryConditionXD對象都有一個方法setVelocityConditionOnBlockBoundaries和一個方法setPressureConditionOnBlockBoundaries,用來設置位於矩形域中的邊界。如果給定二維框邊界上的所有節點都實現Dirichlet條件,則使用以下命令:

Box2D boundaryBox(x0,x1, y0,y1);
boundaryCondition->setVelocityConditionOnBlockBoundaries ( lattice, boundaryBox, locationOfBoundaryNodes );

第一個參數表示邊界所在的方箱形狀,第二個參數指定要在哪個節點上實例化velocity條件。如果所有邊界節點都位於格外邊界框內,則上述命令簡單爲:

boundaryCondition->setVelocityConditionOnBlockBoundaries(lattice, locationOfBoundaryNodes);

最後,如果外邊界框的所有節點都要實現速度條件,則簡化爲:

boundaryCondition->setVelocityConditionOnBlockBoundaries(lattice);

現在,假設在一個二維晶格中,上下壁面(包括角)實現了一個自由滑動條件(切向速度分量爲零梯度),左壁面爲一個Dirichlet條件,右壁面和出流條件(所有速度分量爲零梯度)。這是通過在函數調用中增加一個參數來實現的:

Box2D inlet(0, 0, 2, ny-2);
Box2D outlet(nx-1, nx-1, 2, ny-2);
Box2D bottomWall(0, nx-1, 0, 0);
Box2D topWall(0, nx-1, ny-1, ny-1);
boundaryCondition->setVelocityConditionOnBlockBoundaries ( lattice, inlet );
boundaryCondition->setVelocityConditionOnBlockBoundaries ( lattice, outlet, boundary::outflow );
boundaryCondition->setVelocityConditionOnBlockBoundaries ( lattice, bottomWall, boundary::freeslip );
boundaryCondition->setVelocityConditionOnBlockBoundaries ( lattice, topWall, boundary::freeslip );

請記住,如果邊界不在格的外部邊界上,則必須使用額外的Box3D參數來指定邊界的位置,此外還要說明將添加哪個邊界。這是必要的,因爲Palabos需要知道邊界節點的方向和性質(平牆、角等)。

零梯度條件
The optional arguments like boundary::outflow are of type boundary::BcType, and can have the following values for velocity boundaries:
可選參數如boundary::outflow的類型爲boundary::BcType,速度邊界可以有以下值:

object values explain
boundary::dirichlet (default value) Dirichlet 條件: 實施速度值
boundary::outflow or boundary::neumann 所有速度分量零梯度
boundary::freeslip 切向速度分量爲零梯度,其它分量爲零
boundary::normalOutflow 法向速度分量爲零梯度,其他分量爲零

對於壓力邊界,你有以下選擇:

object values explain
boundary::dirichlet (default value) Dirichlet 條件: 施加壓力值,切向速度分量爲零
boundary::neumann 壓強爲零梯度,切向速度分量爲零

邊界條件不能被覆蓋
不可能重寫邊界的類型。一旦邊界節點是狄利克雷速度節點,它仍然是狄利克雷速度節點。將其重新定義爲,例如,一個壓力邊界節點的結果是未定義的。因此,邊界條件必須謹慎地、分段地定義,如上例所示。另一方面,施加在邊界上的值(即狄利克雷速度邊界上的速度值)可以根據需要隨時改變。

在狄利克雷邊界上設置速度或壓力值
由函數setBoundaryVelocity施加一個恆定的速度值。下面的線將二維域中所有節點上的速度值設爲0,之前定義爲狄利克雷速度節點:

setBoundaryVelocity(lattice, lattice.getBoundingBox(), Array<T,2>(0.,0.) );

在所有其他節點上,此命令無效。要設置一個恆定的壓力值(或等效的密度值),使用命令setBoundaryDensity:

setBoundaryDensity(lattice, lattice.getBoundingBox(), 1. );

A non-constant, space dependent velocity pressure profile can be defined by replacing the velocity resp. pressure argument by either a function or by a function object (an instance of a class which overrides the function call operator) which yields a value of the velocity resp. density as a function of space position. An example is provided in the file examples/showCases/poiseuille/poiseuille.cpp.
一個非恆定的,依賴於空間的速度分佈和壓力分佈可以分別通過替換速度分佈和壓力分佈來確定。一個函數或一個函數對象(一個覆蓋函數調用操作符的類的實例)的壓力參數,該參數分別產生速度和的值作爲空間位置的函數。示例在examples/showCases/poiseuille/poiseuile .cpp文件中提供。

內外邊界
有時,邊界並不位於規則的塊狀區域的表面。在這種情況下,像setVelocityConditionOnBlockBoundaries這樣的函數是沒有用的,必須手工構造邊界。作爲一個例子,這裏展示如何手動構建一個沿頂壁自由滑動的邊界條件,包括角節點:

// The automatic creation of a top-wall ...
Box2D topWall(0, nx-1, ny-1, ny-1);
boundaryCondition->setVelocityConditionOnBlockBoundaries ( lattice, topWall, boundary::freeslip );

// ... can be replaced by a manual construction as follows:
Box2D topLid(1, nx-2, ny-1, ny-1);
boundaryCondition->addVelocityBoundary1P( topLid, lattice, boundary::freeslip );
boundaryCondition->addExternalVelocityCornerNP( 0, ny-1, lattice, boundary::freeslip );
boundaryCondition->addExternalVelocityCornerPP( nx-1, ny-1, lattice, boundary::freeslip );

外部和內部的角是有區別的,這取決於角是凸的還是凹的。在下面的幾何示例中,你會發現五個外部和一個內部角落:
the picture of boundary direction
如上圖所示,邊界條件對象方法末尾的1P、NP和PP等擴展用於指示壁面法線指向流體域外的方向。在一面直牆上,代碼1P表示:“牆法線指向正的y方向”。同樣地,入口將被標記代碼0N爲“負x方向”。在一個角落裏,NP代表“負的x方向和正的y方向”。需要指出的是,這道牆法線完全是幾何圖形,並不取決於給定的牆是具有入口還是出口的功能。在這兩種情況下,壁法線點遠離流體,進入非流體區。
在3D中,你將使用以下功能來製作平面牆:

addVelocityBoundaryDO

方向D可以是x爲0,y爲1,z爲2,方向O爲P爲正,N爲負。邊可以是內部的,也可以是外部的。例如:

addInternalVelocityEdge0NP
addExternalVelocityEdge1PN

其中,代碼的第一個數字表示與邊緣平行的軸,隨後的兩個數字表示與邊緣垂直的平面內邊緣的方向,與2D中的角節點相同。軸是週期性計數的:0NP表示x平面,負的y值,正的z值,而1PN表示y平面,正的z值,負的x值。最後,通過如下所示的函數調用,以與2D相同的方式構造3D角:

addInternalVelocityCornerPNP
addExternalVelocityCornerNNN
週期邊界

Palabos中週期性的概念不同於其他類型邊界條件所選擇的方法。週期性只能在原子塊或多塊的相對、外部邊界上實現。如果多塊具有稀疏內存實現,則此行爲也有效。在這種情況下,如果邊界對面壁上的對應節點是流體節點,則與多塊外邊界接觸的所有流體節點都是週期性的。
在原子塊的情況下(請記住,這種情況是無趣的,因爲無論如何都應該使用多塊),週期性只是流操作符的一個屬性。它對標量場和張量場沒有影響。然而,在多塊的情況下,週期性也會影響標量域和張量域。在這種情況下,週期性意味着如果定義一個訪問最近鄰居節點的非本地數據處理器,這些鄰居節點的值將由沿多塊外部邊界的週期性決定。
默認情況下,所有塊都是非週期性的:如前所述,外部邊界節點的行爲默認爲一個反彈算法的版本。要打開沿x方向的週期性,可以這樣編寫命令

lattice.periodicity().toggle(0, true); // Periodic block-lattice.
scalarField.periodicity().toggle(0, true); // Periodic scalar-field.

你也可以定義所有的邊界是週期性的通過一個單一的命令:

lattice.periodicity().toggleAll(true);

請注意,塊的週期性或非週期性應該在塊創建後儘快定義,因爲Palabos需要知道添加或執行數據處理器時邊界是否是週期性的。
最後,你應該知道Neumann類型的邊界(流出、自由滑動、絕熱熱壁等)不應該被定義爲週期性的。無論如何,這都沒有意義,並且會導致未定義的行爲(即程序崩潰)。

反彈

在Palabos中,晶格中所有非週期的外邊界都會根據下面的算法自動實現一個版本的反彈邊界算法。在碰撞前狀態下,所有未知種羣在前一次迭代結束時,從相反的位置在同一節點上分配碰撞後值。我們稱這個算法爲Bounce-Back 1。
通過顯式地將類型爲BounceBack<T,Descriptor> 分配給所選的單元格,還可以在域中的其他地方使用反彈條件。在這些新分配的節點上,碰撞步驟將被一個反彈過程替換,在此過程中,每個種羣將被對應於相反方向的種羣替換。這樣的節點不能再被認爲是流動節點。例如,在回彈節點上計算種羣的速度矩可以得到任意的結果,因爲有些種羣(那些不是來自流體的種羣)具有任意的值。因此,你不能在這些節點上計算像密度和速度這樣的宏觀量。相反,這些節點應該被視爲純粹的算法實體,其目的是將所有離開域的種羣重新注入流體。考慮與這種彈回單元相鄰的流體節點。如果你仔細想想,你會發現這些節點的行爲與Bounce-Back 1類型的單元完全一樣,只是有一點不同:t時刻的未知種羣從t-2時刻的相反種羣獲得碰撞後的值(而不是像彈回1那樣從t-1時刻獲得碰撞後的值)。我們將把這個版本的反彈稱爲Bounce-Back 2。
這兩個版本的彈回的效果是產生一個位於中間的無滑動的牆節點。在Bounce-Back 1的情況下,這是反彈流體單元的一半寬度,而在Bounce-Back 2的情況下,這是在最後一個流體單元和非流體反彈節點之間的一半寬度。很明顯,由於跨越了時間步長,Bounce-Back 2比Bounce-Back 1浪費了數值精度,因此導致數值方法在時間上的低階精度。此外,這兩個版本的反彈代表了通過樓梯近似值的牆壁位置,這是低階精度(“Order-h”)在空間。另一方面,Bounce-Back 2提供了一個巨大的優勢:它的實現獨立於牆的方向。如果您確定某個節點是回跳節點,可以簡單的這樣說;不需要知道它是一個角落,左邊的牆,還是右邊的牆。然後,構造一個幾何圖形就像用反彈節點填充區域一樣簡單。在許多情況下,這種易用性在很大程度上彌補了牆壁表示的低階精度。在設置新的模擬時,最好先嚐試一下Bounce-Back 2,因爲得到的結果的質量常常令人驚訝。在高度複雜的幾何結構中,如多孔介質,反彈甚至常常被認爲優於其他方法。

解析描述反彈域
Palabos中的規則0需要經常聲明:不要在最終用戶代碼中編寫空間上的循環。當然,當將反彈動力學分配給晶格單元時,也是如此。爲了保證合理的性能,這是在數據處理器中執行的任務,或者更好,使用包裝器函數defineDynamics。這個過程在示例程序examples/codesByTopic/bounceBack/instantiateCylinder.cpp中進行了說明。首先,編寫一個類,該類將反彈節點的位置定義爲單元座標iX和iY的函數。在本例中,節點位於二維圓形域內:

template<typename T>
class CylinderShapeDomain2D : public DomainFunctional2D {
public:
	CylinderShapeDomain2D(plint cx_, plint cy_, plint radius) 
						: cx(cx_), cy(cy_), radiusSqr(util::sqr(radius)) { }
	// 函數調用操作重寫被指定反彈節點的位置。
	virtual bool operator() (plint iX, plint iY) const {
		return util::sqr(iX-cx) + util::sqr(iY-cy) <= radiusSqr;
	}
	virtual CylinderShapeDomain2D<T>* clone() const {
		return new CylinderShapeDomain2D<T>(*this);
	}
private:
	plint cx, cy;
	plint radiusSqr;
};

然後,這個類的一個實例作爲參數提供給defineDynamics:

defineDynamics( lattice, lattice.getBoundingBox(),
				new CylinderShapeDomain2D<T>(cx, cy, radius),
				new BounceBack<T,DESCRIPTOR> );

第二個參數是Box2D類型的,它可以用來提高這個函數調用的效率:在這個框和域函數指定的域之間的交集上的單元格上賦予反彈節點屬性。
如果您的域被指定爲一組更簡單的域的並集,那麼您可以在每個更簡單的域上迭代地使用defineDynamics。如果域是簡單域的交集或其他幾何運算,則需要在域函數的定義中使用布爾運算符。

從布爾掩碼返回域
在本節中,假設定義域的幾何形狀是由外部源規定的。例如,當您模擬多孔介質並擁有所述多孔材料的掃描數據時,就會出現這種情況。這種最簡單的方法是將數據存儲爲ASCII文件,該文件通過0和1區分流動節點和實體節點,用空格、製表符或換行符分隔。該文件表示常規數組的內容,只存儲原始數據,不存儲關於矩陣維的信息。數據佈局必須與Palabos相同:z座標的增量表示文件中的連續進度(在3D中),接着是y座標,然後是x座標。這些數據只是從文件中刷新到一個標量字段中。下面的代碼摘自例子 examples/codesByTopic/io/loadGeometry.cpp:

MultiScalarField2D<T> boolMask(nx, ny);
plb_ifstream ifile("geometry.dat");
ifile >> boolMask;

請注意,目前沒有爲此類I/O操作實現錯誤檢查。您有責任確保標量字段的大小與幾何文件中的數據大小一致。下一步,使用函數defineDynamics的一個版本實例化回跳節點:

defineDynamics(lattice, boolMask, new BounceBack<T,DESCRIPTOR>, true);

這個函數目前僅使用相同的數據類型(在本例中爲T)爲多個塊定義,這解釋了爲什麼scalar-field布爾掩碼的類型是T而不是bool,正如人們所期望的那樣。標量場不是人們所期望的bool,而是真正的T類型。

解釋說明

對於邊界條件,操作方法都在這了,基本沒什麼遺漏,反彈邊界條件是通過動態類設置的,是可以覆蓋設置的,OnLatticeBoundaryConditionXD類,如果沒看錯,是通過使用枚舉值調用特定的函數來實現的,不能重複設置,請注意,一般情況下,因爲我主要是做多孔介質方面,一個一個邊角點設置邊界沒有可能,我選用的就是最基本的反彈邊界條件。
因爲並沒有做到,我並沒有繼續翻譯後面一小段的曲面邊界的內容。如果有需要,請自行觀看實驗。
還應注意,不同的模型對於邊界的處理也是不同的,相應的調用的枚舉值也是不一樣的,需要各位在仔細閱讀相關的模型案例後,自行判斷使用方法。

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