OpenLB 1.7
Loading...
Searching...
No Matches
boundaryPostProcessors2D.hh
Go to the documentation of this file.
1/* This file is part of the OpenLB library
2 *
3 * Copyright (C) 2006, 2007 Jonas Latt
4 * E-mail contact: info@openlb.net
5 * The most recent release of OpenLB can be downloaded at
6 * <http://www.openlb.net/>
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * as published by the Free Software Foundation; either version 2
11 * of the License, or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public
19 * License along with this program; if not, write to the Free
20 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 * Boston, MA 02110-1301, USA.
22*/
23
24#ifndef FD_BOUNDARIES_2D_HH
25#define FD_BOUNDARIES_2D_HH
26
28
30#include "core/util.h"
31
32#include "dynamics/dynamics.h"
33#include "dynamics/lbm.h"
34
35namespace olb {
36
38
39template<typename T, typename DESCRIPTOR, int direction,int orientation>
40template<CONCEPT(Cell) CELL>
42{
43 using namespace olb::util::tensorIndices2D;
44
45 T dx_u[DESCRIPTOR::d], dy_u[DESCRIPTOR::d];
46 T rho, u[DESCRIPTOR::d];
47
48 auto& dynamics = cell.getDynamics();
49
50 cell.computeRhoU(rho,u);
51
52 interpolateGradients<0>(cell, dx_u);
53 interpolateGradients<1>(cell, dy_u);
54
55 T dx_ux = dx_u[0];
56 T dy_ux = dy_u[0];
57 T dx_uy = dx_u[1];
58 T dy_uy = dy_u[1];
59 T omega = dynamics.getOmegaOrFallback(std::numeric_limits<T>::signaling_NaN());
60 T sToPi = - rho / descriptors::invCs2<T,DESCRIPTOR>() / omega;
62 pi[xx] = (T)2 * dx_ux * sToPi;
63 pi[yy] = (T)2 * dy_uy * sToPi;
64 pi[xy] = (dx_uy + dy_ux) * sToPi;
65
66 // Computation of the particle distribution functions
67 // according to the regularized formula
68 for (int iPop = 0; iPop < DESCRIPTOR::q; ++iPop) {
69 cell[iPop] = dynamics.computeEquilibrium(iPop,rho,u)
70 + equilibrium<DESCRIPTOR>::template fromPiToFneq<T>(iPop, pi);
71 }
72}
73
74template<typename T, typename DESCRIPTOR, int direction,int orientation>
75template<int deriveDirection, typename CELL>
77interpolateGradients(CELL& cell, T velDeriv[DESCRIPTOR::d]) const
78{
80 ::interpolateVector(velDeriv, cell);
81}
82
84
85template<typename T, typename DESCRIPTOR, int direction, int orientation>
87StraightConvectionBoundaryProcessor2D(int x0_, int x1_, int y0_, int y1_, T* uAv_)
88 : x0(x0_), x1(x1_), y0(y0_), y1(y1_), uAv(uAv_)
89{
90 OLB_PRECONDITION(x0==x1 || y0==y1);
91
92 this->getName() = "StraightConvectionBoundaryProcessor2D";
93 saveCell = new T** [(size_t)(x1_-x0_+1)];
94 for (int iX=0; iX<=x1_-x0_; ++iX) {
95 saveCell[iX] = new T* [(size_t)(y1_-y0_+1)];
96 for (int iY=0; iY<=y1_-y0_; ++iY) {
97 saveCell[iX][iY] = new T [(size_t)(DESCRIPTOR::q)];
98 for (int iPop=0; iPop<DESCRIPTOR::q; ++iPop) {
99 // default set to -1 in order to avoid wrong results at first call
100 saveCell[iX][iY][iPop] = T(-1);
101 }
102 }
103 }
104}
105
106template<typename T, typename DESCRIPTOR, int direction, int orientation>
109{
110 for (int iX=0; iX<=x1-x0; ++iX) {
111 for (int iY=0; iY<=y1-y0; ++iY) {
112 delete [] saveCell[iX][iY];
113 }
114 delete [] saveCell[iX];
115 }
116 delete [] saveCell;
117}
118
119template<typename T, typename DESCRIPTOR, int direction,int orientation>
121processSubDomain(BlockLattice<T,DESCRIPTOR>& blockLattice, int x0_, int x1_, int y0_, int y1_)
122{
123 using namespace olb::util::tensorIndices2D;
124
125 int newX0, newX1, newY0, newY1;
126 if ( util::intersect (
127 x0, x1, y0, y1,
128 x0_, x1_, y0_, y1_,
129 newX0, newX1, newY0, newY1 ) ) {
130
131 int iX;
132#ifdef PARALLEL_MODE_OMP
133 #pragma omp parallel for
134#endif
135 for (iX=newX0; iX<=newX1; ++iX) {
136 for (int iY=newY0; iY<=newY1; ++iY) {
137 Cell<T,DESCRIPTOR> cell = blockLattice.get(iX,iY);
138 for (int iPop = 0; iPop < DESCRIPTOR::q ; ++iPop) {
139 if (descriptors::c<DESCRIPTOR>(iPop,direction)==-orientation) {
140 // using default -1 avoids wrong first call
141 if (!util::nearZero(1 + saveCell[iX-newX0][iY-newY0][iPop]) ) {
142 cell[iPop] = saveCell[iX-newX0][iY-newY0][iPop];
143 }
144 }
145 }
146
147 T rho0, u0[2];
148 T rho1, u1[2];
149 T rho2, u2[2];
150 if (direction==0) {
151 blockLattice.get(iX,iY).computeRhoU(rho0,u0);
152 blockLattice.get(iX-orientation,iY).computeRhoU(rho1,u1);
153 blockLattice.get(iX-orientation*2,iY).computeRhoU(rho2,u2);
154 }
155 else {
156 blockLattice.get(iX,iY).computeRhoU(rho0,u0);
157 blockLattice.get(iX,iY-orientation).computeRhoU(rho1,u1);
158 blockLattice.get(iX,iY-orientation*2).computeRhoU(rho2,u2);
159 }
160
161 // rho0 = T(1); rho1 = T(1); rho2 = T(1);
162
163 T uDelta[2];
164 T uAverage = rho0*u0[direction];
165 if (uAv!=nullptr) {
166 uAverage = *uAv;
167 }
168 uDelta[0]=-uAverage*0.5*(3*rho0*u0[0]-4*rho1*u1[0]+rho2*u2[0]);
169 uDelta[1]=-uAverage*0.5*(3*rho0*u0[1]-4*rho1*u1[1]+rho2*u2[1]);
170
171 for (int iPop = 0; iPop < DESCRIPTOR::q ; ++iPop) {
172 if (descriptors::c<DESCRIPTOR>(iPop,direction) == -orientation) {
173 saveCell[iX-newX0][iY-newY0][iPop] = cell[iPop] + descriptors::invCs2<T,DESCRIPTOR>()*descriptors::t<T,DESCRIPTOR>(iPop)*(uDelta[0]*descriptors::c<DESCRIPTOR>(iPop,0)+uDelta[1]*descriptors::c<DESCRIPTOR>(iPop,1));
174 }
175 }
176 }
177 }
178 }
179}
180
181template<typename T, typename DESCRIPTOR, int direction,int orientation>
184{
185 processSubDomain(blockLattice, x0, x1, y0, y1);
186}
187
188
190
191template<typename T, typename DESCRIPTOR, int direction,int orientation>
193StraightConvectionBoundaryProcessorGenerator2D(int x0_, int x1_, int y0_, int y1_, T* uAv_)
194 : PostProcessorGenerator2D<T,DESCRIPTOR>(x0_, x1_, y0_, y1_), uAv(uAv_)
195{ }
196
197template<typename T, typename DESCRIPTOR, int direction,int orientation>
204
205template<typename T, typename DESCRIPTOR, int direction,int orientation>
212
213
215
216template<typename T, typename DESCRIPTOR>
218SlipBoundaryProcessor2D(int x0_, int x1_, int y0_, int y1_, int discreteNormalX, int discreteNormalY)
219 : x0(x0_), x1(x1_), y0(y0_), y1(y1_)
220{
221 this->getName() = "SlipBoundaryProcessor2D";
222 OLB_PRECONDITION(x0==x1 || y0==y1);
223 reflectionPop[0] = 0;
224 for (int iPop = 1; iPop < DESCRIPTOR::q; iPop++) {
225 reflectionPop[iPop] = 0;
226 // iPop are the directions which ointing into the fluid, discreteNormal is pointing outwarts
227 if (descriptors::c<DESCRIPTOR>(iPop,0)*discreteNormalX + descriptors::c<DESCRIPTOR>(iPop,1)*discreteNormalY < 0) {
228 // std::cout << "-----" <<s td::endl;
229 int mirrorDirection0;
230 int mirrorDirection1;
231 int mult = 1;
232 if (discreteNormalX*discreteNormalY==0) {
233 mult = 2;
234 }
235 mirrorDirection0 = (descriptors::c<DESCRIPTOR>(iPop,0) - mult*(descriptors::c<DESCRIPTOR>(iPop,0)*discreteNormalX + descriptors::c<DESCRIPTOR>(iPop,1)*discreteNormalY )*discreteNormalX);
236 mirrorDirection1 = (descriptors::c<DESCRIPTOR>(iPop,1) - mult*(descriptors::c<DESCRIPTOR>(iPop,0)*discreteNormalX + descriptors::c<DESCRIPTOR>(iPop,1)*discreteNormalY )*discreteNormalY);
237
238 // computes mirror jPop
239 for (reflectionPop[iPop] = 1; reflectionPop[iPop] < DESCRIPTOR::q ; reflectionPop[iPop]++) {
240 if (descriptors::c<DESCRIPTOR>(reflectionPop[iPop],0)==mirrorDirection0 && descriptors::c<DESCRIPTOR>(reflectionPop[iPop],1)==mirrorDirection1 ) {
241 break;
242 }
243 }
244 //std::cout <<iPop << " to "<< jPop <<" for discreteNormal= "<< discreteNormalX << "/"<<discreteNormalY <<std::endl;
245 }
246 }
247}
248
249template<typename T, typename DESCRIPTOR>
251processSubDomain(BlockLattice<T,DESCRIPTOR>& blockLattice, int x0_, int x1_, int y0_, int y1_)
252{
253 int newX0, newX1, newY0, newY1;
254 if ( util::intersect (
255 x0, x1, y0, y1,
256 x0_, x1_, y0_, y1_,
257 newX0, newX1, newY0, newY1 ) ) {
258
259 int iX;
260#ifdef PARALLEL_MODE_OMP
261 #pragma omp parallel for
262#endif
263 for (iX=newX0; iX<=newX1; ++iX) {
264 for (int iY=newY0; iY<=newY1; ++iY) {
265 for (int iPop = 1; iPop < DESCRIPTOR::q ; ++iPop) {
266 if (reflectionPop[iPop]!=0) {
267 //do reflection
268 blockLattice.get(iX,iY)[iPop] = blockLattice.get(iX,iY)[reflectionPop[iPop]];
269 }
270 }
271 }
272 }
273 }
274}
275
276template<typename T, typename DESCRIPTOR>
279{
280 processSubDomain(blockLattice, x0, x1, y0, y1);
281}
282
284
285template<typename T, typename DESCRIPTOR>
287SlipBoundaryProcessorGenerator2D(int x0_, int x1_, int y0_, int y1_, int discreteNormalX_, int discreteNormalY_)
288 : PostProcessorGenerator2D<T,DESCRIPTOR>(x0_, x1_, y0_, y1_), discreteNormalX(discreteNormalX_), discreteNormalY(discreteNormalY_)
289{ }
290
291template<typename T, typename DESCRIPTOR>
294{
296 ( this->x0, this->x1, this->y0, this->y1, discreteNormalX, discreteNormalY);
297}
298
299template<typename T, typename DESCRIPTOR>
302{
304 (this->x0, this->x1, this->y0, this->y1, discreteNormalX, discreteNormalY);
305}
306
308
309template<typename T, typename DESCRIPTOR>
311PartialSlipBoundaryProcessor2D(T tuner_, int x0_, int x1_, int y0_, int y1_, int discreteNormalX, int discreteNormalY)
312 : x0(x0_), x1(x1_), y0(y0_), y1(y1_), tuner(tuner_)
313{
314 this->getName() = "PartialSlipBoundaryProcessor2D";
315 OLB_PRECONDITION(x0==x1 || y0==y1);
316 reflectionPop[0] = 0;
317 for (int iPop = 1; iPop < DESCRIPTOR::q; iPop++) {
318 reflectionPop[iPop] = 0;
319 // iPop are the directions which ointing into the fluid, discreteNormal is pointing outwarts
320 if (descriptors::c<DESCRIPTOR>(iPop,0)*discreteNormalX + descriptors::c<DESCRIPTOR>(iPop,1)*discreteNormalY < 0) {
321 //std::cout << "-----" <<std::endl;
322 int mirrorDirection0;
323 int mirrorDirection1;
324 int mult = 1;
325 if (discreteNormalX*discreteNormalY==0) {
326 mult = 2;
327 }
328 mirrorDirection0 = (descriptors::c<DESCRIPTOR>(iPop,0) - mult*(descriptors::c<DESCRIPTOR>(iPop,0)*discreteNormalX + descriptors::c<DESCRIPTOR>(iPop,1)*discreteNormalY )*discreteNormalX);
329 mirrorDirection1 = (descriptors::c<DESCRIPTOR>(iPop,1) - mult*(descriptors::c<DESCRIPTOR>(iPop,0)*discreteNormalX + descriptors::c<DESCRIPTOR>(iPop,1)*discreteNormalY )*discreteNormalY);
330
331 // computes mirror jPop
332 for (reflectionPop[iPop] = 1; reflectionPop[iPop] < DESCRIPTOR::q ; reflectionPop[iPop]++) {
333 if (descriptors::c<DESCRIPTOR>(reflectionPop[iPop],0)==mirrorDirection0 && descriptors::c<DESCRIPTOR>(reflectionPop[iPop],1)==mirrorDirection1 ) {
334 break;
335 }
336 }
337 //std::cout <<iPop << " to "<< jPop <<" for discreteNormal= "<< discreteNormalX << "/"<<discreteNormalY <<std::endl;
338 }
339 }
340}
341
342template<typename T, typename DESCRIPTOR>
344processSubDomain(BlockLattice<T,DESCRIPTOR>& blockLattice, int x0_, int x1_, int y0_, int y1_)
345{
346 int newX0, newX1, newY0, newY1;
347 if ( util::intersect (
348 x0, x1, y0, y1,
349 x0_, x1_, y0_, y1_,
350 newX0, newX1, newY0, newY1 ) ) {
351
352 int iX;
353#ifdef PARALLEL_MODE_OMP
354 #pragma omp parallel for
355#endif
356 for (iX=newX0; iX<=newX1; ++iX) {
357 for (int iY=newY0; iY<=newY1; ++iY) {
358 for (int iPop = 1; iPop < DESCRIPTOR::q ; ++iPop) {
359 if (reflectionPop[iPop]!=0) {
360 //do reflection
361 blockLattice.get(iX,iY)[iPop] = tuner*blockLattice.get(iX,iY)[reflectionPop[iPop]];
362 }
363 }
364 for (int iPop = 1; iPop < DESCRIPTOR::q/2 ; ++iPop) {
365 T provv = blockLattice.get(iX,iY)[descriptors::opposite<DESCRIPTOR>(iPop)];
366 blockLattice.get(iX,iY)[descriptors::opposite<DESCRIPTOR>(iPop)] +=
367 (1.-tuner)*blockLattice.get(iX,iY)[iPop];
368 blockLattice.get(iX,iY)[iPop] += (1.-tuner)*provv;
369 }
370 }
371 }
372 }
373}
374
375template<typename T, typename DESCRIPTOR>
378{
379 processSubDomain(blockLattice, x0, x1, y0, y1);
380}
381
383
384template<typename T, typename DESCRIPTOR>
386PartialSlipBoundaryProcessorGenerator2D(T tuner_, int x0_, int x1_, int y0_, int y1_, int discreteNormalX_, int discreteNormalY_)
387 : PostProcessorGenerator2D<T,DESCRIPTOR>(x0_, x1_, y0_, y1_), discreteNormalX(discreteNormalX_), discreteNormalY(discreteNormalY_), tuner(tuner_)
388{ }
389
390template<typename T, typename DESCRIPTOR>
393{
395 (tuner, this->x0, this->x1, this->y0, this->y1, discreteNormalX, discreteNormalY);
396}
397
398template<typename T, typename DESCRIPTOR>
401{
403 (tuner, this->x0, this->x1, this->y0, this->y1, discreteNormalX, discreteNormalY);
404}
405
407
408template<typename T, typename DESCRIPTOR, int xNormal,int yNormal>
409template <CONCEPT(Cell) CELL>
411{
412 using namespace olb::util::tensorIndices2D;
413
414 T rho10 = cell.neighbor({-1*xNormal, -0*yNormal}).computeRho();
415 T rho01 = cell.neighbor({-0*xNormal, -1*yNormal}).computeRho();
416
417 T rho20 = cell.neighbor({-2*xNormal, -0*yNormal}).computeRho();
418 T rho02 = cell.neighbor({-0*xNormal, -2*yNormal}).computeRho();
419
420 T rho = (T)2/(T)3*(rho01+rho10) - (T)1/(T)6*(rho02+rho20);
421
422 T dx_u[DESCRIPTOR::d], dy_u[DESCRIPTOR::d];
425 T dx_ux = dx_u[0];
426 T dy_ux = dy_u[0];
427 T dx_uy = dx_u[1];
428 T dy_uy = dy_u[1];
429
430 auto& dynamics = cell.getDynamics();
431 T omega = dynamics.getOmegaOrFallback(std::numeric_limits<T>::signaling_NaN());
432
433 T sToPi = - rho / descriptors::invCs2<T,DESCRIPTOR>() / omega;
435 pi[xx] = (T)2 * dx_ux * sToPi;
436 pi[yy] = (T)2 * dy_uy * sToPi;
437 pi[xy] = (dx_uy + dy_ux) * sToPi;
438
439 // Computation of the particle distribution functions
440 // according to the regularized formula
441 T u[DESCRIPTOR::d];
442 cell.computeU(u);
443
444 for (int iPop = 0; iPop < DESCRIPTOR::q; ++iPop) {
445 cell[iPop] = dynamics.computeEquilibrium(iPop,rho,u)
446 + equilibrium<DESCRIPTOR>::template fromPiToFneq<T>(iPop, pi);
447 }
448}
449
450
452template<typename T, typename DESCRIPTOR, int NORMAL_X, int NORMAL_Y>
453template <CONCEPT(Cell) CELL, typename PARAMETERS>
455
456 T addend = cell.template getField<descriptors::ADDEND>();
457
458 T rhoAvg = cell.neighbor({-NORMAL_X,-NORMAL_Y}).computeRho();
459 T rhoTmp = 0.;
460
461 for (int iPop = 1; iPop < DESCRIPTOR::q ; ++iPop) {
462 rhoTmp += cell[iPop];
463 }
464
465 T rho = rhoAvg + addend;
466 rho -= rhoTmp;
467
468 cell[0] = rho - 1.;
469}
470
471
473template<typename T, typename DESCRIPTOR, int NORMAL_X, int NORMAL_Y>
474template <CONCEPT(Cell) CELL>
476
477 cell.template setField<descriptors::CHEM_POTENTIAL>(
478 cell.neighbor({-NORMAL_X,-NORMAL_Y}).template getField<descriptors::CHEM_POTENTIAL>());
479
480 T rho0 = cell.computeRho();
481 T rho1 = cell.neighbor({-NORMAL_X,-NORMAL_Y}).computeRho();
482
483 cell.template setField<descriptors::CHEM_POTENTIAL>(
484 cell.template getField<descriptors::CHEM_POTENTIAL>() + (rho1 / rho0 - 1) / descriptors::invCs2<T,DESCRIPTOR>());
485}
486
487template<typename T, typename DESCRIPTOR, int NORMAL_X, int NORMAL_Y>
488template <CONCEPT(Cell) CELL>
490
491 cell.template setField<descriptors::CHEM_POTENTIAL>(
492 cell.neighbor({-NORMAL_X,-NORMAL_Y}).template getField<descriptors::CHEM_POTENTIAL>());
493}
494
495
497template<typename T, typename DESCRIPTOR, int NORMAL_X, int NORMAL_Y>
498template <CONCEPT(Cell) CELL>
500
501 T rho, rho0, rho1, u[2];
502
503 rho0 = cell.computeRho();
504
505 cell.neighbor({-NORMAL_X,-NORMAL_Y}).computeRhoU(rho1, u);
506
507 T uPerp = 0;
508
509 Vector<T,2> normalVec({NORMAL_X,NORMAL_Y});
510
511 if (normalVec[0] == 0) {
512 uPerp = normalVec[1] * u[1];
513 } else if (normalVec[1] == 0) {
514 uPerp = normalVec[0] * u[0];
515 }
516
517 rho = (rho0 + uPerp * rho1) / (1. + uPerp);
518 cell.defineRho(rho);
519}
520
521} // namespace olb
522
523#endif
Platform-abstracted block lattice for external access and inter-block interaction.
Cell< T, DESCRIPTOR > get(CellID iCell)
Get Cell interface for index iCell.
Highest-level interface to Cell data.
Definition cell.h:148
void apply(CELL &cell, PARAMETERS &parameters) any_platform
This class computes a partial slip BC in 2D.
void processSubDomain(BlockLattice< T, DESCRIPTOR > &blockLattice, int x0_, int x1_, int y0_, int y1_) override
Execute post-processing step on a sublattice.
PartialSlipBoundaryProcessor2D(T tuner_, int x0_, int x1_, int y0_, int y1_, int discreteNormalX_, int discreteNormalY_)
void process(BlockLattice< T, DESCRIPTOR > &blockLattice) override
Execute post-processing step.
PartialSlipBoundaryProcessorGenerator2D(T tuner_, int x0_, int x1_, int y0_, int y1_, int discreteNormalX_, int discreteNormalY_)
PostProcessorGenerator2D< T, DESCRIPTOR > * clone() const override
PostProcessor2D< T, DESCRIPTOR > * generate() const override
Interface of 2D post-processing steps.
std::string & getName()
read and write access to name
This class computes a slip BC in 2D.
void processSubDomain(BlockLattice< T, DESCRIPTOR > &blockLattice, int x0_, int x1_, int y0_, int y1_) override
Execute post-processing step on a sublattice.
SlipBoundaryProcessor2D(int x0_, int x1_, int y0_, int y1_, int discreteNormalX_, int discreteNormalY_)
void process(BlockLattice< T, DESCRIPTOR > &blockLattice) override
Execute post-processing step.
PostProcessorGenerator2D< T, DESCRIPTOR > * clone() const override
PostProcessor2D< T, DESCRIPTOR > * generate() const override
SlipBoundaryProcessorGenerator2D(int x0_, int x1_, int y0_, int y1_, int discreteNormalX_, int discreteNormalY_)
This class computes a convection BC on a flat wall in 2D.
StraightConvectionBoundaryProcessor2D(int x0_, int x1_, int y0_, int y1_, T *uAv_=NULL)
void process(BlockLattice< T, DESCRIPTOR > &blockLattice) override
Execute post-processing step.
void processSubDomain(BlockLattice< T, DESCRIPTOR > &blockLattice, int x0_, int x1_, int y0_, int y1_) override
Execute post-processing step on a sublattice.
PostProcessor2D< T, DESCRIPTOR > * generate() const override
PostProcessorGenerator2D< T, DESCRIPTOR > * clone() const override
StraightConvectionBoundaryProcessorGenerator2D(int x0_, int x1_, int y0_, int y1_, T *uAv_=NULL)
This class computes the skordos BC on a flat wall in 2D but with a limited number of terms added to t...
Plain old scalar vector.
Definition vector.h:47
bool intersect(int x0, int x1, int y0, int y1, int x0_, int x1_, int y0_, int y1_, int &newX0, int &newX1, int &newY0, int &newY1)
Definition util.h:89
bool nearZero(const ADf< T, DIM > &a)
Definition aDiff.h:1087
Top level namespace for all of OpenLB.
#define OLB_PRECONDITION(COND)
Definition olbDebug.h:46
#define any_platform
Define preprocessor macros for device-side functions, constant storage.
Definition platform.h:78
static void interpolateVector(T velDeriv[DESCRIPTOR::d], BlockLattice< T, DESCRIPTOR > const &blockLattice, int iX, int iY)
Compute number of elements of a symmetric d-dimensional tensor.
Definition util.h:210
Set of functions commonly used in LB computations – header file.