/*
 *  Lattice Boltzmann grid refinement sample, written in C++,
 *  using the OpenLB library
 *
 *  Copyright (C) 2019 Adrian Kummerländer
 *  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.
 */
#include "olb2D.h"
#include "olb2D.hh"
using namespace olb;
typedef double T;
#define DESCRIPTOR descriptors::D2Q9<>
/// Setup geometry relative to cylinder diameter as defined by [SchaeferTurek96]
const T   cylinderD = 0.1;
const int N         = 5;                      // resolution of the cylinder
const T   Re        = 100.;                   // Reynolds number
const T   tau       = 0.51;                   // relaxation time
const T   maxPhysT  = 16.;                    // max. simulation time in s, SI unit
const Characteristics PhysCharacteristics(
  cylinderD,   // char. phys. length
  1.0,         // char. phys. velocity
  0.1/Re,      // phsy. kinematic viscosity
  1.0);        // char. phys. density
#include "../common/model.h"
void setupRefinement(Grid2D& coarseGrid,
                     Vector domainOrigin, Vector domainExtend,
                     Vector cylinderCenter)
{
  const auto coarseDeltaX = coarseGrid.getConverter().getPhysDeltaX();
  const Vector fineOutflowExtend {1*cylinderD, domainExtend[1]};
  const Vector fineOutflowOrigin {domainExtend[0]-1*cylinderD, 0};
  auto& fineOutflowGrid = coarseGrid.refine(fineOutflowOrigin, fineOutflowExtend, false);
  SchaeferTurek::prepareGeometry(fineOutflowGrid, domainOrigin, domainExtend);
  {
    const Vector origin = fineOutflowGrid.getOrigin();
    const Vector extend = fineOutflowGrid.getExtend();
    const Vector extendY = {0,extend[1]};
    coarseGrid.addFineCoupling(fineOutflowGrid, origin, extendY);
    coarseGrid.addCoarseCoupling(fineOutflowGrid, origin + Vector {coarseDeltaX,0}, extendY);
    IndicatorCuboid2D refined(extend, origin + Vector {2*coarseDeltaX,0});
    coarseGrid.getSuperGeometry().reset(refined);
  }
  const Vector fineOutflowExtend2 {0.5*cylinderD, domainExtend[1]};
  const Vector fineOutflowOrigin2 {domainExtend[0]-0.5*cylinderD, 0};
  auto& fineOutflowGrid2 = fineOutflowGrid.refine(fineOutflowOrigin2, fineOutflowExtend2, false);
  SchaeferTurek::prepareGeometry(fineOutflowGrid2, domainOrigin, domainExtend);
  {
    const Vector origin = fineOutflowGrid2.getOrigin();
    const Vector extend = fineOutflowGrid2.getExtend();
    const Vector extendY = {0,extend[1]};
    fineOutflowGrid.addFineCoupling(fineOutflowGrid2, origin, extendY);
    fineOutflowGrid.addCoarseCoupling(fineOutflowGrid2, origin + Vector {coarseDeltaX,0}, extendY);
    IndicatorCuboid2D refined(extend, origin + Vector {coarseDeltaX,0});
    fineOutflowGrid.getSuperGeometry().reset(refined);
  }
  const Vector fineExtend {10.5*cylinderD, domainExtend[1]-2*coarseDeltaX};
  const Vector fineOrigin {0.5*cylinderD, coarseDeltaX};
  auto& fineGrid = coarseGrid.refine(fineOrigin, fineExtend);
  SchaeferTurek::prepareGeometry(fineGrid, domainOrigin, domainExtend);
  const Vector fineExtend2 {5*cylinderD, fineGrid.getExtend()[1]-2*coarseDeltaX};
  const Vector fineOrigin2 {1*cylinderD, (domainExtend[1]-fineExtend2[1])/2};
  auto& fineGrid2 = fineGrid.refine(fineOrigin2, fineExtend2);
  SchaeferTurek::prepareGeometry(fineGrid2, domainOrigin, domainExtend);
  const Vector fineExtend3 {1.25*cylinderD, 1.25*cylinderD};
  const Vector fineOrigin3 {cylinderCenter[0]-fineExtend3[0]/2, cylinderCenter[1]-fineExtend3[1]/2};
  auto& fineGrid3 = fineGrid2.refine(fineOrigin3, fineExtend3);
  SchaeferTurek::prepareGeometry(fineGrid3, domainOrigin, domainExtend);
}
int main(int argc, char* argv[])
{
  olbInit(&argc, &argv);
  singleton::directories().setOutputDir("./tmp/");
  OstreamManager clout(std::cout,"main");
  IndicatorCuboid2D coarseCuboid(SchaeferTurek::modelExtend, SchaeferTurek::modelOrigin);
  Grid2D coarseGrid(
    coarseCuboid,
    RelaxationTime(tau),
    N,
    PhysCharacteristics);
  const Vector domainOrigin = coarseGrid.getSuperGeometry().getStatistics().getMinPhysR(0);
  const Vector domainExtend = coarseGrid.getSuperGeometry().getStatistics().getPhysExtend(0);
  SchaeferTurek::prepareGeometry(coarseGrid, domainOrigin, domainExtend);
  setupRefinement(coarseGrid, domainOrigin, domainExtend, SchaeferTurek::cylinderCenter);
  coarseGrid.forEachGrid(SchaeferTurek::prepareLattice);
  clout << "Total number of active cells: " << coarseGrid.getActiveVoxelN() << endl;
  clout << "Starting simulation..." << endl;
  const int statIter = coarseGrid.getConverter().getLatticeTime(0.5);
  Timer timer(
    coarseGrid.getConverter().getLatticeTime(maxPhysT),
    coarseGrid.getSuperGeometry().getStatistics().getNvoxel());
  timer.start();
  Grid2D& cylinderGrid = coarseGrid.locate(SchaeferTurek::cylinderCenter);
  for (int iT = 0; iT <= coarseGrid.getConverter().getLatticeTime(maxPhysT); ++iT) {
    SchaeferTurek::setBoundaryValues(coarseGrid, iT);
    coarseGrid.collideAndStream();
    if (iT == 0 || iT%statIter == 0) {
      timer.update(iT);
      timer.printStep();
      coarseGrid.forEachGrid("cylinder2d", [&](Grid2D& grid, const std::string& id) {
        SchaeferTurek::getResults(grid, id, iT);
      });
      SchaeferTurek::takeMeasurements(cylinderGrid, iT);
    }
  }
  timer.stop();
  timer.printSummary();
}