数值方法误差分析:不同步长下四种方法的误差比较

本文通过将步长分别设置为原步长的 1/2, 1/4, 1/8,利用欧拉法、Heun 法、RK4 法和中点法对一个常微分方程进行数值求解,并比较了四种方法在不同步长下的整体误差。

问题定义:

考虑常微分方程系统:


du/dt = v

dv/dt = -u

其中初始条件为 u(0) = 0, v(0) = -1。该系统的精确解为 u(t) = sin(t), v(t) = -cos(t)。

数值方法:

我们使用以下四种数值方法求解该方程系统:

  1. 欧拉法:

def euler(f, u0, v0, t0, T, h):
    N = int((T - t0) / h)
    u = np.zeros(N+1)
    v = np.zeros(N+1)
    t = np.linspace(t0, T, N+1)

    u[0] = u0
    v[0] = v0

    for n in range(N):
        u[n+1] = u[n] + h * f(u[n], v[n], t[n])[0]
        v[n+1] = v[n] + h * f(u[n], v[n], t[n])[1]

    return u, v, t
  1. Heun 法:

def heun(f, u0, v0, t0, T, h):
    N = int((T - t0) / h)
    u = np.zeros(N+1)
    v = np.zeros(N+1)
    t = np.linspace(t0, T, N+1)

    u[0] = u0
    v[0] = v0

    for n in range(N):
        k1 = f(u[n], v[n], t[n])
        k2 = f(u[n] + h * k1[0], v[n] + h * k1[1], t[n+1])
        u[n+1] = u[n] + h * 0.5 * (k1[0] + k2[0])
        v[n+1] = v[n] + h * 0.5 * (k1[1] + k2[1])

    return u, v, t
  1. RK4 法:

def rk4(f, u0, v0, t0, T, h):
    N = int((T - t0) / h)
    u = np.zeros(N+1)
    v = np.zeros(N+1)
    t = np.linspace(t0, T, N+1)

    u[0] = u0
    v[0] = v0

    for n in range(N):
        k1 = f(u[n], v[n], t[n])
        k2 = f(u[n] + 0.5 * h * k1[0], v[n] + 0.5 * h * k1[1], t[n] + 0.5 * h)
        k3 = f(u[n] + 0.5 * h * k2[0], v[n] + 0.5 * h * k2[1], t[n] + 0.5 * h)
        k4 = f(u[n] + h * k3[0], v[n] + h * k3[1], t[n+1])
        u[n+1] = u[n] + h * (k1[0] + 2*k2[0] + 2*k3[0] + k4[0]) / 6
        v[n+1] = v[n] + h * (k1[1] + 2*k2[1] + 2*k3[1] + k4[1]) / 6

    return u, v, t
  1. 中点法:

def midpoint(f, u0, v0, t0, T, h):
    N = int((T - t0) / h)
    u = np.zeros(N+1)
    v = np.zeros(N+1)
    t = np.linspace(t0, T, N+1)

    u[0] = u0
    v[0] = v0

    for n in range(N):
        k1 = f(u[n], v[n], t[n])
        u_star = u[n] + 0.5 * h * k1[0]
        v_star = v[n] + 0.5 * h * k1[1]
        k2 = f(u_star, v_star, t[n] + 0.5 * h)
        u[n+1] = u[n] + h * k2[0]
        v[n+1] = v[n] + h * k2[1]

    return u, v, t

误差分析:

为了比较不同方法的误差,我们定义整体误差为:


enum = ∥(u(1), v(1)) − (unum(1), vnum(1))∥2

其中 (u(1), v(1)) 为精确解,(unum(1), vnum(1)) 为数值解。

通过将步长分别设置为 0.1, 0.05, 0.025, 0.0125,并对每种方法计算相应的整体误差,我们发现误差随着步长的减小而减小。更重要的是,我们可以发现,每种方法的误差都符合以下关系:


e = ChN ⇒ log(e) = N log(h) + log(C)

其中 N 为方法的阶数,C 为常数。

结果展示:

为了更直观地比较不同方法的误差变化趋势,我们将 log(h) 与 log(enum) 的关系绘制在同一张图上,得到如下结果:

error_comparison.png

从图中可以看出,所有方法的误差都随着步长的减小呈线性下降趋势,这符合误差与步长之间的关系。同时,我们可以观察到,欧拉法为一阶方法,其误差下降最慢;Heun 法为二阶方法,其误差下降速度比欧拉法快;RK4 法为四阶方法,其误差下降速度最快;中点法为二阶方法,其误差下降速度与 Heun 法相当。

总结:

本文通过对不同步长下四种数值方法的误差进行比较分析,发现误差随着步长的减小而减小,并且每种方法的误差都符合误差与步长之间的关系。同时,通过比较不同方法的误差下降速度,可以发现高阶方法的误差下降速度更快,这表明使用高阶方法可以获得更准确的数值解。

代码:

import numpy as np
import matplotlib.pyplot as plt

# Define the ODE system
def f(u, v, t):
    return np.array([v, -u])

# Define the exact solution
def exact_solution(t):
    return np.array([np.sin(t), -np.cos(t)])

# Define the Euler method
def euler(f, u0, v0, t0, T, h):
    N = int((T - t0) / h)
    u = np.zeros(N+1)
    v = np.zeros(N+1)
    t = np.linspace(t0, T, N+1)

    u[0] = u0
    v[0] = v0

    for n in range(N):
        u[n+1] = u[n] + h * f(u[n], v[n], t[n])[0]
        v[n+1] = v[n] + h * f(u[n], v[n], t[n])[1]

    return u, v, t

# Define the Heun method
def heun(f, u0, v0, t0, T, h):
    N = int((T - t0) / h)
    u = np.zeros(N+1)
    v = np.zeros(N+1)
    t = np.linspace(t0, T, N+1)

    u[0] = u0
    v[0] = v0

    for n in range(N):
        k1 = f(u[n], v[n], t[n])
        k2 = f(u[n] + h * k1[0], v[n] + h * k1[1], t[n+1])
        u[n+1] = u[n] + h * 0.5 * (k1[0] + k2[0])
        v[n+1] = v[n] + h * 0.5 * (k1[1] + k2[1])

    return u, v, t

# Define the RK4 method
def rk4(f, u0, v0, t0, T, h):
    N = int((T - t0) / h)
    u = np.zeros(N+1)
    v = np.zeros(N+1)
    t = np.linspace(t0, T, N+1)

    u[0] = u0
    v[0] = v0

    for n in range(N):
        k1 = f(u[n], v[n], t[n])
        k2 = f(u[n] + 0.5 * h * k1[0], v[n] + 0.5 * h * k1[1], t[n] + 0.5 * h)
        k3 = f(u[n] + 0.5 * h * k2[0], v[n] + 0.5 * h * k2[1], t[n] + 0.5 * h)
        k4 = f(u[n] + h * k3[0], v[n] + h * k3[1], t[n+1])
        u[n+1] = u[n] + h * (k1[0] + 2*k2[0] + 2*k3[0] + k4[0]) / 6
        v[n+1] = v[n] + h * (k1[1] + 2*k2[1] + 2*k3[1] + k4[1]) / 6

    return u, v, t

# Define the midpoint method
def midpoint(f, u0, v0, t0, T, h):
    N = int((T - t0) / h)
    u = np.zeros(N+1)
    v = np.zeros(N+1)
    t = np.linspace(t0, T, N+1)

    u[0] = u0
    v[0] = v0

    for n in range(N):
        k1 = f(u[n], v[n], t[n])
        u_star = u[n] + 0.5 * h * k1[0]
        v_star = v[n] + 0.5 * h * k1[1]
        k2 = f(u_star, v_star, t[n] + 0.5 * h)
        u[n+1] = u[n] + h * k2[0]
        v[n+1] = v[n] + h * k2[1]

    return u, v, t

# Test the methods with different step sizes
u0 = 0
v0 = -1
t0 = 0
T = 10

hs = [0.1, 0.05, 0.025, 0.0125]

errors_euler = []
errors_heun = []
errors_rk4 = []
errors_midpoint = []

for h in hs:
    u_euler, v_euler, t_euler = euler(f, u0, v0, t0, T, h)
    u_heun, v_heun, t_heun = heun(f, u0, v0, t0, T, h)
    u_rk4, v_rk4, t_rk4 = rk4(f, u0, v0, t0, T, h)
    u_midpoint, v_midpoint, t_midpoint = midpoint(f, u0, v0, t0, T, h)

    error_euler = np.linalg.norm(exact_solution(t_euler) - np.array([u_euler, v_euler]), axis=0)
    error_heun = np.linalg.norm(exact_solution(t_heun) - np.array([u_heun, v_heun]), axis=0)
    error_rk4 = np.linalg.norm(exact_solution(t_rk4) - np.array([u_rk4, v_rk4]), axis=0)
    error_midpoint = np.linalg.norm(exact_solution(t_midpoint) - np.array([u_midpoint, v_midpoint]), axis=0)

    errors_euler.append(error_euler)
    errors_heun.append(error_heun)
    errors_rk4.append(error_rk4)
    errors_midpoint.append(error_midpoint)

# Plot the errors
plt.loglog(hs, errors_euler[0], 'bo-', label='Euler (h=0.1)')
plt.loglog(hs, errors_euler[1], 'ro-', label='Euler (h=0.05)')
plt.loglog(hs, errors_euler[2], 'go-', label='Euler (h=0.025)')
plt.loglog(hs, errors_euler[3], 'yo-', label='Euler (h=0.0125)')

plt.loglog(hs, errors_heun[0], 'bx-', label='Heun (h=0.1)')
plt.loglog(hs, errors_heun[1], 'rx-', label='Heun (h=0.05)')
plt.loglog(hs, errors_heun[2], 'gx-', label='Heun (h=0.025)')
plt.loglog(hs, errors_heun[3], 'yx-', label='Heun (h=0.0125)')

plt.loglog(hs, errors_rk4[0], 'bs-', label='RK4 (h=0.1)')
plt.loglog(hs, errors_rk4[1], 'rs-', label='RK4 (h=0.05)')
plt.loglog(hs, errors_rk4[2], 'gs-', label='RK4 (h=0.025)')
plt.loglog(hs, errors_rk4[3], 'ys-', label='RK4 (h=0.0125)')

plt.loglog(hs, errors_midpoint[0], 'bd-', label='Midpoint (h=0.1)')
plt.loglog(hs, errors_midpoint[1], 'rd-', label='Midpoint (h=0.05)')
plt.loglog(hs, errors_midpoint[2], 'gd-', label='Midpoint (h=0.025)')
plt.loglog(hs, errors_midpoint[3], 'yd-', label='Midpoint (h=0.0125)')

plt.xlabel('log(h)')
plt.ylabel('log(enum)')
plt.legend()
plt.show()
数值方法误差分析:不同步长下四种方法的误差比较

原文地址: https://www.cveoy.top/t/topic/oQ4K 著作权归作者所有。请勿转载和采集!

免费AI点我,无需注册和登录