
rklib: A modern Fortran library of fixed and variable-step Runge-Kutta solvers.
The focus of this library is single-step, explicit Runge-Kutta solvers for 1st order differential equations.
stepsize_class in the code. The options include the error norm (max, 2-norm, RMS, or user-defined), the scaling of the relative tolerance, the step size limits and safety factor, the PI (Lund) stabilized step size control, and how the two error estimates of rkdp85 are combined. With the appropriate options, the step size control of Hairer's DOPRI5 and DOP853 codes is reproduced (see the documentation of stepsize_class).real kind is selectable via a compiler directive (REAL32, REAL64, or REAL128).| Name | Description | Properties | Order | Stages | Registers | CFL | Reference |
|---|---|---|---|---|---|---|---|
euler |
Euler | 1 | 1 | 1 | 1.0 | Euler (1768) | |
midpoint |
Midpoint | 2 | 2 | 2 | ? | ||
heun |
Heun | 2 | 2 | 2 | ? | ||
rkssp22 |
2-stage, 2nd order TVD Runge-Kutta Shu-Osher | SSP | 2 | 2 | 1 | 1.0 | Shu & Oscher (1988) |
rk3 |
3th order Runge-Kutta | 3 | 3 | 3 | ? | ||
rkssp33 |
3-stage, 3rd order TVD Runge-Kutta Shu-Osher | SSP | 3 | 3 | 1 | 1.0 | Shu & Oscher (1988) |
rkssp53 |
5-stage, 3rd order SSP Runge-Kutta Spiteri-Ruuth | SSP | 3 | 5 | 2 | 2.65 | Ruuth (2006) |
rk4 |
Classic 4th order Runge-Kutta | 4 | 4 | 4 | Kutta (1901) | ||
rks4 |
4th order Runge-Kutta Shanks | 4 | 4 | 4 | Shanks (1965) | ||
rkr4 |
4th order Runge-Kutta Ralston | 4 | 4 | 4 | Ralston (1962) | ||
rkls44 |
4-stage, 4th order low storage non-TVD Runge-Kutta Jiang-Shu | LS | 4 | 4 | 2 | Jiang and Shu (1988) | |
rkls54 |
5-stage, 4th order low storage Runge-Kutta Carpenter-Kennedy | LS | 4 | 5 | 2 | 0.32 | Carpenter & Kennedy (1994) |
rkssp54 |
5-stage, 4th order SSP Runge-Kutta Spiteri-Ruuth | SSP | 4 | 5 | 4 | 1.51 | Ruuth (2006) |
rks5 |
5th order Runge-Kutta Shanks | 5 | 5 | 5 | Shanks (1965) | ||
rk5 |
5th order Runge-Kutta | 5 | 6 | 6 | ? | ||
rkc5 |
5th order Runge-Kutta Cassity | 5 | 6 | 6 | Cassity (1966) | ||
rkl5 |
5th order Runge-Kutta Lawson | 5 | 6 | 6 | Lawson (1966) | ||
rklk5a |
5th order Runge-Kutta Luther-Konen 1 | 5 | 6 | 6 | Luther & Konen (1965) | ||
rklk5b |
5th order Runge-Kutta Luther-Konen 2 | 5 | 6 | 6 | Luther & Konen (1965) | ||
rkb6 |
6th order Runge-Kutta Butcher | 6 | 7 | 7 | Butcher (1963) | ||
rk7 |
7th order Runge-Kutta Shanks | 7 | 9 | 9 | Shanks (1965) | ||
rk8_10 |
10-stage, 8th order Runge-Kutta Shanks | 8 | 10 | 10 | Shanks (1965) | ||
rkcv8 |
11-stage, 8th order Runge-Kutta Cooper-Verner | 8 | 11 | 11 | Cooper & Verner (1972) | ||
rk8_12 |
12-stage, 8th order Runge-Kutta Shanks | 8 | 12 | 12 | Shanks (1965) | ||
rks10 |
10th order Runge-Kutta Stepanov | 10 | 15 | 15 | Stepanov (2025) | ||
rkz10 |
10th order Runge-Kutta Zhang | 10 | 16 | 16 | Zhang (2019) | ||
rko10 |
10th order Runge-Kutta Ono | 10 | 17 | 17 | Ono (2003) | ||
rkh10 |
10th order Runge-Kutta Hairer | 10 | 17 | 17 | Hairer (1978) |
| Name | Description | Properties | Order | Stages | Registers | CFL | Reference |
|---|---|---|---|---|---|---|---|
rkbs32 |
Bogacki & Shampine 3(2) | FSAL | 3 | 4 | 4 | Bogacki & Shampine (1989) | |
rkssp43 |
4-stage, 3rd order SSP | SSP, LS | 3 | 4 | 2 | 2.0 | Kraaijevanger (1991), Conde et al. (2018) |
rkf45 |
Fehlberg 4(5) | 4 | 6 | 6 | Fehlberg (1969) | ||
rkck54 |
Cash & Karp 5(4) | 5 | 6 | 6 | Cash & Karp (1990) | ||
rkdp54 |
Dormand-Prince 5(4) | FSAL, DENSE | 5 | 7 | 7 | Dormand & Prince (1980) | |
rkt54 |
Tsitouras 5(4) | FSAL | 5 | 7 | 7 | Tsitouras (2011) | |
rks54 |
Stepanov 5(4) | FSAL | 5 | 7 | 7 | Stepanov (2022) | |
rkpp54 |
Papakostas-PapaGeorgiou 5(4) | FSAL | 5 | 7 | 7 | Papakostas & Papageorgiou (1996) | |
rkpp54b |
Papakostas-PapaGeorgiou 5(4) b | FSAL | 5 | 7 | 7 | Papakostas & Papageorgiou (1996) | |
rkbs54 |
Bogacki & Shampine 5(4) | 5 | 8 | 8 | Bogacki & Shampine (1996) | ||
rkss54 |
Sharp & Smart 5(4) | 5 | 7 | 7 | Sharp & Smart (1993) | ||
rkdp65 |
Dormand-Prince 6(5) | 6 | 8 | 8 | Dormand & Prince (1981) | ||
rkc65 |
Calvo 6(5) | 6 | 9 | 9 | Calvo (1990) | ||
rktp64 |
Tsitouras & Papakostas NEW6(4) | 6 | 7 | 7 | Tsitouras & Papakostas (1999) | ||
rkv65e |
Verner efficient (9,6(5)) | FSAL, DENSE | 6 | 9 | 9 | Verner (1994), Verner (2010) | |
rkv65r |
Verner robust (9,6(5)) | FSAL, DENSE | 6 | 9 | 9 | Verner (1994), Verner (2010) | |
rkv65 |
Verner 6(5) | 6 | 8 | 8 | Verner (2006) | ||
dverk65 |
Verner 6(5) "DVERK" | 6 | 8 | 8 | Verner (?) | ||
rktf65 |
Tsitouras & Famelis 6(5) | FSAL | 6 | 9 | 9 | Tsitouras & Famelis (2006) | |
rktp75 |
Tsitouras & Papakostas NEW7(5) | 7 | 9 | 9 | Tsitouras & Papakostas (1999) | ||
rktmy7 |
7th order Tanaka-Muramatsu-Yamashita | 7 | 10 | 10 | Tanaka, Muramatsu & Yamashita (1992) | ||
rktmy7s |
7th order Stable Tanaka-Muramatsu-Yamashita | 7 | 10 | 10 | Tanaka, Muramatsu & Yamashita (1992) | ||
rkv76e |
Verner efficient (10:7(6)) | DENSE | 7 | 10 | 10 | Verner (1978), Verner (2010) | |
rkv76r |
Verner robust (10:7(6)) | DENSE | 7 | 10 | 10 | Verner (1978), Verner (2010) | |
rkss76 |
Sharp & Smart 7(6) | 7 | 11 | 11 | Sharp & Smart (1993) | ||
rkf78 |
Fehlberg 7(8) | 7 | 13 | 13 | Fehlberg (1968) | ||
rkv78 |
Verner 7(8) | 7 | 13 | 13 | Verner (1978) | ||
dverk78 |
Verner "Maple" 7(8) | DENSE | 7 | 13 | 13 | Verner (?), Stone (dense) | |
rkdp85 |
Dormand-Prince 8(5) | DENSE | 8 | 12 | 12 | Hairer (1993) | |
rktp86 |
Tsitouras & Papakostas NEW8(6) | 8 | 12 | 12 | Tsitouras & Papakostas (1999) | ||
rkdp87 |
Dormand & Prince RK8(7)13M | DENSE | 8 | 13 | 13 | Prince & Dormand (1981), Stone (dense) | |
rkv87e |
Verner efficient (8)7 | DENSE | 8 | 13 | 13 | Verner (1978), Verner (2010) | |
rkv87r |
Verner robust (8)7 | DENSE | 8 | 13 | 13 | Verner (1978), Verner (2010) | |
rkev87 |
Enright-Verner (8)7 | DENSE | 8 | 13 | 13 | Enright (1993), Stone (dense) | |
rkk87 |
Kovalnogov-Fedorov-Karpukhina-Simos-Tsitouras 8(7) | 8 | 13 | 13 | Kovalnogov, Fedorov, Karpukhina, Simos, Tsitouras (2022) | ||
rkf89 |
Fehlberg 8(9) | 8 | 17 | 17 | Fehlberg (1968) | ||
rkv89 |
Verner 8(9) | 8 | 16 | 16 | Verner (1978) | ||
rkt98a |
Tsitouras 9(8) A | 9 | 16 | 16 | Tsitouras (2001) | ||
rkv98e |
Verner efficient (16:9(8)) | DENSE | 9 | 16 | 16 | Verner (1978), Verner (2010) | |
rkv98r |
Verner robust (16:9(8)) | DENSE | 9 | 16 | 16 | Verner (1978), Verner (2010) | |
rks98 |
Sharp 9(8) | 9 | 16 | 16 | Sharp (2000) | ||
rkf108 |
Feagin 8(10) | 10 | 17 | 17 | Feagin (2006) | ||
rkc108 |
Curtis 10(8) | 10 | 21 | 21 | Curtis (1975) | ||
rkb109 |
Baker 10(9) | 10 | 21 | 21 | Baker (?) | ||
rks1110a |
Stone 11(10) | 11 | 26 | 26 | Stone (2015) | ||
rkf1210 |
Feagin 12(10) | 12 | 25 | 25 | Feagin (2006) | ||
rko129 |
Ono 12(9) | 12 | 29 | 29 | Ono (2006) | ||
rkf1412 |
Feagin 14(12) | 14 | 35 | 35 | Feagin (2006) |
Basic use of the library is shown here (this uses the rktp86 method):
program rklib_example
use rklib_module, wp => rk_module_rk
use iso_fortran_env, only: output_unit
implicit none
integer,parameter :: n = 2 !! dimension of the system
real(wp),parameter :: tol = 1.0e-12_wp !! integration tolerance
real(wp),parameter :: t0 = 0.0_wp !! initial t value
real(wp),parameter :: dt = 1.0_wp !! initial step size
real(wp),parameter :: tf = 100.0_wp !! endpoint of integration
real(wp),dimension(n),parameter :: x0 = [0.0_wp,0.1_wp] !! initial x value
real(wp),dimension(n) :: xf !! final x value
type(rktp86_class) :: prop
character(len=:),allocatable :: message
call prop%initialize(n=n,f=fvpol,rtol=[tol],atol=[tol])
call prop%integrate(t0,x0,dt,tf,xf)
call prop%status(message=message)
write (output_unit,'(A)') message
write (output_unit,'(A,F7.2/,A,2E18.10)') &
'tf =',tf ,'xf =',xf(1),xf(2)
contains
subroutine fvpol(me,t,x,f)
!! Right-hand side of van der Pol equation
class(rk_class),intent(inout) :: me
real(wp),intent(in) :: t
real(wp),dimension(:),intent(in) :: x
real(wp),dimension(:),intent(out) :: f
f(1) = x(2)
f(2) = 0.2_wp*(1.0_wp-x(1)**2)*x(2) - x(1)
end subroutine fvpol
end program rklib_example
The result is:
Success
tf = 100.00
xf = -0.1360372426E+01 0.1325538438E+01
Variable-step methods with the DENSE property have an interpolating polynomial over each step, so the solution can be computed at any time within a step without taking extra integration steps. It is off by default (so there is no overhead), and is enabled when initializing the integrator:
dense_output=.true. : enables the interpolate and dense_interval methods, and uses the dense output for root finding in integrate_to_event (which saves function evaluations).dense_output_step=dt : the report function is called at a fixed interval (t0, t0+dt, t0+2*dt, ...), plus the final time (or the event), regardless of the steps the integrator takes. This also enables the dense output.For example, to report every 0.1 time units:
type(rkdp85_class) :: prop
call prop%initialize(n=n,f=fvpol,rtol=[tol],atol=[tol],report=report,dense_output_step=0.1_wp)
call prop%integrate(t0,x0,dt,tf,xf)
Or, to compute points yourself within the report function (which is called after each step):
subroutine report(me,t,x)
class(rk_class),intent(inout) :: me
real(wp),intent(in) :: t
real(wp),dimension(:),intent(in) :: x
logical :: available
real(wp) :: t_prev
real(wp),dimension(size(x)) :: x_mid
call me%dense_interval(available,t0=t_prev) ! the step that was just taken: [t_prev, t]
if (available) call me%interpolate((t_prev+t)/2.0_wp,x_mid) ! e.g., the midpoint
end subroutine report
When to use it:
fixed_step_mode), since the integrator still takes the largest steps that meet the error tolerance.Notes: Only the most recent step can be interpolated, and interpolate must not be called from the derivative function. Some methods (e.g., rkdp85) need additional function evaluations for the dense output, but these are only done for steps where it is used.
Running the unit tests will generate some performance plots. The following is for the variable-step methods compiled with quadruple precision (e.g, fpm test rk_test_variable_step --compiler ifort --flag "-DREAL128"):
A Fortran Package Manager manifest file is included, so that the library and test cases can be compiled with FPM. For example:
fpm build --profile release
fpm test --profile release
To use rklib within your FPM project, add the following to your fpm.toml file:
[dependencies]
rklib = { git="https://github.com/jacobwilliams/rklib.git" }
By default, the library is built with double precision (real64) real values. Explicitly specifying the real kind can be done using the following processor flags:
| Preprocessor flag | Kind | Number of bytes |
|---|---|---|
REAL32 |
real(kind=real32) |
4 |
REAL64 |
real(kind=real64) |
8 |
REAL128 |
real(kind=real128) |
16 |
For example, to build a single precision version of the library, use:
fpm build --profile release --flag "-DREAL32"
To generate the documentation using FORD, run:
ford ford.md
coefficients app (not required to use the library, but used to generate some of the code) requires the mpfun2020-var1 arbitrary precision library.All of these will be automatically downloaded by FPM.
The latest API documentation for the master branch can be found here. This was generated from the source code using FORD.
The original version of this code was split off from the Fortran Astrodynamics Toolkit in September 2022.
To add a new method to this library:
scripts/generate_files.py)python scripts/generate_files.py to update all the include files. This script will generate all the boilerplate code for all the methods. It will also this README file.rklib_fixed_steps.f90 or rklib_variable_steps.f90). Note that you can generate a template of an RK step function using the scripts/generate_rk_code.py script. The two command line arguments are the number of function evaluations required and the method name (e.g., 'rk4'). Edit the template accordingly (note at the FSAL ones have a slightly different format).DENSE to its properties in the table, and add a <name>_dense_coefficients routine to rklib_variable_dense.f90. This returns the coefficients as they are published, in either the polynomial form (dense_form_polynomial) or the form used in DOPRI5/DOP853 (dense_form_dopri). If the dense output requires additional function evaluations, also add the method to the dense_extra_stage_methods list, and add a <name>_dense_extra_stages routine that computes them (these are only computed when the dense output is used). When enabled (with dense_output=.true. in initialize), the interpolated state can then be computed with the interpolate method (e.g., from the report function, using dense_interval to get the interval of the step that was just taken), and it is used for event location in integrate_to_event.rkv65e, rkv65r, rkv76e, rkv76r, rkv87e, rkv87r, rkv98e, rkv98r) and for rkdp87, rkev87 and dverk78 are generated by python scripts/generate_dense.py, from Verner's coefficient files (downloaded from the Internet Archive, since Verner's site is no longer online) and from Peter Stone's Maple worksheets. The script checks the coefficients against the step coefficients in rklib_variable_steps.f90, converts the interpolants to the form used in DOPRI5/DOP853 (which has a much smaller roundoff error for these coefficients), and updates the marked block in rklib_variable_dense.f90. Use --check to also verify all the order conditions of the interpolants. See the docstring of that script for details.The rklib source code and related files and documentation are distributed under a permissive free software license (BSD-3).