/*  This file is part of the OpenLB library
 *
 *  Copyright (C) 2006, 2007 Jonas Latt
 *  E-mail contact: info@openlb.net
 *  The most recent release of OpenLB can be downloaded at
 *  
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License
 *  as published by the Free Software Foundation; either version 2
 *  of the License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public
 *  License along with this program; if not, write to the Free
 *  Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 *  Boston, MA  02110-1301, USA.
*/
#ifndef FD_BOUNDARIES_2D_HH
#define FD_BOUNDARIES_2D_HH
#include "boundaryPostProcessors2D.h"
#include "core/finiteDifference2D.h"
#include "core/blockLattice2D.h"
#include "core/util.h"
#include "dynamics/lbHelpers.h"
#include "dynamics/firstOrderLbHelpers.h"
namespace olb {
///////////  StraightFdBoundaryProcessor2D ///////////////////////////////////
template
StraightFdBoundaryProcessor2D::
StraightFdBoundaryProcessor2D(int x0_, int x1_, int y0_, int y1_)
  : x0(x0_), x1(x1_), y0(y0_), y1(y1_)
{
  OLB_PRECONDITION(x0==x1 || y0==y1);
}
template
void StraightFdBoundaryProcessor2D::
processSubDomain(BlockLattice2D& blockLattice, int x0_, int x1_, int y0_, int y1_)
{
  using namespace olb::util::tensorIndices2D;
  int newX0, newX1, newY0, newY1;
  if ( util::intersect (
         x0, x1, y0, y1,
         x0_, x1_, y0_, y1_,
         newX0, newX1, newY0, newY1 ) ) {
    int iX;
#ifdef PARALLEL_MODE_OMP
    #pragma omp parallel for
#endif
    for (iX=newX0; iX<=newX1; ++iX) {
      T dx_u[DESCRIPTOR::d], dy_u[DESCRIPTOR::d];
      for (int iY=newY0; iY<=newY1; ++iY) {
        Cell& cell = blockLattice.get(iX,iY);
        Dynamics* dynamics = blockLattice.getDynamics(iX, iY);
        T rho, u[DESCRIPTOR::d];
        cell.computeRhoU(rho,u);
        interpolateGradients<0>(blockLattice, dx_u, iX, iY);
        interpolateGradients<1>(blockLattice, dy_u, iX, iY);
        T dx_ux = dx_u[0];
        T dy_ux = dy_u[0];
        T dx_uy = dx_u[1];
        T dy_uy = dy_u[1];
        T omega = dynamics->getOmega();
        T sToPi = - rho / descriptors::invCs2() / omega;
        T pi[util::TensorVal::n];
        pi[xx] = (T)2 * dx_ux * sToPi;
        pi[yy] = (T)2 * dy_uy * sToPi;
        pi[xy] = (dx_uy + dy_ux) * sToPi;
        // Computation of the particle distribution functions
        // according to the regularized formula
        T uSqr = util::normSqr(u);
        for (int iPop = 0; iPop < DESCRIPTOR::q; ++iPop) {
          cell[iPop] = dynamics -> computeEquilibrium(iPop,rho,u,uSqr) +
                       firstOrderLbHelpers::fromPiToFneq(iPop, pi);
        }
      }
    }
  }
}
template
void StraightFdBoundaryProcessor2D::
process(BlockLattice2D& blockLattice)
{
  processSubDomain(blockLattice, x0, x1, y0, y1);
}
template
template
void StraightFdBoundaryProcessor2D::
interpolateGradients(BlockLattice2D const& blockLattice,
                     T velDeriv[DESCRIPTOR::d], int iX, int iY) const
{
  fd::DirectedGradients2D::
  interpolateVector(velDeriv, blockLattice, iX, iY);
}
////////  StraightFdBoundaryProcessorGenerator2D ////////////////////////////////
template
StraightFdBoundaryProcessorGenerator2D::
StraightFdBoundaryProcessorGenerator2D(int x0_, int x1_, int y0_, int y1_)
  : PostProcessorGenerator2D(x0_, x1_, y0_, y1_)
{ }
template
PostProcessor2D*
StraightFdBoundaryProcessorGenerator2D::generate() const
{
  return new StraightFdBoundaryProcessor2D
         ( this->x0, this->x1, this->y0, this->y1);
}
template
PostProcessorGenerator2D*
StraightFdBoundaryProcessorGenerator2D::clone() const
{
  return new StraightFdBoundaryProcessorGenerator2D
         (this->x0, this->x1, this->y0, this->y1);
}
////////  StraightConvectionBoundaryProcessor2D ////////////////////////////////
template
StraightConvectionBoundaryProcessor2D::
StraightConvectionBoundaryProcessor2D(int x0_, int x1_, int y0_, int y1_, T* uAv_)
  : x0(x0_), x1(x1_), y0(y0_), y1(y1_), uAv(uAv_)
{
  OLB_PRECONDITION(x0==x1 || y0==y1);
  saveCell = new T** [(size_t)(x1_-x0_+1)];
  for (int iX=0; iX<=x1_-x0_; ++iX) {
    saveCell[iX] = new T* [(size_t)(y1_-y0_+1)];
    for (int iY=0; iY<=y1_-y0_; ++iY) {
      saveCell[iX][iY] = new T [(size_t)(DESCRIPTOR::q)];
      for (int iPop=0; iPop
StraightConvectionBoundaryProcessor2D::
~StraightConvectionBoundaryProcessor2D()
{
  for (int iX=0; iX<=x1-x0; ++iX) {
    for (int iY=0; iY<=y1-y0; ++iY) {
      delete [] saveCell[iX][iY];
    }
    delete [] saveCell[iX];
  }
  delete [] saveCell;
}
template
void StraightConvectionBoundaryProcessor2D::
processSubDomain(BlockLattice2D& blockLattice, int x0_, int x1_, int y0_, int y1_)
{
  using namespace olb::util::tensorIndices2D;
  int newX0, newX1, newY0, newY1;
  if ( util::intersect (
         x0, x1, y0, y1,
         x0_, x1_, y0_, y1_,
         newX0, newX1, newY0, newY1 ) ) {
    int iX;
#ifdef PARALLEL_MODE_OMP
    #pragma omp parallel for
#endif
    for (iX=newX0; iX<=newX1; ++iX) {
      for (int iY=newY0; iY<=newY1; ++iY) {
        Cell& cell = blockLattice.get(iX,iY);
        for (int iPop = 0; iPop < DESCRIPTOR::q ; ++iPop) {
          if (descriptors::c(iPop,direction)==-orientation) {
            // using default -1 avoids wrong first call
            if (!util::nearZero(1 + saveCell[iX-newX0][iY-newY0][iPop]) ){
              cell[iPop] = saveCell[iX-newX0][iY-newY0][iPop];
            }
          }
        }
        T rho0, u0[2];
        T rho1, u1[2];
        T rho2, u2[2];
        if (direction==0) {
          blockLattice.get(iX,iY).computeRhoU(rho0,u0);
          blockLattice.get(iX-orientation,iY).computeRhoU(rho1,u1);
          blockLattice.get(iX-orientation*2,iY).computeRhoU(rho2,u2);
        } else {
          blockLattice.get(iX,iY).computeRhoU(rho0,u0);
          blockLattice.get(iX,iY-orientation).computeRhoU(rho1,u1);
          blockLattice.get(iX,iY-orientation*2).computeRhoU(rho2,u2);
        }
        // rho0 = T(1); rho1 = T(1); rho2 = T(1);
        T uDelta[2];
        T uAverage = rho0*u0[direction];
        if (uAv!=nullptr) {
          uAverage = *uAv;
        }
        uDelta[0]=-uAverage*0.5*(3*rho0*u0[0]-4*rho1*u1[0]+rho2*u2[0]);
        uDelta[1]=-uAverage*0.5*(3*rho0*u0[1]-4*rho1*u1[1]+rho2*u2[1]);
        for (int iPop = 0; iPop < DESCRIPTOR::q ; ++iPop) {
          if (descriptors::c(iPop,direction) == -orientation) {
            saveCell[iX-newX0][iY-newY0][iPop] = cell[iPop] + descriptors::invCs2()*descriptors::t(iPop)*(uDelta[0]*descriptors::c(iPop,0)+uDelta[1]*descriptors::c(iPop,1));
          }
        }
      }
    }
  }
}
template
void StraightConvectionBoundaryProcessor2D::
process(BlockLattice2D& blockLattice)
{
  processSubDomain(blockLattice, x0, x1, y0, y1);
}
////////  StraightConvectionBoundaryProcessorGenerator2D ////////////////////////////////
template
StraightConvectionBoundaryProcessorGenerator2D::
StraightConvectionBoundaryProcessorGenerator2D(int x0_, int x1_, int y0_, int y1_, T* uAv_)
  : PostProcessorGenerator2D(x0_, x1_, y0_, y1_), uAv(uAv_)
{ }
template
PostProcessor2D*
StraightConvectionBoundaryProcessorGenerator2D::generate() const
{
  return new StraightConvectionBoundaryProcessor2D
         ( this->x0, this->x1, this->y0, this->y1, uAv);
}
template
PostProcessorGenerator2D*
StraightConvectionBoundaryProcessorGenerator2D::clone() const
{
  return new StraightConvectionBoundaryProcessorGenerator2D
         (this->x0, this->x1, this->y0, this->y1, uAv);
}
////////  SlipBoundaryProcessor2D ////////////////////////////////
template
SlipBoundaryProcessor2D::
SlipBoundaryProcessor2D(int x0_, int x1_, int y0_, int y1_, int discreteNormalX, int discreteNormalY)
  : x0(x0_), x1(x1_), y0(y0_), y1(y1_)
{
  OLB_PRECONDITION(x0==x1 || y0==y1);
  reflectionPop[0] = 0;
  for (int iPop = 1; iPop < DESCRIPTOR::q; iPop++) {
    reflectionPop[iPop] = 0;
    // iPop are the directions which ointing into the fluid, discreteNormal is pointing outwarts
    if (descriptors::c(iPop,0)*discreteNormalX + descriptors::c(iPop,1)*discreteNormalY < 0) {
      // std::cout << "-----" <(iPop,0) - mult*(descriptors::c(iPop,0)*discreteNormalX + descriptors::c(iPop,1)*discreteNormalY )*discreteNormalX);
      mirrorDirection1 = (descriptors::c(iPop,1) - mult*(descriptors::c(iPop,0)*discreteNormalX + descriptors::c(iPop,1)*discreteNormalY )*discreteNormalY);
      // computes mirror jPop
      for (reflectionPop[iPop] = 1; reflectionPop[iPop] < DESCRIPTOR::q ; reflectionPop[iPop]++) {
        if (descriptors::c(reflectionPop[iPop],0)==mirrorDirection0 && descriptors::c(reflectionPop[iPop],1)==mirrorDirection1 ) {
          break;
        }
      }
      //std::cout <
void SlipBoundaryProcessor2D::
processSubDomain(BlockLattice2D& blockLattice, int x0_, int x1_, int y0_, int y1_)
{
  int newX0, newX1, newY0, newY1;
  if ( util::intersect (
         x0, x1, y0, y1,
         x0_, x1_, y0_, y1_,
         newX0, newX1, newY0, newY1 ) ) {
    int iX;
#ifdef PARALLEL_MODE_OMP
    #pragma omp parallel for
#endif
    for (iX=newX0; iX<=newX1; ++iX) {
      for (int iY=newY0; iY<=newY1; ++iY) {
        for (int iPop = 1; iPop < DESCRIPTOR::q ; ++iPop) {
          if (reflectionPop[iPop]!=0) {
            //do reflection
            blockLattice.get(iX,iY)[iPop] = blockLattice.get(iX,iY)[reflectionPop[iPop]];
          }
        }
      }
    }
  }
}
template
void SlipBoundaryProcessor2D::
process(BlockLattice2D& blockLattice)
{
  processSubDomain(blockLattice, x0, x1, y0, y1);
}
////////  SlipBoundaryProcessorGenerator2D ////////////////////////////////
template
SlipBoundaryProcessorGenerator2D::
SlipBoundaryProcessorGenerator2D(int x0_, int x1_, int y0_, int y1_, int discreteNormalX_, int discreteNormalY_)
  : PostProcessorGenerator2D(x0_, x1_, y0_, y1_), discreteNormalX(discreteNormalX_), discreteNormalY(discreteNormalY_)
{ }
template
PostProcessor2D*
SlipBoundaryProcessorGenerator2D::generate() const
{
  return new SlipBoundaryProcessor2D
         ( this->x0, this->x1, this->y0, this->y1, discreteNormalX, discreteNormalY);
}
template
PostProcessorGenerator2D*
SlipBoundaryProcessorGenerator2D::clone() const
{
  return new SlipBoundaryProcessorGenerator2D
         (this->x0, this->x1, this->y0, this->y1, discreteNormalX, discreteNormalY);
}
////////  PartialSlipBoundaryProcessor2D ////////////////////////////////
template
PartialSlipBoundaryProcessor2D::
PartialSlipBoundaryProcessor2D(T tuner_, int x0_, int x1_, int y0_, int y1_, int discreteNormalX, int discreteNormalY)
  : x0(x0_), x1(x1_), y0(y0_), y1(y1_), tuner(tuner_)
{
  OLB_PRECONDITION(x0==x1 || y0==y1);
  reflectionPop[0] = 0;
  for (int iPop = 1; iPop < DESCRIPTOR::q; iPop++) {
    reflectionPop[iPop] = 0;
    // iPop are the directions which ointing into the fluid, discreteNormal is pointing outwarts
    if (descriptors::c(iPop,0)*discreteNormalX + descriptors::c(iPop,1)*discreteNormalY < 0) {
      //std::cout << "-----" <(iPop,0) - mult*(descriptors::c(iPop,0)*discreteNormalX + descriptors::c(iPop,1)*discreteNormalY )*discreteNormalX);
      mirrorDirection1 = (descriptors::c(iPop,1) - mult*(descriptors::c(iPop,0)*discreteNormalX + descriptors::c(iPop,1)*discreteNormalY )*discreteNormalY);
      // computes mirror jPop
      for (reflectionPop[iPop] = 1; reflectionPop[iPop] < DESCRIPTOR::q ; reflectionPop[iPop]++) {
        if (descriptors::c(reflectionPop[iPop],0)==mirrorDirection0 && descriptors::c(reflectionPop[iPop],1)==mirrorDirection1 ) {
          break;
        }
      }
      //std::cout <
void PartialSlipBoundaryProcessor2D::
processSubDomain(BlockLattice2D& blockLattice, int x0_, int x1_, int y0_, int y1_)
{
  int newX0, newX1, newY0, newY1;
  if ( util::intersect (
         x0, x1, y0, y1,
         x0_, x1_, y0_, y1_,
         newX0, newX1, newY0, newY1 ) ) {
    int iX;
#ifdef PARALLEL_MODE_OMP
    #pragma omp parallel for
#endif
    for (iX=newX0; iX<=newX1; ++iX) {
      for (int iY=newY0; iY<=newY1; ++iY) {
        for (int iPop = 1; iPop < DESCRIPTOR::q ; ++iPop) {
          if (reflectionPop[iPop]!=0) {
            //do reflection
            blockLattice.get(iX,iY)[iPop] = tuner*blockLattice.get(iX,iY)[reflectionPop[iPop]];
          }
        }
        for (int iPop = 1; iPop < DESCRIPTOR::q/2 ; ++iPop) {
          T provv = blockLattice.get(iX,iY)[descriptors::opposite(iPop)];
          blockLattice.get(iX,iY)[descriptors::opposite(iPop)] +=
                                         (1.-tuner)*blockLattice.get(iX,iY)[iPop];
          blockLattice.get(iX,iY)[iPop] += (1.-tuner)*provv;
        }
      }
    }
  }
}
template
void PartialSlipBoundaryProcessor2D::
process(BlockLattice2D& blockLattice)
{
  processSubDomain(blockLattice, x0, x1, y0, y1);
}
////////  PartialSlipBoundaryProcessorGenerator2D ////////////////////////////////
template
PartialSlipBoundaryProcessorGenerator2D::
PartialSlipBoundaryProcessorGenerator2D(T tuner_, int x0_, int x1_, int y0_, int y1_, int discreteNormalX_, int discreteNormalY_)
  : PostProcessorGenerator2D(x0_, x1_, y0_, y1_), discreteNormalX(discreteNormalX_), discreteNormalY(discreteNormalY_), tuner(tuner_)
{ }
template
PostProcessor2D*
PartialSlipBoundaryProcessorGenerator2D::generate() const
{
  return new PartialSlipBoundaryProcessor2D
         (tuner, this->x0, this->x1, this->y0, this->y1, discreteNormalX, discreteNormalY);
}
template
PostProcessorGenerator2D*
PartialSlipBoundaryProcessorGenerator2D::clone() const
{
  return new PartialSlipBoundaryProcessorGenerator2D
         (tuner, this->x0, this->x1, this->y0, this->y1, discreteNormalX, discreteNormalY);
}
/////////// OuterVelocityCornerProcessor2D /////////////////////////////////////
template
OuterVelocityCornerProcessor2D::
OuterVelocityCornerProcessor2D(int x_, int y_)
  : x(x_), y(y_)
{ }
template
void OuterVelocityCornerProcessor2D::
process(BlockLattice2D& blockLattice)
{
  using namespace olb::util::tensorIndices2D;
  T rho10 = blockLattice.get(x-1*xNormal, y-0*yNormal).computeRho();
  T rho01 = blockLattice.get(x-0*xNormal, y-1*yNormal).computeRho();
  T rho20 = blockLattice.get(x-2*xNormal, y-0*yNormal).computeRho();
  T rho02 = blockLattice.get(x-0*xNormal, y-2*yNormal).computeRho();
  T rho = (T)2/(T)3*(rho01+rho10) - (T)1/(T)6*(rho02+rho20);
  T dx_u[DESCRIPTOR::d], dy_u[DESCRIPTOR::d];
  fd::DirectedGradients2D::interpolateVector(dx_u, blockLattice, x,y);
  fd::DirectedGradients2D::interpolateVector(dy_u, blockLattice, x,y);
  T dx_ux = dx_u[0];
  T dy_ux = dy_u[0];
  T dx_uy = dx_u[1];
  T dy_uy = dy_u[1];
  Cell& cell = blockLattice.get(x,y);
  Dynamics* dynamics = blockLattice.getDynamics(x, y);
  T omega = dynamics -> getOmega();
  T sToPi = - rho / descriptors::invCs2() / omega;
  T pi[util::TensorVal::n];
  pi[xx] = (T)2 * dx_ux * sToPi;
  pi[yy] = (T)2 * dy_uy * sToPi;
  pi[xy] = (dx_uy + dy_ux) * sToPi;
  // Computation of the particle distribution functions
  // according to the regularized formula
  T u[DESCRIPTOR::d];
  blockLattice.get(x,y).computeU(u);
  T uSqr = util::normSqr(u);
  for (int iPop = 0; iPop < DESCRIPTOR::q; ++iPop) {
    cell[iPop] =
      dynamics -> computeEquilibrium(iPop,rho,u,uSqr) +
      firstOrderLbHelpers::fromPiToFneq(iPop, pi);
  }
}
template
void OuterVelocityCornerProcessor2D::
processSubDomain(BlockLattice2D& blockLattice,
                 int x0_, int x1_, int y0_, int y1_ )
{
  if (util::contained(x, y, x0_, x1_, y0_, y1_)) {
    process(blockLattice);
  }
}
////////  OuterVelocityCornerProcessorGenerator2D ////////////////////////////
template
OuterVelocityCornerProcessorGenerator2D::
OuterVelocityCornerProcessorGenerator2D(int x_, int y_)
  : PostProcessorGenerator2D(x_, x_, y_, y_)
{ }
template
PostProcessor2D*
OuterVelocityCornerProcessorGenerator2D::generate() const
{
  return new OuterVelocityCornerProcessor2D
         ( this->x0, this->y0);
}
template
PostProcessorGenerator2D*
OuterVelocityCornerProcessorGenerator2D::
clone() const
{
  return new OuterVelocityCornerProcessorGenerator2D
         ( this->x0, this->y0);
}
template
FreeEnergyWallProcessor2D::
FreeEnergyWallProcessor2D(int x0_, int x1_, int y0_, int y1_, int discreteNormalX_, int discreteNormalY_, T addend_)
  : x0(x0_), x1(x1_), y0(y0_), y1(y1_),
    discreteNormalX(discreteNormalX_), discreteNormalY(discreteNormalY_),
    addend(addend_)
{ }
template
void FreeEnergyWallProcessor2D::
processSubDomain(BlockLattice2D& blockLattice, int x0_, int x1_, int y0_, int y1_)
{
  int newX0, newX1, newY0, newY1;
  if ( util::intersect (
         x0, x1, y0, y1,
         x0_, x1_, y0_, y1_,
         newX0, newX1, newY0, newY1 ) ) {
    for (int iX=newX0; iX<=newX1; ++iX) {
      for (int iY=newY0; iY<=newY1; ++iY) {
        T rhoAvg = blockLattice.get(iX-discreteNormalX, iY-discreteNormalY).computeRho();
        T rhoTmp = 0.;
        for (int iPop = 1; iPop < DESCRIPTOR::q ; ++iPop) {
            rhoTmp += blockLattice.get(iX,iY)[iPop];
        }
        T rho = rhoAvg + addend;
        rho -= rhoTmp;
        blockLattice.get(iX,iY)[0] = rho-1.;
      }
    }
  }
}
template
void FreeEnergyWallProcessor2D::
process(BlockLattice2D& blockLattice)
{
processSubDomain(blockLattice, x0, x1, y0, y1);
}
template
FreeEnergyWallProcessorGenerator2D::
FreeEnergyWallProcessorGenerator2D(int x0_, int x1_, int y0_, int y1_, int discreteNormalX_,
    int discreteNormalY_, T addend_)
: PostProcessorGenerator2D(x0_, x1_, y0_, y1_), discreteNormalX(discreteNormalX_), discreteNormalY(discreteNormalY_), addend(addend_)
{ }
template
PostProcessor2D*
FreeEnergyWallProcessorGenerator2D::generate() const
{
return new FreeEnergyWallProcessor2D
     (this->x0, this->x1, this->y0, this->y1, discreteNormalX, discreteNormalY, addend);
}
template
PostProcessorGenerator2D*
FreeEnergyWallProcessorGenerator2D::clone() const
{
  return new FreeEnergyWallProcessorGenerator2D
         (this->x0, this->x1, this->y0, this->y1, discreteNormalX, discreteNormalY, addend);
}
////////  FreeEnergyChemPotBoundaryProcessor2D ////////////////////////////
template
FreeEnergyChemPotBoundaryProcessor2D::
FreeEnergyChemPotBoundaryProcessor2D(
  int x0_, int x1_, int y0_, int y1_, int discreteNormalX_, int discreteNormalY_, int latticeNumber_)
  : x0(x0_), x1(x1_), y0(y0_), y1(y1_), discreteNormalX(discreteNormalX_),
    discreteNormalY(discreteNormalY_), latticeNumber(latticeNumber_)
{ }
template
void FreeEnergyChemPotBoundaryProcessor2D::
processSubDomain(
  BlockLattice2D& blockLattice, int x0_, int x1_, int y0_, int y1_)
{
  int newX0, newX1, newY0, newY1;
  if ( util::intersect (
         x0, x1, y0, y1,
         x0_, x1_, y0_, y1_,
         newX0, newX1, newY0, newY1 ) ) {
    for (int iX=newX0; iX<=newX1; ++iX) {
      for (int iY=newY0; iY<=newY1; ++iY) {
        blockLattice.get(iX,iY).template setField(
          blockLattice.get(iX-discreteNormalX, iY-discreteNormalY).template getField()
        );
        if (latticeNumber == 1) {
          T rho0 = blockLattice.get(iX, iY).computeRho();
          T rho1 = blockLattice.get(iX-discreteNormalX, iY-discreteNormalY).computeRho();
          *(blockLattice.get(iX,iY).template getFieldPointer()) += (rho1 / rho0 - 1) / descriptors::invCs2();
        }
      }
    }
  }
}
template
void FreeEnergyChemPotBoundaryProcessor2D::
process(BlockLattice2D& blockLattice)
{
processSubDomain(blockLattice, x0, x1, y0, y1);
}
////////  FreeEnergyChemPotBoundaryProcessorGenerator2D ////////////////////////////
template
FreeEnergyChemPotBoundaryProcessorGenerator2D::
FreeEnergyChemPotBoundaryProcessorGenerator2D(int x0_, int x1_, int y0_, int y1_,
  int discreteNormalX_, int discreteNormalY_, int latticeNumber_)
  : PostProcessorGenerator2D(x0_, x1_, y0_, y1_), discreteNormalX(discreteNormalX_),
    discreteNormalY(discreteNormalY_), latticeNumber(latticeNumber_)
{ }
template
PostProcessor2D*
FreeEnergyChemPotBoundaryProcessorGenerator2D::generate() const
{
return new FreeEnergyChemPotBoundaryProcessor2D
     (this->x0, this->x1, this->y0, this->y1, discreteNormalX, discreteNormalY, latticeNumber);
}
template
PostProcessorGenerator2D*
FreeEnergyChemPotBoundaryProcessorGenerator2D::clone() const
{
  return new FreeEnergyChemPotBoundaryProcessorGenerator2D
         (this->x0, this->x1, this->y0, this->y1, discreteNormalX, discreteNormalY, latticeNumber);
}
////////  FreeEnergyConvectiveProcessor2D ////////////////////////////
template
FreeEnergyConvectiveProcessor2D::
FreeEnergyConvectiveProcessor2D(
  int x0_, int x1_, int y0_, int y1_, int discreteNormalX_, int discreteNormalY_)
  : x0(x0_), x1(x1_), y0(y0_), y1(y1_),
    discreteNormalX(discreteNormalX_), discreteNormalY(discreteNormalY_)
{ }
template
void FreeEnergyConvectiveProcessor2D::
processSubDomain(
  BlockLattice2D& blockLattice, int x0_, int x1_, int y0_, int y1_)
{
  int newX0, newX1, newY0, newY1;
  if ( util::intersect (
         x0, x1, y0, y1,
         x0_, x1_, y0_, y1_,
         newX0, newX1, newY0, newY1 ) ) {
    for (int iX=newX0; iX<=newX1; ++iX) {
      for (int iY=newY0; iY<=newY1; ++iY) {
        T rho, rho0, rho1, u[2];
        rho0 = blockLattice.get(iX,iY).computeRho();
        blockLattice.get(iX-discreteNormalX,iY-discreteNormalY).computeRhoU(rho1, u);
        T uPerp = 0;
        if (discreteNormalX == 0) {
          uPerp = discreteNormalY * u[1];
        } else if (discreteNormalY == 0) {
          uPerp = discreteNormalX * u[0];
        }
        rho = (rho0 + uPerp * rho1) / (1. + uPerp);
        blockLattice.get(iX,iY).defineRho(rho);
      }
    }
  }
}
template
void FreeEnergyConvectiveProcessor2D::
process(BlockLattice2D& blockLattice)
{
processSubDomain(blockLattice, x0, x1, y0, y1);
}
////////  FreeEnergyConvectiveProcessorGenerator2D ////////////////////////////
template
FreeEnergyConvectiveProcessorGenerator2D::
FreeEnergyConvectiveProcessorGenerator2D(int x0_, int x1_, int y0_, int y1_,
  int discreteNormalX_, int discreteNormalY_)
  : PostProcessorGenerator2D(x0_, x1_, y0_, y1_),
    discreteNormalX(discreteNormalX_), discreteNormalY(discreteNormalY_)
{ }
template
PostProcessor2D*
FreeEnergyConvectiveProcessorGenerator2D::generate() const
{
return new FreeEnergyConvectiveProcessor2D
     (this->x0, this->x1, this->y0, this->y1, discreteNormalX, discreteNormalY);
}
template
PostProcessorGenerator2D*
FreeEnergyConvectiveProcessorGenerator2D::clone() const
{
  return new FreeEnergyConvectiveProcessorGenerator2D
         (this->x0, this->x1, this->y0, this->y1, discreteNormalX, discreteNormalY);
}
}  // namespace olb
#endif