from pylab import *
from copy import deepcopy
def euler_step(t, h, f, y):
"""Evaluates one step of the Euler method. Takes the current value of the
independent variable (t), the step size (h), a system of first-order
differential equations (f), and the current values of the equations (y).
Returns new values for t and y."""
y = deepcopy(y) # to avoid changing the inputs
for i in xrange(len(f)): # for each equation in the system
# find the slope at the beginning of the interval
k = f[i](t, y)
# estimate the next point with the slope
y[i] = y[i] + k * h
return t + h, y
def RK4_step(t, h, f, y):
"""Evaluates one step of the fourth-order Runge-Kutta method. Same parameters
as euler_step, above."""
y = deepcopy(y) # to avoid changing the inputs
o = len(f)
for i in xrange(o):
k1 = []; k2 = []; k3 = []; k4 = []
# find k terms
for i in xrange(o):
k1.append(h * f[i](t, y))
for i in xrange(o):
k2.append(h * f[i](t + h/2.0, y + array(k1)/2.0))
for i in xrange(o):
k3.append(h * f[i](t + h/2.0, y + array(k2)/2.0))
for i in xrange(o):
k4.append(h * f[i](t + h, y + array(k3)))
# compute new values of y
for i in xrange(o):
y[i] = y[i] + (k1[i]/6.0 + k2[i]/3.0 + k3[i]/3.0 + k4[i]/6.0)
return t+h, y
def solver(step_fn, f, f0, interval, h):
"""Generic numeric solver. step_fn is a function that evaluates one step of the
method to be used, given the current value of the independent variable, the step
size, a system of first order differential equations, and current values for
each of the equations. f is a system of first-order differential equations; f0
are initial values for the functions in f, h is the step size, and interval is a
tuple. Returned values are t and y, where y are the values of each equation in
the system at each step."""
a, b = interval
t = [a]
y = map(lambda x: [x], f0)
y_ = array(f0); t_ = a
err_local = [0.0]
for n in xrange(int((float(b) - float(a)) / h)):
# evaluate as two steps for local error estimate
t2, y2 = step_fn(t_, h/2.0, f, y_)
t2, y2 = step_fn(t2, h/2.0, f, y2)
# then actually take the step
t_, y_ = step_fn(t_, h, f, y_)
err_local.append(abs(y2[0] - y_[0]))
for i in xrange(len(f)):
y[i].append(y_[i])
t.append(t_)
return t, y, err_local
def solver_adaptive(step_fn, f, f0, interval, err_target, err_max, h_initial, kp):
a, b = interval
t = [a]
y = map(lambda x: [x], f0)
y_ = array(f0); t_ = a
err_local = [0.0]
err_prev = 0.0
err_int = 0.0
h = h_initial
stepsize = [h]
steps_discarded = 0
while t_ < b:
while True:
# evaluate as two steps for local error estimate
t2, y2 = step_fn(t_, h/2.0, f, y_)
t2, y2 = step_fn(t2, h/2.0, f, y2)
# then actually take the step
t1, y1 = step_fn(t_, h, f, y_)
# compute the error, update the step size with a very simple control loop
err = abs(y2[0] - y1[0])
h += (err_target - err) * kp
if err < err_max:
break
else:
steps_discarded += 1
t_ = t1
y_ = y1
stepsize.append(h)
print "%f" % err
err_local.append(err)
for i in xrange(len(f)):
y[i].append(y_[i])
t.append(t_)
print "Steps discarded: %d" % steps_discarded
return t, y, err_local, stepsize
if __name__ == "__main__":
# Our system of differential equations
f = (
lambda t, y: y[1], # y1' = y2
lambda t, y: -1.0 * y[0] # y2' = -y1
)
# Initial conditions
f0 = (1.0, 0.0)
# Step size
h = 0.1
# Interval
interval = (0.0, 100.0 * pi)
# Solve with Euler method and RK4:
t, y, err_local_euler = solver(euler_step, f, f0, interval, h)
t2, y2, err_local_rk4 = solver(RK4_step, f, f0, interval, h)
t3, y3, err_local_adaptive, h_adaptive = solver_adaptive(RK4_step, f, f0, interval,
0.00001, 0.00002, h, 750.0)
#mean = lambda x: sum(x) / len(x)
print "Average local error:"
print " Euler: ", mean(err_local_euler)
print " RK4: ", mean(err_local_rk4), max(err_local_rk4), len(err_local_rk4)
print ( " RK4a: ", mean(err_local_adaptive), max(err_local_adaptive),
len(err_local_adaptive) )
print "Average stepsize: ", mean(h_adaptive)
# Evaluate the analytic solution for reference:
xa = frange(interval[0], interval[1], 0.01)
ya = cos(xa)
# Plot!
plot(xa, ya, t, y[0], t2, y2[0], t3, y3[0])
legend(("Analytic", "Euler", "RK4", "Adaptive"))
#plot(t3, h_adaptive)
show()