From b3629a86bd452ed7b046b9e33e54e50e179adaaf Mon Sep 17 00:00:00 2001 From: Antoine Hoffmann <antoine.hoffmann@epfl.ch> Date: Tue, 4 Jul 2023 15:54:48 +0200 Subject: [PATCH] preparation of data struc. for adaptive time stepping and cleaning --- src/adaptive_timestep_mod.F90 | 130 ++++++++++++ src/advance_field_mod.F90 | 2 +- src/basic_mod.F90 | 9 +- src/time_integration_mod.F90 | 375 +++++++++++++++++++++------------- 4 files changed, 366 insertions(+), 150 deletions(-) create mode 100644 src/adaptive_timestep_mod.F90 diff --git a/src/adaptive_timestep_mod.F90 b/src/adaptive_timestep_mod.F90 new file mode 100644 index 00000000..c8581ea6 --- /dev/null +++ b/src/adaptive_timestep_mod.F90 @@ -0,0 +1,130 @@ +!! This module is a compilation of the routines implemented in GBS by L. Stenger +!! for adaptive time stepping. +MODULE adaptive_timestep + USE prec_const, ONLY: xp + + IMPLICIT NONE + + logical, public, protected :: time_scheme_is_adaptive = .false. + logical, public, protected :: should_discard_step = .false. + real(xp) :: adaptive_accuracy_ratio = 1._dp + 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_dp, adaptive_error_rtol=1e-3_dp + !> @} + integer, public, protected :: ntimelevel_rhs=4 !< number of time levels required for the rhs + + + CONTAINS + + + SUBROUTINE adaptive_timestep_readinputs + ! Read the input parameters + USE prec_const + USE basic, ONLY : lu_in + IMPLICIT NONE + namelist /TIME_INTEGRATION_PAR/ adaptive_safety, adaptive_error_atol, adaptive_error_rtol + + READ(lu_in,time_integration_par) + CALL set_numerical_scheme + END SUBROUTINE adaptive_timestep_readinputs + + !> 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._dp) 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: cstepP, dt, meW, stepP + 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(meW==0 .and. dt/=old_dt) print *, "step discarded. dt update ", old_dt, " => ", dt + if(meW==0 .and. dt/=old_dt) print *, "errscale was ", adaptive_accuracy_ratio + 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 + +END MODULE adaptive_timestep \ No newline at end of file diff --git a/src/advance_field_mod.F90 b/src/advance_field_mod.F90 index 9ee8c4be..d67d080c 100644 --- a/src/advance_field_mod.F90 +++ b/src/advance_field_mod.F90 @@ -36,7 +36,7 @@ CONTAINS DO ia =1,local_na IF( evolve_mom(ipi,iji) )& moments(ia,ipi,iji,iky,ikx,izi,1) = moments(ia,ipi,iji,iky,ikx,izi,1) & - + dt*b_E(istage)*moments_rhs(ia,ip,ij,iky,ikx,iz,istage) + + dt*b_E(istage,1)*moments_rhs(ia,ip,ij,iky,ikx,iz,istage) END DO END DO END DO diff --git a/src/basic_mod.F90 b/src/basic_mod.F90 index 704aad2a..0c45dd45 100644 --- a/src/basic_mod.F90 +++ b/src/basic_mod.F90 @@ -45,7 +45,7 @@ MODULE basic ! Routines interfaces PUBLIC :: allocate_array, basic_outputinputs,basic_data,& speak, str, increase_step, increase_cstep, increase_time, display_h_min_s,& - set_basic_cp, daytim, start_chrono, stop_chrono + set_basic_cp, daytim, start_chrono, stop_chrono, change_dt ! Interface for allocating arrays, these routines allocate and initialize directly to zero INTERFACE allocate_array MODULE PROCEDURE allocate_array_xp1,allocate_array_xp2,allocate_array_xp3, & @@ -123,6 +123,11 @@ CONTAINS IMPLICIT NONE cstep = cstep + 1 END SUBROUTINE + SUBROUTINE change_dt(new_dt) + IMPLICIT NONE + REAL(xp), INTENT(IN) :: new_dt + dt = new_dt + END SUBROUTINE SUBROUTINE increase_time IMPLICIT NONE time = time + dt @@ -244,7 +249,7 @@ CONTAINS ! "Convert an integer to string." integer, intent(in) :: k character(len=10) :: str_ - write (str_, "(i2.2)") k + write (str_, "(I8)") k str_ = adjustl(str_) end function str_int diff --git a/src/time_integration_mod.F90 b/src/time_integration_mod.F90 index e1a2aa51..5b8e5654 100644 --- a/src/time_integration_mod.F90 +++ b/src/time_integration_mod.F90 @@ -1,33 +1,52 @@ 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,A_I - real(xp),PUBLIC,PROTECTED,DIMENSION(:),ALLOCATABLE :: b_E,b_Es,b_I - real(xp),PUBLIC,PROTECTED,DIMENSION(:),ALLOCATABLE :: c_E,c_I !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 + 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 CONTAINS - SUBROUTINE set_updatetlevel(new_updatetlevel) - INTEGER, INTENT(in) :: new_updatetlevel - updatetlevel = new_updatetlevel - END SUBROUTINE set_updatetlevel - SUBROUTINE time_integration_readinputs ! Read the input parameters USE prec_const USE basic, ONLY : lu_in IMPLICIT NONE NAMELIST /TIME_INTEGRATION_PAR/ numerical_scheme + namelist /TIME_INTEGRATION_PAR/ adaptive_safety, adaptive_error_atol, adaptive_error_rtol + READ(lu_in,time_integration_par) CALL set_numerical_scheme END SUBROUTINE time_integration_readinputs @@ -52,24 +71,21 @@ CONTAINS ! Order 2 methods CASE ('RK2') CALL RK2 - CASE ('SSPx_RK2') - CALL SSPx_RK2 ! Order 3 methods CASE ('RK3') CALL RK3 CASE ('SSP_RK3') CALL SSP_RK3 - CASE ('SSPx_RK3') - CALL SSPx_RK3 - CASE ('IMEX_SSP2') - CALL IMEX_SSP2 - CASE ('ARK2') - CALL ARK2 + ! 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 IF (my_id .EQ. 0) WRITE(*,*) 'Cannot initialize time integration scheme. Name invalid.' @@ -85,41 +101,16 @@ CONTAINS IMPLICIT NONE INTEGER,PARAMETER :: nbstep = 2 CALL allocate_array(c_E,1,nbstep) - CALL allocate_array(b_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._xp/2._xp - b_E(2) = 1._xp/2._xp + 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 - SUBROUTINE SSPx_RK2 - ! DOESNT WORK - ! Butcher coeff for modified strong stability preserving RK2 - ! used in GX (Hammett 2022, Mandell et al. 2022) - USE basic - USE prec_const - IMPLICIT NONE - INTEGER,PARAMETER :: nbstep = 2 - REAL(xp) :: alpha, beta - alpha = 1._xp/SQRT(2._xp) - beta = SQRT(2._xp) - 1._xp - CALL allocate_array(c_E,1,nbstep) - CALL allocate_array(b_E,1,nbstep) - CALL allocate_array(A_E,1,nbstep,1,nbstep) - ntimelevel = 2 - c_E(1) = 0.0_xp - c_E(2) = 1.0_xp/2.0_xp - b_E(1) = alpha*beta/2._xp - b_E(2) = alpha/2._xp - A_E(2,1) = alpha - ! b_E(1) = 1._xp - ! b_E(2) = 1._xp/SQRT(2._xp) - ! A_E(2,1) = 1._xp/SQRT(2._xp) - END SUBROUTINE SSPx_RK2 - !!! third order time schemes SUBROUTINE RK3 ! Butcher coeff for classical RK3 @@ -128,98 +119,20 @@ CONTAINS IMPLICIT NONE INTEGER,PARAMETER :: nbstep = 3 CALL allocate_array(c_E,1,nbstep) - CALL allocate_array(b_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._xp/6._xp - b_E(2) = 2._xp/3._xp - b_E(3) = 1._xp/6._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 SSPx_RK3 - ! DOESNT WORK - ! Butcher coeff for modified strong stability preserving RK3 - ! used in GX (Hammett 2022, Mandell et al. 2022) - USE basic - USE prec_const - IMPLICIT NONE - INTEGER,PARAMETER :: nbstep = 3 - REAL(xp) :: a1, a2, a3, w1, w2, w3 - a1 = (1._xp/6._xp)**(1._xp/3._xp)! (1/6)^(1/3) - ! a1 = 0.5503212081491044571635029569733887910843_xp ! (1/6)^(1/3) - a2 = a1 - a3 = a1 - w1 = 0.5_xp*(-1._xp + SQRT( 9._xp - 2._xp * 6._xp**(2._xp/3._xp))) ! (-1 + sqrt(9-2*6^(2/3)))/2 - ! w1 = 0.2739744023885328783052273138309828937054_xp ! (sqrt(9-2*6^(2/3))-1)/2 - w2 = 0.5_xp*(-1._xp + 6._xp**(2._xp/3._xp) - SQRT(9._xp - 2._xp * 6._xp**(2._xp/3._xp))) ! (6^(2/3)-1-sqrt(9-2*6^(2/3)))/2 - ! w2 = 0.3769892220587804931852815570891834795475_xp ! (6^(2/3)-1-sqrt(9-2*6^(2/3)))/2 - w3 = 1._xp/a1 - w2 * (1._xp + w1) - ! w3 = 1.3368459739528868457369981115334667265415_xp - CALL allocate_array(c_E,1,nbstep) - CALL allocate_array(b_E,1,nbstep) - 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/2.0_xp - b_E(1) = a1 * (w1*w2 + w3) - b_E(2) = a2 * w2 - b_E(3) = a3 - A_E(2,1) = a1 - A_E(3,1) = a1 * w1 - A_E(3,2) = a2 - END SUBROUTINE SSPx_RK3 - - SUBROUTINE IMEX_SSP2 - !! Version of Rokhzadi 2017 (An Optimally Stable and Accurate Second-Order - ! SSP Runge-Kutta IMEX Scheme for Atmospheric Applications) - 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) - CALL allocate_array(A_E,1,nbstep,1,nbstep) - ntimelevel = 3 - c_E(1) = 0._xp - c_E(2) = 0.711664700366941_xp - c_E(3) = 0.994611536833690_xp - b_E(1) = 0.398930808264688_xp - b_E(2) = 0.345755244189623_xp - b_E(3) = 0.255313947545689_xp - A_E(2,1) = 0.711664700366941_xp - A_E(3,1) = 0.077338168947683_xp - A_E(3,2) = 0.917273367886007_xp - END SUBROUTINE IMEX_SSP2 - - SUBROUTINE ARK2 - !! Version of Rokhzadi 2017 (An Optimally Stable and Accurate Second-Order - ! SSP Runge-Kutta IMEX Scheme for Atmospheric Applications) - 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) - CALL allocate_array(A_E,1,nbstep,1,nbstep) - ntimelevel = 3 - c_E(1) = 0._xp - c_E(2) = 2._xp*(1._xp - 1._xp/SQRT2) - c_E(3) = 1._xp - b_E(1) = 1._xp/(2._xp*SQRT2) - b_E(2) = 1._xp/(2._xp*SQRT2) - b_E(3) = 1._xp - 1._xp/SQRT2 - A_E(2,1) = 2._xp*(1._xp - 1._xp/SQRT2) - A_E(3,1) = 1._xp - (3._xp + 2._xp*SQRT2)/6._xp - A_E(3,2) = (3._xp + 2._xp*SQRT2)/6._xp - END SUBROUTINE ARK2 - SUBROUTINE SSP_RK3 ! Butcher coeff for strong stability preserving RK3 USE basic @@ -227,15 +140,15 @@ CONTAINS IMPLICIT NONE INTEGER,PARAMETER :: nbstep = 3 CALL allocate_array(c_E,1,nbstep) - CALL allocate_array(b_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._xp/6._xp - b_E(2) = 1._xp/6._xp - b_E(3) = 2._xp/3._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 @@ -249,17 +162,17 @@ CONTAINS IMPLICIT NONE INTEGER,PARAMETER :: nbstep = 4 CALL allocate_array(c_E,1,nbstep) - CALL allocate_array(b_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.0_xp/6.0_xp - b_E(2) = 1.0_xp/3.0_xp - b_E(3) = 1.0_xp/3.0_xp - b_E(4) = 1.0_xp/6.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 @@ -274,20 +187,26 @@ CONTAINS IMPLICIT NONE INTEGER,PARAMETER :: nbstep =7 CALL allocate_array(c_E,1,nbstep) - CALL allocate_array(b_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 - c_E(1) = 0._xp + 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,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 @@ -304,13 +223,175 @@ CONTAINS 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(1) = 35.0_xp/384.0_xp - b_E(2) = 0._xp - b_E(3) = 500.0_xp/1113.0_xp - b_E(4) = 125.0_xp/192.0_xp - b_E(5) = -2187.0_xp/6784.0_xp - b_E(6) = 11.0_xp/84.0_xp - b_E(7) = 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)) + if(dt/=old_dt) CALL speak("errscale was "//str(adaptive_accuracy_ratio)) + 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 -- GitLab