ExplicitMethods.f90 Source File


Source Code

!
!////////////////////////////////////////////////////////////////////////
!
!      RK integrators for DG approximation to conservation
!      laws in 3D
!
!////////////////////////////////////////////////////////////////////////
!
#include "Includes.h"
MODULE ExplicitMethods
   USE SMConstants
   use HexMeshClass
   use TimeIntegratorDefinitions
   use DGSEMClass, only: ComputeTimeDerivative_f
   use ParticlesClass
   use PhysicsStorage, only: CTD_IGNORE_MODE
   IMPLICIT NONE

   private
   public   EULER_NAME, RK3_NAME, RK5_NAME, OPTRK_NAME, SSPRK33_NAME, SSPRK43_NAME, EULER_RK3_NAME
   public   EULER_KEY, RK3_KEY, RK5_KEY, OPTRK_KEY, SSPRK33_KEY, SSPRK43_KEY, EULER_RK3_KEY
   public   TakeExplicitEulerStep, TakeRK3Step, TakeRK5Step, TakeRKOptStep
   public   TakeSSPRK33Step, TakeSSPRK43Step, TakeEulerRK3Step
   public   Enable_CTD_AFTER_STEPS, Enable_limiter, CTD_AFTER_STEPS, LIMITED, LIMITER_MIN

   integer,  protected :: eBDF_order = 3
   logical,  protected :: CTD_AFTER_STEPS = .false.
   logical,  protected :: LIMITED = .false.
   real(RP), protected :: LIMITER_MIN = 1e-13_RP
!
!  Implemented integration methods
!  -------------------------------
!  Remember to add them to `Enable_limiter` if they support limiting (or any kind of stage
!  callbacks if that is ever implemented)
!  ---------------------------------------------------------------------------------------
   character(len=*), parameter :: EULER_NAME   = "euler"
   character(len=*), parameter :: RK3_NAME     = "rk3"
   character(len=*), parameter :: RK5_NAME     = "rk5"
   character(len=*), parameter :: OPTRK_NAME   = "optimal rk"
   character(len=*), parameter :: SSPRK33_NAME = "ssprk33"
   character(len=*), parameter :: SSPRK43_NAME = "ssprk43"
   character(len=*), parameter :: EULER_RK3_NAME = "euler rk3"

   integer, parameter :: EULER_KEY   = 1
   integer, parameter :: RK3_KEY     = 2
   integer, parameter :: RK5_KEY     = 3
   integer, parameter :: OPTRK_KEY   = 4
   integer, parameter :: SSPRK33_KEY = 5
   integer, parameter :: SSPRK43_KEY = 6
   integer, parameter :: EULER_RK3_KEY = 7
!========
 CONTAINS
!
!///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
!
!  ------------------------------
!  Routine for taking an explicit Euler - RK3 step depending on the polynomial order of each element.
!  ------------------------------
 SUBROUTINE TakeEulerRK3Step( mesh, particles, t, deltaT, ComputeTimeDerivative, dt_vec, dts, global_dt, iter)
!
!     ----------------------------------
!     Williamson's 3rd order Runge-Kutta
!     ----------------------------------
!
      IMPLICIT NONE
!
!     -----------------
!     Input parameters:
!     -----------------
!
      type(HexMesh)      :: mesh
#if defined(FLOW) || defined(SCALAR)
      type(Particles_t)  :: particles
#else
      logical            :: particles
#endif
      REAL(KIND=RP)   :: t, deltaT, tk
      real(kind=RP), allocatable, dimension(:), intent(in), optional :: dt_vec
      procedure(ComputeTimeDerivative_f)    :: ComputeTimeDerivative
      logical, intent(in), optional :: dts
      real(kind=RP), intent(in), optional :: global_dt
      integer, intent(in), optional :: iter
!
!     ---------------
!     Local variables
!     ---------------
!
      REAL(KIND=RP), DIMENSION(3) :: a = (/0.0_RP       , -5.0_RP /9.0_RP , -153.0_RP/128.0_RP/)
      REAL(KIND=RP), DIMENSION(3) :: b = (/0.0_RP       ,  1.0_RP /3.0_RP ,    3.0_RP/4.0_RP  /)
      REAL(KIND=RP), DIMENSION(3) :: c = (/1.0_RP/3.0_RP,  15.0_RP/16.0_RP,    8.0_RP/15.0_RP /)


      INTEGER :: i, j, k, id, eID
      integer :: interval

      interval = 10 !Compute time derivative each interval for those elements whose pmax = 1

      if (present(dt_vec)) then   
         
         do k = 1,3
            tk = t + b(k)*deltaT
            if ((k==1) .and. (mod(iter, interval) == 0)) then
               call ComputeTimeDerivative( mesh, particles, tk, CTD_IGNORE_MODE)
            else
               call ComputeTimeDerivative( mesh, particles, tk, CTD_IGNORE_MODE, .true.)
            endif
            if ( present(dts) ) then
               if (dts) call ComputePseudoTimeDerivative(mesh, tk, global_dt)
            end if

            if (k==1) then
!$omp parallel do schedule(runtime) private(eID)
               do id = 1, SIZE( mesh % LO_Elements )
                  ! Explicit Euler
                  eID = mesh % LO_Elements(id)
#ifdef FLOW 
                  mesh % elements(eID) % storage % Q = mesh % elements(eID) % storage % Q + dt_vec(eID)*mesh % elements(eID) % storage % QDot
#endif

#if (defined(CAHNHILLIARD)) && (!defined(FLOW))
                  mesh % elements(eID) % storage % c = mesh % elements(eID) % storage % c + dt_vec(eID)*mesh % elements(eID) % storage % cDot
#endif
#ifdef SCALAR
                  mesh % elements(eID) % storage % slr = mesh % elements(eID) % storage % slr + dt_vec(eID)*mesh % elements(eID) % storage % slrDot
#endif
               end do ! id
!$omp end parallel do
            end if

!$omp parallel do schedule(runtime) private(eID)
            do id = 1, SIZE( mesh % HO_Elements )
               ! Runge-Kutta 3
               eID = mesh % HO_Elements(id)
#ifdef FLOW
               mesh % elements(eID) % storage % G_NS = a(k)* mesh % elements(eID) % storage % G_NS  +              mesh % elements(eID) % storage % QDot
               mesh % elements(eID) % storage % Q =       mesh % elements(eID) % storage % Q  + c(k)*dt_vec(eID)* mesh % elements(eID) % storage % G_NS
#endif

#if (defined(CAHNHILLIARD)) && (!defined(FLOW))
               mesh % elements(eID) % storage % G_CH = a(k)*mesh % elements(eID) % storage % G_CH + mesh % elements(eID) % storage % cDot
               mesh % elements(eID) % storage % c    = mesh % elements(eID) % storage % c         + c(k)*dt_vec(eID)* mesh % elements(eID) % storage % G_CH
#endif

#ifdef SCALAR
               mesh % elements(eID) % storage % G_SLR = a(k)*mesh % elements(eID) % storage % G_SLR   + mesh % elements(eID) % storage % slrDot
               mesh % elements(eID) % storage % slr   = mesh % elements(eID) % storage % slr          + c(k)*dt_vec(eID)* mesh % elements(eID) % storage % G_SLR
#endif
            end do ! id
!$omp end parallel do

         end do ! k

      else !not present dt_vec

         do k = 1,3
            tk = t + b(k)*deltaT
            if ((k==1) .and. (mod(iter, interval) == 0)) then
               call ComputeTimeDerivative( mesh, particles, tk, CTD_IGNORE_MODE)
            else
               call ComputeTimeDerivative( mesh, particles, tk, CTD_IGNORE_MODE, .true.)
            endif
            if ( present(dts) ) then
               if (dts) call ComputePseudoTimeDerivative(mesh, tk, global_dt)
            end if

            if (k==1) then
!$omp parallel do schedule(runtime) private(eID)
               do id = 1, SIZE( mesh % LO_Elements )
                  ! Explicit Euler
                  eID = mesh % LO_Elements(id)
#ifdef FLOW 
                  mesh % elements(eID) % storage % Q = mesh % elements(eID) % storage % Q + deltaT*mesh % elements(eID) % storage % QDot
#endif

#if (defined(CAHNHILLIARD)) && (!defined(FLOW))
                  mesh % elements(eID) % storage % c = mesh % elements(eID) % storage % c + deltaT*mesh % elements(eID) % storage % cDot
#endif

#ifdef SCALAR
                  mesh % elements(eID) % storage % slr = mesh % elements(eID) % storage % slr + deltaT*mesh % elements(eID) % storage % slrDot
#endif
               end do ! id
!$omp end parallel do
            end if

!$omp parallel do schedule(runtime) private(eID)
            do id = 1, SIZE( mesh % HO_Elements )
               ! Runge-Kutta 3
               eID = mesh % HO_Elements(id)
#ifdef FLOW
               mesh % elements(eID) % storage % G_NS = a(k)* mesh % elements(eID) % storage % G_NS  +              mesh % elements(eID) % storage % QDot
               mesh % elements(eID) % storage % Q =       mesh % elements(eID) % storage % Q  + c(k)*deltaT* mesh % elements(eID) % storage % G_NS
#endif

#if (defined(CAHNHILLIARD)) && (!defined(FLOW))
               mesh % elements(eID) % storage % G_CH = a(k)*mesh % elements(eID) % storage % G_CH + mesh % elements(eID) % storage % cDot
               mesh % elements(eID) % storage % c    = mesh % elements(eID) % storage % c         + c(k)*deltaT* mesh % elements(eID) % storage % G_CH
#endif

#ifdef SCALAR
               mesh % elements(eID) % storage % G_SLR = a(k)*mesh % elements(eID) % storage % G_SLR   + mesh % elements(eID) % storage % slrDot
               mesh % elements(eID) % storage % slr   = mesh % elements(eID) % storage % slr          + c(k)*deltaT* mesh % elements(eID) % storage % G_SLR

#endif

            end do ! id
!$omp end parallel do

         end do ! k

      end if
!
!     To obtain the updated residuals
      if ( CTD_AFTER_STEPS ) CALL ComputeTimeDerivative( mesh, particles, t+deltaT, CTD_IGNORE_MODE)

      call checkForNan(mesh, t)

   END SUBROUTINE TakeEulerRK3Step

!  ------------------------------
!  Routine for taking a RK3 step.
!  ------------------------------
   SUBROUTINE TakeRK3Step( mesh, particles, t, deltaT, ComputeTimeDerivative, dt_vec, dts, global_dt, iter)
!
!     ----------------------------------
!     Williamson's 3rd order Runge-Kutta
!     ----------------------------------
!
      IMPLICIT NONE
!
!     -----------------
!     Input parameters:
!     -----------------
!
      type(HexMesh)      :: mesh
#if defined(FLOW) || defined(SCALAR)
      type(Particles_t)  :: particles
#else
      logical            :: particles
#endif
      REAL(KIND=RP)   :: t, deltaT, tk
      real(kind=RP), allocatable, dimension(:), intent(in), optional :: dt_vec
      procedure(ComputeTimeDerivative_f)    :: ComputeTimeDerivative
      logical, intent(in), optional :: dts
      real(kind=RP), intent(in), optional :: global_dt
      integer, intent(in), optional :: iter
!
!     ---------------
!     Local variables
!     ---------------
!
      REAL(KIND=RP), DIMENSION(3) :: a = (/0.0_RP       , -5.0_RP /9.0_RP , -153.0_RP/128.0_RP/)
      REAL(KIND=RP), DIMENSION(3) :: b = (/0.0_RP       ,  1.0_RP /3.0_RP ,    3.0_RP/4.0_RP  /)
      REAL(KIND=RP), DIMENSION(3) :: c = (/1.0_RP/3.0_RP,  15.0_RP/16.0_RP,    8.0_RP/15.0_RP /)


      INTEGER :: i, j, k, id

      if (present(dt_vec)) then   
         
         do k = 1,3
            tk = t + b(k)*deltaT
            call ComputeTimeDerivative( mesh, particles, tk, CTD_IGNORE_MODE)
            if ( present(dts) ) then
               if (dts) call ComputePseudoTimeDerivative(mesh, tk, global_dt)
            end if

!$omp parallel do schedule(runtime)
            do id = 1, SIZE( mesh % elements )
#ifdef FLOW
                  mesh % elements(id) % storage % G_NS = a(k)* mesh % elements(id) % storage % G_NS  +              mesh % elements(id) % storage % QDot
                  mesh % elements(id) % storage % Q =       mesh % elements(id) % storage % Q  + c(k)*dt_vec(id)* mesh % elements(id) % storage % G_NS
#endif

#if (defined(CAHNHILLIARD)) && (!defined(FLOW))
                  mesh % elements(id) % storage % G_CH = a(k)*mesh % elements(id) % storage % G_CH + mesh % elements(id) % storage % cDot
                  mesh % elements(id) % storage % c    = mesh % elements(id) % storage % c         + c(k)*dt_vec(id)* mesh % elements(id) % storage % G_CH
#endif

#ifdef SCALAL
                  mesh % elements(id) % storage % G_SLR = a(k)*mesh % elements(id) % storage % G_SLR + mesh % elements(id) % storage % slrDot
                  mesh % elements(id) % storage % slr   = mesh % elements(id) % storage % slr         + c(k)*dt_vec(id)* mesh % elements(id) % storage % G_SLR
#endif

            end do ! id
!$omp end parallel do

         end do ! k

      else

         do k = 1,3
            tk = t + b(k)*deltaT
            call ComputeTimeDerivative( mesh, particles, tk, CTD_IGNORE_MODE)
            if ( present(dts) ) then
               if (dts) call ComputePseudoTimeDerivative(mesh, tk, global_dt)
            end if

!$omp parallel do schedule(runtime)
            do id = 1, SIZE( mesh % elements )
#ifdef FLOW
                  mesh % elements(id) % storage % G_NS = a(k)* mesh % elements(id) % storage % G_NS  +              mesh % elements(id) % storage % QDot
                  mesh % elements(id) % storage % Q =       mesh % elements(id) % storage % Q  + c(k)*deltaT* mesh % elements(id) % storage % G_NS
#endif

#if (defined(CAHNHILLIARD)) && (!defined(FLOW))
                  mesh % elements(id) % storage % G_CH = a(k)*mesh % elements(id) % storage % G_CH + mesh % elements(id) % storage % cDot
                  mesh % elements(id) % storage % c    = mesh % elements(id) % storage % c         + c(k)*deltaT* mesh % elements(id) % storage % G_CH
#endif

#ifdef SCALAR
                  mesh % elements(id) % storage % G_SLR = a(k)*mesh % elements(id) % storage % G_SLR + mesh % elements(id) % storage % slrDot
                  mesh % elements(id) % storage % slr   = mesh % elements(id) % storage % slr        + c(k)*deltaT* mesh % elements(id) % storage % G_SLR

#endif
            end do ! id
!$omp end parallel do

         end do ! k

      end if
!
!     To obtain the updated residuals
      if ( CTD_AFTER_STEPS ) CALL ComputeTimeDerivative( mesh, particles, t+deltaT, CTD_IGNORE_MODE)

      call checkForNan(mesh, t)

   END SUBROUTINE TakeRK3Step

   SUBROUTINE TakeRK5Step( mesh, particles, t, deltaT, ComputeTimeDerivative , dt_vec, dts, global_dt, iter)
!
!        *****************************************************************************************
!           These coefficients have been extracted from the paper: "Fourth-Order 2N-Storage
!          Runge-Kutta Schemes", written by Mark H. Carpented and Christopher A. Kennedy
!        *****************************************************************************************
!
      implicit none
      type(HexMesh)                   :: mesh
#if defined(FLOW) || defined(SCALAR)
      type(Particles_t)  :: particles
#else
      logical            :: particles
#endif
      REAL(KIND=RP)                   :: t, deltaT, tk
      procedure(ComputeTimeDerivative_f)      :: ComputeTimeDerivative
      real(kind=RP), allocatable, dimension(:), intent(in), optional :: dt_vec
      logical, intent(in), optional :: dts
      real(kind=RP), intent(in), optional :: global_dt
      integer, intent(in), optional :: iter
!
!     ---------------
!     Local variables
!     ---------------
!
      integer                    :: id, i, j, k
      integer, parameter         :: N_STAGES = 5
      real(kind=RP), parameter  :: a(N_STAGES) = [0.0_RP , -0.4178904745_RP, -1.192151694643_RP ,     -1.697784692471_RP , -1.514183444257_RP ]
      real(kind=RP), parameter  :: b(N_STAGES) = [0.0_RP , 0.1496590219993_RP , 0.3704009573644_RP , 0.6222557631345_RP , 0.9582821306748_RP ]
      real(kind=RP), parameter  :: c(N_STAGES) = [0.1496590219993_RP , 0.3792103129999_RP , 0.8229550293869_RP , 0.6994504559488_RP , 0.1530572479681_RP]


      if (present(dt_vec)) then 

      DO k = 1, N_STAGES

         tk = t + b(k)*deltaT
         CALL ComputeTimeDerivative( mesh, particles, tk, CTD_IGNORE_MODE)
         if ( present(dts) ) then
            if (dts) call ComputePseudoTimeDerivative(mesh, tk, global_dt)
         end if

!$omp parallel do schedule(runtime)
         DO id = 1, SIZE( mesh % elements )
#ifdef FLOW
             mesh % elements(id) % storage % G_NS = a(k)* mesh % elements(id) % storage % G_NS  +              mesh % elements(id) % storage % QDot
             mesh % elements(id) % storage % Q =       mesh % elements(id) % storage % Q  + c(k)*dt_vec(id)* mesh % elements(id) % storage % G_NS
#endif

#if (defined(CAHNHILLIARD)) && (!defined(FLOW))
            mesh % elements(id) % storage % G_CH = a(k)*mesh % elements(id) % storage % G_CH + mesh % elements(id) % storage % cDot
            mesh % elements(id) % storage % c    = mesh % elements(id) % storage % c         + c(k)*dt_vec(id)* mesh % elements(id) % storage % G_CH
#endif
         END DO
!$omp end parallel do

      END DO

      else

      DO k = 1, N_STAGES

         tk = t + b(k)*deltaT
         CALL ComputeTimeDerivative( mesh, particles, tk, CTD_IGNORE_MODE)
         if ( present(dts) ) then
            if (dts) call ComputePseudoTimeDerivative(mesh, tk, global_dt)
         end if

!$omp parallel do schedule(runtime)
         DO id = 1, SIZE( mesh % elements )
#ifdef FLOW
             mesh % elements(id) % storage % G_NS = a(k)* mesh % elements(id) % storage % G_NS  +              mesh % elements(id) % storage % QDot
             mesh % elements(id) % storage % Q =       mesh % elements(id) % storage % Q  + c(k)*deltaT* mesh % elements(id) % storage % G_NS
#endif

#if (defined(CAHNHILLIARD)) && (!defined(FLOW))
            mesh % elements(id) % storage % G_CH = a(k)*mesh % elements(id) % storage % G_CH + mesh % elements(id) % storage % cDot
            mesh % elements(id) % storage % c    = mesh % elements(id) % storage % c         + c(k)*deltaT* mesh % elements(id) % storage % G_CH
#endif
         END DO
!$omp end parallel do

      END DO

      end if

      call checkForNan(mesh, t)
!
!     To obtain the updated residuals
      if ( CTD_AFTER_STEPS ) CALL ComputeTimeDerivative( mesh, particles, tk, CTD_IGNORE_MODE)

   end subroutine TakeRK5Step
!
!///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
!
!  ------------------------------
!  Routine for taking a SSP-RK 3-stage 3rd-order step.
!  ------------------------------
   subroutine TakeSSPRK33Step( mesh, particles, t, deltaT, ComputeTimeDerivative, dt_vec, dts, global_dt, iter)
      implicit none
!
!     ----------------
!     Input parameters
!     ----------------
!
      type(HexMesh)                               :: mesh
#if defined(FLOW) || defined(SCALAR)
      type(Particles_t)                           :: particles
#else
      logical                                     :: particles
#endif
      real(RP)                                    :: t
      real(RP)                                    :: deltaT
      procedure(ComputeTimeDerivative_f)          :: ComputeTimeDerivative
      real(RP), allocatable, optional, intent(in) :: dt_vec(:)
      logical,               optional, intent(in) :: dts
      real(RP),              optional, intent(in) :: global_dt
      integer,               optional, intent(in) :: iter
!
!     ---------------
!     Local variables
!     ---------------
!
      real(RP), parameter :: a(3) = [1.0_RP, 3.0_RP/4.0_RP, 1.0_RP/3.0_RP]
      real(RP), parameter :: b(3) = [0.0_RP, 1.0_RP/4.0_RP, 2.0_RP/3.0_RP]
      real(RP), parameter :: c(3) = [1.0_RP, 1.0_RP/4.0_RP, 2.0_RP/3.0_RP]
      real(RP), parameter :: d(3) = [0.0_RP, 1.0_RP,        0.5_RP]
      real(RP) :: tk
      integer  :: i, j, k, id


      if (present(dt_vec)) then

!$omp parallel do
         do id = 1, size(mesh % elements)
#if defined(FLOW)
            mesh % elements(id) % storage % G_NS = mesh % elements(id) % storage % Q
#elif defined(CAHNHILLIARD)
            mesh % elements(id) % storage % G_CH = mesh % elements(id) % storage % c
#endif
         end do
!$omp end parallel do

         do k = 1, 3
            tk = t + d(k)*deltaT
            call ComputeTimeDerivative( mesh, particles, tk, CTD_IGNORE_MODE)
            if ( present(dts) ) then
               if (dts) call ComputePseudoTimeDerivative(mesh, tk, global_dt)
            end if

!$omp parallel do
            do id = 1, size( mesh % elements )
#if defined(FLOW)
               mesh % elements(id) % storage % Q = a(k) * mesh % elements(id) % storage % G_NS &
                                                 + b(k) * mesh % elements(id) % storage % Q    &
                                                 + c(k) * dt_vec(id) * mesh % elements(id) % storage % Qdot
#elif defined(CAHNHILLIARD)
               mesh % elements(id) % storage % c = a(k) * mesh % elements(id) % storage % G_CH &
                                                 + b(k) * mesh % elements(id) % storage % c    &
                                                 + c(k) * dt_vec(id) * mesh % elements(id) % storage % cDot
#endif
            end do ! id
!$omp end parallel do

            if (LIMITED) then
               call stage_limiter(mesh)
            end if

         end do ! k

      else

!$omp parallel do
         do id = 1, size(mesh % elements)
#if defined(FLOW)
            mesh % elements(id) % storage % G_NS = mesh % elements(id) % storage % Q
#elif defined(CAHNHILLIARD)
            mesh % elements(id) % storage % G_CH = mesh % elements(id) % storage % c
#endif
         end do
!$omp end parallel do

         do k = 1, 3
            tk = t + d(k) * deltaT
            call ComputeTimeDerivative( mesh, particles, tk, CTD_IGNORE_MODE)
            if ( present(dts) ) then
               if (dts) call ComputePseudoTimeDerivative(mesh, tk, global_dt)
            end if

!$omp parallel do
            do id = 1, size( mesh % elements )
#if defined(FLOW)
               mesh % elements(id) % storage % Q = a(k) * mesh % elements(id) % storage % G_NS &
                                                 + b(k) * mesh % elements(id) % storage % Q    &
                                                 + c(k) * deltaT * mesh % elements(id) % storage % Qdot
#elif defined(CAHNHILLIARD)
               mesh % elements(id) % storage % c = a(k) * mesh % elements(id) % storage % G_CH &
                                                 + b(k) * mesh % elements(id) % storage % c    &
                                                 + c(k) * deltaT * mesh % elements(id) % storage % cDot
#endif
            end do ! id
!$omp end parallel do

            if (LIMITED) then
               call stage_limiter(mesh)
            end if

         end do ! k

      end if
!
!     To obtain the updated residuals
      if ( CTD_AFTER_STEPS ) CALL ComputeTimeDerivative( mesh, particles, t+deltaT, CTD_IGNORE_MODE)

      call checkForNan(mesh, t)

   END SUBROUTINE TakeSSPRK33Step
!
!///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
!
!  ------------------------------
!  Routine for taking a SSP-RK 4-stage 3rd-order step.
!  ------------------------------
   subroutine TakeSSPRK43Step( mesh, particles, t, deltaT, ComputeTimeDerivative, dt_vec, dts, global_dt, iter)
      implicit none
!
!     ----------------
!     Input parameters
!     ----------------
!
      type(HexMesh)                               :: mesh
#if defined(FLOW) || defined(SCALAR)
      type(Particles_t)                           :: particles
#else
      logical                                     :: particles
#endif
      real(RP)                                    :: t
      real(RP)                                    :: deltaT
      procedure(ComputeTimeDerivative_f)          :: ComputeTimeDerivative
      real(RP), allocatable, optional, intent(in) :: dt_vec(:)
      logical,               optional, intent(in) :: dts
      real(RP),              optional, intent(in) :: global_dt
      integer,               optional, intent(in) :: iter
      
!
!     ---------------
!     Local variables
!     ---------------
!
      real(RP), parameter :: a(4) = [1.0_RP, 0.0_RP, 2.0_RP/3.0_RP, 0.0_RP]
      real(RP), parameter :: b(4) = [0.0_RP, 1.0_RP, 1.0_RP/3.0_RP, 1.0_RP]
      real(RP), parameter :: c(4) = [0.5_RP, 0.5_RP, 1.0_RP/6.0_RP, 0.5_RP]
      real(RP), parameter :: d(4) = [0.0_RP, 0.5_RP, 1.0_RP,        0.5_RP]
      real(RP) :: tk
      integer  :: i, j, k, id


      if (present(dt_vec)) then

!$omp parallel do
         do id = 1, size(mesh % elements)
#if defined(FLOW)
            mesh % elements(id) % storage % G_NS = mesh % elements(id) % storage % Q
#elif defined(CAHNHILLIARD)
            mesh % elements(id) % storage % G_CH = mesh % elements(id) % storage % c
#endif
         end do
!$omp end parallel do

         do k = 1, 4
            tk = t + d(k)*deltaT
            call ComputeTimeDerivative( mesh, particles, tk, CTD_IGNORE_MODE)
            if ( present(dts) ) then
               if (dts) call ComputePseudoTimeDerivative(mesh, tk, global_dt)
            end if

!$omp parallel do
            do id = 1, size( mesh % elements )
#if defined(FLOW)
               mesh % elements(id) % storage % Q = a(k) * mesh % elements(id) % storage % G_NS &
                                                 + b(k) * mesh % elements(id) % storage % Q    &
                                                 + c(k) * dt_vec(id) * mesh % elements(id) % storage % Qdot
#elif defined(CAHNHILLIARD)
               mesh % elements(id) % storage % c = a(k) * mesh % elements(id) % storage % G_CH &
                                                 + b(k) * mesh % elements(id) % storage % c    &
                                                 + c(k) * dt_vec(id) * mesh % elements(id) % storage % cDot
#endif
            end do ! id
!$omp end parallel do

            if (LIMITED) then
               call stage_limiter(mesh)
            end if

         end do ! k

      else

!$omp parallel do
         do id = 1, size(mesh % elements)
#if defined(FLOW)
            mesh % elements(id) % storage % G_NS = mesh % elements(id) % storage % Q
#elif defined(CAHNHILLIARD)
            mesh % elements(id) % storage % G_CH = mesh % elements(id) % storage % c
#endif
         end do
!$omp end parallel do

         do k = 1, 4
            tk = t + d(k) * deltaT
            call ComputeTimeDerivative( mesh, particles, tk, CTD_IGNORE_MODE)
            if ( present(dts) ) then
               if (dts) call ComputePseudoTimeDerivative(mesh, tk, global_dt)
            end if

!$omp parallel do
            do id = 1, size( mesh % elements )
#if defined(FLOW)
               mesh % elements(id) % storage % Q = a(k) * mesh % elements(id) % storage % G_NS &
                                                 + b(k) * mesh % elements(id) % storage % Q    &
                                                 + c(k) * deltaT * mesh % elements(id) % storage % Qdot
#elif defined(CAHNHILLIARD)
               mesh % elements(id) % storage % c = a(k) * mesh % elements(id) % storage % G_CH &
                                                 + b(k) * mesh % elements(id) % storage % c    &
                                                 + c(k) * deltaT * mesh % elements(id) % storage % cDot
#endif

            end do ! id
!$omp end parallel do

            if (LIMITED) then
               call stage_limiter(mesh)
            end if

         end do ! k

      end if
!
!     To obtain the updated residuals
      if ( CTD_AFTER_STEPS ) CALL ComputeTimeDerivative( mesh, particles, t+deltaT, CTD_IGNORE_MODE)

      call checkForNan(mesh, t)

   END SUBROUTINE TakeSSPRK43Step

   SUBROUTINE TakeExplicitEulerStep( mesh, particles, t, deltaT, ComputeTimeDerivative , dt_vec, dts, global_dt, iter)
!
!        *****************************************************************************************
!           These coefficients have been extracted from the paper: "Fourth-Order 2N-Storage
!          Runge-Kutta Schemes", written by Mark H. Carpented and Christopher A. Kennedy
!        *****************************************************************************************
!
      implicit none
      type(HexMesh)                   :: mesh
#if defined(FLOW) || defined(SCALAR)
      type(Particles_t)  :: particles
#else
      logical            :: particles
#endif
      REAL(KIND=RP)                   :: t, deltaT, tk
      procedure(ComputeTimeDerivative_f)      :: ComputeTimeDerivative
      real(kind=RP), allocatable, dimension(:), intent(in), optional :: dt_vec
      logical, intent(in), optional :: dts
      real(kind=RP), intent(in), optional :: global_dt
      integer, intent(in), optional :: iter
!
!     ---------------
!     Local variables
!     ---------------
!
      integer                    :: id, k

      CALL ComputeTimeDerivative( mesh, particles, t, CTD_IGNORE_MODE)
      if ( present(dts) ) then
         if (dts) call ComputePseudoTimeDerivative(mesh, tk, global_dt)
      end if

      if (present(dt_vec)) then
!$omp parallel do schedule(runtime)
         DO id = 1, SIZE( mesh % elements )
            mesh % elements(id) % storage % Q = mesh % elements(id) % storage % Q + dt_vec(id)*mesh % elements(id) % storage % QDot
         END DO
!$omp end parallel do
      else
!$omp parallel do schedule(runtime)
         DO id = 1, SIZE( mesh % elements )
            mesh % elements(id) % storage % Q = mesh % elements(id) % storage % Q + deltaT*mesh % elements(id) % storage % QDot
         END DO
!$omp end parallel do
      end if

      call checkForNan(mesh, t)
!
!     To obtain the updated residuals
      if ( CTD_AFTER_STEPS ) CALL ComputeTimeDerivative( mesh, particles, tk, CTD_IGNORE_MODE)

   end subroutine TakeExplicitEulerStep

   subroutine TakeExplicitBDFStep(mesh, particles, t, deltaT, ComputeTimeDerivative)
      implicit none
      type(HexMesh)                      :: mesh
#if defined(FLOW) || defined(SCALAR)
      type(Particles_t)                  :: particles
#else
      logical                            :: particles
#endif
      REAL(KIND=RP)                      :: t, deltaT, tk
      procedure(ComputeTimeDerivative_f) :: ComputeTimeDerivative
!
!     ---------------
!     Local variables
!     ---------------
!
      integer                  :: id
      real(kind=RP), parameter :: invGamma1 = 1.0_RP, invGamma2 = 2.0_RP/3.0_RP, invGamma3 = 6.0_RP / 11.0_RP
      logical, save            :: isFirst = .true., isSecond = .false., isThird = .false.

      if (isThird) then
!
!        Perform the third order stages
!        ------------------------------
!$omp parallel do schedule(runtime)
         do id = 1, size(mesh % elements)
!           Set y^{*,n+1} in Q and downgrade y^n and y^{n-1}
!           ------------------------------------------------
            mesh % elements(id) % storage % QDot = mesh % elements(id) % storage % prevQ(2) % Q
            mesh % elements(id) % storage % prevQ(2) % Q = mesh % elements(id) % storage % prevQ(1) % Q
            mesh % elements(id) % storage % prevQ(1) % Q = mesh % elements(id) % storage % Q
            mesh % elements(id) % storage % Q =   3.0_RP * mesh % elements(id) % storage % prevQ(1) % Q &
                                                  - 3.0_RP * mesh % elements(id) % storage % prevQ(2) % Q &
                                                  + mesh % elements(id) % storage % QDot
         end do
!$omp end parallel do
!
!        Compute QDot
!        ------------
         CALL ComputeTimeDerivative( mesh, particles, tk, CTD_IGNORE_MODE)
!
!        Perform the time-step
!        ---------------------
!$omp parallel do schedule(runtime)
         do id = 1, size(mesh % elements)
            mesh % elements(id) % storage % Q =   2.0_RP * mesh % elements(id) % storage % prevQ(1) % Q &
                                                - 0.5_RP * mesh % elements(id) % storage % prevQ(2) % Q &
                                                + (1.0_RP/3.0_RP) * mesh % elements(id) % storage % Q &
                                                + deltaT * mesh % elements(id) % storage % QDot
            mesh % elements(id) % storage % Q = invGamma3 * mesh % elements(id) % storage % Q
         end do
!$omp end parallel do


      elseif (isSecond) then
!
!        Perform the second order stages
!        -------------------------------
         if (eBDF_ORDER > 2) then
!$omp parallel do schedule(runtime)
            do id = 1, size(mesh % elements)
!
!              Set for the previous solution
!              -----------------------------
               mesh % elements(id) % storage % prevQ(2) % Q = mesh % elements(id) % storage % prevQ(1) % Q
            end do
!$omp end parallel do
         end if

!$omp parallel do schedule(runtime)
         do id = 1, size(mesh % elements)
!
!           Set y^{*,n+1} in Q
!           ------------------
            mesh % elements(id) % storage % Q = 2.0_RP*mesh % elements(id) % storage % Q -mesh % elements(id) % storage % prevQ(1) % Q
!
!           Set y^{n} in prevQ
!           --------------------
            mesh % elements(id) % storage % prevQ(1) % Q = 0.5_RP*(mesh % elements(id) % storage % Q + mesh % elements(id) % storage % prevQ(1) % Q)

         end do
!$omp end parallel do
!
!        Compute QDot
!        ------------
         CALL ComputeTimeDerivative( mesh, particles, tk, CTD_IGNORE_MODE)
!
!        Perform the time-step
!        ---------------------
!$omp parallel do schedule(runtime)
         do id = 1, size(mesh % elements)
            mesh % elements(id) % storage % Q =   mesh % elements(id) % storage % prevQ(1) % Q + 0.5_RP*mesh % elements(id) % storage % Q  &
                                                + deltaT * mesh % elements(id) % storage % QDot
            mesh % elements(id) % storage % Q = invGamma2 * mesh % elements(id) % storage % Q

         end do
!$omp end parallel do

         if (eBDF_ORDER > 1 ) then
!
!           Move to third order
!           -------------------
            isFirst = .false.
            isSecond = .false.
            isThird = .true.
         end if

      elseif (isFirst) then
!
!        Perform the first order stages
!        ------------------------------
         CALL ComputeTimeDerivative( mesh, particles, tk, CTD_IGNORE_MODE)

!$omp parallel do schedule(runtime)
         DO id = 1, SIZE( mesh % elements )
            if (eBDF_ORDER > 1 ) then
!
!              Set for the previous solution
!              -----------------------------
               mesh % elements(id) % storage % prevQ(1) % Q = mesh % elements(id) % storage % Q

            end if

            mesh % elements(id) % storage % Q = mesh % elements(id) % storage % Q + deltaT*mesh % elements(id) % storage % QDot

         END DO
!$omp end parallel do

            if (eBDF_ORDER > 1 ) then
!
!              Move to second order
!              --------------------
               isFirst = .false.
               isSecond = .true.
            end if

      end if




   end subroutine TakeExplicitBDFStep

   subroutine Enable_CTD_AFTER_STEPS()
      implicit none
      CTD_AFTER_STEPS = .true.
   end subroutine Enable_CTD_AFTER_STEPS
!
!///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
!
   SUBROUTINE TakeRKOptStep( mesh, particles, t, deltaT, ComputeTimeDerivative , N_STAGES, dt_vec, dts, global_dt, iter)
!
!        *****************************************************************************************
!       Optimal RK coefficients from Bassi2009
!        *****************************************************************************************
!
      implicit none
      type(HexMesh)                   :: mesh
#if defined(FLOW) || defined(SCALAR)
      type(Particles_t)  :: particles
#else
      logical            :: particles
#endif
      REAL(KIND=RP)                   :: t, deltaT, tk
      procedure(ComputeTimeDerivative_f)      :: ComputeTimeDerivative
      integer, intent(in)         :: N_STAGES
      real(kind=RP), allocatable, dimension(:), intent(in), optional :: dt_vec
      logical, intent(in), optional :: dts
      real(kind=RP), intent(in), optional :: global_dt
      integer, intent(in), optional :: iter
!
!     ---------------
!     Local variables
!     ---------------
!
      integer                    :: id, i, j, k
      real(kind=RP), dimension(6,7) :: Am, Bm
      real(kind=RP) :: a(N_STAGES), b(N_STAGES)

      Bm(1,:) = (/ 0.9998596842476678, 0.5, 0.0, 0.0, 0.0, 0.0, 0.0 /)
      Bm(2,:) = (/ 0.528003175664866, 0.5193233361621609, 0.3209132144853066, 0.0, 0.0, 0.0, 0.0 /)
      Bm(3,:) = (/ 0.4062766523561424, 0.3590274668186006, 0.2782786562184366, 0.3031000737788218, 0.0, 0.0, 0.0 /)
      Bm(4,:) = (/ 0.32451232607547, 0.2850381110111294, 0.2299189950459751, 0.3245118697485674, 0.1925289659886354, 0.0, 0.0 /)
      Bm(5,:) = (/ 0.2712469842000987, 0.2506886071464794, 0.1571621623659122, 0.2281484031198761, 0.2523511867383585, 0.1918765315676819, 0.0 /)
      Bm(6,:) = (/ 0.2328811281838825, 0.2007437175473038, 0.1577268986576998, 0.2052094755795549, 0.2138853585222901, 0.192146045128098, 0.1428487872093191 /)

      Am(1,:) = (/ 0.0 / 1.0, -0.4998596842476678 / 0.5, 0.0, 0.0, 0.0, 0.0, 0.0 /)
      Am(2,:) = (/ 0.0 / 1.0, -0.1645541151603977 / 0.315637725010225, -0.20368561115193584 / 0.3209132144853066, 0.0, 0.0, 0.0, 0.0 /)
      Am(3,:) = (/ 0.0 / 1.0, -0.11076800268308828 / 0.1937832814991212, -0.1652441853194794 / 0.207607995049003, &
            -0.0706706611694336 / 0.3031000737788218, 0.0, 0.0, 0.0 /)
      Am(4,:) = (/ 0.0 / 1.0, -0.11517541944711737 / 0.1896693024156952, -0.0953688085954342 / 0.2159361297115306, &
            -0.01398286533444451 / 0.1925286952557861, -0.1319831744927813 / 0.1925289659886354, 0.0, 0.0 /)
      Am(5,:) = (/ 0.0 / 1.0, -0.056511008554826186 / 0.1229547684000589, -0.1277338387464205 / 0.1094339401033201, &
            -0.047728222262592115 / 0.1824888900957738, -0.045659513024102316 / 0.1785098941878928,-0.0738412925504657 / 0.1918765315676819, 0.0 /)
      Am(6,:) = (/ 0.0 / 1.0, -0.044038551801784204 / 0.1267393084745009, -0.0740044090728029 / 0.1061061571001407, &
            -0.051620741557559094 / 0.1525740388611983, -0.052635436718356604 / 0.1650271578073516,-0.04885820071493849 / 0.1178619741653911, &
             -0.0742840709627069 / 0.1428487872093191 /)

      a = Am(N_STAGES-1,1:N_STAGES)
      b = Bm(N_STAGES-1,1:N_STAGES)

      tk = t + deltaT

      if (present(dt_vec)) then 

      DO k = 1, N_STAGES

         CALL ComputeTimeDerivative( mesh, particles, tk, CTD_IGNORE_MODE)
         if ( present(dts) ) then
            if (dts) call ComputePseudoTimeDerivative(mesh, tk, global_dt)
         end if

!$omp parallel do schedule(runtime)
         DO id = 1, SIZE( mesh % elements )
#ifdef FLOW
             mesh % elements(id) % storage % G_NS = a(k)* mesh % elements(id) % storage % G_NS  +  dt_vec(id) * mesh % elements(id) % storage % QDot
             mesh % elements(id) % storage % Q =       mesh % elements(id) % storage % Q   +  b(k) * mesh % elements(id) % storage % G_NS
#endif

#if (defined(CAHNHILLIARD)) && (!defined(FLOW))
            mesh % elements(id) % storage % G_CH = a(k)*mesh % elements(id) % storage % G_CH  +  dt_vec(id) * mesh % elements(id) % storage % cDot
            mesh % elements(id) % storage % c    = mesh % elements(id) % storage % c          +  b(k) * mesh % elements(id) % storage % G_CH
#endif
         END DO
!$omp end parallel do

      END DO

      else

      DO k = 1, N_STAGES

         CALL ComputeTimeDerivative( mesh, particles, tk, CTD_IGNORE_MODE)
         if ( present(dts) ) then
            if (dts) call ComputePseudoTimeDerivative(mesh, tk, global_dt)
         end if

!$omp parallel do schedule(runtime)
         DO id = 1, SIZE( mesh % elements )
#ifdef FLOW
             mesh % elements(id) % storage % G_NS = a(k)* mesh % elements(id) % storage % G_NS  + deltaT * mesh % elements(id) % storage % QDot
             mesh % elements(id) % storage % Q =       mesh % elements(id) % storage % Q  + b(k) * mesh % elements(id) % storage % G_NS
#endif

#if (defined(CAHNHILLIARD)) && (!defined(FLOW))
            mesh % elements(id) % storage % G_CH = a(k)*mesh % elements(id) % storage % G_CH + deltaT * mesh % elements(id) % storage % cDot
            mesh % elements(id) % storage % c    = mesh % elements(id) % storage % c         + b(k) * mesh % elements(id) % storage % G_CH
#endif
         END DO
!$omp end parallel do

      END DO

      end if

      call checkForNan(mesh, t)
!
!     To obtain the updated residuals
      if ( CTD_AFTER_STEPS ) CALL ComputeTimeDerivative( mesh, particles, tk, CTD_IGNORE_MODE)

   end subroutine TakeRKOptStep
!
!///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
!
   subroutine Enable_limiter(integrator, minimum)
!
!     ---------
!     Interface
!     ---------
      integer,            intent(in) :: integrator
      real(RP), optional, intent(in) :: minimum


      LIMITED = (integrator == SSPRK33_KEY) .or. (integrator == SSPRK43_KEY)

      if (present(minimum)) then
         LIMITER_MIN = minimum
      end if

   end subroutine Enable_limiter

#if defined(NAVIERSTOKES) || defined(SPALARTALMARAS)
   subroutine stage_limiter(mesh)
!
!     -------
!     Modules
!     -------
!
      use ElementClass,      only: Element
      use NodalStorageClass, only: NodalStorage
      use FluidData,         only: thermodynamics
!
!     ---------
!     Interface
!     ---------
!
      type(HexMesh), target, intent(inout) :: mesh
!
!     ---------------
!     Local variables
!     ---------------
!
      real(RP)               :: m
      real(RP)               :: Q(5), Qavg(5)
      real(RP)               :: rho, minrho
      real(RP)               :: p, pavg, minp
      real(RP)               :: theta
      real(RP)               :: gm1
      integer                :: eID
      integer                :: i, j, k
      type(Element), pointer :: e
      real(RP),      pointer :: wx(:), wy(:), wz(:)


      gm1 = thermodynamics % gammaMinus1

!$omp parallel do default(private) shared(mesh, NodalStorage) firstprivate(gm1, LIMITER_MIN)
      do eID = 1, mesh % no_of_elements
         e  => mesh % elements(eID)
         wx => NodalStorage(e % Nxyz(1)) % w
         wy => NodalStorage(e % Nxyz(2)) % w
         wz => NodalStorage(e % Nxyz(3)) % w

         ! Compute averages
         Qavg = 0.0_RP
         do k = 0, e % Nxyz(3); do j = 0, e % Nxyz(2); do i = 0, e % Nxyz(1)
            Qavg = Qavg + e % storage % Q(:,i,j,k) * wx(i) * wy(j) * wz(k) * e % geom % jacobian(i,j,k)
         end do               ; end do               ; end do
         Qavg = Qavg / e % geom % volume

         ! Density first
         minrho = huge(1.0_RP)
         do k = 0, e % Nxyz(3); do j = 0, e % Nxyz(2); do i = 0, e % Nxyz(1)
            rho = e % storage % Q(1,i,j,k)
            if (rho < minrho) minrho = rho
         end do               ; end do               ; end do

         if (Qavg(1) /= minrho) then
            m = min(LIMITER_MIN, Qavg(1))
            theta = abs((Qavg(1) - m) / (Qavg(1) - minrho))
            if (theta <= 1.0_RP) then
               e % storage % Q(1,:,:,:) = theta * (e % storage % Q(1,:,:,:) - Qavg(1)) + Qavg(1)
            end if
         end if

         ! Pressure now (Jensen's inequality is NOT conservative for the pressure)
         minp = huge(1.0_RP)
         pavg = 0.0_RP
         do k = 0, e % Nxyz(3); do j = 0, e % Nxyz(2); do i = 0, e % Nxyz(1)
            Q = e % storage % Q(:,i,j,k)
            p = gm1 * (Q(5) - 0.5_RP * (Q(2)**2 + Q(3)**2 + Q(4)**2) / Q(1))
            pavg = pavg + p * wx(i) * wy(j) * wz(k) * e % geom % jacobian(i,j,k)
            if (p < minp) minp = p
         end do               ; end do               ; end do
         pavg = pavg / e % geom % volume

         if (pavg /= minp) then
            m = min(LIMITER_MIN, pavg)
            theta = abs((pavg - m) / (pavg - minp))
            if (theta <= 1.0_RP) then
               do k = 0, e % Nxyz(3); do j = 0, e % Nxyz(2); do i = 0, e % Nxyz(1)
                  e % storage % Q(:,i,j,k) = theta * (e % storage % Q(:,i,j,k) - Qavg) + Qavg
               end do               ; end do               ; end do
            end if
         end if

      end do
!$omp end parallel do

      nullify(e)
      nullify(wx)
      nullify(wy)
      nullify(wz)

   end subroutine stage_limiter
#else
   subroutine stage_limiter(mesh)
      type(HexMesh), intent(in) :: mesh
   end subroutine stage_limiter
#endif
!
!///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
!
   Subroutine checkForNan(mesh, t)
!
!        **************************************************************************************************************
!        Look if there is a nan in the solution, if at least one if found, stops and creates a hsol for user inspection
!        **************************************************************************************************************
!
      use MPI_Process_Info
#ifdef _HAS_MPI_
      use mpi
#endif
      implicit none

      type(HexMesh), intent(in)     :: mesh
      real(kind=RP), intent(in)     :: t

      !local variables
      integer                    :: eID
      CHARACTER(len=LINE_LENGTH) :: FinalName      !  Final name for particular restart file
      logical                    :: NanNotFound, allNan
      integer                    :: ierr

      ! use not found instead of found as OMP reduction initialized the private value as true
      ! this is redundant for the OMP but left for non parallel compilations
      NanNotFound = .TRUE.

!$omp parallel do reduction(.AND.:NanNotFound) schedule(runtime)
      do eID=1, mesh % no_of_elements
         if ( any(isnan(mesh % elements(eID) % storage % Q))) then
            NanNotFound = .FALSE.
         endif
      end do
!$omp end parallel do

#ifdef _HAS_MPI_
      call mpi_allreduce(NanNotFound, allNan, 1, MPI_LOGICAL, MPI_LOR, MPI_COMM_WORLD, ierr)
      NanNotFound = allNan
#endif

      if (.not. NanNotFound) then
          if ( MPI_Process % isRoot ) print*, "Numerical divergence obtained in solver."
          WRITE(FinalName,'(A,ES11.5,A)')  'RESULTS/horses_divergence_',t,'.hsol'
          if ( MPI_Process % isRoot ) print *, "Writing failed solution: ", FinalName
          call mesh % SaveSolution(0, t, FinalName, .FALSE.)
      end if

#ifdef _HAS_MPI_
     call mpi_barrier(MPI_COMM_WORLD, ierr)
#endif

      if (.not. NanNotFound) call exit(99)


   End Subroutine checkForNan
!
!///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
!
END MODULE ExplicitMethods