手里有个微分方程,解析解求不动,怎么办?前段时间我在调一个动力学模型时遇到的就是这种事:dy/dt = -y² sin(t),初值 y(0) = 1,手算根本行不通。当时打开编辑器,脑子里第一个跳出来的就是龙格库塔法,用 Python 实现只需要十几行代码,却能把这类常微分方程“模拟”得相当准。如果你也在学 Python 数值计算、做物理仿真、研究控制系统,或者只是想把 SciPy 求解器背后的原理弄明白,这篇文章值得看完。我会从最朴素的欧拉法讲起,把 RK4 的核心思路拆开,再给一套可以直接抄走的 Python 实现,最后聊一聊步长、精度和稳定性这些在实际项目中绕不开的坑。
1. 从欧拉法说起:为什么只用起始斜率会“漂”
1.1 初值问题的标准写法与“切线段拼接”模型
先统一一下概念。我们讨论的问题长这样:
dy/dt = f(t, y),并且给定 y(t₀) = y₀
这叫常微分方程初值问题。t 是自变量,y 可以是标量,也可以是向量(比如三维坐标、一组状态变量)。f 告诉我们的是“当前时刻、当前位置的瞬时变化率”,也就是切线的斜率。
数值求解的基本思路其实特别朴素:把连续的时间切成很多小段,每段步长为 h,从已知的 y(t₀) 出发,一步一步往前推。每一步都相当于“沿着当前点的切线方向走一小段”,然后把新位置作为下一步的起点。整个解曲线,就是用很多条切线段拼接出来的折线。
这个画面很重要,因为它直接决定了误差从哪来。真实曲线是弯的,而我们的折线是直的,用直的代替弯的,每走一步都会产生偏差。偏差要是只积累在一个方向还好,问题是它会一步步叠加放大,最后结果可能完全不可信。
1.2 欧拉法大步长的翻车实验
在龙格库塔法之前,最原始的方法叫显式欧拉法,公式极其简单:
y_{n+1} = y_n + h · f(t_n, y_n)
这个公式的意思就是:用当前点的斜率,把 y 直线外推一个步长。代码写出来也简单:
python复制import numpy as np
def euler_step(f, t, y, h):
return y + h * f(t, y)
def euler_solve(f, y0, t0, t_end, h):
t, y = t0, y0
ts, ys = [t0], [y0]
while t < t_end - 1e-12:
y = euler_step(f, t, y, h)
t += h
ts.append(t)
ys.append(y)
return np.array(ts), np.array(ys)
拿一个特别简单的方程来测:dy/dt = -2y,解析解是 y = e^(-2t)。初值取 y(0) = 1,从 t=0 算到 t=2,真实值是 e^(-4) ≈ 0.0183。步长 h 取 0.5,欧拉法算出来是什么结果?
y₁ = 1 + 0.5 × (-2×1) = 0,y₂ = 0 + 0.5 × (-2×0) = 0
四步之后它直接趴到 0,误差约 0.0183。你可能觉得 h=0.5 太大了,那换 h=0.1 呢?算到 t=2,终端误差大约 0.007,相对误差超过 37%。对一个这么温顺的线性方程来说,这个精度确实说不过去。
1.3 为什么只用起点斜率注定不靠谱
问题出在哪?欧拉法用一个点的瞬时斜率,代表了整个步长区间的平均变化速度。真实解在 [t, t+h] 区间里斜率一直在变,你拿起点那一刻的斜率当整段路的平均速度,走快了还是走慢了只能听天由命。
这就好比导航说前一个小时你踩油门 (以当前速度开一小时),可实际上路况在变、车速在变,一小时后的位置肯定和导航估的差很远。
想要提高精度,有两个方向:一是把步长 h 拼命调小,计算量暴涨;二是想个办法,在每一步里多看几个位置的斜率,综合判断这一小段的“平均斜率”。龙格库塔法走的是后一条路,效果比盲目加密步长高一到几个数量级。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
