Initial value problems

Initial value problems#

Runge-Kutta orbit integrations#

import numpy as np
import matplotlib.pyplot as plt
import scipy.integrate
def derivs_orbit(t, x):
    rr = (x[0]**2 + x[1]**2)**0.5
    vxdot = -x[0]/rr**3
    vydot = -x[1]/rr**3
    xdot = x[2]
    ydot = x[3]
    return np.array((xdot,ydot,vxdot,vydot))
def euler(nsteps, dt, x0, derivs):
    x = np.zeros((nsteps,len(x0)))
    x[0] = x0
    for i in range(1,nsteps):    
        f = derivs((i-1)*dt, x[i-1])
        x[i] = x[i-1] + f*dt
    return x

def midpoint(nsteps, dt, x0, derivs):
    x = np.zeros((nsteps,len(x0)))
    x[0] = x0
    for i in range(1,nsteps):    
        f = derivs((i-1)*dt, x[i-1])
        f1 = derivs((i-1)*dt + dt/2, x[i-1] + f*dt/2)
        x[i] = x[i-1] + f1*dt        
    return x

def rk4(nsteps, dt, x0, derivs):
    x = np.zeros((nsteps,len(x0)))
    x[0] = x0
    for i in range(1,nsteps):    
        f = derivs((i-1)*dt, x[i-1])
        f1 = derivs((i-1)*dt + dt/2, x[i-1] + f*dt/2)
        f2 = derivs((i-1)*dt + dt/2, x[i-1] + f1*dt/2)
        f3 = derivs(i*dt, x[i-1] + f2*dt)
        x[i] = x[i-1] + dt*(f + 2*f1 + 2*f2 + f3)/6
    return x
# Compute the orbit with different numbers of steps and methods to compare

for nsteps in np.array((10,100,1000,10000)):

    dt = 2 * np.pi / (nsteps-1)
    t = np.arange(nsteps)*dt
    x_start = np.array((1,0,0,1))
    x1 = euler(nsteps, dt, x_start, derivs_orbit)
    x2 = midpoint(nsteps, dt, x_start, derivs_orbit)
    x4 = rk4(nsteps, dt, x_start, derivs_orbit)

    print("nsteps = %d, err1 = %lg, err2 = %lg, err4 = %lg" % (nsteps, x1[-1,1], x2[-1,1], x4[-1,1]))
    
plt.plot(x1[:,0], x1[:,1])
plt.plot(x2[:,0], x2[:,1])
plt.plot(x4[:,0], x4[:,1])

plt.plot(x4[0,0],x4[0,1],'ko')
plt.plot(x4[-1,0],x4[-1,1],'kx')

result = scipy.integrate.solve_ivp(derivs_orbit, (0.0,2.0*np.pi), x_start, t_eval=t, atol=1e-6, rtol=1e-6)

x,y, vx, vy = result.y

print('scipy gives ', y[-1], ' with %d evaluations' % (result.nfev,))
plt.plot(x,y)
plt.show()
nsteps = 10, err1 = 2.59535, err2 = -0.997537, err4 = 0.174884
nsteps = 100, err1 = -1.61962, err2 = -0.0155004, err4 = 3.17648e-06
nsteps = 1000, err1 = -0.358534, err2 = -0.000145888, err4 = 2.33419e-10
nsteps = 10000, err1 = -0.0371289, err2 = -1.44817e-06, err4 = 7.6971e-14
scipy gives  0.0001564960035684848  with 140 evaluations
_images/60b29e6241a5f0e160223733a589b66a048bd15cbc5bf94c7388cce1e7d2e586.png
# Calculate the scaling with N

nvals = np.array([])
evals1 = np.array([])
evals2 = np.array([])
evals4 = np.array([])

for n in np.arange(1,6.5,0.5):

    nsteps = int(10**n)
    dt = 2 * np.pi / (nsteps-1)
    t = np.arange(nsteps)*dt
    x_start = np.array((1,0,0,1))
    x1 = euler(nsteps, dt, x_start, derivs_orbit)
    x2 = midpoint(nsteps, dt, x_start, derivs_orbit)
    x4 = rk4(nsteps, dt, x_start, derivs_orbit)

    print("nsteps = %d, err4 = %lg" % (nsteps, x4[-1,1]))
    nvals = np.append(nvals, nsteps)
    evals1 = np.append(evals1, abs(x1[-1,1]))
    evals2 = np.append(evals2, abs(x2[-1,1]))
    evals4 = np.append(evals4, abs(x4[-1,1]))

plt.plot(nvals, evals1, label='Euler')
plt.plot(nvals, evals2, label='Midpoint')
plt.plot(nvals, evals4, label='RK4')
plt.legend()
plt.yscale('log')
plt.xscale('log')
plt.show()
nsteps = 10, err4 = 0.174884
nsteps = 31, err4 = 0.000602067
nsteps = 100, err4 = 3.17648e-06
nsteps = 316, err4 = 2.53835e-08
nsteps = 1000, err4 = 2.33419e-10
nsteps = 3162, err4 = 2.36498e-12
nsteps = 10000, err4 = 7.6971e-14
nsteps = 31622, err4 = 1.44745e-13
nsteps = 100000, err4 = 1.34684e-13
nsteps = 316227, err4 = 5.66926e-14
---------------------------------------------------------------------------
KeyboardInterrupt                         Traceback (most recent call last)
Cell In[5], line 16
     14 x1 = euler(nsteps, dt, x_start, derivs_orbit)
     15 x2 = midpoint(nsteps, dt, x_start, derivs_orbit)
---> 16 x4 = rk4(nsteps, dt, x_start, derivs_orbit)
     18 print("nsteps = %d, err4 = %lg" % (nsteps, x4[-1,1]))
     19 nvals = np.append(nvals, nsteps)

Cell In[3], line 26, in rk4(nsteps, dt, x0, derivs)
     24     f2 = derivs((i-1)*dt + dt/2, x[i-1] + f1*dt/2)
     25     f3 = derivs(i*dt, x[i-1] + f2*dt)
---> 26     x[i] = x[i-1] + dt*(f + 2*f1 + 2*f2 + f3)/6
     27 return x

KeyboardInterrupt: 

Implicit methods#

# The equations are linear with constant coefficients, so don't need
# to do a Newton solve
# We just need to specify the matrix of coefficients
C = np.array([[-998,-1998],[999,1999]])

h = 1e-2
nsteps = 100

x = np.zeros((nsteps,2))

# Explicit update
#A = np.identity(2) - h*C
# Implicit update
A = np.linalg.inv(np.identity(2) + h*C)

# Initial conditions
x[0,0] = 1
x[0,1] = 0

for i in range(1,nsteps):
    x[i] = A@x[i-1]
    
t = h * np.arange(nsteps)

plt.plot(t,x[:,0])
plt.plot(t,x[:,1])

# Plot the analytic solutions for comparison
t_anal = np.linspace(t[0], t[-1], 1000)
plt.plot(t_anal, 2*np.exp(-t_anal)-np.exp(-1000*t_anal),"--")
plt.plot(t_anal, -np.exp(-t_anal)+np.exp(-1000*t_anal),"--")

plt.show()
_images/47aa104f440c8f2f74830f24ab833a826efb15e9794af9831161ddb2c362e62a.png