rkf45_module.f90 Source File


Contents

Source Code


Source Code

module rkf45_module

    use fffc_kinds, only: rk => fffc_real_kind

    private
    public :: rkf45

    abstract interface
        !> `fcn` evaluates the derivative for the ODE.
        subroutine fcn(t, y, yp)
            import
            real(kind=rk), intent(in) :: t
            real(kind=rk), intent(in) :: y(:)
            real(kind=rk), intent(out) :: yp(:)
        end subroutine fcn
    end interface

contains
    !> rkf45 is primarily designed to solve non-stiff and mildly stiff
    !> differential equations when derivative evaluations are inexpensive.
    !> rkf45 should generally not be used when the user is demanding
    !> high accuracy.
    !> 在计算量不大的计算中,rkf45主要用于求解非刚性和轻微刚性的常微分方程。
    subroutine rkf45(f, neqn, y, t, tout, relerr, abserr, iflag, work, iwork)

        !     fehlberg fourth-fifth order runge-kutta method

        !     written by h.a.watts and l.f.shampine
        !                   sandia laboratories
        !                  albuquerque,new mexico

        ! abstract

        !    subroutine  rkf45  integrates a system of neqn first order
        !    ordinary differential equations of the form
        !             dy(i)/dt = f(t,y(1),y(2),...,y(neqn))
        !              where the y(i) are given at t .
        !    typically the subroutine is used to integrate from t to tout but it
        !    can be used as a one-step integrator to advance the solution a
        !    single step in the direction of tout.  on return the parameters in
        !    the call list are set for continuing the integration. the user has
        !    only to call rkf45 again (and perhaps define a new value for tout).
        !    actually, rkf45 is an interfacing routine which calls subroutine
        !    rkfs for the solution.  rkfs in turn calls subroutine  fehl which
        !    computes an approximate solution over one step.

        !    rkf45  uses the runge-kutta-fehlberg (4,5)  method described
        !    in the reference
        !    e.fehlberg , low-order classical runge-kutta formulas with stepsize
        !                 control , nasa tr r-315

        !    the performance of rkf45 is illustrated in the reference
        !    l.f.shampine,h.a.watts,s.davenport, solving non-stiff ordinary
        !                 differential equations-the state of the art ,
        !                 sandia laboratories report sand75-0182 ,
        !                 to appear in siam review.

        !    the parameters represent-
        !      f -- subroutine f(t,y,yp) to evaluate derivatives yp(i)=dy(i)/dt
        !      neqn -- number of equations to be integrated
        !      y(*) -- solution vector at t
        !      t -- independent variable
        !      tout -- output point at which solution is desired
        !      relerr,abserr -- relative and absolute error tolerances for local
        !            error test. at each step the code requires that
        !                 abs(local error) .le. relerr*abs(y) + abserr
        !            for each component of the local error and solution vectors
        !      iflag -- indicator for status of integration
        !      work(*) -- array to hold information internal to rkf45 which is
        !            necessary for subsequent calls. must be dimensioned
        !            at least  3+6*neqn
        !      iwork(*) -- integer array used to hold information internal to
        !            rkf45 which is necessary for subsequent calls. must be
        !            dimensioned at least  5

        !  first call to rkf45

        !    the user must provide storage in his calling program for the arrays
        !    in the call list  -      y(neqn) , work(3+6*neqn) , iwork(5)  ,
        !    declare f in an external statement, supply subroutine f(t,y,yp) and
        !    initialize the following parameters-

        !      neqn -- number of equations to be integrated.  (neqn .ge. 1)
        !      y(*) -- vector of initial conditions
        !      t -- starting point of integration , must be a variable
        !      tout -- output point at which solution is desired.
        !            t=tout is allowed on the first call only, in which case
        !            rkf45 returns with iflag=2 if continuation is possible.
        !      relerr,abserr -- relative and absolute local error tolerances
        !            which must be non-negative. relerr must be a variable while
        !            abserr may be a constant. the code should normally not be
        !            used with relative error control smaller than about 1.e-8 .
        !            to avoid limiting precision difficulties the code requires
        !            relerr to be larger than an internally computed relative
        !            error parameter which is machine dependent. in particular,
        !            pure absolute error is not permitted. if a smaller than
        !            allowable value of relerr is attempted, rkf45 increases
        !            relerr appropriately and returns control to the user before
        !            continuing the integration.
        !      iflag -- +1,-1  indicator to initialize the code for each new
        !            problem. normal input is +1. the user should set iflag=-1
        !            only when one-step integrator control is essential. in this
        !            case, rkf45 attempts to advance the solution a single step
        !            in the direction of tout each time it is called. since this
        !            mode of operation results in extra computing overhead, it
        !            should be avoided unless needed.

        !  output from rkf45

        !      y(*) -- solution at t
        !      t -- last point reached in integration.
        !      iflag = 2 -- integration reached tout. indicates successful retur
        !                   and is the normal mode for continuing integration.
        !            =-2 -- a single successful step in the direction of tout
        !                   has been taken. normal mode for continuing
        !                   integration one step at a time.
        !            = 3 -- integration was not completed because relative error
        !                   tolerance was too small. relerr has been increased
        !                   appropriately for continuing.
        !            = 4 -- integration was not completed because more than
        !                   3000 derivative evaluations were needed. this
        !                   is approximately 500 steps.
        !            = 5 -- integration was not completed because solution
        !                   vanished making a pure relative error test
        !                   impossible. must use non-zero abserr to continue.
        !                   using the one-step integration mode for one step
        !                   is a good way to proceed.
        !            = 6 -- integration was not completed because requested
        !                   accuracy could not be achieved using smallest
        !                   allowable stepsize. user must increase the error
        !                   tolerance before continued integration can be
        !                   attempted.
        !            = 7 -- it is likely that rkf45 is inefficient for solving
        !                   this problem. too much output is restricting the
        !                   natural stepsize choice. use the one-step integrator
        !                   mode.
        !            = 8 -- invalid input parameters
        !                   this indicator occurs if any of the following is
        !                   satisfied -   neqn .le. 0
        !                                 t=tout  and  iflag .ne. +1 or -1
        !                                 relerr or abserr .lt. 0.
        !                                 iflag .eq. 0  or  .lt. -2  or  .gt. 8
        !      work(*),iwork(*) -- information which is usually of no interest
        !                   to the user but necessary for subsequent calls.
        !                   work(1),...,work(neqn) contain the first derivatives
        !                   of the solution vector y at t. work(neqn+1) contains
        !                   the stepsize h to be attempted on the next step.
        !                   iwork(1) contains the derivative evaluation counter.

        !  subsequent calls to rkf45

        !    subroutine rkf45 returns with all information needed to continue
        !    the integration. if the integration reached tout, the user need onl
        !    define a new tout and call rkf45 again. in the one-step integrator
        !    mode (iflag=-2) the user must keep in mind that each step taken is
        !    in the direction of the current tout. upon reaching tout (indicated
        !    by changing iflag to 2),the user must then define a new tout and
        !    reset iflag to -2 to continue in the one-step integrator mode.

        !    if the integration was not completed but the user still wants to
        !    continue (iflag=3,4 cases), he just calls rkf45 again. with iflag=3
        !    the relerr parameter has been adjusted appropriately for continuing
        !    the integration. in the case of iflag=4 the function counter will
        !    be reset to 0 and another 3000 function evaluations are allowed.

        !    however,in the case iflag=5, the user must first alter the error
        !    criterion to use a positive value of abserr before integration can
        !    proceed. if he does not,execution is terminated.

        !    also,in the case iflag=6, it is necessary for the user to reset
        !    iflag to 2 (or -2 when the one-step integration mode is being used)
        !    as well as increasing either abserr,relerr or both before the
        !    integration can be continued. if this is not done, execution will
        !    be terminated. the occurrence of iflag=6 indicates a trouble spot
        !    (solution is changing rapidly,singularity may be present) and it
        !    often is inadvisable to continue.

        !    if iflag=7 is encountered, the user should use the one-step
        !    integration mode with the stepsize determined by the code or
        !    consider switching to the adams codes de/step,intrp. if the user
        !    insists upon continuing the integration with rkf45, he must reset
        !    iflag to 2 before calling rkf45 again. otherwise,execution will be
        !    terminated.

        !    if iflag=8 is obtained, integration can not be continued unless
        !    the invalid input parameters are corrected.

        !    it should be noted that the arrays work,iwork contain information
        !    required for subsequent integration. accordingly, work and iwork
        !    should not be altered.

        integer, intent(in) :: neqn
        real(kind=rk), intent(inout) :: y(neqn)
        real(kind=rk), intent(inout) :: t
        real(kind=rk), intent(in) :: tout
        integer, intent(inout) :: iflag, iwork(5)
        real(kind=rk), intent(inout) :: relerr, work(*)
        real(kind=rk), intent(in) :: abserr

        procedure(fcn) :: f

        integer :: k1, k2, k3, k4, k5, k6, k1m

        !     compute indices for the splitting of the work array

        k1m = neqn + 1
        k1 = k1m + 1
        k2 = k1 + neqn
        k3 = k2 + neqn
        k4 = k3 + neqn
        k5 = k4 + neqn
        k6 = k5 + neqn

        !     this interfacing routine merely relieves the user of a long
        !     calling list via the splitting apart of two working storage
        !     arrays. if this is not compatible with the users compiler,
        !     he must use rkfs directly.

        call rkfs(f, neqn, y, t, tout, relerr, abserr, iflag, &
                  work(1), work(k1m), work(k1), work(k2), work(k3), work(k4), work(k5), work(k6), work(k6 + 1), &
                  iwork(1), iwork(2), iwork(3), iwork(4), iwork(5))

        return
    end subroutine rkf45

    !> rkfs integrates a system of first order ordinary differential
    !> equations as described in the comments for rkf45 .
    subroutine rkfs(f, neqn, y, t, tout, relerr, abserr, iflag, &
                    yp, h, f1, f2, f3, f4, f5, savre, savae, &
                    nfe, kop, init, jflag, kflag)

        !     rkfs integrates a system of first order ordinary differential
        !     equations as described in the comments for rkf45 .
        !     the arrays yp,f1,f2,f3,f4,and f5 (of dimension at least neqn) and
        !     the variables h,savre,savae,nfe,kop,init,jflag,and kflag are used
        !     internally by the code and appear in the call list to eliminate
        !     local retention of variables between calls. accordingly, they
        !     should not be altered. items of possible interest are
        !         yp - derivative of solution vector at t
        !         h  - an appropriate stepsize to be used for the next step
        !         nfe- counter on the number of derivative function evaluations

        logical :: hfaild, output

        integer, intent(in) :: neqn
        real(kind=rk), intent(inout) :: y(neqn)
        real(kind=rk), intent(inout) :: t
        real(kind=rk), intent(in) :: tout
        integer, intent(inout) :: iflag
        real(kind=rk), intent(inout) :: relerr

        integer :: nfe, kop, init, jflag, kflag
        real(kind=rk) :: abserr, h, yp(neqn), f1(neqn), f2(neqn), f3(neqn), f4(neqn), f5(neqn), savre, savae

        procedure(fcn) :: f

        real(kind=rk) a, ae, dt, ee, eeoet, esttol, et, hmin, remin, rer, s, scale, tol, toln, twoeps, u26, ypk

        integer :: k, maxnfe, mflag

        !  remin is the minimum acceptable value of relerr.  attempts
        !  to obtain higher accuracy with this subroutine are usually
        !  very expensive and often unsuccessful.

        data remin/1.e-12_rk/

        !     the expense is controlled by restricting the number
        !     of function evaluations to be approximately maxnfe.
        !     as set, this corresponds to about 500 steps.

        data maxnfe/3000/

        !   here two constants emboding the machine epsilon is present
        !   twoesp is set to twice the machine epsilon while u26 is set
        !   to 26 times the machine epsilon

        data twoeps, u26/4.4e-16, 5.72e-15/

        !> Check input parameters
        if (neqn < 1 .or. relerr < 0.0_rk .or. abserr < 0.0_rk) goto 10
        mflag = abs(iflag)
        if ((mflag >= 1) .and. (mflag <= 8)) goto 20

        !     invalid input
10      iflag = 8
        return

        !     is this the first call
20      if (mflag == 1) goto 50

        !     check continuation possibilities

        if ((abs(t - tout) <= twoeps) .and. (kflag /= 3)) goto 10
        if (mflag /= 2) goto 25

        !     iflag = +2 or -2
        if (kflag == 3) goto 45
        if (init == 0) goto 45
        if (kflag == 4) goto 40
        if ((kflag == 5) .and. (abserr == 0.0_rk)) goto 30
        if ((kflag == 6) .and. (relerr <= savre) .and. (abserr <= savae)) goto 30
        goto 50

        !     iflag = 3,4,5,6,7 or 8
25      if (iflag == 3) goto 45
        if (iflag == 4) goto 40
        if ((iflag == 5) .and. (abserr > 0.0_rk)) goto 45

        !     integration cannot be continued since user did not respond to
        !     the instructions pertaining to iflag=5,6,7 or 8
30      stop

        !     reset function evaluation counter
40      nfe = 0
        if (mflag == 2) goto 50

        !     reset flag value from previous call
45      iflag = jflag
        if (kflag == 3) mflag = abs(iflag)

        !     save input iflag and set continuation flag value for subsequent
        !     input checking
50      jflag = iflag
        kflag = 0

        !     save relerr and abserr for checking input on subsequent calls
        savre = relerr
        savae = abserr

        !     restrict relative error tolerance to be at least as large as
        !     2*eps+remin to avoid limiting precision difficulties arising
        !     from impossible accuracy requests

        rer = twoeps + remin
        if (relerr >= rer) goto 55

        !     relative error tolerance too small
        relerr = rer
        iflag = 3
        kflag = 3
        return

55      dt = tout - t

        if (mflag == 1) goto 60
        if (init == 0) goto 65
        goto 80

        !     initialization --
        !                       set initialization completion indicator,init
        !                       set indicator for too many output points,kop
        !                       evaluate initial derivatives
        !                       set counter for function evaluations,nfe
        !                       evaluate initial derivatives
        !                       set counter for function evaluations,nfe
        !                       estimate starting stepsize

60      init = 0
        kop = 0

        a = t
        call f(a, y, yp)
        nfe = 1
        if (t /= tout) goto 65
        iflag = 2
        return

65      init = 1
        h = abs(dt)
        toln = 0.
        do k = 1, neqn
            tol = relerr*abs(y(k)) + abserr
            if (tol <= 0.) goto 70
            toln = tol
            ypk = abs(yp(k))
            if (ypk*h**5 > tol) h = (tol/ypk)**0.2_rk
70      end do
        if (toln <= 0.0_rk) h = 0.0_rk
        h = max(h, u26*max(abs(t), abs(dt)))
        jflag = sign(2, iflag)

        !     set stepsize for integration in the direction from t to tout

80      h = sign(h, dt)

        !     test to see if rkf45 is being severely impacted by too many
        !     output points

        if (abs(h) >= 2.0_rk*abs(dt)) kop = kop + 1
        if (kop /= 100) goto 85

        !     unnecessary frequency of output
        kop = 0
        iflag = 7
        return

85      if (abs(dt) > u26*abs(t)) goto 95

        !     if too close to output point,extrapolate and return

        do k = 1, neqn
            y(k) = y(k) + dt*yp(k)
        end do
        a = tout
        call f(a, y, yp)
        nfe = nfe + 1
        goto 300

        !     initialize output point indicator

95      output = .false.

        !     to avoid premature underflow in the error tolerance function,
        !     scale the error tolerances

        scale = 2.0_rk/relerr
        ae = scale*abserr

        !     step by step integration

100     hfaild = .false.

        !     set smallest allowable stepsize

        hmin = u26*abs(t)

        !     adjust stepsize if necessary to hit the output point.
        !     look ahead two steps to avoid drastic changes in the stepsize and
        !     thus lessen the impact of output points on the code.

        dt = tout - t
        if (abs(dt) >= 2.0_rk*abs(h)) goto 200
        if (abs(dt) > abs(h)) goto 150

        !     the next successful step will complete the integration to the
        !     output point

        output = .true.
        h = dt
        goto 200

150     h = 0.5_rk*dt

        !     core integrator for taking a single step

        !     the tolerances have been scaled to avoid premature underflow in
        !     computing the error tolerance function et.
        !     to avoid problems with zero crossings,relative error is measured
        !     using the average of the magnitudes of the solution at the
        !     beginning and end of a step.
        !     the error estimate formula has been grouped to control loss of
        !     significance.
        !     to distinguish the various arguments, h is not permitted
        !     to become smaller than 26 units of roundoff in t.
        !     practical limits on the change in the stepsize are enforced to
        !     smooth the stepsize selection process and to avoid excessive
        !     chattering on problems having discontinuities.
        !     to prevent unnecessary failures, the code uses 9/10 the stepsize
        !     it estimates will succeed.
        !     after a step failure, the stepsize is not allowed to increase for
        !     the next attempted step. this makes the code more efficient on
        !     problems having discontinuities and more effective in general
        !     since local extrapolation is being used and extra caution seems
        !     warranted.

        !     test number of derivative function evaluations.
        !     if okay,try to advance the integration from t to t+h

200     if (nfe <= maxnfe) goto 220

        !     too much work
        iflag = 4
        kflag = 4
        return

        !     advance an approximate solution over one step of length h

220     call fehl(f, neqn, y, t, h, yp, f1, f2, f3, f4, f5, f1)
        nfe = nfe + 5

        !     compute and test allowable tolerances versus local error estimates
        !     and remove scaling of tolerances. note that relative error is
        !     measured with respect to the average of the magnitudes of the
        !     solution at the beginning and end of the step.

        eeoet = 0.0_rk
        do k = 1, neqn
            et = abs(y(k)) + abs(f1(k)) + ae
            if (et > 0.0_rk) goto 240

            !       inappropriate error tolerance
            iflag = 5
            return

240         ee = abs((-2090.0_rk*yp(k) + &
                      (21970.0_rk*f3(k) - 15048.0_rk*f4(k))) + &
                     (22528.0_rk*f2(k) - 27360.0_rk*f5(k)))
            eeoet = max(eeoet, ee/et)
        end do

        esttol = abs(h)*eeoet*scale/752400.0_rk

        if (esttol <= 1.0_rk) goto 260

        !     unsuccessful step
        !                       reduce the stepsize , try again
        !                       the decrease is limited to a factor of 1/10

        hfaild = .true.
        output = .false.
        s = 0.1_rk
        if (esttol < 59049.0_rk) s = 0.9_rk/esttol**0.2_rk
        h = s*h
        if (abs(h) > hmin) goto 200

        !     requested error unattainable at smallest allowable stepsize
        iflag = 6
        kflag = 6
        return

        !     successful step
        !                        store solution at t+h
        !                        and evaluate derivatives there

260     t = t + h
        do k = 1, neqn
            y(k) = f1(k)
        end do
        a = t
        call f(a, y, yp)
        nfe = nfe + 1

        !                       choose next stepsize
        !                       the increase is limited to a factor of 5
        !                       if step failure has just occurred, next
        !                          stepsize is not allowed to increase

        s = 5.0_rk
        if (esttol > 1.889568e-4_rk) s = 0.9_rk/esttol**0.2_rk
        if (hfaild) s = min(s, 1.0_rk)
        h = sign(max(s*abs(h), hmin), h)

        !     end of core integrator

        !     should we take another step

        if (output) goto 300
        if (iflag > 0) goto 100

        !     integration successfully completed

        !     one-step mode
        iflag = -2
        return

        !     interval mode
300     t = tout
        iflag = 2
        return

    end subroutine rkfs

    !> fehl integrates a system of neqn first order
    !> ordinary differential equations of the form
    !>          dy(i)/dt=f(t,y(1),---,y(neqn))
    !> where the initial values y(i) and the initial derivatives
    !> yp(i) are specified at the starting point t.
    subroutine fehl(f, neqn, y, t, h, yp, f1, f2, f3, f4, f5, s)

        !    fehl advances the solution over the fixed step h and returns
        !    the fifth order (sixth order accurate locally) solution
        !    approximation at t+h in array s(i).
        !    f1,---,f5 are arrays of dimension neqn which are needed
        !    for internal storage.
        !    the formulas have been grouped to control loss of significance.
        !    fehl should be called with an h not smaller than 13 units of
        !    roundoff in t so that the various independent arguments can be
        !    distinguished.

        procedure(fcn) :: f
        integer, intent(in) :: neqn
        real(kind=rk) y(neqn), t, h, yp(neqn), f1(neqn), f2(neqn), f3(neqn), f4(neqn), f5(neqn), s(neqn)

        real(kind=rk) ch
        integer k

        ch = h/4.0_rk
        do k = 1, neqn
            f5(k) = y(k) + ch*yp(k)
        end do
        call f(t + ch, f5, f1)

        ch = 3.0_rk*h/32.0_rk
        do k = 1, neqn
            f5(k) = y(k) + ch*(yp(k) + 3.0_rk*f1(k))
        end do
        call f(t + 3.0_rk*h/8.0_rk, f5, f2)

        ch = h/2197.0_rk
        do k = 1, neqn
            f5(k) = y(k) + ch*(1932.0_rk*yp(k) + (7296.0_rk*f2(k) - 7200.0_rk*f1(k)))
        end do
        call f(t + 12.0_rk*h/13.0_rk, f5, f3)

        ch = h/4104.0_rk
        do k = 1, neqn
            f5(k) = y(k) + ch*((8341.0_rk*yp(k) - 845.0_rk*f3(k)) + &
                               (29440.0_rk*f2(k) - 32832.0_rk*f1(k)))
        end do
        call f(t + h, f5, f4)

        ch = h/20520.0_rk
        do k = 1, neqn
            f1(k) = y(k) + ch*((-6080.0_rk*yp(k) + &
                                (9295.0_rk*f3(k) - 5643.0_rk*f4(k))) &
                               + (41040.0_rk*f1(k) - 28352.0_rk*f2(k)))
        end do
        call f(t + h/2.0_rk, f1, f5)

        !> compute approximate solution at t+h

        ch = h/7618050.0_rk
        do k = 1, neqn
            s(k) = y(k) + ch*((902880.0_rk*yp(k) + (3855735.0_rk*f3(k) - &
                                                                 1371249.0_rk*f4(k))) &
                              + (3953664.0_rk*f2(k) + 277020.0_rk*f5(k)))
        end do

        return
    end subroutine fehl

end module rkf45_module