Program Listing for File timeIntegrator.cpp

Return to documentation for file (timeIntegrator.cpp)

// ***********************************************************************************
// Idefix MHD astrophysical code
// Copyright(C) 2020-2022 Geoffroy R. J. Lesur <geoffroy.lesur@univ-grenoble-alpes.fr>
// and other code contributors
// Licensed under CeCILL 2.1 License, see COPYING for more information
// ***********************************************************************************

#include <cstdio>
#include <iomanip>
#include <string>
#include "idefix.hpp"
#include "timeIntegrator.hpp"
#include "input.hpp"
#include "stateContainer.hpp"


TimeIntegrator::TimeIntegrator(Input & input, DataBlock & data) {
  idfx::pushRegion("TimeIntegrator::TimeIntegrator(Input...)");

  this->timer.reset();
  this->lastLog=timer.seconds();
  this->lastMpiLog=idfx::mpiCallsTimer + idfx::mpiCallsTimer;

  nstages=input.Get<int>("TimeIntegrator","nstages",0);

  if(input.CheckEntry("TimeIntegrator","fixed_dt")>0) {
    this->haveFixedDt = true;
    this->fixedDt = input.Get<real>("TimeIntegrator","fixed_dt",0);
    data.dt=fixedDt;
  }

  if(!haveFixedDt) {
    cfl=input.Get<real>("TimeIntegrator","CFL",0);
    cflMaxVar = input.GetOrSet<real>("TimeIntegrator","CFL_max_var",0, 1.1);
    data.dt = input.GetOrSet<real>("TimeIntegrator","first_dt",0, 1.0e-10);
  }

  this->cyclePeriod = input.GetOrSet<int>("Output","log",0, 100);
  this->maxRuntime = 3600*input.GetOrSet<double>("TimeIntegrator","max_runtime",0.0,-1.0);

  // check nan periodicity every 100 loops
  this->checkNanPeriodicity = input.GetOrSet<int>("TimeIntegrator","check_nan", 0, 100);

  #ifndef SINGLE_PRECISION
    const real maxdivBDefault = 1e-6;
  #else
    const real maxdivBDefault = 1e-2;
  #endif

  this->maxdivB = input.GetOrSet<real>("TimeIntegrator","maxdivB", 0,maxdivBDefault);


  data.t=0.0;
  ncycles=0;

  if(nstages==2) {
    wc[0] = 0.5;
    w0[0] = 0.5;
  }
  if(nstages==3) {
    wc[0] = 0.25;
    w0[0] = 0.75;
    wc[1] = 2.0/3.0;
    w0[1] = 1.0/3.0;
  }

  // Init the RKL scheme if it's needed
  if(data.hydro.haveRKLParabolicTerms) {
    rkl.Init(input,data);
    haveRKL = true;
  }

  // If multi-stage, create a new state in the datablock called "begin"
  if(nstages>1) {
    data.states["begin"] = StateContainer();
    data.states["begin"].AllocateAs(data.states["current"]);
  }

  idfx::popRegion();
}


void TimeIntegrator::ShowLog(DataBlock &data) {
  if(isSilent) return;
  double rawperf = (timer.seconds()-lastLog)/data.mygrid->np_int[IDIR]/data.mygrid->np_int[JDIR]
                      /data.mygrid->np_int[KDIR]/cyclePeriod;
#ifdef WITH_MPI
  // measure time spent in expensive MPI calls
  double mpiCycleTime = idfx::mpiCallsTimer - lastMpiLog;
  // reduce to an normalized overhead in %
  double mpiOverhead = 100.0 * mpiCycleTime / (timer.seconds() - lastLog);
  lastMpiLog = idfx::mpiCallsTimer;
#endif
  lastLog = timer.seconds();


  int col_width{16};
  if (ncycles == 0) {
    idfx::cout << "TimeIntegrator: ";
    idfx::cout << std::setw(col_width) << "time";
    idfx::cout << " | " << std::setw(col_width) << "cycle";
    idfx::cout << " | " << std::setw(col_width) << "time step";
    idfx::cout << " | " << std::setw(col_width) << "cell (updates/s)";
#ifdef WITH_MPI
    idfx::cout << " | " << std::setw(col_width) << "MPI overhead (%)";
#endif
#if MHD == YES
    idfx::cout << " | " << std::setw(col_width) << "div B";
#endif
    if(haveRKL) {
      idfx::cout << " | " << std::setw(col_width) << "RKL stages";
    }
    idfx::cout << std::endl;
  }

  idfx::cout << "TimeIntegrator: ";
  idfx::cout << std::scientific;
  idfx::cout << std::setw(col_width) << data.t;
  idfx::cout << " | " << std::setw(col_width) << ncycles;
  idfx::cout << " | " << std::setw(col_width) << data.dt;
  if(ncycles>=cyclePeriod) {
    idfx::cout << " | " << std::setw(col_width) << 1 / rawperf;
#ifdef WITH_MPI
  idfx::cout << std::fixed;
    idfx::cout << " | " << std::setw(col_width) << mpiOverhead;
#endif
  } else {
    idfx::cout << " | " << std::setw(col_width) << "N/A";
#if WITH_MPI
    idfx::cout << " | " << std::setw(col_width) << "N/A";
#endif
  }


#if MHD == YES
  // Check divB
  real divB =  data.hydro.CheckDivB();
  idfx::cout << std::scientific;
  idfx::cout << " | " << std::setw(col_width) << divB;

  if(divB>maxdivB) {
    std::stringstream msg;
    msg << std::endl << "divB too large, check your calculation";
    throw std::runtime_error(msg.str());
  }
#endif
  if(haveRKL) {
    idfx::cout << " | " << std::setw(col_width) << rkl.stage;
  }
  idfx::cout << std::endl;
}


// Compute one full cycle of the time Integrator
void TimeIntegrator::Cycle(DataBlock &data) {
  // Do one cycle
  IdefixArray3D<real> InvDt = data.hydro.InvDt;
  real newdt;

  idfx::pushRegion("TimeIntegrator::Cycle");

  if(ncycles%cyclePeriod==0) ShowLog(data);

  if(haveRKL && (ncycles%2)==1) {    // Runge-Kutta-Legendre cycle
    rkl.Cycle();
  }

  // save t at the begining of the cycle
  const real t0 = data.t;

  // Reinit datablock for a new stage
  data.ResetStage();

#ifdef WITH_MPI
  MPI_Request dtReduce;
#endif

  // BEGIN STAGES LOOP                           //
  for(int stage=0; stage < nstages ; stage++) {
    // Apply Boundary conditions
    data.SetBoundaries();

    // Remove Fargo velocity so that the integrator works on the residual
    if(data.haveFargo) data.fargo.SubstractVelocity(data.t);

    // Convert current state into conservative variable and save it
    data.hydro.ConvertPrimToCons();

    // Store (deep copy) initial stage for multi-stage time integrators
    if(nstages>1 && stage==0) {
      data.states["begin"].CopyFrom(data.states["current"]);
    }
    // If gravity is needed, update it
    if(data.haveGravity) data.gravity.ComputeGravity();

    // Update Uc & Vs
    data.EvolveStage();

    // evolve dt accordingly
    data.t += data.dt;

    // Look for Nans every now and then (this actually cost a lot of time on GPUs
    // because streams are divergent)
    if(ncycles%checkNanPeriodicity==0) {
      if(data.CheckNan()>0) {
        throw std::runtime_error(std::string("Nan found after integration cycle"));
      }
    }

    // Compute next time_step during first stage
    if(stage==0) {
      if(!haveFixedDt) {
        newdt = cfl*data.ComputeTimestep();
        #ifdef WITH_MPI
          if(idfx::psize>1) {
            MPI_SAFE_CALL(MPI_Iallreduce(MPI_IN_PLACE, &newdt, 1, realMPI, MPI_MIN, MPI_COMM_WORLD,
                                        &dtReduce));
          }
        #endif
      }
    }

    // Is this not the first stage?
    if(stage>0) {
      // do the partial evolution required by the multi-step
      real wcs=wc[stage-1];
      real w0s=w0[stage-1];
      data.states["current"].AddAndStore(wcs, w0s, data.states["begin"]);

      // update t
      data.t = wcs*data.t + w0s*t0;
    }
    // Shift solution according to fargo if this is our last stage
    if(data.haveFargo && stage==nstages-1) {
      data.fargo.ShiftSolution(t0,data.dt);
    }

    // Coarsen conservative variables once they have been evolved
    if(data.haveGridCoarsening) {
      data.Coarsen();
    }

    // Back to using Vc
    data.hydro.ConvertConsToPrim();

    // Add back fargo velocity so that boundary conditions are applied on the total V
    if(data.haveFargo) data.fargo.AddVelocity(data.t);
  }
  // END STAGES LOOP                             //

  // Wait for dt MPI reduction
#ifdef WITH_MPI
  if(!haveFixedDt && idfx::psize>1) {
    MPI_SAFE_CALL(MPI_Wait(&dtReduce, MPI_STATUS_IGNORE));
  }
#endif

  if(haveRKL && (ncycles%2)==0) {    // Runge-Kutta-Legendre cycle
    rkl.Cycle();
  }

  // Coarsen the grid
  if(data.haveGridCoarsening) {
    data.Coarsen();
  }
  // Update current time (should have already been done, but this gets rid of roundoff errors)
  data.t=t0+data.dt;

  if(haveRKL) {
    // update next time step
    real tt = newdt/rkl.dt;
    newdt *= std::fmin(ONE_F, rkl.rmax_par/(tt));
  }

  // Next time step
  if(!haveFixedDt) {
    if(newdt>cflMaxVar*data.dt) {
      data.dt=cflMaxVar*data.dt;
    } else {
      if(ncycles==0 && newdt < 0.5*data.dt) {
        std::stringstream msg;
        msg << "Your guessed first_dt is too large. My next dt=" << newdt << std::endl;
        msg << "Try to reduce first_dt in the ini file.";
        IDEFIX_ERROR(msg);
      }
      data.dt=newdt;
    }
    if(data.dt < 1e-15) {
      std::stringstream msg;
      msg << "dt = " << data.dt << " is too small.";
      throw std::runtime_error(msg.str());
    }
  } else {
    data.dt = fixedDt;
  }


  ncycles++;

  idfx::popRegion();
}



int64_t TimeIntegrator::GetNCycles() {
  return(ncycles);
}

// Check whether our maximumruntime has been reached. Reduce the results on all of the cores
// to make sure they stop simultaneously even if running time are not perfectly in sync
bool TimeIntegrator::CheckForMaxRuntime() {
  idfx::pushRegion("TimeIntegrator::CheckForMaxRuntime");
  // if maxRuntime is negative, this function is disabled (default)
  if(this->maxRuntime < 0) {
    idfx::popRegion();
    return(false);
  }

  double runtime = timer.seconds();
  bool runtimeReached{false};
#ifdef WITH_MPI
  int runtimeValue = 0;
  if(runtime >= this->maxRuntime) runtimeValue = 1;
  MPI_Bcast(&runtimeValue, 1, MPI_INT, 0, MPI_COMM_WORLD);
  runtimeReached = runtimeValue > 0;
#else
  runtimeReached = runtime >= this->maxRuntime;
#endif
  if(runtimeReached) {
    idfx::cout << "TimeIntegrator:CheckForMaxRuntime: Maximum runtime reached."
               << std::endl;
  }
  idfx::popRegion();
  return(runtimeReached);
}

void TimeIntegrator::ShowConfig() {
  if(nstages==1) {
    idfx::cout << "TimeIntegrator: using 1st Order (EULER) integrator." << std::endl;
  } else if(nstages==2) {
    idfx::cout << "TimeIntegrator: using 2nd Order (RK2) integrator." << std::endl;
  } else if(nstages==3) {
    idfx::cout << "TimeIntegrator: using 3rd Order (RK3) integrator." << std::endl;
  } else {
    IDEFIX_ERROR("Unknown time integrator");
  }
  if(haveFixedDt) {
    idfx::cout << "TimeIntegrator: Using fixed dt=" << fixedDt << ". Ignoring CFL and first_dt."
              << std::endl;
  } else {
    idfx::cout << "TimeIntegrator: Using adaptive dt with CFL=" << cfl << " ." << std::endl;
  }
  if(maxRuntime>0) {
    idfx::cout << "TimeIntegrator: will stop after " << maxRuntime/3600 << " hours." << std::endl;
  }

  if(haveRKL) {
    rkl.ShowConfig();
  }
}