Fourth order Runge-Kutta numerical integration

Here’s a Python implementation of RK4, hardcoded for double-integrating the second derivative (acceleration up to position). For a more generalized solution, see my other implementation. I tried to keep this as simple as I could, so people can easily see the relation between the ‘math form’ and ‘code form’ of the algorithm.

def rk4(x, v, a, dt):
	"""Returns final (position, velocity) tuple after
	time dt has passed.
 
	x: initial position (number-like object)
	v: initial velocity (number-like object)
	a: acceleration function a(x,v,dt) (must be callable)
	dt: timestep (number)"""
	x1 = x
	v1 = v
	a1 = a(x1, v1, 0)
 
	x2 = x + 0.5*v1*dt
	v2 = v + 0.5*a1*dt
	a2 = a(x2, v2, dt/2.0)
 
	x3 = x + 0.5*v2*dt
	v3 = v + 0.5*a2*dt
	a3 = a(x3, v3, dt/2.0)
 
	x4 = x + v3*dt
	v4 = v + a3*dt
	a4 = a(x4, v4, dt)
 
	xf = x + (dt/6.0)*(v1 + 2*v2 + 2*v3 + v4)
	vf = v + (dt/6.0)*(a1 + 2*a2 + 2*a3 + a4)
 
	return xf, vf

Here is an example usage of the function and a comparison to Euler integration:

def accel(x, v, dt):
	"""Determines acceleration from current position,
	velocity, and timestep. This particular acceleration
	function models a spring."""
	stiffness = 1
	damping = -0.005
	return -stiffness*x - damping*v
 
t = 0
dt = 1.0/40 # Timestep of 1/40 second
state = 50, 5 # Position, velocity
euler = 50, 5 # For comparison with Euler integration
 
print "Initial    -position: %6.2f, velocity: %6.2f"%state
 
# Run for 100 seconds
while t < 100:
	t += dt
	state = rk4(state[0], state[1], accel, dt)
 
	# Integrate using Euler's method
	euler = (
		euler[0] + euler[1]*dt,
		euler[1] + accel(euler[0],euler[1],dt)*dt
	)
 
print "Final RK4  -position: %6.2f, velocity: %6.2f"%state
print "Final Euler-position: %6.2f, velocity: %6.2f"%euler

The output of this really shows how much more accurate RK4 integration can be:

Initial    -position:  50.00, velocity:   5.00
Final RK4  -position:  52.18, velocity:  38.05
Final Euler-position: 178.38, velocity: 137.62

As the timestep is decreased (meaning more computation), Euler approaches RK4 (shown at timestep of 1/400 seconds):

Initial    -position:  50.00, velocity:   5.00
Final RK4  -position:  52.28, velocity:  37.92
Final Euler-position:  59.20, velocity:  43.02
This entry was posted in Mathematics, Physics, Programming. Bookmark the permalink.

6 Responses to Fourth order Runge-Kutta numerical integration

  1. Pingback: Improved RK4 Implementation « Doswa

  2. Fantastic code! Just one thing your damping should be positive in order to be consistent with your other signs; currently it is adding energy to the system not absorbing energy. Also I added a plotting function and did a pendulum. So using the rk4 function as above this will make a nifty animated pendulum:

    def accel(x, v, dt):
        """Determines acceleration from current position,
        velocity, and timestep. This particular acceleration
        function models a pendulum.  And plots!"""
        g=-9.8
        l=100.0
        return (g/l)*x
    
    l=100.0 #pendulum_length
    p=10 #postion for x axis
    c=(np.pi/180.0) #conversion to radians
    t = 0
    dt = 1.0/10 # Timestep of 1/40 second
    state =20*c, 0*c # start Position, velocity
    
    do_plot=1 #switch off of zero to turn off plotting
    if (do_plot==1):
      ion()
      line, =plot([p,p],[l,0],'o-')
    
    # Run for 100 seconds
    while t < 100:
        t += dt
        state = rk4(state[0], state[1], accel, dt)
        if (do_plot==1):
          line.set_xdata([p,p+np.sin(state[0])])
          line.set_ydata([l,l*(1.0-np.cos(state[0]))])
          draw()
    
  3. simmuskhan says:

    Thanks soooo much for the simple way you put that RK4 code together, really helped me see how it works, as I’ve had difficulty with some of the other ways I’ve seen it written.

    Just a few questions if you’ve got the time/inclination to help out!

    I’m putting together an orbital code, and need to use an Runge Kutta Fehlberg routine, but can’t see how to adapt it to work. I think I’m just not sure how the time fractions fit in with the function fractions.

    Thanks again for the easy to read code, worked a treat!

  4. David says:

    Hi simmuskhan:

    I had never heard of the RKF method until now, but after looking into it, I feel that it overcomplicates things. For many situations, RK4 is “good enough”.

    However, here’s a basic implementation of RKF. You’ll probably want to add some code to put minimum/maximum values on dt. This one is hardcoded for 1st order differential equations, but it shouldn’t be too hard to modify for higher orders.

    def rkf45(t, dt, y, f, tolerance=1e-5):
    	"""Runge-Kutta-Fehlberg method.
     
    	t = Current time.
    	dt = Timestep.
    	y = Initial value.
    	f = Derivative function y' = f(t, y).
    	tolerance = Error tolerance.
     
    	Returns a (tf, dtf, yf) tuple after time dt has passed, where:
    	tf = Final time.
    	yf = Final value.
    	dtf = Error-corrected timestep for next step."""
     
     
    	# Calculate the slopes at various points.
    	# Values taken from http://en.wikipedia.org/wiki/Runge-Kutta-Fehlberg_method.
    	k1 = f(t, y)
    	k2 = f(t+(1/4.0)*dt, y + k1*(1/4.0)*dt)
    	k3 = f(t+(3/8.0)*dt, y + k1*(3/32.0)*dt + k2*(9/32.0)*dt)
    	k4 = f(t+(12/13.0)*dt, y + k1*(1932/2197.0)*dt + k2*(-7200/2197.0)*dt + k3*(7296/2197.0)*dt)
    	k5 = f(t+dt, y + k1*(439/216.0)*dt + k2*(-8)*dt + k3*(3680/513.0)*dt + k4*(-845/4104.0)*dt)
    	k6 = f(t+(1/2.0)*dt, y + k1*(-8/27.0)*dt + k2*(2)*dt + k3*(-3544/2565.0)*dt + k4*(1859/4104.0)*dt + k5*(-11/40.0)*dt)
     
    	# 4th order approximation of the final value.
    	yf4 = y + k1*(25/216.0)*dt + k3*(1408/2565.0)*dt + k4*(2197/4104.0)*dt + k5*(-1/5.0)*dt
     
    	# 5th order approximation of the final value.
    	yf5 = y + k1*(16/135.0)*dt + k3*(6656/12825.0)*dt + k4*(28561/56430.0)*dt + k5*(-9/50.0)*dt + k6*(2/55.0)*dt
     
    	# Timestep scaling factor. From http://math.fullerton.edu/mathews/n2003/RungeKuttaFehlbergMod.html.
    	err = abs(yf5-yf4)
    	s = 1 if err==0 else (tolerance*dt/(2*err))**(1/4.0)
     
    	return t+dt, s*dt, yf4

    And here’s an example usage of that:

    import math
     
    t = 0
    dt = 1/40.0
    dt_min = 1/100.0
    dt_max = 1/10.0
    y = math.sin(t)
     
    print "Integrating cos(t) over 0<t<100."
     
    while t < 100:
    	t, dt, y = rkf45(t, dt, y, lambda t,y: math.cos(t))
    	if dt < dt_min: dt = dt_min
    	if dt > dt_max: dt = dt_max
     
    print "Final value with RKF45: "+str(y)
    print "Final value (exact): "+str(math.sin(100)) # sin(x) = integral(cos(x))
  5. Pingback: Improved RK4 Implementation | Doswa

  6. Jerry Rude says:

    What a pretty little snippet of code. I was playing with the Runge-Kutta method today and stumbled upon your code. I am playing around with some stellar atmosphere equations and decided I might be able to produce a computer model. I always assumed numerically solving a differential equation was very difficult, however this proved me wrong. Now I am off to figure out how to solve simultaneous DE’s, what a treat!

    Cheers,
    Jerry

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="">