$treeview $search $mathjax
Palabos  Version 1.1
$projectbrief
$projectbrief
$searchbox

guoOffLatticeModel3D.hh

Go to the documentation of this file.
00001 /* This file is part of the Palabos library.
00002  *
00003  * Copyright (C) 2011 FlowKit Sarl
00004  * Avenue de Chailly 23
00005  * 1012 Lausanne, Switzerland
00006  * E-mail contact: contact@flowkit.com
00007  *
00008  * The most recent release of Palabos can be downloaded at 
00009  * <http://www.palabos.org/>
00010  *
00011  * The library Palabos is free software: you can redistribute it and/or
00012  * modify it under the terms of the GNU Affero General Public License as
00013  * published by the Free Software Foundation, either version 3 of the
00014  * License, or (at your option) any later version.
00015  *
00016  * The library is distributed in the hope that it will be useful,
00017  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00018  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00019  * GNU Affero General Public License for more details.
00020  *
00021  * You should have received a copy of the GNU Affero General Public License
00022  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
00023 */
00024 
00025 #ifndef GUO_OFF_LATTICE_MODEL_3D_HH
00026 #define GUO_OFF_LATTICE_MODEL_3D_HH
00027 
00028 #include "offLattice/guoOffLatticeModel3D.h"
00029 #include "offLattice/nextNeighbors3D.h"
00030 #include "latticeBoltzmann/geometricOperationTemplates.h"
00031 #include "latticeBoltzmann/externalFieldAccess.h"
00032 #include <algorithm>
00033 #include <cmath>
00034 
00035 namespace plb {
00036 
00037 template<typename T, template<typename U> class Descriptor>
00038 GuoOffLatticeModel3D<T,Descriptor>::LiquidNeighbor::LiquidNeighbor
00039             (plint iNeighbor_, plint depth_, plint iTriangle_, Array<T,3> wallNormal)
00040         : iNeighbor(iNeighbor_),
00041           depth(depth_),
00042           iTriangle(iTriangle_)
00043 {
00044     int const* c = NextNeighbor<T>::c[iNeighbor];
00045     Array<T,3> neighborVect(c[0],c[1],c[2]);
00046     cosAngle = fabs(dot(neighborVect,wallNormal))*NextNeighbor<T>::invD[iNeighbor];
00047 }
00048 
00049 template<typename T, template<typename U> class Descriptor>
00050 bool GuoOffLatticeModel3D<T,Descriptor>::LiquidNeighbor::
00051          operator<(LiquidNeighbor const& rhs) const
00052 {
00053     return cosAngle < rhs.cosAngle;
00054 }
00055 
00056 template<typename T, template<typename U> class Descriptor>
00057 GuoOffLatticeModel3D<T,Descriptor>::GuoOffLatticeModel3D (
00058         BoundaryShape3D<T,Array<T,3> >* shape_, int flowType_, bool useAllDirections_ )
00059     : OffLatticeModel3D<T,Array<T,3> >(shape_, flowType_),
00060       useAllDirections(useAllDirections_),
00061       regularizedModel(true),
00062       secondOrderFlag(true),
00063       computeStat(true)
00064 { }
00065 
00066 template<typename T, template<typename U> class Descriptor>
00067 GuoOffLatticeModel3D<T,Descriptor>* GuoOffLatticeModel3D<T,Descriptor>::clone() const {
00068     return new GuoOffLatticeModel3D(*this);
00069 }
00070 
00071 template<typename T, template<typename U> class Descriptor>
00072 plint GuoOffLatticeModel3D<T,Descriptor>::getNumNeighbors() const {
00073     return 2;
00074 }
00075 
00076 template<typename T, template<typename U> class Descriptor>
00077 void GuoOffLatticeModel3D<T,Descriptor>::prepareCell (
00078         Dot3D const& cellLocation,
00079         AtomicContainerBlock3D& container )
00080 {
00081     Dot3D offset = container.getLocation();
00082     GuoOffLatticeInfo3D* info =
00083         dynamic_cast<GuoOffLatticeInfo3D*>(container.getData());
00084     PLB_ASSERT( info );
00085     std::vector<LiquidNeighbor> liquidNeighbors;
00086     if (!this->isFluid(cellLocation+offset)) {
00087         for (int iNeighbor=0; iNeighbor<NextNeighbor<T>::numNeighbors; ++iNeighbor) {
00088             int const* c = NextNeighbor<T>::c[iNeighbor];
00089             Dot3D neighbor(cellLocation.x+c[0], cellLocation.y+c[1], cellLocation.z+c[2]);
00090             // If the non-fluid node has a fluid neighbor ...
00091             if (this->isFluid(neighbor+offset)) {
00092                 // ... check how many fluid nodes it has ahead of it ...
00093                 int depth = 1;
00094                 for (int iDepth=2; iDepth<=getNumNeighbors(); ++iDepth) {
00095                     Dot3D nextNeighbor(cellLocation.x+iDepth*c[0],
00096                                        cellLocation.y+iDepth*c[1],
00097                                        cellLocation.z+iDepth*c[2]);
00098                     if (this->isFluid(nextNeighbor+offset)) {
00099                         depth = iDepth;
00100                     }
00101                     else {
00102                         break;
00103                     }
00104                 }
00105                 plint iTriangle=-1;
00106                 global::timer("intersect").start();
00107                 Array<T,3> locatedPoint;
00108                 T distance;
00109                 Array<T,3> wallNormal;
00110                 Array<T,3> surfaceData;
00111                 OffBoundary::Type bdType;
00112 #ifdef PLB_DEBUG
00113                 bool ok =
00114 #endif
00115                     this->pointOnSurface (
00116                             cellLocation+offset, Dot3D(c[0],c[1],c[2]), locatedPoint, distance,
00117                             wallNormal, surfaceData, bdType, iTriangle );
00118                 // In the following, the importance of directions is sorted wrt. how well they
00119                 //   are aligned with the wall normal. It is better to take the continuous normal,
00120                 //   because it is not sensitive to the choice of the triangle when we shoot at
00121                 //   an edge.
00122                 //wallNormal = this->computeContinuousNormal(locatedPoint, iTriangle);
00123                 global::timer("intersect").stop();
00124                 PLB_ASSERT( ok );
00125                 // ... then add this node to the list.
00126                 liquidNeighbors.push_back(LiquidNeighbor(iNeighbor, depth, iTriangle, wallNormal));
00127             }
00128         }
00129         if (!liquidNeighbors.empty()) {
00130             info->getDryNodes().push_back(cellLocation);
00131             std::sort(liquidNeighbors.begin(), liquidNeighbors.end());
00132             std::vector<std::pair<int,int> > neighborDepthPairs;
00133             std::vector<plint> ids;
00134             if (useAllDirections) {
00135                 for (pluint i=0; i<liquidNeighbors.size(); ++i) {
00136                     neighborDepthPairs.push_back(std::make_pair(liquidNeighbors[i].iNeighbor, liquidNeighbors[i].depth));
00137                     ids.push_back(liquidNeighbors[i].iTriangle);
00138                 }
00139             }
00140             else {
00141                 plint i = liquidNeighbors.size()-1;
00142                 neighborDepthPairs.push_back(std::make_pair(liquidNeighbors[i].iNeighbor, liquidNeighbors[i].depth));
00143                 ids.push_back(liquidNeighbors[i].iTriangle);
00144             }
00145             info->getDryNodeFluidDirections().push_back(neighborDepthPairs);
00146             info->getDryNodeIds().push_back(ids);
00147         }
00148     }
00149 }
00150 
00151 template<typename T, template<typename U> class Descriptor>
00152 ContainerBlockData*
00153     GuoOffLatticeModel3D<T,Descriptor>::generateOffLatticeInfo() const
00154 {
00155     return new GuoOffLatticeInfo3D;
00156 }
00157 
00158 template<typename T, template<typename U> class Descriptor>
00159 Array<T,3> GuoOffLatticeModel3D<T,Descriptor>::getLocalForce (
00160                 AtomicContainerBlock3D& container ) const
00161 {
00162     GuoOffLatticeInfo3D* info =
00163         dynamic_cast<GuoOffLatticeInfo3D*>(container.getData());
00164     PLB_ASSERT( info );
00165     return info->getLocalForce();
00166 }
00167 
00168 template<typename T, template<typename U> class Descriptor>
00169 void GuoOffLatticeModel3D<T,Descriptor>::boundaryCompletion (
00170         AtomicBlock3D& nonTypeLattice,
00171         AtomicContainerBlock3D& container,
00172         std::vector<AtomicBlock3D const*> const& args )
00173 {
00174     BlockLattice3D<T,Descriptor>& lattice =
00175         dynamic_cast<BlockLattice3D<T,Descriptor>&> (nonTypeLattice);
00176     GuoOffLatticeInfo3D* info =
00177         dynamic_cast<GuoOffLatticeInfo3D*>(container.getData());
00178     PLB_ASSERT( info );
00179     std::vector<Dot3D> const&
00180         dryNodes = info->getDryNodes();
00181     std::vector<std::vector<std::pair<int,int> > > const&
00182         dryNodeFluidDirections = info->getDryNodeFluidDirections();
00183     std::vector<std::vector<plint> > const&
00184         dryNodeIds = info->getDryNodeIds();
00185     PLB_ASSERT( dryNodes.size() == dryNodeFluidDirections.size() );
00186 
00187     Dot3D absoluteOffset = lattice.getLocation();
00188 
00189     Array<T,3>& localForce = info->getLocalForce();
00190     localForce.resetToZero();
00191     for (pluint iDry=0; iDry<dryNodes.size(); ++iDry) {
00192         cellCompletion (
00193             lattice, dryNodes[iDry], dryNodeFluidDirections[iDry],
00194             dryNodeIds[iDry], absoluteOffset, localForce, args );
00195     }
00196 }
00197 
00198 template<typename T, template<typename U> class Descriptor>
00199 void GuoOffLatticeModel3D<T,Descriptor>::cellCompletion (
00200         BlockLattice3D<T,Descriptor>& lattice,
00201         Dot3D const& guoNode,
00202         std::vector<std::pair<int,int> > const& dryNodeFluidDirections,
00203         std::vector<plint> const& dryNodeIds, Dot3D const& absoluteOffset,
00204         Array<T,3>& localForce, std::vector<AtomicBlock3D const*> const& args )
00205 {
00206     typedef Descriptor<T> D;
00207     Cell<T,Descriptor>& cell =
00208         lattice.get(guoNode.x, guoNode.y, guoNode.z);
00209     plint numDirections = (plint)dryNodeFluidDirections.size();
00210     std::vector<T> weights(numDirections);
00211     std::vector<T> rhoBar_vect(numDirections);
00212     std::vector<Array<T,Descriptor<T>::d> > j_vect(numDirections);
00213     // The following variables are used if regularized model is switched off.
00214     std::vector<Array<T,Descriptor<T>::q> > fNeq_vect(numDirections);
00215     // The following variables are used if regularized model is switched on.
00216     std::vector<Array<T,SymmetricTensor<T,Descriptor>::n> > PiNeq_vect(numDirections);
00217     T sumWeights = T();
00218     Array<T,3> wallNormal;
00219     for (plint iDirection=0; iDirection<numDirections; ++iDirection) {
00220         int iNeighbor = dryNodeFluidDirections[iDirection].first;
00221         int const* c = NextNeighbor<T>::c[iNeighbor];
00222         Dot3D fluidDirection(c[0],c[1],c[2]);
00223         plint dryNodeId = dryNodeIds[iDirection];
00224         int depth = dryNodeFluidDirections[iDirection].second;
00225 
00226         Array<T,3> wallNode, wall_vel;
00227         T wallDistance;
00228         OffBoundary::Type bdType;
00229 #ifdef PLB_DEBUG
00230         bool ok =
00231 #endif
00232         pointOnSurface( guoNode+absoluteOffset, fluidDirection,
00233                         wallNode, wallDistance, wallNormal,
00234                         wall_vel, bdType, dryNodeId );
00235         PLB_ASSERT( ok );
00236         if (bdType==OffBoundary::dirichlet) {
00237             for (int iD=0; iD<Descriptor<T>::d; ++iD) {
00238                 // Use the formula uLB = uP - 1/2 g. If there is no external force,
00239                 //   the force term automatically evaluates to zero.
00240                 wall_vel[iD] -= 0.5*getExternalForceComponent(cell,iD);
00241             }
00242         }
00243         T invDistanceToNeighbor = NextNeighbor<T>::invD[iNeighbor];
00244         PLB_ASSERT( wallDistance <= NextNeighbor<T>::d[iNeighbor] );
00245         T delta = (T)1. - wallDistance * invDistanceToNeighbor;
00246         Array<T,3> normalFluidDirection((T)fluidDirection.x, (T)fluidDirection.y, (T)fluidDirection.z);
00247         normalFluidDirection *= invDistanceToNeighbor;
00248         weights[iDirection] = fabs(dot(normalFluidDirection, wallNormal));
00249         sumWeights += weights[iDirection];
00250 
00251         if (usesRegularizedModel()) {
00252             computeRhoBarJPiNeq (
00253                     lattice, guoNode, fluidDirection, depth,
00254                     wallNode, delta, wall_vel, bdType, wallNormal, dryNodeId,
00255 
00256                     rhoBar_vect[iDirection], j_vect[iDirection], PiNeq_vect[iDirection], args );
00257         }
00258         else {
00259             computeRhoBarJfNeq (
00260                     lattice, guoNode, fluidDirection, depth,
00261                     wallNode, delta, wall_vel, bdType, wallNormal, dryNodeId,
00262 
00263                     rhoBar_vect[iDirection], j_vect[iDirection], fNeq_vect[iDirection], args );
00264         }
00265     }
00266 
00267     T rhoBar = T();
00268     Array<T,D::d> j; j.resetToZero();
00269     Array<T,SymmetricTensor<T,Descriptor>::n> PiNeq; PiNeq.resetToZero();
00270     Array<T,Descriptor<T>::q> fNeq; fNeq.resetToZero();
00271     for (plint iDirection=0; iDirection<numDirections; ++iDirection) {
00272         rhoBar += rhoBar_vect[iDirection] * weights[iDirection];
00273         j += j_vect[iDirection] * weights[iDirection];
00274         if (usesRegularizedModel()) {
00275             PiNeq += PiNeq_vect[iDirection] * weights[iDirection];
00276         }
00277         else {
00278             fNeq += fNeq_vect[iDirection] * weights[iDirection];
00279         }
00280     }
00281     rhoBar /= sumWeights;
00282     j /= sumWeights;
00283     if (usesRegularizedModel()) {
00284         PiNeq /= sumWeights;
00285     }
00286     else {
00287         fNeq /= sumWeights;
00288     }
00289 
00290     T jSqr = normSqr(j);
00291 
00292     Array<T,D::d> deltaJ;
00293     deltaJ.resetToZero();
00294     if (computeStat) {
00295         for (plint iDirection=0; iDirection<numDirections; ++iDirection) {
00296             int iNeighbor = dryNodeFluidDirections[iDirection].first;
00297             int iPop = nextNeighborPop<T,Descriptor>(iNeighbor);
00298             if (iPop>=0) {
00299                 plint oppPop = indexTemplates::opposite<D>(iPop);
00300                 deltaJ[0] += D::c[oppPop][0]*cell[oppPop];
00301                 deltaJ[1] += D::c[oppPop][1]*cell[oppPop];
00302                 deltaJ[2] += D::c[oppPop][2]*cell[oppPop];
00303             }
00304         }
00305     }
00306     Dynamics<T,Descriptor> const& dynamics = cell.getDynamics();
00307     if (this->getPartialReplace()) {
00308         if (usesRegularizedModel()) {
00309             Cell<T,Descriptor> saveCell(cell);
00310             dynamics.regularize(cell, rhoBar, j, jSqr, PiNeq);
00311             for (plint iDirection=0; iDirection<numDirections; ++iDirection) {
00312                 int iNeighbor = dryNodeFluidDirections[iDirection].first;
00313                 plint iPop = nextNeighborPop<T,Descriptor>(iNeighbor);
00314                 plint oppPop = indexTemplates::opposite<D>(iPop);
00315                 cell[oppPop] = saveCell[oppPop];
00316             }
00317         }
00318         else {
00319             for (plint iDirection=0; iDirection<numDirections; ++iDirection) {
00320                 int iNeighbor = dryNodeFluidDirections[iDirection].first;
00321                 plint iPop = nextNeighborPop<T,Descriptor>(iNeighbor);
00322                 cell[iPop] = cell.computeEquilibrium(iPop, rhoBar, j, jSqr)+fNeq[iPop];
00323             }
00324         }
00325     }
00326     else {
00327         if (usesRegularizedModel()) {
00328             dynamics.regularize(cell, rhoBar, j, jSqr, PiNeq);
00329         }
00330         else {
00331             for (plint iPop=0; iPop<Descriptor<T>::q; ++iPop) {
00332                 cell[iPop] = cell.computeEquilibrium(iPop, rhoBar, j, jSqr)+fNeq[iPop];
00333             }
00334         }
00335     }
00336 
00337     if (computeStat) {
00338         Cell<T,Descriptor> collidedCell(cell);
00339         BlockStatistics statsCopy(lattice.getInternalStatistics());
00340         collidedCell.collide(statsCopy);
00341 
00342         for (plint iDirection=0; iDirection<numDirections; ++iDirection) {
00343             int iNeighbor = dryNodeFluidDirections[iDirection].first;
00344             plint iPop = nextNeighborPop<T,Descriptor>(iNeighbor);
00345             if (iPop>=0) {
00346                 deltaJ[0] -= D::c[iPop][0]*collidedCell[iPop];
00347                 deltaJ[1] -= D::c[iPop][1]*collidedCell[iPop];
00348                 deltaJ[2] -= D::c[iPop][2]*collidedCell[iPop];
00349             }
00350         }
00351         // Don't divide by rho. Here we just divide by rho0=1. Remember that,
00352         //   while j is conserved along a channel, rho is not due to the
00353         //   pressure drop. Dividing by rho instead of rho0 would yield a
00354         //   larger result upstream than downstream, independent on whether
00355         //   the velocity is u or j.
00356         localForce += deltaJ;
00357     }
00358 }
00359 
00360 template<typename T, template<typename U> class Descriptor>
00361 void GuoOffLatticeModel3D<T,Descriptor>::computeRhoBarJPiNeq (
00362           BlockLattice3D<T,Descriptor> const& lattice, Dot3D const& guoNode,
00363           Dot3D const& fluidDirection, int depth, Array<T,3> const& wallNode, T delta,
00364           Array<T,3> const& wall_vel, OffBoundary::Type bdType,
00365           Array<T,3> const& wallNormal, plint triangleId,
00366           T& rhoBar, Array<T,Descriptor<T>::d>& j,
00367           Array<T,SymmetricTensor<T,Descriptor>::n>& PiNeq,
00368           std::vector<AtomicBlock3D const*> const& args ) const
00369 {
00370     if (!usesSecondOrder()) {
00371         depth=1;
00372     }
00373     T rhoBar1;
00374     Array<T,Descriptor<T>::d> j1, j2;
00375     Cell<T,Descriptor> const& cell1 =
00376         lattice.get( guoNode.x+fluidDirection.x,
00377                      guoNode.y+fluidDirection.y,
00378                      guoNode.z+fluidDirection.z );
00379     cell1.getDynamics().computeRhoBarJPiNeq(cell1, rhoBar1, j1, PiNeq);
00380     if (args.empty()) {
00381         Cell<T,Descriptor> const& cell2 =
00382             lattice.get( guoNode.x+2*fluidDirection.x,
00383                          guoNode.y+2*fluidDirection.y,
00384                          guoNode.z+2*fluidDirection.z );
00385 
00386         T tmpRhoBar;
00387         cell2.getDynamics().computeRhoBarJ(cell2, tmpRhoBar, j2);
00388     }
00389     else {
00390         PLB_ASSERT( (plint)args.size()==1 );
00391         NTensorField3D<T> const* macroField =
00392             dynamic_cast<NTensorField3D<T> const*>( args[0] );
00393         PLB_ASSERT( macroField );
00394         // 1 Variable for rhoBar, 3 variables for j.
00395         PLB_ASSERT( macroField->getNdim()==4 );
00396         Dot3D offset = computeRelativeDisplacement(lattice, *macroField);
00397         T const* macroscopic = macroField->get (
00398                 guoNode.x+2*fluidDirection.x+offset.x,
00399                 guoNode.y+2*fluidDirection.y+offset.y,
00400                 guoNode.z+2*fluidDirection.z+offset.z );
00401         j2.from_cArray(macroscopic+1);
00402     }
00403 
00404     if ( bdType==OffBoundary::constRhoInlet ||
00405          bdType==OffBoundary::densityNeumann )
00406     {
00407         rhoBar=Descriptor<T>::rhoBar(wall_vel[0]);
00408     }
00409     else {
00410         rhoBar = rhoBar1;
00411     }
00412     Array<T,3> wall_j (
00413             this->velIsJ() ? wall_vel :
00414                              (Descriptor<T>::fullRho(rhoBar)*wall_vel) );
00415     if (depth < 2) {
00416         if (delta < (T)0.25) {
00417             j = wall_j;
00418         }
00419         else {
00420             //j = (T)1./delta * (wall_j+(delta-(T)1.)*j1);
00421             j = wall_j; // Temporary fix for complex geometries.
00422         }
00423     }
00424     else {  // depth >= 2           d=1   d=0.5   d=0
00425         if (delta < (T)0.75) {   // x---|=========o-------------o
00426             j = wall_j + (delta-(T)1.)*j1 +
00427                 ((T)1.-delta)/((T)1.+delta)*((T)2.*wall_j+(delta-(T)1.)*j2);
00428         }  //       d=1   d=0.5   d=0
00429         else {   // x===|---------o-------------o
00430             j = (T)1./delta * (wall_j+(delta-(T)1.)*j1);
00431         }
00432     }
00433     if ( bdType==OffBoundary::neumann )
00434     {
00435         j = j1;
00436     }
00437     else if ( bdType==OffBoundary::densityNeumann ) {
00438         j = dot(j1,wallNormal)*wallNormal;
00439     }
00440     else if ( bdType==OffBoundary::freeSlip ) {
00441         Array<T,3> continuousNormal =
00442             this->computeContinuousNormal(wallNode, triangleId);
00443         j = j1-dot(j1,continuousNormal)*continuousNormal;
00444     }
00445 }
00446 
00447 template<typename T, template<typename U> class Descriptor>
00448 void GuoOffLatticeModel3D<T,Descriptor>::computeRhoBarJfNeq (
00449           BlockLattice3D<T,Descriptor> const& lattice, Dot3D const& guoNode,
00450           Dot3D const& fluidDirection, int depth, Array<T,3> const& wallNode, T delta,
00451           Array<T,3> const& wall_vel, OffBoundary::Type bdType,
00452           Array<T,3> const& wallNormal, plint triangleId,
00453           T& rhoBar, Array<T,Descriptor<T>::d>& j,
00454           Array<T,Descriptor<T>::q>& fNeq,
00455           std::vector<AtomicBlock3D const*> const& args ) const
00456 {
00457     if (!usesSecondOrder()) {
00458         depth=1;
00459     }
00460     T rhoBar1, rhoBar2;
00461     Array<T,Descriptor<T>::d> j1, j2;
00462     Array<T,Descriptor<T>::q> fNeq1, fNeq2;
00463     Cell<T,Descriptor> const& cell1 =
00464         lattice.get( guoNode.x+fluidDirection.x,
00465                      guoNode.y+fluidDirection.y,
00466                      guoNode.z+fluidDirection.z );
00467     Cell<T,Descriptor> const& cell2 =
00468         lattice.get( guoNode.x+2*fluidDirection.x,
00469                      guoNode.y+2*fluidDirection.y,
00470                      guoNode.z+2*fluidDirection.z );
00471     cell1.getDynamics().computeRhoBarJ(cell1, rhoBar1, j1);
00472     cell2.getDynamics().computeRhoBarJ(cell2, rhoBar2, j2);
00473     T j1sqr = normSqr(j1);
00474     T j2sqr = normSqr(j2);
00475     for(plint iPop=0; iPop<Descriptor<T>::q; ++iPop) {
00476         fNeq1[iPop] = cell1[iPop]-cell1.getDynamics().computeEquilibrium(iPop, rhoBar1, j1, j1sqr);
00477         fNeq2[iPop] = cell2[iPop]-cell2.getDynamics().computeEquilibrium(iPop, rhoBar2, j2, j2sqr);
00478     }
00479 
00480     if ( bdType==OffBoundary::constRhoInlet ||
00481          bdType==OffBoundary::densityNeumann )
00482     {
00483         rhoBar=Descriptor<T>::rhoBar(wall_vel[0]);
00484     }
00485     else {
00486         rhoBar = rhoBar1;
00487     }
00488     Array<T,3> wall_j (
00489             this->velIsJ() ? wall_vel :
00490                              (Descriptor<T>::fullRho(rhoBar)*wall_vel) );
00491     Array<T,3> jNeumann;
00492     fNeq = fNeq1;
00493     jNeumann = j1;
00494     if (depth < 2) {
00495         if (delta < (T)0.25) {
00496             j = wall_j;
00497         }
00498         else {
00499             j = (T)1./delta * (wall_j+(delta-(T)1.)*j1);
00500         }
00501     }
00502     else {  // depth >= 2
00503         if (delta < (T)0.75) {
00504             j = wall_j + (delta-(T)1.)*j1 +
00505                 ((T)1.-delta)/((T)1.+delta)*((T)2.*wall_j+(delta-(T)1.)*j2);
00506         }
00507         else {
00508             j = (T)1./delta * (wall_j+(delta-(T)1.)*j1);
00509         }
00510     }
00511     if ( bdType==OffBoundary::neumann )
00512     {
00513         j = jNeumann;
00514     }
00515     else if ( bdType==OffBoundary::densityNeumann ) {
00516         j = dot(jNeumann,wallNormal)*wallNormal;
00517     }
00518     else if ( bdType==OffBoundary::freeSlip ) {
00519         Array<T,3> continuousNormal =
00520             this->computeContinuousNormal(wallNode, triangleId);
00521         j = jNeumann-dot(jNeumann,continuousNormal)*continuousNormal;
00522     }
00523 }
00524 
00525 }  // namespace plb
00526 
00527 #endif  // GUO_OFF_LATTICE_MODEL_3D_HH