GithubHelp home page GithubHelp logo

jacobwilliams / rklib Goto Github PK

View Code? Open in Web Editor NEW
69.0 5.0 3.0 16.09 MB

Fixed and variable-step Runge-Kutta solvers in Modern Fortran

Home Page: https://jacobwilliams.github.io/rklib/

License: BSD 3-Clause "New" or "Revised" License

Fortran 96.35% Python 3.65%
fortran fortran-package-manager ode-solver runge-kutta runge-kutta-adaptive-step-size runge-kutta-fehlberg differential-equations ode root-finding

rklib's Introduction

rklib

rklib: A modern Fortran library of fixed and variable-step Runge-Kutta solvers.

Language GitHub release CI Status codecov last-commit

Description

The focus of this library is single-step, explicit Runge-Kutta solvers for 1st order differential equations.

Novel features:

  • The library includes a wide range of both fixed and variable-step Runge-Kutta methods, from very low to very high order.
  • It is object-oriented and written in modern Fortran.
  • It allows for defining a variable-step size integrator with a custom-tuned step size selection method. See stepsize_class in the code.
  • The real kind is selectable via a compiler directive (REAL32, REAL64, or REAL128).
  • Integration to an event is also supported. The root-finding method is also selectable (via the roots-fortran library).

Available Runge-Kutta methods:

  • Number of fixed-step methods: 27
  • Number of variable-step methods: 48
  • Total number of methods: 75

Fixed-step methods:

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)
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)

Variable-step methods:

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 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 6 9 9 Verner (1994)
rkv65r Verner robust (9,6(5)) FSAL 6 9 9 Verner (1994)
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)) 7 10 10 Verner (1978)
rkv76r Verner robust (10:7(6)) 7 10 10 Verner (1978)
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) 7 13 13 Verner (?)
rkdp85 Dormand-Prince 8(5) 8 12 12 Hairer (1993)
rktp86 Tsitouras & Papakostas NEW8(6) 8 12 12 Tsitouras & Papakostas (1999)
rkdp87 Dormand & Prince RK8(7)13M 8 13 13 Prince & Dormand (1981)
rkv87e Verner efficient (8)7 8 13 13 Verner (1978)
rkv87r Verner robust (8)7 8 13 13 Verner (1978)
rkev87 Enright-Verner (8)7 8 13 13 Enright (1993)
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)) 9 16 16 Verner (1978)
rkv98r Verner robust (16:9(8)) 9 16 16 Verner (1978)
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)

Properties key:

  • LS = Low storage
  • SSP = Strong stability preserving
  • FSAL = First same as last
  • CFL = Courant-Friedrichs-Lewy

Example use case

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

Example performance comparison

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"): rk_test_variable_step_R16.pdf

Compiling

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

3rd Party Dependencies

  • The library requires roots-fortran.
  • The unit tests require pyplot-fortran, to generate the performance plots.
  • The 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.

Documentation

The latest API documentation for the master branch can be found here. This was generated from the source code using FORD.

Notes

The original version of this code was split off from the Fortran Astrodynamics Toolkit in September 2022.

For developers

To add a new method to this library:

  • Update the tables (either the fixed or variable one in scripts/generate_files.py)
  • Run 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.
  • Add a step function (either in 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).
  • Update the unit tests.

License

The rklib source code and related files and documentation are distributed under a permissive free software license (BSD-3).

References

See also

  • FOODIE Fortran Object-Oriented Differential-equations Integration Environment
  • FLINT Fortran Library for numerical INTegration of differential equations
  • DDEABM Modern Fortran implementation of the DDEABM Adams-Bashforth algorithm
  • DOP853 Modern Fortran Edition of Hairer's DOP853 ODE Solver. An explicit Runge-Kutta method of order 8(5,3) for problems y'=f(x,y); with dense output of order 7
  • DVODE Modern Fortran Edition of the DVODE ODE Solver
  • ODEPACK Work in Progress to refactor and modernize the ODEPACK Library
  • libode Easy-to-compile, high-order ODE solvers as C++ classes

rklib's People

Contributors

hugomvale avatar jacobwilliams avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

rklib's Issues

Error in DVERK65

Line 1561 of file rklib_variable_steps.f90 contains
call me%f(t+h, x,f1)
The correct version is
call me%f(t, x,f1)

Structure of the files

I moved stuff into Fortran submodules because I didn't want everything all in one file. But then I didn't like all that boilerplate that had to be in the main file, so I moved those into .inc files, which isn't great either.

Is there a better way?

Also, GitHub thinks the .inc files are C++. Is there an extension it would recognize as a Fortran include file?

Discrepancy between some rational number coefficients and their decimal values

Lines 3950-3954 of file rklib_variable_steps.f90 read:

real(wp),parameter :: d7   = 7170564158.0_wp / 30263027435.0_wp    ! 0.01158056612815422_wp
real(wp),parameter :: d8   = 7206303747.0_wp / 16758195910.0_wp    ! -0.7627079959184843_wp
real(wp),parameter :: d9   = -1059739258.0_wp / 8472387467.0_wp    ! 2.046330367018225_wp
real(wp),parameter :: d10  = 16534129531.0_wp / 11550853505.0_wp   ! -4.163198889384351_wp
real(wp),parameter :: d11  = -3.0_wp / 2.0_wp                      ! 2.901200440989918_wp

The decimal value shown in the comment section of each of these lines does not agree with the rational number on the same line, sometimes even the sign is wrong. Look, for example, at d11.

Plot line style/color

Add some code to automatically change the color and/or line style for the methods in the plots, so we don't have to manually pick colors for each one.

Merge `x` and `xf` into single inout argument in step procedures

Currently, step methods have an interface like so:

subroutine step_func_fixed(me,t,x,h,xf)
!! rk step function for the fixed-step methods.
import :: rk_fixed_step_class,wp
implicit none
  class(rk_fixed_step_class),intent(inout) :: me
  real(wp),intent(in)                      :: t  !! initial time
  real(wp),dimension(me%n),intent(in)      :: x  !! initial state vector
  real(wp),intent(in)                      :: h  !! time step \( |\Delta t| \)
  real(wp),dimension(me%n),intent(out)     :: xf !! final state vector
end subroutine step_func_fixed

There is no need for a separate arguments x intent(in) and xf, intent(out). They can be merged into a single argument x, intent(inout), thereby avoiding a redundant state vector.

For instance, for Euler:

module procedure euler
associate (f1 => me%funcs(:,1))
    call me%f(t,x,f1)
    x = x + h*f1
end associate
end procedure euler

The change should be more or less straightforward for all methods expressed in the classical the rk form. For SSP and LS methods, one registry will probably have to be added because the implementations were already making use of the extra x. I can help with that; just let me know what you think.

Check results of order test

See #31

Investigate the cases where this test is failing and see if there is some error.

Currently, the ones failing are:

    Method  Variable                      xf  Success
 ----------------------------------------------------
      rkr4         F   1.699041129050761E+00        F
      rkc5         F   1.555555555555555E+00        F
      rkl5         F   1.388888888888889E+00        F
    rklk5a         F   1.416666666666667E+00        F
     rko10         F   1.333333333333333E+00        F
     rkh10         F   1.333333333333333E+00        F
    rkdp65         T   1.423611111111112E+00        F
    rkv65e         T   9.999999999997726E-01        F
     rkv65         T   1.438888888888889E+00        F
   dverk65         T   1.450000000000000E+00        F
    rktp75         T   7.022258831547581E-01        F
    rktmy7         T   2.639828876496722E+00        F
    rkv76r         T   1.331980864835947E+00        F
   dverk78         T   1.000000000000023E+00        F
    rkdp85         T   1.076664947110369E+00        F
    rktp86         T   2.389109029661114E-01        F
    rkv87e         T   9.999999999999840E-01        F
    rkv87r         T   1.357836517333566E+00        F
    rkev87         T   1.351341629155199E+00        F
     rkk87         T   1.370168115647311E+00        F
    rkt98a         T   9.999999999999893E-01        F
    rkv98r         T   1.131299675056499E+00        F
     rks98         T   1.266647547720950E+00        F
    rkc108         T   1.333333333333333E+00        F
    rkb109         T   1.307444093579318E+00        F
  rks1110a         T   1.281633046469373E+00        F

Is check `if (h==zero)` required?

All fixed-step methods have the following check at the start:

if (h==zero) then
    xf = x
    return
end if

I have the impression that this check is not required and actually a bit "detrimental":

  • strictly speaking, a real==real comparison is questionable.
  • if such a check were necessary, wouldn't it be preferable to move it up the procedure hierarchy, to avoid repeating it in every step call and every method implementation (DRY principle)?
  • all methods must anyway be consistent, in the sense that dx = xf -x must be 0 for h=0. So, there is no need to protect against a step call with h=0.

Notation `terr`

In the variable-step method definitions, the symbols x and t are used to the denote the dependent and independent variables, respectively.

In variable-step methods the truncation error (on $x$) is denoted terr. I suppose the first letter "t" stands for "truncation", but my eyes+brain automatically link first letter "t" to the symbol t, i.e. to time. So, my reflex is to read: "time error".

Perhaps, you could rename terr as xerr, to make it more obvious that we are talking about an error on x.

More unit tests

Should have some more comprehensive tests of all the methods, and their performance comparisons for different problems.

Other root finder inputs

See #26

Allow for the additional root finder options to be optional user inputs for the even finding integration.

possible issue in the relative step error computations in case of variable step-size integrators

I am not 100% on this because you are using slightly different step size computation formulae. In step computation routine for each integrator (variable-step), you compute the step errors as this

xerr = sum (h*e(i)*F(i))

Then in step size computation routine (rklib_module.F90) , the max_err is computed like this

max_err = abs(xerr*h/tol)

So you are saying that step error for your integrators is O(h^2) and not O(h). Is that correct? Since you have included so many different algorithms so it may be you are using a different step size formulae than what is more typically used (esp. with DOP54 and DOP853). In case your formula is correct then you can close this issue.

Improve support for method properties (name, etc.)

I think it would be helpful if the method classes included a character component storing the name of the method. Among other things, we could then run tests in a more programmatic manner.

Actually, pushing the idea one step further and combing it with #22, I was wondering if it would make sense to add the properties of a method as components of the corresponding concrete class:

type, extends (rk_fixed_step_class), public :: euler_class
    !! Basic Euler method
    integer :: p =1 ! order
    integer :: ncache = 1 ! cache size
    character(:), allocatable ::  name = "Euler"
    contains
    procedure :: step => euler
end type euler_class

I believe this would also avoid the need to have $N$ "method_order" procedures, whose sole purpose is to return p.

Unused parameters in `rklib_variable_steps.f90`

I've just built the lib with fpm build --verbose and noticed some warnings about unused parameters in rklib_variable_steps.f90. Maybe it's nothing, but it could also be the consequence of some typo (e.g., one parameter is used two twice and another none).

Cache for auxiliary variables in step procedures

The step procedures require a number of auxiliary variables to carry out the corresponding calculations, for instance f1,...f6 in the algorithm below.

module procedure rkf45
real(wp),dimension(me%n) :: f1,f2,f3,f4,f5,f6
...

For each time step, memory to store these variables needs to be allocated and then freed up. For very large ODE sets, these vectors will be huge and the corresponding memory operations can be significantly time consuming.

For optimal efficiency, the corresponding memory should be allocated once and then reused at each step, without repeated (de)allocation. AFAIU, DifferentialEquations.js also does so as well.

In principle, the abstract rk_class could include an allocatable component real(wp), allocatable :: cache(:,:). The cache would be allocated during the instantiation of the concrete method class. Inside the step method, we could then associate:

module procedure rkf45
associate (f1=>me%cache(:,1), f2=>me%cache(:,2), ... )
...

Quite likely, I am missing some important aspects, but I hope the main idea makes some sense.

Dense output

Some of the methods have dense interpolates. Those should be added to support dense output.

Selectable root finder

Currently the event-finding algorithm uses the brent_solver from roots-fortran. This could be an optional input, so the caller can select any of the methods in that library.

Notation in readme example

In the example where the lib is used to solve the van der Pol oscillator, the independent variable is x. This choice of symbol is counter-intuitive because the oscillator is a function of time. Thus it would be preferable to use t, i.e. $y(t)$ or $x(t)$.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.