avatar

Xiaojun Wu

Ph.D. Candidate, HKUST(GZ)
Affiliate LLM Researcher, IDEA Research
wuxiaojun@idea.edu.cn & wuxiaojun@pku.edu.cn & xwu647@connect.hkust-gz.edu.cn


← Blog

python数学建模实践

Python 微分方程(一):从 solve_ivp 开始

在知乎查看原文

阅读 知乎未公开 收藏 26 点赞 6

互动数据为 2026-09-13 的知乎数据快照;阅读量未在当前可用的知乎公开接口中提供。

运筹优化更像是在回答“怎么分配才最优”,微分方程更像是在回答“系统会怎么演化”。
这两条线在数学建模里经常并排出现:一条处理决策,一条处理动态。

这一篇先不碰复杂模型,只做一件事:

假设药物进入身体后会按比例慢慢减少,怎么用 Python 算出接下来 10 小时的浓度曲线?

这个问题足够小,但已经包含了微分方程建模的完整闭环:

建模 - 求解 - 画图 - 解释。

0. 先记一句人话

微分方程先别急着背定义,它最朴素的意思是:现在有多少,下一瞬间会怎么变。

  • 药在体内会慢慢少
  • 水温会慢慢降
  • 人群里感染者会带来新增感染

这些都是“量随时间变化”的问题。微分方程做的事,就是把这种变化写成可以计算的规则。

这一节的 takeaway:微分方程的入口不是定义,而是“我关心的量会怎么随时间变”。

1. 先看问题,再看变化率

写微分方程模型时,先问四个很普通的问题:

  • 我盯的是哪个量
  • 它是在变大还是变小
  • 变化受什么因素影响
  • 一开始从哪里出发

常微分方程的英文是 ordinary differential equation,所以很多资料会把它简称为 ODE。
这个名字不用先背,它只是强调一件事:我们只沿着一个自变量变化,最常见的自变量就是时间 t。

一般可以写成:

 \frac{dy}{dt} = f(t, y; \theta), \quad y(t_0)=y_0

这行公式对应上面的四个问题:

  • y:我盯着的量
  • dy/dt:它此刻变快还是变慢
  • theta:影响变化的参数
  • y(t0)=y0:从哪个初始状态出发

其中 y 不一定只是一个数,更多时候是一个向量。
所以在 Python 里,即使只有一个方程,也建议把状态写成数组,这样后面扩展成方程组会更自然。

这一节的 takeaway:先说清“量、速度、参数、初值”,再写公式。

2. 怎么让电脑一步步往前算

当方程没法直接手算时,就让电脑按时间一点点往前推。
solve_ivp 做的就是这件事。

先把函数名拆开:

  • solve:求解
  • ivp:initial value problem,初值问题

所以 solve_ivp 的人话版本就是:

给我起点和变化速度,我帮你把后面的轨迹算出来。

常见的几个参数先记住就够了:

  • t_span:从什么时候算到什么时候
  • y0:一开始的状态
  • t_eval:希望在哪些时间点看到结果,可以先理解成“拍照点”
  • method:内部怎么一步步走,默认常用 RK45
  • dense_output=True:如果想在任意时间点查询平滑结果,可以打开连续插值

RK45 这个名字第一篇不需要展开太深。
现在只要先记住:它会试着往前走一小步,并估计这一步准不准。

如果系统里有的量变化极快、有的量变化很慢,就可能遇到“刚性”问题。
Radau 和 BDF 是这类场景常用的方法名,第一篇只需要知道它们存在,第三篇以后再慢慢拆。

这一节的 takeaway:solve_ivp 的核心输入是“起点”和“变化速度”,t_eval 只是你想看结果的位置。


这张解释图对应这一篇的主线:从“初值”出发,沿着“变化速度”一步步得到“时间轨迹”。

3. 例子一:指数衰减

先看最简单的模型:

 \frac{dy}{dt} = -ky,\qquad y(0)=y_0

这可以对应药物浓度衰减、放射性衰变,或者某些简化的温差衰减过程。

你可以把它理解成一句话:每一时刻,剩下的量都按当前量的一定比例继续减少。

它的解析解是

 y(t)=y_0 e^{-kt}

这里我先用 SymPy 验一下公式,再用 solve_ivp 数值求解:

# 对应本节的理论方程:dy/dt = -k y
from sympy import Eq, Function, dsolve, symbols

# t 是自变量,k 是衰减系数,y0 是初值
t, k, y0 = symbols("t k y0", positive=True)
y = Function("y")

# 直接对照理论公式 y(0)=y0
print(dsolve(Eq(y(t).diff(t), -k * y(t)), y(t), ics={y(0): y0}))

数值解写成这样:

import numpy as np
from scipy.integrate import solve_ivp

# 对应理论里的参数 k 和初值 y(0)=y0
k = 0.35
y0 = 100.0
t_eval = np.linspace(0, 10, 200)

# 右端项对应 dy/dt = -k y
def rhs(t, y):
    return [-k * y[0]]

# solve_ivp 对应“初值问题数值求解”
sol = solve_ivp(
    rhs,
    (t_eval[0], t_eval[-1]),
    [y0],
    t_eval=t_eval,
    method="RK45",
)

# 用解析解做误差对照,检查数值解是否合理
y_exact = y0 * np.exp(-k * t_eval)

实际脚本里还会检查 sol.success。
它的人话含义是:求解器自己有没有承认这次求解成功完成。

这类问题很适合做第一篇,因为读者能一眼看出曲线应当往哪儿走。
如果数值解和解析解都对得上,就说明你的建模、编码、求解器调用都没跑偏。

这一节的 takeaway:指数衰减是最小可用例子,因为它既能手算公式,也能检验数值结果。

4. 例子二:Logistic 增长

第二个模型稍微像一点“真实世界”:

 \frac{dN}{dt} = rN\left(1-\frac{N}{K}\right)

这里 N 是种群规模,r 是增长率,K 是环境容量。
前期增长快,后期会被容量限制压住,最后趋于稳定。

import numpy as np
from scipy.integrate import solve_ivp

# 对应理论里的增长率 r、容量 K、初值 N0
r = 0.8
K = 100.0
N0 = 8.0
t_eval = np.linspace(0, 20, 200)

# 右端项对应 dN/dt = rN(1 - N/K)
def logistic_rhs(t, y):
    return [r * y[0] * (1 - y[0] / K)]

# 同样是初值问题,使用 solve_ivp 数值积分
sol = solve_ivp(
    logistic_rhs,
    (t_eval[0], t_eval[-1]),
    [N0],
    t_eval=t_eval,
    method="RK45",
)

这个例子比指数衰减更像建模,因为它开始涉及“增长”和“约束”之间的平衡。
后面写传染病模型、资源竞争模型、舆情传播模型时,思路基本都能沿着这条线展开。

如果只记一个判断,Logistic 的直觉就是:前面长得快,后面被环境压住。

这一节的 takeaway:Logistic 把“自然增长”和“资源限制”放进同一个变化率里。

图里左边是指数衰减的数值解和解析解对照,右边是 Logistic 增长曲线。
这张图的作用很朴素:一眼看出 solve_ivp 的结果有没有跑偏。

5. 一个可复用的模板

以后写 ODE 题,基本都可以按这个骨架走:

# 通用导入:数值积分 + 时间网格 + 画图
from scipy.integrate import solve_ivp
import numpy as np
import matplotlib.pyplot as plt

# model_rhs 对应正文中的 f(t, y; theta)
def model_rhs(t, y, params):
    ...

# t_eval 对应我们要输出结果的离散时间点
t_eval = np.linspace(t0, t1, 200)

# y0 对应初值;solve_ivp 对应数值求解器
sol = solve_ivp(
    lambda t, y: model_rhs(t, y, params),
    (t0, t1),
    y0,
    t_eval=t_eval,
    method="RK45",
)

plt.plot(sol.t, sol.y[0])
plt.show()

要记住的一点是:solve_ivp 给你的不是“公式”,而是“沿时间点的状态序列”。
这正是建模里最常见的输出形式。

这一节的 takeaway:以后换模型,通常只需要换右端函数、参数和初值。

6. 三个小习惯

  • 先写清变量单位和初始条件
  • 先说明为什么选这个求解器
  • 尽量和解析解、极限情形或直觉做一次对照
  • 记得看 sol.success,不要默认求解器一定成功

这一节的 takeaway:教程先给读者一个能站稳的例子,再逐步加术语。

7. 小结

这一篇的目标很单纯:把 solve_ivp 跑通,并把常微分方程,也就是 ODE,建模的基本流程立住。

下一篇我会接着写经典的 SIR 传染病模型,把“单方程”升级成“方程组”,也顺手把参数含义、峰值解释和疫情曲线一起讲清楚。

参考


© 2025 Xiaojun Wu. All rights reserved. Powered by Jekyll and Minimal Light theme.