欧拉法求解谐振子问题结果过于准确,请求代码问题排查
哈哈,你大概率是用了半隐式欧拉法(欧拉-克罗默法)!
我太懂这种疑惑了——显式欧拉法解谐振子明明应该误差随时间越拉越大,结果你用大步长还能和精确解几乎重合,这绝对不是bug,而是你不小心用了个“升级版”的欧拉法!
先给你掰扯清楚两种方法的核心区别:
普通显式欧拉法(误差会炸的版本)
谐振子的微分方程组是:dx/dt = vdv/dt = -w²x
显式欧拉的更新逻辑是先更新位置,再用旧位置更新速度:
x_new = x_old + dt * v_oldv_new = v_old + dt * (-w² * x_old)
这种方法的问题在于,计算速度时用的还是“过时”的位置,导致系统能量不断增加,模拟出来的轨迹会越来越偏离精确的余弦曲线,误差随时间累积得特别明显。
半隐式欧拉法(欧拉-克罗默法,你可能不小心写了这个)
只需要把更新顺序反过来——先更新速度,再用新速度更新位置:
v_new = v_old + dt * (-w² * x_old)x_new = x_old + dt * v_new
这个小改动直接让方法能近似守恒系统能量!所以哪怕步长不算小,模拟结果也会和精确解(x(t) = x0*cos(wt))几乎重合,看起来“过于准确”完全是正常的。
对照你的代码检查
你贴的代码只到开头部分,我赌一块钱你是先更新了速度,再用新速度算的位置。给你贴两个对比代码,一眼就能看出区别:
显式欧拉(误差会累积的版本)
import numpy as np import matplotlib.pyplot as plt w = 1.0 x_0 = 1.0 dt = 0.5 # 故意用大步长 t = np.arange(0, 20, dt) x = np.zeros_like(t) v = np.zeros_like(t) x[0] = x_0 v[0] = 0.0 # 假设初速度为0 for i in range(1, len(t)): # 先更新位置,用旧速度 x[i] = x[i-1] + dt * v[i-1] # 再更新速度,用旧位置 v[i] = v[i-1] - dt * w**2 * x[i-1] # 绘制对比 x_exact = x_0 * np.cos(w*t) plt.plot(t, x, label="显式欧拉") plt.plot(t, x_exact, label="精确解", linestyle="--") plt.legend() plt.show()
运行这个你就能看到,显式欧拉的曲线会慢慢飘走,和精确解差距越来越大。
半隐式欧拉(你可能写的版本)
import numpy as np import matplotlib.pyplot as plt w = 1.0 x_0 = 1.0 dt = 0.5 # 同样大步长 t = np.arange(0, 20, dt) x = np.zeros_like(t) v = np.zeros_like(t) x[0] = x_0 v[0] = 0.0 for i in range(1, len(t)): # 先更新速度,用旧位置 v[i] = v[i-1] - dt * w**2 * x[i-1] # 再更新位置,用新速度! x[i] = x[i-1] + dt * v[i] x_exact = x_0 * np.cos(w*t) plt.plot(t, x, label="半隐式欧拉") plt.plot(t, x_exact, label="精确解", linestyle="--") plt.legend() plt.show()
这个代码跑出来,两条线几乎重合,完全符合你说的“过于准确”的情况。
总结
如果你的代码是先更新速度再更新位置,那完全没问题——这是半隐式欧拉法的特性,它就是比显式欧拉稳定得多。要是想看到显式欧拉的误差累积效果,把更新顺序调换一下就行啦!
内容的提问来源于stack exchange,提问作者curious_cosmo
相关产品推荐
相关产品推荐

