-
Antoine Cyril David Hoffmann authored
- VERBOSE_LBL in basic mod: set the amount of output in the std - Title: there is now a title with the version of the code
Antoine Cyril David Hoffmann authored- VERBOSE_LBL in basic mod: set the amount of output in the std - Title: there is now a title with the version of the code
time_integration_mod.F90 13.68 KiB
MODULE time_integration
USE prec_const
IMPLICIT NONE
PRIVATE
INTEGER, PUBLIC, PROTECTED :: ntimelevel=4 ! Total number of time levels required by the numerical scheme
INTEGER, PUBLIC, PROTECTED :: updatetlevel ! Current time level to be updated
real(xp),PUBLIC,PROTECTED,DIMENSION(:,:),ALLOCATABLE :: A_E
real(xp),PUBLIC,PROTECTED,DIMENSION(:,:),ALLOCATABLE :: b_E
real(xp),PUBLIC,PROTECTED,DIMENSION(:),ALLOCATABLE :: c_E !Coeff for Expl/Implic time integration in case of time dependent RHS (i.e. dy/dt = f(y,t)) see Baptiste Frei CSE Rapport 06/17
character(len=10),PUBLIC,PROTECTED :: numerical_scheme='RK4'
!!---- start: This part is taken from GBS adaptive time stepping by L. Stenger
! For more information check gbs.epfl.ch
logical, public, protected :: time_scheme_is_adaptive = .false.
logical, public, protected :: should_discard_step = .false.
real(xp) :: adaptive_accuracy_ratio = 1._xp
integer :: adaptive_error_estimator_order = -1
real(xp) :: dt_min = 1e-8_xp !< Minimum allowable timestep for adaptive time schemes
real(xp) :: dt_max = 1e1_xp !< Maximum allowable timestep for adaptive time schemes
real(xp) :: adaptive_safety = 0.9_xp !< Safety factor of the adaptive timestep controller
!> @{
!> @brief Error tolerance for adaptive time schemes
!> @details
!> Typically, an adaptive time scheme will provide an error estimation for a
!> given time step "witdh". The allowable error will be defined as
!> `atol + rtol * abs(f)` (where `f` the quantity which is being integrated).
real(xp), public, protected :: adaptive_error_atol=1e-6_xp, adaptive_error_rtol=1e-3_xp
!> @}
integer, public, protected :: ntimelevel_rhs = 4 !< number of time levels required for the rhs
integer, public, protected :: updatetlevel_rhs = 1 !< time level to be updated for rhs
!!---- end
PUBLIC :: set_updatetlevel, time_integration_readinputs, time_integration_outputinputs, &
adaptive_time_scheme_setup, adaptive_set_error, adaptive_must_recompute_step, &
set_updatetlevel_rhs
CONTAINS
SUBROUTINE time_integration_readinputs
! Read the input parameters
USE prec_const
USE basic, ONLY : lu_in
IMPLICIT NONE
NAMELIST /TIME_INTEGRATION/ numerical_scheme
namelist /TIME_INTEGRATION/ adaptive_safety, adaptive_error_atol, adaptive_error_rtol
READ(lu_in,time_integration)
CALL set_numerical_scheme
END SUBROUTINE time_integration_readinputs
SUBROUTINE time_integration_outputinputs(fid)
! Write the input parameters to the results_xx.h5 file
USE futils, ONLY: attach, creatd
IMPLICIT NONE
INTEGER, INTENT(in) :: fid
CHARACTER(len=256) :: str
WRITE(str,'(a)') '/data/input/time_integration'
CALL creatd(fid, 0,(/0/),TRIM(str),'Time Integration Input')
CALL attach(fid, TRIM(str), "numerical_scheme", numerical_scheme)
END SUBROUTINE time_integration_outputinputs
SUBROUTINE set_numerical_scheme
! Initialize Butcher coefficient of set_numerical_scheme
use parallel, ONLY: my_id
use basic, ONLY: speak
IMPLICIT NONE
SELECT CASE (numerical_scheme)
! Order 1 method
CASE ('EE')
CALL EE
! Order 2 methods
CASE ('RK2')
CALL RK2
! Order 3 methods
CASE ('RK3')
CALL RK3
CASE ('SSP_RK3')
CALL SSP_RK3
! Adaptative scheme
CASE ('ODE23')
time_scheme_is_adaptive = .true.
CALL ode23
! Order 4 methods
CASE ('RK4')
CALL RK4
! Order 5 methods
CASE ('DOPRI5')
time_scheme_is_adaptive = .true.
CALL DOPRI5
CASE DEFAULT
ERROR STOP 'Cannot initialize time integration scheme. Name invalid.'
END SELECT
CALL speak("-Time integration with "// numerical_scheme,2)
END SUBROUTINE set_numerical_scheme
!!! first order time schemes
SUBROUTINE EE
! Butcher coeff for Euler Explicit scheme
USE basic
USE prec_const
IMPLICIT NONE
INTEGER,PARAMETER :: nbstep = 1
CALL allocate_array(c_E,1,nbstep)
CALL allocate_array(b_E,1,nbstep,1,1)
CALL allocate_array(A_E,1,nbstep,1,nbstep)
ntimelevel = 1
c_E(1) = 0._xp
b_E(1,1) = 1._xp
A_E(1,1) = 0._xp
END SUBROUTINE EE
!!! second order time schemes
SUBROUTINE RK2
! Butcher coeff for clasical RK2 (Heun's)
USE basic
USE prec_const
IMPLICIT NONE
INTEGER,PARAMETER :: nbstep = 2
CALL allocate_array(c_E,1,nbstep)
CALL allocate_array(b_E,1,nbstep,1,1)
CALL allocate_array(A_E,1,nbstep,1,nbstep)
ntimelevel = 2
c_E(1) = 0.0_xp
c_E(2) = 1.0_xp
b_E(1,1) = 1._xp/2._xp
b_E(2,1) = 1._xp/2._xp
A_E(2,1) = 1._xp
END SUBROUTINE RK2
!!! third order time schemes
SUBROUTINE RK3
! Butcher coeff for classical RK3
USE basic
USE prec_const
IMPLICIT NONE
INTEGER,PARAMETER :: nbstep = 3
CALL allocate_array(c_E,1,nbstep)
CALL allocate_array(b_E,1,nbstep,1,1)
CALL allocate_array(A_E,1,nbstep,1,nbstep)
ntimelevel = 3
c_E(1) = 0.0_xp
c_E(2) = 1.0_xp/2.0_xp
c_E(3) = 1.0_xp
b_E(1,1) = 1._xp/6._xp
b_E(2,1) = 2._xp/3._xp
b_E(3,1) = 1._xp/6._xp
A_E(2,1) = 1.0_xp/2.0_xp
A_E(3,1) = -1._xp
A_E(3,2) = 2._xp
END SUBROUTINE RK3
SUBROUTINE SSP_RK3
! Butcher coeff for strong stability preserving RK3
USE basic
USE prec_const
IMPLICIT NONE
INTEGER,PARAMETER :: nbstep = 3
CALL allocate_array(c_E,1,nbstep)
CALL allocate_array(b_E,1,nbstep,1,1)
CALL allocate_array(A_E,1,nbstep,1,nbstep)
ntimelevel = 3
c_E(1) = 0.0_xp
c_E(2) = 1.0_xp
c_E(3) = 1.0_xp/2.0_xp
b_E(1,1) = 1._xp/6._xp
b_E(2,1) = 1._xp/6._xp
b_E(3,1) = 2._xp/3._xp
A_E(2,1) = 1._xp
A_E(3,1) = 1._xp/4._xp
A_E(3,2) = 1._xp/4._xp
END SUBROUTINE SSP_RK3
!!! fourth order time schemes
SUBROUTINE RK4
! Butcher coeff for RK4 (default)
USE basic
USE prec_const
IMPLICIT NONE
INTEGER,PARAMETER :: nbstep = 4
CALL allocate_array(c_E,1,nbstep)
CALL allocate_array(b_E,1,nbstep,1,1)
CALL allocate_array(A_E,1,nbstep,1,nbstep)
ntimelevel = 4
c_E(1) = 0.0_xp
c_E(2) = 1.0_xp/2.0_xp
c_E(3) = 1.0_xp/2.0_xp
c_E(4) = 1.0_xp
b_E(1,1) = 1.0_xp/6.0_xp
b_E(2,1) = 1.0_xp/3.0_xp
b_E(3,1) = 1.0_xp/3.0_xp
b_E(4,1) = 1.0_xp/6.0_xp
A_E(2,1) = 1.0_xp/2.0_xp
A_E(3,2) = 1.0_xp/2.0_xp
A_E(4,3) = 1.0_xp
END SUBROUTINE RK4
!!! fifth order time schemes
SUBROUTINE DOPRI5
! Butcher coeff for DOPRI5 --> Stiffness detection
! DOPRI5 used for stiffness detection.
! 5 order method/7 stages
USE basic
IMPLICIT NONE
INTEGER,PARAMETER :: nbstep =7
CALL allocate_array(c_E,1,nbstep)
CALL allocate_array(b_E,1,nbstep,1,2)
CALL allocate_array(A_E,1,nbstep,1,nbstep)
adaptive_error_estimator_order = 4
ntimelevel = 7
ntimelevel_rhs = 7
c_E = 0._xp
c_E(2) = 1.0_xp/5.0_xp
c_E(3) = 3.0_xp /10.0_xp
c_E(4) = 4.0_xp/5.0_xp
c_E(5) = 8.0_xp/9.0_xp
c_E(6) = 1.0_xp
c_E(7) = 1.0_xp
A_E = 0._xp
A_E(2,1) = 1.0_xp/5.0_xp
A_E(3,1) = 3.0_xp/40.0_xp
A_E(3,2) = 9.0_xp/40.0_xp
A_E(4,1) = 44.0_xp/45.0_xp
A_E(4,2) = -56.0_xp/15.0_xp
A_E(4,3) = 32.0_xp/9.0_xp
A_E(5,1 ) = 19372.0_xp/6561.0_xp
A_E(5,2) = -25360.0_xp/2187.0_xp
A_E(5,3) = 64448.0_xp/6561.0_xp
A_E(5,4) = -212.0_xp/729.0_xp
A_E(6,1) = 9017.0_xp/3168.0_xp
A_E(6,2)= -355.0_xp/33.0_xp
A_E(6,3) = 46732.0_xp/5247.0_xp
A_E(6,4) = 49.0_xp/176.0_xp
A_E(6,5) = -5103.0_xp/18656.0_xp
A_E(7,1) = 35.0_xp/384.0_xp
A_E(7,3) = 500.0_xp/1113.0_xp
A_E(7,4) = 125.0_xp/192.0_xp
A_E(7,5) = -2187.0_xp/6784.0_xp
A_E(7,6) = 11.0_xp/84.0_xp
b_E = 0._xp
b_E(1, 1) = 35._xp/384._xp
b_E(2, 1) = 0._xp
b_E(3, 1) = 500._xp/1113._xp
b_E(4, 1) = 125._xp/192._xp
b_E(5, 1) = -2187._xp/6784._xp
b_E(6, 1) = 11._xp/84._xp
b_E(7, 1) = 0._xp
b_E(1, 2) = 5179._xp/57600._xp
b_E(2, 2) = 0._xp
b_E(3, 2) = 7571._xp/16695._xp
b_E(4, 2) = 393._xp/640._xp
b_E(5, 2) = -92097._xp/339200._xp
b_E(6, 2) = 187._xp/2100._xp
b_E(7, 2) = 1._xp/40._xp
END SUBROUTINE DOPRI5
!!-------------------------------------------------------------------------
!!---- This part is taken from GBS adaptive time stepping by L. Stenger ---
!> Updates the time step
!>
!> If the new time step in not within the closed interval `[dt_min, dt_max]`,
!> the simulation will be terminated
!>
!> @param[in] new_dt Desired time step value
subroutine set_dt(new_dt)
use basic, only: dt, nlend, change_dt
use parallel, ONLY: comm_p, my_id
real(xp), intent(in) :: new_dt
integer :: ierr
logical :: mlend
mlend = .false.
if(dt<=dt_min .or. dt>=dt_max) then
mlend = .true.
if(my_id == 0) then
print "(A, 3(G12.5,A))", "The adaptive time integration scheme tried to set the time step to ", dt, &
", which is outside of the allowed range of [", dt_min, ", ", dt_max, &
"]." // new_line("A") // "The simulation will be terminated."
end if
else
CALL change_dt(new_dt)
end if
call mpi_allreduce(mlend, nlend, 1, MPI_LOGICAL, MPI_LOR, comm_p, ierr)
end subroutine set_dt
!> Computes the next time step for adaptive time schemes
!>
!> Provided a relative stepping error, defined by `tolerance/error` where
!> `tolerance` is the admissible error and `error` the estimated error of the
!> current step, compute an optimal `dt` for the next step.
!>
!> @param[in] dt Current time step
!> @param[in] errscale Normalized error of the current step
!> @returns Optimal `dt` for the next step
pure function adaptive_compute_next_dt(dt, errscale) result(next_dt)
real(xp), intent(in) :: dt, errscale
real(xp), parameter :: MINSCALE=.1_xp, MAXSCALE=5._xp
real(xp) :: next_dt, error_exponent
! The parameters in this routine seem to work "fine". Attempts to be too
! ambitious, especially with `adaptive_safety`, usually increase the rate
! at which time steps are discarded!
if(errscale > 1._xp) then
error_exponent = 1 / real(adaptive_error_estimator_order + 1, xp)
else
error_exponent = 1 / real(adaptive_error_estimator_order, xp)
end if
next_dt = dt * max(MINSCALE, min(MAXSCALE, adaptive_safety * errscale**error_exponent))
end function adaptive_compute_next_dt
!> Setup for adaptive time schemes
!>
!> Prepares the next full embedded Runge-Kutta step (not substep) by adjusting
!> basic::dt based on the previous time step, along with extra cleanup in case
!> the previous step was discarded.
subroutine adaptive_time_scheme_setup()
use basic, only: dt, str, speak
real(xp) :: old_dt
old_dt = dt
call set_dt(adaptive_compute_next_dt(dt, adaptive_accuracy_ratio))
if(should_discard_step) then
! Note this relates to the previous step!
if(dt/=old_dt) CALL speak("step discarded. dt update "//str(old_dt)//" => "//str(dt),2)
if(dt/=old_dt) CALL speak("errscale was "//str(adaptive_accuracy_ratio),2)
end if
! Setting `adaptive_accuracy_ratio` is equivalent to saying that the step
! has zero error, i.e. "assume the step is perfect now, and update later
! when actually computing the error after performing the step"
should_discard_step = .false.
adaptive_accuracy_ratio = huge(adaptive_accuracy_ratio)
end subroutine adaptive_time_scheme_setup
!> Updates the normalized adaptive time scheme error
!>
!> This routine is useful to combine the normalized error of the full system
!> of equation by recording the worst (highest) error, which will eventually
!> be passed to time_integration::adaptive_compute_next_dt.
!>
!> @param[in] errscale Normalized time step error
!>
!> @sa time_integration::adaptive_compute_next_dt
subroutine adaptive_set_error(errscale)
real(xp), intent(in) :: errscale
logical :: satisfies_tolerance
satisfies_tolerance = errscale > 1._xp
adaptive_accuracy_ratio = min(adaptive_accuracy_ratio, errscale)
should_discard_step = should_discard_step .or. (.not. satisfies_tolerance)
end subroutine adaptive_set_error
!> Returns `.true.` if the current step should be discarded.
pure function adaptive_must_recompute_step() result(res)
logical :: res
res = should_discard_step
end function adaptive_must_recompute_step
subroutine set_updatetlevel(new_updatetlevel)
integer, intent(in) :: new_updatetlevel
updatetlevel = new_updatetlevel
end subroutine set_updatetlevel
subroutine set_updatetlevel_rhs
select case (numerical_scheme)
case ('rkc2')
if (updatetlevel == 1)then
updatetlevel_rhs = 1
else
updatetlevel_rhs = 2
end if
case default
updatetlevel_rhs = updatetlevel
end select
end subroutine set_updatetlevel_rhs
!> Sets up Butcher coefficients for the Bogacki-Shampine method (orders 3 and 2)
subroutine ode23
use basic
integer,parameter :: nbstep = 4
CALL allocate_array(c_E,1,nbstep)
CALL allocate_array(b_E,1,nbstep, 1, 2)
CALL allocate_array(A_E,1,nbstep,1,nbstep)
adaptive_error_estimator_order = 2
ntimelevel = 4
ntimelevel_rhs = 4
c_E = 0._xp
c_E(2) = 1.0_xp/2.0_xp
c_E(3) = 3.0_xp /4.0_xp
c_E(4) = 1._xp
A_E = 0._xp
A_E(2,1) = 1.0_xp/2.0_xp
A_E(3,2) = 3.0_xp/4.0_xp
A_E(4,1) = 2.0_xp/9.0_xp
A_E(4,2) = 1.0_xp/3.0_xp
A_E(4,3) = 4.0_xp/9.0_xp
b_E = 0._xp
b_E(1, 1) = 2._xp/9._xp
b_E(2, 1) = 1._xp/3._xp
b_E(3, 1) = 4._xp/9._xp
b_E(4, 1) = 0._xp
b_E(1, 2) = 7._xp/24._xp
b_E(2, 2) = 1._xp/4._xp
b_E(3, 2) = 1._xp/3._xp
b_E(4, 2) = 1._xp/8._xp
end subroutine ode23
!!-------------------------------------------------------------------------
!!-------------------------------------------------------------------------
END MODULE time_integration