Notebook Status: Validated
Validation Notes: This tutorial notebook has been confirmed to be self-consistent with its corresponding NRPy+ module, as documented below. In addition, each of these Butcher tables has been verified to yield an RK method to the expected local truncation error in a challenging battery of ODE tests in the RK Butcher Table Validation tutorial notebook.
The family of explicit Runge Kutta-like methods are commonly used when numerically solving ordinary differential equation (ODE) initial value problems of the form
y′(t)=f(y,t), y(t0)=y0.These methods can be extended to solve time-dependent partial differential equations (PDEs) via the Method of Lines. In the Method of Lines, the above ODE can be generalized to N coupled ODEs, all written as first-order-in-time PDEs of the form
∂tu(t,x,y,u1,u2,u3,...)=f(t,x,y,...,u1,u1,x,...),where u and f are vectors. The spatial partial derivatives of components of u, e.g., u1,x, may be computed using approximate numerical differentiation, like finite differences.
As any explicit Runge-Kutta method has its own unique local truncation error, can in principle be used to solve time-dependent PDEs using the Method of Lines, and may be stable under different Courant-Friedrichs-Lewy (CFL) conditions, it is useful to have multiple methods at one's disposal. This module provides a number of such methods.
More details about the Method of Lines are discussed further in the Tutorial-RK_Butcher_Table_Generating_C_Code module where we generate the C code to implement the Method of Lines, and additional description can be found in the Numerically Solving the Scalar Wave Equation: A Complete C Code NRPy+ tutorial notebook.
This notebook is organized as follows
MoLtimestepping.RK_Butcher_Table_Dictionary
NRPy+ module# Step 1: Initialize needed Python modules
import sympy as sp # SymPy: The Python computer algebra package upon which NRPy+ depends
In general, a predictor-corrector method performs an estimate timestep from n to n+1, using e.g., a Runge Kutta method, to get a prediction of the solution at timestep n+1. This is the "predictor" step. Then it uses this prediction to perform another, "corrector" step, designed to increase the accuracy of the solution.
Let us focus on the ordinary differential equation (ODE)
y′(t)=f(y,t),which acts as an analogue for a generic PDE ∂tu(t,x,y,...)=f(t,x,y,...,u,ux,...).
The general family of Runge Kutta "explicit" timestepping methods are implemented using the following scheme:
yn+1=yn+s∑i=1bikiwhere
k1=Δtf(yn,tn)k2=Δtf(yn+[a21k1],tn+c2Δt)k3=Δtf(yn+[a31k1+a32k2],tn+c3Δt) ⋮ks=Δtf(yn+[as1k1+as2k2+⋯+as,s−1ks−1],tn+csΔt).Note s is the number of right-hand side evaluations necessary for any given method, i.e., for RK2 s=2 and for RK4 s=4, and for RK6 s=7. These schemes are often written in the form of a so-called "Butcher tableau" or "Butcher table."
0c2a21c3a31a32⋮⋮⋱csas1as2⋯as,s−1b1b2⋯bs−1bsAs an example, the "classic" fourth-order Runge Kutta (RK4) method obtains the solution y(t) to the single-variable ODE y′(t)=f(y(t),t) at time tn+1 from tn via:
k1=Δtf(yn,tn),k2=Δtf(yn+12k1,tn+Δt2),k3=Δtf(yn+12k2,tn+Δt2),k4=Δtf(yn+k3,tn+Δt),yn+1=yn+16(k1+2k2+2k3+k4)+O((Δt)5).Its corresponding Butcher table is constructed as follows:
01/21/21/201/210011/61/31/31/6This is one example of many explicit Runge Kutta methods. Throughout the following sections we will highlight different Runge Kutta schemes and their Butcher tables from the first-order Euler's method up to and including an eighth-order method.
We can store all of the Butcher tables in Python's Dictionary format using the curly brackets {} and 'key':value pairs. The 'key' will be the name of the Runge Kutta method and the value will be the Butcher table itself stored as a list of lists. The convergence order for each Runge Kutta method is also stored. We will construct the dictionary Butcher_dict
one Butcher table at a time in the following sections.
# Step 2a: Generating a Dictionary of Butcher Tables for Explicit Runge Kutta Techniques
# Initialize the dictionary Butcher_dict
Butcher_dict = {}
Forward Euler's method is a first order Runge Kutta method. Euler's method obtains the solution y(t) at time tn+1 from tn via: yn+1=yn+Δtf(yn,tn)
# Step 2.a.i: Euler's Method
Butcher_dict['Euler'] = (
[[sp.sympify(0)],
["", sp.sympify(1)]]
, 1)
Heun's method is a second-order RK method that obtains the solution y(t) at time tn+1 from tn via: k1=Δtf(yn,tn),k2=Δtf(yn+k1,tn+Δt),yn+1=yn+12(k1+k2)+O((Δt)3)
# Step 2.a.ii: RK2 Heun's Method
Butcher_dict['RK2 Heun'] = (
[[sp.sympify(0)],
[sp.sympify(1), sp.sympify(1)],
["", sp.Rational(1,2), sp.Rational(1,2)]]
, 2)
Midpoint method is a second-order RK method that obtains the solution y(t) at time tn+1 from tn via: k1=Δtf(yn,tn),k2=Δtf(yn+23k1,tn+23Δt),yn+1=yn+12k2+O((Δt)3)
# Step 2.a.iii: RK2 Midpoint (MP) Method
Butcher_dict['RK2 MP'] = (
[[sp.sympify(0)],
[sp.Rational(1,2), sp.Rational(1,2)],
["", sp.sympify(0), sp.sympify(1)]]
, 2)
Ralston's method (see Ralston (1962), is a second-order RK method that obtains the solution y(t) at time tn+1 from tn via: k1=Δtf(yn,tn),k2=Δtf(yn+12k1,tn+12Δt),yn+1=yn+14k1+34k2+O((Δt)3)
# Step 2.a.iv: RK2 Ralston's Method
Butcher_dict['RK2 Ralston'] = (
[[sp.sympify(0)],
[sp.Rational(2,3), sp.Rational(2,3)],
["", sp.Rational(1,4), sp.Rational(3,4)]]
, 2)
Kutta's third-order method obtains the solution y(t) at time tn+1 from tn via: k1=Δtf(yn,tn),k2=Δtf(yn+12k1,tn+12Δt),k3=Δtf(yn−k1+2k2,tn+Δt)yn+1=yn+16k1+23k2+16k3+O((Δt)4)
# Step 2.a.v: Kutta's Third-order Method
Butcher_dict['RK3'] = (
[[sp.sympify(0)],
[sp.Rational(1,2), sp.Rational(1,2)],
[sp.sympify(1), sp.sympify(-1), sp.sympify(2)],
["", sp.Rational(1,6), sp.Rational(2,3), sp.Rational(1,6)]]
, 3)
Heun's third-order method obtains the solution y(t) at time tn+1 from tn via:
k1=Δtf(yn,tn),k2=Δtf(yn+13k1,tn+13Δt),k3=Δtf(yn+23k2,tn+23Δt)yn+1=yn+14k1+34k3+O((Δt)4)with corresponding Butcher table
01/31/32/302/31/403/4.# Step 2.a.vi: RK3 Heun's Method
Butcher_dict['RK3 Heun'] = (
[[sp.sympify(0)],
[sp.Rational(1,3), sp.Rational(1,3)],
[sp.Rational(2,3), sp.sympify(0), sp.Rational(2,3)],
["", sp.Rational(1,4), sp.sympify(0), sp.Rational(3,4)]]
, 3)
Ralston's third-order method (see Ralston (1962), obtains the solution y(t) at time tn+1 from tn via:
k1=Δtf(yn,tn),k2=Δtf(yn+12k1,tn+12Δt),k3=Δtf(yn+34k2,tn+34Δt)yn+1=yn+29k1+13k2+49k3+O((Δt)4)with corresponding Butcher table 01/21/23/403/42/91/34/9.
# Step 2.a.vii: RK3 Ralton's Method
Butcher_dict['RK3 Ralston'] = (
[[0],
[sp.Rational(1,2), sp.Rational(1,2)],
[sp.Rational(3,4), sp.sympify(0), sp.Rational(3,4)],
["", sp.Rational(2,9), sp.Rational(1,3), sp.Rational(4,9)]]
, 3)
The Strong Stability Preserving Runge-Kutta (SSPRK3) method obtains the solution y(t) at time tn+1 from tn via:
k1=Δtf(yn,tn),k2=Δtf(yn+k1,tn+Δt),k3=Δtf(yn+14k1+14k2,tn+12Δt)yn+1=yn+16k1+16k2+23k3+O((Δt)4)with corresponding Butcher table 0111/21/41/41/61/62/3.
# Step 2.a.viii: Strong Stability Preserving Runge-Kutta (SSPRK3) Method
Butcher_dict['SSPRK3'] = (
[[0],
[sp.sympify(1), sp.sympify(1)],
[sp.Rational(1,2), sp.Rational(1,4), sp.Rational(1,4)],
["", sp.Rational(1,6), sp.Rational(1,6), sp.Rational(2,3)]]
, 3)
The classic RK4 method obtains the solution y(t) at time tn+1 from tn via:
k1=Δtf(yn,tn),k2=Δtf(yn+12k1,tn+Δt2),k3=Δtf(yn+12k2,tn+Δt2),k4=Δtf(yn+k3,tn+Δt),yn+1=yn+16(k1+2k2+2k3+k4)+O((Δt)5)with corresponding Butcher table
01/21/21/201/210011/61/31/31/6.# Step 2.a.vix: Classic RK4 Method
Butcher_dict['RK4'] = (
[[sp.sympify(0)],
[sp.Rational(1,2), sp.Rational(1,2)],
[sp.Rational(1,2), sp.sympify(0), sp.Rational(1,2)],
[sp.sympify(1), sp.sympify(0), sp.sympify(0), sp.sympify(1)],
["", sp.Rational(1,6), sp.Rational(1,3), sp.Rational(1,3), sp.Rational(1,6)]]
, 4)
The fifth-order Dormand-Prince (DP) method from the RK5(4) family (see Dormand, J. R.; Prince, P. J. (1980)) Butcher table is:
01515310340940454445−561532989193726561−253602187644486561−212729190173168−3553346732524749176−51031865613538405001113125192−2187678411843538405001113125192−2187678411840.# Step 2.a.x: RK5 Dormand-Prince Method
Butcher_dict['DP5'] = (
[[0],
[sp.Rational(1,5), sp.Rational(1,5)],
[sp.Rational(3,10),sp.Rational(3,40), sp.Rational(9,40)],
[sp.Rational(4,5), sp.Rational(44,45), sp.Rational(-56,15), sp.Rational(32,9)],
[sp.Rational(8,9), sp.Rational(19372,6561), sp.Rational(-25360,2187), sp.Rational(64448,6561), sp.Rational(-212,729)],
[sp.sympify(1), sp.Rational(9017,3168), sp.Rational(-355,33), sp.Rational(46732,5247), sp.Rational(49,176), sp.Rational(-5103,18656)],
[sp.sympify(1), sp.Rational(35,384), sp.sympify(0), sp.Rational(500,1113), sp.Rational(125,192), sp.Rational(-2187,6784), sp.Rational(11,84)],
["", sp.Rational(35,384), sp.sympify(0), sp.Rational(500,1113), sp.Rational(125,192), sp.Rational(-2187,6784), sp.Rational(11,84), sp.sympify(0)]]
, 5)
The fifth-order Dormand-Prince (DP) method from the RK6(5) family (see Dormand, J. R.; Prince, P. J. (1981)) Butcher table is:
011011029−2812081376151372−270343105313723532435500−5455509497150049981787545−26492371257255280823375−2420637125338459155612376−3511−2411731603899983200772−5225183639254056821108000196837182517527391260039536727852704350.# Step 2.a.xi: RK5 Dormand-Prince Method Alternative
Butcher_dict['DP5alt'] = (
[[0],
[sp.Rational(1,10), sp.Rational(1,10)],
[sp.Rational(2,9), sp.Rational(-2, 81), sp.Rational(20, 81)],
[sp.Rational(3,7), sp.Rational(615, 1372), sp.Rational(-270, 343), sp.Rational(1053, 1372)],
[sp.Rational(3,5), sp.Rational(3243, 5500), sp.Rational(-54, 55), sp.Rational(50949, 71500), sp.Rational(4998, 17875)],
[sp.Rational(4, 5), sp.Rational(-26492, 37125), sp.Rational(72, 55), sp.Rational(2808, 23375), sp.Rational(-24206, 37125), sp.Rational(338, 459)],
[sp.sympify(1), sp.Rational(5561, 2376), sp.Rational(-35, 11), sp.Rational(-24117, 31603), sp.Rational(899983, 200772), sp.Rational(-5225, 1836), sp.Rational(3925, 4056)],
["", sp.Rational(821, 10800), sp.sympify(0), sp.Rational(19683, 71825), sp.Rational(175273, 912600), sp.Rational(395, 3672), sp.Rational(785, 2704), sp.Rational(3, 50)]]
, 5)
The fifth-order Cash-Karp Method (see J. R. Cash, A. H. Karp. (1980)) Butcher table is:
0151531034094035310−910651−115452−70273527781631552961755125751382444275110592253409637378025062112559405121771.# Step 2.a.xii: RK5 Cash-Karp Method
Butcher_dict['CK5'] = (
[[0],
[sp.Rational(1,5), sp.Rational(1,5)],
[sp.Rational(3,10),sp.Rational(3,40), sp.Rational(9,40)],
[sp.Rational(3,5), sp.Rational(3,10), sp.Rational(-9,10), sp.Rational(6,5)],
[sp.sympify(1), sp.Rational(-11,54), sp.Rational(5,2), sp.Rational(-70,27), sp.Rational(35,27)],
[sp.Rational(7,8), sp.Rational(1631,55296), sp.Rational(175,512), sp.Rational(575,13824), sp.Rational(44275,110592), sp.Rational(253,4096)],
["",sp.Rational(37,378), sp.sympify(0), sp.Rational(250,621), sp.Rational(125,594), sp.sympify(0), sp.Rational(512,1771)]]
, 5)
The sixth-order Dormand-Prince method (see Dormand, J. R.; Prince, P. J. (1981)) Butcher Table is:
011011029−2812081376151372−270343105313723532435500−5455509497150049981787545−26492371257255280823375−2420637125338459155612376−3511−2411731603899983200772−52251836392540561465467266112−29451232−561020114158144105135733212352−424325205632376225454272061864098415321776168071460161375734413755408−371120110.# Step 2.a.xiii: RK6 Dormand-Prince Method
Butcher_dict['DP6'] = (
[[0],
[sp.Rational(1,10), sp.Rational(1,10)],
[sp.Rational(2,9), sp.Rational(-2, 81), sp.Rational(20, 81)],
[sp.Rational(3,7), sp.Rational(615, 1372), sp.Rational(-270, 343), sp.Rational(1053, 1372)],
[sp.Rational(3,5), sp.Rational(3243, 5500), sp.Rational(-54, 55), sp.Rational(50949, 71500), sp.Rational(4998, 17875)],
[sp.Rational(4, 5), sp.Rational(-26492, 37125), sp.Rational(72, 55), sp.Rational(2808, 23375), sp.Rational(-24206, 37125), sp.Rational(338, 459)],
[sp.sympify(1), sp.Rational(5561, 2376), sp.Rational(-35, 11), sp.Rational(-24117, 31603), sp.Rational(899983, 200772), sp.Rational(-5225, 1836), sp.Rational(3925, 4056)],
[sp.sympify(1), sp.Rational(465467, 266112), sp.Rational(-2945, 1232), sp.Rational(-5610201, 14158144), sp.Rational(10513573, 3212352), sp.Rational(-424325, 205632), sp.Rational(376225, 454272), sp.sympify(0)],
["", sp.Rational(61, 864), sp.sympify(0), sp.Rational(98415, 321776), sp.Rational(16807, 146016), sp.Rational(1375, 7344), sp.Rational(1375, 5408), sp.Rational(-37, 1120), sp.Rational(1,10)]]
, 6)
Luther's sixth-order method (see H. A. Luther (1968)) Butcher table is: 01112381823827227827(7−q)14(−21+9q)392(−56+8q)392(336−48q)392(−63+3q)392(7+q)14(−1155−255q)1960(−280−40q)1960320q1960(63+363q)1960(2352+392q)19601(330+105q)18023(−200+280q)180(126−189q)180(−686−126q)180(490−70q)1801200164504918049180120
where q=√21.
# Step 2.a.xiv: RK6 Luther's Method
q = sp.sqrt(21)
Butcher_dict['L6'] = (
[[0],
[sp.sympify(1), sp.sympify(1)],
[sp.Rational(1,2), sp.Rational(3,8), sp.Rational(1,8)],
[sp.Rational(2,3), sp.Rational(8,27), sp.Rational(2,27), sp.Rational(8,27)],
[(7 - q)/14, (-21 + 9*q)/392, (-56 + 8*q)/392, (336 -48*q)/392, (-63 + 3*q)/392],
[(7 + q)/14, (-1155 - 255*q)/1960, (-280 - 40*q)/1960, (-320*q)/1960, (63 + 363*q)/1960, (2352 + 392*q)/1960],
[sp.sympify(1), ( 330 + 105*q)/180, sp.Rational(2,3), (-200 + 280*q)/180, (126 - 189*q)/180, (-686 - 126*q)/180, (490 - 70*q)/180],
["", sp.Rational(1, 20), sp.sympify(0), sp.Rational(16, 45), sp.sympify(0), sp.Rational(49, 180), sp.Rational(49, 180), sp.Rational(1, 20)]]
, 6)
The eighth-order Dormand-Prince Method (see Dormand, J. R.; Prince, P. J. (1981)) Butcher table is:
01181181121481161813203325165160−75647564383800031632059400294438416145639060077736538692538347−28693883112500000023124283180000000093200160161419466929110061564180158732637227897136334457775458157362771057229−1801936671043307555549002324897191698213963270857359108300−433636366683701615−421739975261629230110030283172342305979020416483981308780063531037830712871320246121993134084778700−3769504279515268766246−3091217441061227803−12992083490766935600594349321089478693930062171396673457123872331100102978912011468111299019798−102846818984618001400847823578350851285213117294951432422823−103041299951701304382−487779250593047939560153367262481032824649−4544286818133984676963065993473597172653118589217771811604300−3185094517667107341−4777554141098053517−703635378230739211573156678710275455275232866602850066563−40936645358086882573962137247180595741865686358487910083140386385449106310900−5068492393434740067−41142199754304380565278362791429660411173962825925320556−13158990841618472703439366476291978049680−16052805968517852524863810314135310600140054513354800640000−592384931068277825181606767758867731561292985797845732−104189143013713435297604172391151165299118820643751138087−528747749222060717014.# Step 2.a.xv: RK8 Dormand-Prince Method
Butcher_dict['DP8']=(
[[0],
[sp.Rational(1, 18), sp.Rational(1, 18)],
[sp.Rational(1, 12), sp.Rational(1, 48), sp.Rational(1, 16)],
[sp.Rational(1, 8), sp.Rational(1, 32), sp.sympify(0), sp.Rational(3, 32)],
[sp.Rational(5, 16), sp.Rational(5, 16), sp.sympify(0), sp.Rational(-75, 64), sp.Rational(75, 64)],
[sp.Rational(3, 8), sp.Rational(3, 80), sp.sympify(0), sp.sympify(0), sp.Rational(3, 16), sp.Rational(3, 20)],
[sp.Rational(59, 400), sp.Rational(29443841, 614563906), sp.sympify(0), sp.sympify(0), sp.Rational(77736538, 692538347), sp.Rational(-28693883, 1125000000), sp.Rational(23124283, 1800000000)],
[sp.Rational(93, 200), sp.Rational(16016141, 946692911), sp.sympify(0), sp.sympify(0), sp.Rational(61564180, 158732637), sp.Rational(22789713, 633445777), sp.Rational(545815736, 2771057229), sp.Rational(-180193667, 1043307555)],
[sp.Rational(5490023248, 9719169821), sp.Rational(39632708, 573591083), sp.sympify(0), sp.sympify(0), sp.Rational(-433636366, 683701615), sp.Rational(-421739975, 2616292301), sp.Rational(100302831, 723423059), sp.Rational(790204164, 839813087), sp.Rational(800635310, 3783071287)],
[sp.Rational(13, 20), sp.Rational(246121993, 1340847787), sp.sympify(0), sp.sympify(0), sp.Rational(-37695042795, 15268766246), sp.Rational(-309121744, 1061227803), sp.Rational(-12992083, 490766935), sp.Rational(6005943493, 2108947869), sp.Rational(393006217, 1396673457), sp.Rational(123872331, 1001029789)],
[sp.Rational(1201146811, 1299019798), sp.Rational(-1028468189, 846180014), sp.sympify(0), sp.sympify(0), sp.Rational(8478235783, 508512852), sp.Rational(1311729495, 1432422823), sp.Rational(-10304129995, 1701304382), sp.Rational(-48777925059, 3047939560), sp.Rational(15336726248, 1032824649), sp.Rational(-45442868181, 3398467696), sp.Rational(3065993473, 597172653)],
[sp.sympify(1), sp.Rational(185892177, 718116043), sp.sympify(0), sp.sympify(0), sp.Rational(-3185094517, 667107341), sp.Rational(-477755414, 1098053517), sp.Rational(-703635378, 230739211), sp.Rational(5731566787, 1027545527), sp.Rational(5232866602, 850066563), sp.Rational(-4093664535, 808688257), sp.Rational(3962137247, 1805957418), sp.Rational(65686358, 487910083)],
[sp.sympify(1), sp.Rational(403863854, 491063109), sp.sympify(0), sp.sympify(0), sp.Rational(-5068492393, 434740067), sp.Rational(-411421997, 543043805), sp.Rational(652783627, 914296604), sp.Rational(11173962825, 925320556), sp.Rational(-13158990841, 6184727034), sp.Rational(3936647629, 1978049680), sp.Rational(-160528059, 685178525), sp.Rational(248638103, 1413531060), sp.sympify(0)],
["", sp.Rational(14005451, 335480064), sp.sympify(0), sp.sympify(0), sp.sympify(0), sp.sympify(0), sp.Rational(-59238493, 1068277825), sp.Rational(181606767, 758867731), sp.Rational(561292985, 797845732), sp.Rational(-1041891430, 1371343529), sp.Rational(760417239, 1151165299), sp.Rational(118820643, 751138087), sp.Rational(-528747749, 2220607170), sp.Rational(1, 4)]]
, 8)
In the previous parts of this notebook, we worked exclusively with Explicit Runge-Kutta-Like Schemes. There is a second category of schemes that are useful, however, and those are the Adaptive Runge-Kutta-Like Schemes. These methods, while technically still explicit in function, nonetheless need to be implemented differently. They have an extra row at the bottom of their butcher tables which define a second method of a different order. Adaptive Runge-Kutta-Like methods will calculate the predicted values of both results and use the difference between them to estimate the error of the lower-order method in a highly efficient manner. Details can be found on the Runge-Kutta wikipedia page.
As a reminder, the general result of any Runge-Kutta-Like scheme is
yn+1=yn+s∑i=1biki.An Adaptive method will also calculate the following
y∗n+1=yn+s∑i=1b∗ikiWhere the b∗ coefficients are stored in the butcher table on an extra row, like so:
0c2a21c3a31a32⋮⋮⋮⋱csas1as2⋯as,s−1b1b2⋯bs−1bsb∗1b∗2⋯b∗s−1b∗sFrom this method the error estimate is calcualted as en+1=yn+1−y∗n+1. The exact use of this error is implementation-dependent, but in general, if the error exceeds a provided threshold the scheme will discard the step and try again at a smaller timestep, and if the error is below a provided threshold the scheme will increase the step size (but in general it will not discard the data, it already calculated it, why waste it?).
The higher order method is the higher row of b-coefficients.
As before, we can store all of the Butcher tables in Python's Dictionary format using the curly brackets {} and 'key':value pairs. The 'key' will be the name of the Runge Kutta method and the value will be the Butcher table itself stored as a list of lists. The convergence order for each Runge Kutta method is also stored. We will add to the dictionary Butcher_dict
one Butcher table at a time in the following sections.
Adaptive methods are all prefaced with an A in their name, for clarity's sake.
The Heun-Euler method is a combination of Heun's second-order method and the first-order Euler method, creating the simplest Adaptive method.
0111/21/210Butcher_dict['AHE'] = (
[[sp.sympify(0)],
[sp.sympify(1), sp.sympify(1)],
["", sp.Rational(1,2), sp.Rational(1,2)],
["", sp.sympify(1), sp.sympify(0)]]
, 2)
The Bogacki-Shampine Method is a third and second order method.
01/21/23/403/412/91/34/92/91/34/907/241/41/31/8Butcher_dict['ABS'] = (
[[sp.sympify(0)],
[sp.Rational(1,2), sp.Rational(1,2)],
[sp.Rational(3,4), sp.sympify(0), sp.Rational(3,4)],
[sp.sympify(1), sp.Rational(2,9), sp.Rational(1,3), sp.Rational(4,9)],
["", sp.Rational(2,9), sp.Rational(1,3), sp.Rational(4,9), sp.sympify(0)],
["", sp.Rational(7,24), sp.Rational(1,4), sp.Rational(1,3), sp.Rational(1,8)]]
, 3)
The Runge-Kutta-Fehlberg Method is a fifth and fourth order method.
0141438332932121319322197−72002197729621971439216−83680513−845410412−8272−354425651859410411401613506656128252856156430−9502552521601408256521974104−150Butcher_dict['ARKF'] = (
[[sp.sympify(0)],
[sp.Rational(1,4), sp.Rational(1,4)],
[sp.Rational(3,8), sp.Rational(3,32), sp.Rational(9,32)],
[sp.Rational(12,13), sp.Rational(1932,2197), sp.Rational(-7200,2197), sp.Rational(7296,2197)],
[sp.sympify(1), sp.Rational(439,216), sp.sympify(-8), sp.Rational(3680,513), sp.Rational(-845,4104)],
[sp.Rational(1,2), sp.Rational(-8,27), sp.sympify(2), sp.Rational(-3544,2565), sp.Rational(1859,4104), sp.Rational(-11,40)],
["", sp.Rational(16,135), sp.sympify(0), sp.Rational(6656,12825), sp.Rational(28561,56430), sp.Rational(-9,50), sp.Rational(2,55)],
["", sp.Rational(25,216), sp.sympify(0), sp.Rational(1408,2565), sp.Rational(2197,4104), sp.Rational(-1,5), sp.sympify(0)]]
, 5)
The Cash-Karp Method is a fifth and fourth order method.
0151531034094035310−910651−115452−7027352778163155296175512575138444275110592−2534096373780250621125594051217712825276480185754838413525552962771433614Butcher_dict['ACK'] = (
[[0],
[sp.Rational(1,5), sp.Rational(1,5)],
[sp.Rational(3,10),sp.Rational(3,40), sp.Rational(9,40)],
[sp.Rational(3,5), sp.Rational(3,10), sp.Rational(-9,10), sp.Rational(6,5)],
[sp.sympify(1), sp.Rational(-11,54), sp.Rational(5,2), sp.Rational(-70,27), sp.Rational(35,27)],
[sp.Rational(7,8), sp.Rational(1631,55296), sp.Rational(175,512), sp.Rational(575,13824), sp.Rational(44275,110592), sp.Rational(253,4096)],
["",sp.Rational(37,378), sp.sympify(0), sp.Rational(250,621), sp.Rational(125,594), sp.sympify(0), sp.Rational(512,1771)],
["",sp.Rational(2825,27648), sp.sympify(0), sp.Rational(18575,48384), sp.Rational(13525,55296), sp.Rational(277,14336), sp.Rational(1,4)]]
, 5)
The Dormand-Prince5(4) Method is a fifth and fourth order method.
01515310340940454445−561532989193726561−253602187644486561−212729190173168−3553346732524749176−51031865613538405001113125192−2187678411843538405001113125192−21876784118405179576000757116695393640−920973392001872100140.Butcher_dict['ADP5'] = (
[[0],
[sp.Rational(1,5), sp.Rational(1,5)],
[sp.Rational(3,10),sp.Rational(3,40), sp.Rational(9,40)],
[sp.Rational(4,5), sp.Rational(44,45), sp.Rational(-56,15), sp.Rational(32,9)],
[sp.Rational(8,9), sp.Rational(19372,6561), sp.Rational(-25360,2187), sp.Rational(64448,6561), sp.Rational(-212,729)],
[sp.sympify(1), sp.Rational(9017,3168), sp.Rational(-355,33), sp.Rational(46732,5247), sp.Rational(49,176), sp.Rational(-5103,18656)],
[sp.sympify(1), sp.Rational(35,384), sp.sympify(0), sp.Rational(500,1113), sp.Rational(125,192), sp.Rational(-2187,6784), sp.Rational(11,84)],
["", sp.Rational(35,384), sp.sympify(0), sp.Rational(500,1113), sp.Rational(125,192), sp.Rational(-2187,6784), sp.Rational(11,84), sp.sympify(0)],
["", sp.Rational(5179,57600), sp.sympify(0), sp.Rational(7571,16695), sp.Rational(393,640), sp.Rational(-92097,339200), sp.Rational(187,2100), sp.Rational(1,40)]]
, 5)
The Domand-Prince 8(7) Method is an eighth and seventh order method.
01181181121481161813203325165160−75647564383800031632059400294438416145639060077736538692538347−28693883112500000023124283180000000093200160161419466929110061564180158732637227897136334457775458157362771057229−1801936671043307555549002324897191698213963270857359108300−433636366683701615−421739975261629230110030283172342305979020416483981308780063531037830712871320246121993134084778700−3769504279515268766246−3091217441061227803−12992083490766935600594349321089478693930062171396673457123872331100102978912011468111299019798−102846818984618001400847823578350851285213117294951432422823−103041299951701304382−487779250593047939560153367262481032824649−4544286818133984676963065993473597172653118589217771811604300−3185094517667107341−4777554141098053517−703635378230739211573156678710275455275232866602850066563−40936645358086882573962137247180595741865686358487910083140386385449106310900−5068492393434740067−41142199754304380565278362791429660411173962825925320556−13158990841618472703439366476291978049680−16052805968517852524863810314135310600140054513354800640000−592384931068277825181606767758867731561292985797845732−104189143013713435297604172391151165299118820643751138087−528747749222060717014134519324551766230000−80871984697600014517570044685645159321656045339265891186−38675747211518517206465885868322736535530112386675167192450.Butcher_dict['ADP8']=(
[[0],
[sp.Rational(1, 18), sp.Rational(1, 18)],
[sp.Rational(1, 12), sp.Rational(1, 48), sp.Rational(1, 16)],
[sp.Rational(1, 8), sp.Rational(1, 32), sp.sympify(0), sp.Rational(3, 32)],
[sp.Rational(5, 16), sp.Rational(5, 16), sp.sympify(0), sp.Rational(-75, 64), sp.Rational(75, 64)],
[sp.Rational(3, 8), sp.Rational(3, 80), sp.sympify(0), sp.sympify(0), sp.Rational(3, 16), sp.Rational(3, 20)],
[sp.Rational(59, 400), sp.Rational(29443841, 614563906), sp.sympify(0), sp.sympify(0), sp.Rational(77736538, 692538347), sp.Rational(-28693883, 1125000000), sp.Rational(23124283, 1800000000)],
[sp.Rational(93, 200), sp.Rational(16016141, 946692911), sp.sympify(0), sp.sympify(0), sp.Rational(61564180, 158732637), sp.Rational(22789713, 633445777), sp.Rational(545815736, 2771057229), sp.Rational(-180193667, 1043307555)],
[sp.Rational(5490023248, 9719169821), sp.Rational(39632708, 573591083), sp.sympify(0), sp.sympify(0), sp.Rational(-433636366, 683701615), sp.Rational(-421739975, 2616292301), sp.Rational(100302831, 723423059), sp.Rational(790204164, 839813087), sp.Rational(800635310, 3783071287)],
[sp.Rational(13, 20), sp.Rational(246121993, 1340847787), sp.sympify(0), sp.sympify(0), sp.Rational(-37695042795, 15268766246), sp.Rational(-309121744, 1061227803), sp.Rational(-12992083, 490766935), sp.Rational(6005943493, 2108947869), sp.Rational(393006217, 1396673457), sp.Rational(123872331, 1001029789)],
[sp.Rational(1201146811, 1299019798), sp.Rational(-1028468189, 846180014), sp.sympify(0), sp.sympify(0), sp.Rational(8478235783, 508512852), sp.Rational(1311729495, 1432422823), sp.Rational(-10304129995, 1701304382), sp.Rational(-48777925059, 3047939560), sp.Rational(15336726248, 1032824649), sp.Rational(-45442868181, 3398467696), sp.Rational(3065993473, 597172653)],
[sp.sympify(1), sp.Rational(185892177, 718116043), sp.sympify(0), sp.sympify(0), sp.Rational(-3185094517, 667107341), sp.Rational(-477755414, 1098053517), sp.Rational(-703635378, 230739211), sp.Rational(5731566787, 1027545527), sp.Rational(5232866602, 850066563), sp.Rational(-4093664535, 808688257), sp.Rational(3962137247, 1805957418), sp.Rational(65686358, 487910083)],
[sp.sympify(1), sp.Rational(403863854, 491063109), sp.sympify(0), sp.sympify(0), sp.Rational(-5068492393, 434740067), sp.Rational(-411421997, 543043805), sp.Rational(652783627, 914296604), sp.Rational(11173962825, 925320556), sp.Rational(-13158990841, 6184727034), sp.Rational(3936647629, 1978049680), sp.Rational(-160528059, 685178525), sp.Rational(248638103, 1413531060), sp.sympify(0)],
["", sp.Rational(14005451, 335480064), sp.sympify(0), sp.sympify(0), sp.sympify(0), sp.sympify(0), sp.Rational(-59238493, 1068277825), sp.Rational(181606767, 758867731), sp.Rational(561292985, 797845732), sp.Rational(-1041891430, 1371343529), sp.Rational(760417239, 1151165299), sp.Rational(118820643, 751138087), sp.Rational(-528747749, 2220607170), sp.Rational(1, 4)],
["", sp.Rational(13451932, 455176623), sp.sympify(0), sp.sympify(0), sp.sympify(0), sp.sympify(0), sp.Rational(-808719846, 976000145), sp.Rational(1757004468, 5645159321), sp.Rational(656045339, 265891186), sp.Rational(-3867574721, 1518517206), sp.Rational(465885868, 322736535), sp.Rational(53011238, 667516719), sp.Rational(2, 45), sp.sympify(0)]]
, 8)
There is one method we have on offer that is not an RK-style method. Instead of taking the previous step and evolving to the next one, it takes into account several prior steps and extrapolates from them to the next step. This is the Adams-Bashforth method. Its primary weakness is that it requires several points to be evaluated before it can run (specifically, points equal to the desired order of the method). However, its strengths are that it takes into account longer-scale patterns as well as being able to be scaled up to arbitrary order. More detailed information can be found on the Wikipedia page.
The first few Adams-Bashforth methods are as follows.
yn+1=yn+hf(yn,tn)Careful readers may have noticed that the first Adams-Bashforth (AB) method is identical to the Euler method. Note that the second and third-order AB methods require 2 and 3 previous points, respectively, to evaluate the next one. This trend continues to arbitrary order. In general, a method of order s can be stated as
yn+s=yn+s−1+hs−1∑i=0aif(yn+i,tn+i).For any singular method, only a vector needs to be specified, the values ai for the order in question. These coefficients have a closed-form expression for them, although it isn't neat.
as−j−1=(−1)jj!(s−j−1)!∫10s−1∏i=0;i≠j(u+i)duFrom this arbitrary order, methods can be chosen. In order to get the order number of points that AB requires to extrapolate, there are a couple of options. If the user does not wish to use RK methods, one can use a "ramping up" effect, starting with the first order AB method, then using those values in the second order, then the second order in the third, and so on until the desired order is reached. More often, though, an RK method is used at the start and then extrapolation is passed to the AB method. Do note: the AB method as dictated here requires that the step size be constant since it cares a lot about the relation between the previous points. While it is possible to make a generic AB method that can take points separated by any variable step size, that functionality is not included here as it would ruin the closed-form relatively simple analytic expression for the coefficients.
Rather than providing a single table, we actually have a code that can generate a table for any AB method, though by default we have it create one of order 19, chosen because it's an arbitrarily large number that we should never need to use. Strictly speaking, only one vector of values is required for an AB method, but when we generate the 19th order vector, we also choose to generate all previous orders so if we wished to use a pure-AB method without relying on RK to seed values, we have access to the lower-order methods.
Of note: experiments with using the AB method indicate that the really high-order methods are not necessarily more accurate, believed to be due to roundoff error accumulating over time. It has been observed (though not rigorously proven) that once an AB method is calculating near roundoff error, higher order methods will be less accurate, so the best AB order to use is likely the lowest order one that evaluates near roundoff error.
import numpy as np
from sympy import symbols
from sympy import integrate
# okay so our goal here is to calculate the coefficients to arbitrary precision for
# Adams-Bashforth methods.
# Careful! AB methods are NOT structured the same as RK methods, the same code
# cannot read them!
# this does not work to generate an order 1 method. But why would you need to generate
# that, it's just a 1x1 matrix with a 1 in it.
order = 19 # change this if you want different orders. Default 19, which takes several seconds to generate.
pythonButcher = [[sp.Rational(0.0/1.0),sp.Rational(0.0/1.0)],
[sp.Rational(0.0/1.0),sp.Rational(0.0/1.0)]]
# just to initialize it, we'll set it in a minute
n = 1
while n < order+1:
j = 0
while j < n: # the number of coefficients in each method is equal to the order.
# set up the product
x = symbols('x')
expr = x
i = 0
while i < n:
if i == j:
i = i+1
else:
expr = expr*(x + i)
i = i+1
expr = expr/x
expr2 = integrate(expr,(x,0,1))
expr2 = expr2 * sp.Rational((-1.0)**j,(sp.factorial(j)* sp.factorial(n - j - 1)))
if (len(pythonButcher) < n):
pythonButcher.append([sp.Rational(0.0/1.0),sp.Rational(0.0/1.0)])
# add another row if we need it.
if (len(pythonButcher[n-1]) < j+1):
pythonButcher[n-1].append(expr2)
i = 0
while i < n:
# Uncomment this section to fill the matrix with zeroes
# if(len(pythonButcher[i]) < len(pythonButcher[n-1])):
# pythonButcher[i].append(sp.sympify(0))
i = i+1
else:
pythonButcher[n-1][j] = expr2
j = j+1
n = n+1
# Due to the way we set this up to make sure everything was explicitly an array,
# We need to remove a single trailing zero.
# Comment out if you want the zeroes.
pythonButcher[0] = [1]
Butcher_dict['AB']=(
pythonButcher
, order)
MoLtimestepping.RK_Butcher_Table_Dictionary
NRPy+ module [Back to top]¶As a code validation check, we verify agreement in the dictionary of Butcher tables between
We analyze all key/value entries in the dictionary for consistency.
# Step 3: Code validation against MoLtimestepping.RK_Butcher_Table_Dictionary NRPy+ module
import sys # Standard Python module for multiplatform OS-level functions
from MoLtimestepping.RK_Butcher_Table_Dictionary import Butcher_dict as B_dict
valid = True
for key, value in Butcher_dict.items():
if Butcher_dict[key] != B_dict[key]:
valid = False
print(key)
if valid == True and len(Butcher_dict.items()) == len(B_dict.items()):
print("The dictionaries match!")
else:
print("ERROR: Dictionaries don't match!")
sys.exit(1)
The dictionaries match!
The following code cell converts this Jupyter notebook into a proper, clickable LATEX-formatted PDF file. After the cell is successfully run, the generated PDF may be found in the root NRPy+ tutorial directory, with filename Tutorial-RK_Butcher_Table_Dictionary.pdf. (Note that clicking on this link may not work; you may need to open the PDF file through another means.)
import cmdline_helper as cmd # NRPy+: Multi-platform Python command-line interface
cmd.output_Jupyter_notebook_to_LaTeXed_PDF("Tutorial-RK_Butcher_Table_Dictionary")
Created Tutorial-RK_Butcher_Table_Dictionary.tex, and compiled LaTeX file to PDF file Tutorial-RK_Butcher_Table_Dictionary.pdf