285-codes/kutta.py

117 lines
3.5 KiB
Python
Raw Normal View History

import numpy as np
import matplotlib.pyplot as plt
def RK_n(y0, t, n=4):
ODE = lambda t, y: t**3 + y**3
#note: t is a vector of time points, y0 is the initial value
steps = len(t)
y = np.zeros(steps)
h = (t[-1] - t[0]) / (steps - 1)
if t[0] > 0:
raise ValueError("t[0] must be less than or equal to 0")
#find the index of the first non-negative time point
t0_idx = np.where(t >= 0)[0][0]
y[t0_idx] = y0 # set the initial condition at t[0]
#forward RK
for i in range(t0_idx, steps - 1):
#n-th order Runge-Kutta method
k = np.zeros(n)
k[0] = h * ODE(t[i], y[i])
for j in range(1, n):
t_j = t[i] + h * (j / n)
y_j = y[i] + k[j - 1] / n
k[j] = h * ODE(t_j, y_j)
y[i + 1] = y[i] + np.sum(k) / n
#backward RK
for i in range(t0_idx - 1, -1, -1):
#n-th order Runge-Kutta method
k = np.zeros(n)
k[0] = h * ODE(t[i], y[i])
for j in range(1, n):
t_j = t[i] + h * (j / n)
y_j = y[i] + k[j - 1] / n
k[j] = h * ODE(t_j, y_j)
y[i] = y[i + 1] - np.sum(k) / n
return y
case_num = 3
if case_num == 1:
plt.title("RK4 result with IVPs for large t")
initial_values = [-1, 0, 1]
t = np.linspace(0, 2, 1000)
for i, y0 in enumerate(initial_values):
res = RK_n(y0, t, 4)
plt.plot(t, res, label=f'y(0)={y0}, n=4')
plt.ylim(-5,5)
#observation: the solutions diverge as t increases and seem to have an asymptote which depends on y(0)
# and converge to a same solution as t decreases.
elif case_num == 2:
plt.title("RK4 result with IVPs for small t")
initial_values = [-1, 0, 1]
t = np.linspace(-0.5, 0.5, 1000)
for i, y0 in enumerate(initial_values):
res = RK_n(y0, t, 4)
plt.plot(t, res, label=f'y(0)={y0}, n=4')
plt.ylim(-5,5)
elif case_num == 3:
plt.title("Comparing different order RK methods")
order = [1, 2, 4, 5]
t = np.linspace(-1, 1.4, 1000)
res_5 = RK_n(0, t, 5)
for n in order:
res = RK_n(0, t, n)
if n == 1:
label = 'Euler - RK5'
elif n == 2:
label = 'Improved Euler - RK5'
else:
label = f'RK{n} - RK5'
plt.plot(t, res - res_5, label=label)
plt.legend()
elif case_num == 4:
plt.title("RK4 with IVP from 0 to 1")
initial_values = np.linspace(0, 1, 5)
t = np.linspace(-3, 10, 1000)
for i, y0 in enumerate(initial_values):
res = RK_n(y0, t, 4)
plt.plot(t, res, label=f'y(0)={y0}')
plt.ylim(-5,10)
plt.legend()
elif case_num == 5:
fig, axs = plt.subplots(2, 2, figsize=(10, 8))
plt.title("RK4 with different y-scale in the negatives")
scale = [5, 10, 20, 50]
t = np.linspace(-10, 1, 1000)
res = RK_n(0, t, 4)
for i, s in enumerate(scale):
ax = axs[i // 2, i % 2]
ax.plot(t, res / s, label=f'scale={s}')
ax.set_ylim(-1, scale[i])
ax.axhline(0, color='black', lw=0.5)
ax.axvline(0, color='black', lw=0.5)
ax.set_title(f'RK4 for t < {s}')
# observation: There is no asymptote in the negative y-scale
elif case_num == 6:
plt.title("RK4 with different step sizes")
steps = [5, 10, 100, 1000]
for n in steps:
t = np.linspace(0, 1, n)
res = RK_n(0, t, 4)
plt.plot(t, res, label=f'{n} steps')
plt.legend()
plt.axhline(0, color='black', lw=0.5)
plt.axvline(0, color='black', lw=0.5)
plt.show()
# plt.legend()