def modified_euler(f, t0, T, x0, N):
h = (T - t0) / N
t = np.linspace(t0, T, N+1)
X = np.zeros(N+1); X[0] = x0
for n in range(N):
Xt = X[n] + h * f(t[n], X[n])
X[n+1] = X[n] + (h/2) * (f(t[n], X[n]) + f(t[n+1], Xt))
return t, X
def rk4(f, t0, T, x0, N):
h = (T - t0) / N
t = np.linspace(t0, T, N+1)
X = np.zeros(N+1); X[0] = x0
for n in range(N):
k1 = f(t[n], X[n])
k2 = f(t[n]+h/2, X[n]+h/2*k1)
k3 = f(t[n]+h/2, X[n]+h/2*k2)
k4 = f(t[n]+h, X[n]+h*k3)
X[n+1] = X[n] + (h/6)*(k1 + 2*k2 + 2*k3 + k4)
return t, X
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
# Left: solution comparison with h=0.5
N_demo = 4
t_fine = np.linspace(0, 2, 400)
axes[0].plot(t_fine, exact_ex(t_fine), 'steelblue', lw=2.8,
label='Exact')
for method, color, lbl in [
(euler, 'crimson', 'Euler (RK1)'),
(modified_euler, 'darkorange', 'Modified Euler (RK2)'),
(rk4, 'seagreen', 'Runge–Kutta (RK4)')]:
t_m, X_m = method(f_ex, 0, 2, 1, N_demo)
axes[0].plot(t_m, X_m, marker='o', markersize=6, lw=2.0,
color=color, label=lbl)
axes[0].set_xlabel('$t$'); axes[0].set_ylabel('$x$')
axes[0].set_title('Solution comparison ($h=0.5$)')
axes[0].legend(fontsize=9)
# Right: convergence log-log
h_vals = [0.5, 0.25, 0.125, 0.0625, 0.03125]
for method, color, lbl, order in [
(euler, 'crimson', 'Euler $O(h)$', 1),
(modified_euler, 'darkorange', 'RK2 $O(h^2)$', 2),
(rk4, 'seagreen', 'RK4 $O(h^4)$', 4)]:
errs = []
for h in h_vals:
N_ = int(2/h)
_, X_m = method(f_ex, 0, 2, 1, N_)
errs.append(abs(exact_ex(2) - X_m[-1]))
axes[1].loglog(h_vals, errs, marker='o', color=color, lw=2.0, label=lbl)
# Reference slope
h_arr = np.array(h_vals)
axes[1].loglog(h_arr, errs[0]*(h_arr/h_vals[0])**order,
ls='--', color=color, lw=1.0, alpha=0.6)
axes[1].set_xlabel('Step size $h$'); axes[1].set_ylabel('Global error at $t=2$')
axes[1].set_title('Convergence (log–log)')
axes[1].legend(fontsize=9)
axes[1].grid(True, which='both', alpha=0.3)
plt.suptitle(r"Method comparison: $x'=t-x$, $x(0)=1$", fontsize=12)
plt.tight_layout()
plt.show()