前几篇我们一直在做一类题:
给一个起点,往后推一条曲线。
这一篇换一种更麻烦、也更常见的题:
如果题目不是给你一个起点,而是把条件放在两头,你还怎么把中间那条曲线找出来?
先给一句人话答案:
这类题不能只从左边往右走,因为你走到右边以后还得刚好满足右端条件。
这就是边值问题的核心。
边值问题的英文是 boundary value problem,简称 BVP。
先别急着背这个缩写,它只是说:
你要满足的不止是起点,还有边界。
这一节的 takeaway:初值问题是“从起点出发”,边值问题是“两头都要对”。
0. 先看最小问题
想象一根长度固定的细杆,两端温度都被固定成 0。
杆中间有一点加热,所以温度在中间会鼓起来,往两边慢慢降下去。
我们想问的不是“它会怎么随时间变化”,而是:
在稳态下,整根杆的温度曲线长什么样?
这类题很常见:
- 两端固定温度的导热棒
- 两端固定位置的绳子
- 两头都定死条件的梁挠度
这一节的 takeaway:有些题不是“时间演化题”,而是“空间上怎么连起来”的题。
1. 变量和方程
我们把空间位置记成 x,温度曲线记成 u(x)。
稳态情况下,最简单的模型可以写成:
边界条件是:
这里的意思是:
- 左端温度固定为 0
- 右端温度固定为 0
- 中间有均匀的“抬高”作用,所以曲线会鼓起来
为什么这个模型好?
因为它足够简单,但已经包含了边值问题的全部难点:
方程在中间,条件在两头。
这一节的 takeaway:边值问题的难点不在公式长,而在条件分散在不同位置。
2. 为什么不能直接像 solve_ivp 那样算
如果这是初值问题,我们只需要知道:
- 起点在哪里
- 起点斜率是多少
然后就能一路往前推。
可边值问题不一样。
你虽然知道 u(0)=0,但你不知道 u'(0) 应该是多少。
而右端又要求 u(1)=0。
所以如果你只在左边随便猜一个斜率,曲线大概率会在右边偏掉。
这就逼出了一个很自然的办法:
先猜左边斜率,再看右边对不对,不对就改猜。
这就是射击法(shooting method)的直觉。
这一节的 takeaway:边值问题不是不能算,而是不能一次把所有条件都同时从左边带过去。
3. 射击法的想法
射击法的人话版本是:
- 先猜一个左端斜率
- 把它当成初值问题往右算
- 看右端边界有没有命中
- 没命中就改斜率,再来一次
这很像往靶子上射箭:
- 你知道靶子在哪里
- 但你不知道第一箭该抬多高
- 所以你得试几次
数学上,我们把二阶方程改成一阶方程组:
这样就能用 solve_ivp 去“猜斜率再积分”。
这一节的 takeaway:射击法就是把边值问题拆成“猜斜率 + 初值积分 + 反复修正”。
4. 直接用 solve_bvp
SciPy 里其实有现成的边值问题求解器,叫 solve_bvp。
它做的事就是:
直接在两头条件都知道的情况下,把整条曲线找出来。
对应代码可以写成:
def bvp_rhs(x, y):
# y[0] = u, y[1] = u'
# u' = v
# v' = -1
return np.vstack((y[1], -np.ones_like(x)))
def bc(ya, yb):
# ya 是 x=0 处的状态,yb 是 x=1 处的状态
# 两端温度都等于 0
return np.array([ya[0], yb[0]])这里最值得注意的是:
-
ya代表左端 -
yb代表右端 -
bc就是boundary conditions,边界条件
它和 rhs 很像,但不是同一个东西:
-
rhs负责方程中间怎么变 -
bc负责两头怎么对
这一节的 takeaway:solve_bvp 不是在猜一个起点,而是在同时满足中间方程和两端边界。
5. 最小可用例子
这道题其实有解析解:
你可以直接代回去验证:
这很好,因为它能拿来检验数值结果有没有跑偏。
如果 solve_bvp 算出来的曲线和这个抛物线接近,就说明求解过程是对的。
这一节的 takeaway:边值问题也要尽量找一个最小例子做验算。
6. 用代码算
完整脚本可以这样写:
import numpy as np
from scipy.integrate import solve_bvp, solve_ivp
def bvp_rhs(x, y):
# y[0] = u, y[1] = u'
return np.vstack((y[1], -np.ones_like(x)))
def bc(ya, yb):
# 左右两端温度都固定为 0
return np.array([ya[0], yb[0]])
# 先给一个网格和初猜
x = np.linspace(0, 1, 50)
y_guess = np.vstack((0.1 * x * (1 - x), 0.1 - 0.2 * x))
# solve_bvp 直接求解边值问题
sol = solve_bvp(bvp_rhs, bc, x, y_guess)这里 y_guess 是初猜,不是最终答案。
边值问题通常都需要一个比较像样的初猜,因为求解器要从一个近似曲线慢慢修。
如果你想看射击法,也可以这样做:
def shooting_residual(s):
# 先猜左端斜率 s,把边值问题变成初值问题
def ivp_rhs(x, y):
return [y[1], -1.0]
# 从左端出发,初值是 u(0)=0, u'(0)=s
sol_ivp = solve_ivp(ivp_rhs, (0.0, 1.0), [0.0, s], t_eval=np.linspace(0, 1, 200))
# 右端温度离 0 有多远,就是这次射击偏了多少
return sol_ivp.y[0, -1]射击法的关键不是“猜中一次”,而是:
你能不能不断修正那根起始斜率。
这一节的 takeaway:边值问题可以直接解,也可以先猜后修正。

7. 结果怎么看
这道题的结果应该是一条向上鼓起的抛物线。
它告诉我们:
- 两端因为边界条件固定,所以都落在 0
- 中间因为有“抬高”作用,所以会高起来
- 整条曲线是被两头条件钉住的
如果你改错了边界条件,形状就会完全不一样。
所以边值问题里最容易错的不是求解器,而是:
边界条件有没有写对。
这一节的 takeaway:结果形状是否合理,首先取决于边界条件有没有写对。

8. 一个实用判断
遇到边值问题时,可以先问自己三个问题:
- 条件是在起点,还是在两头
- 我是不是把问题误当成了初值问题
- 有没有一个简单曲线能先用来验算
只要这三件事想清楚,solve_bvp 和射击法就不再神秘。
这一节的 takeaway:边值问题最怕的不是难,而是把它当成初值问题硬推。
9. 小结
这一篇压成一句话就是:
边值问题不是从一个起点往前推,而是要让整条曲线同时满足两头的条件。
如果只记五句话,就记这五句:
-
BVP是boundary value problem - 条件放在两头时,不能只从左边一路算到底
- 射击法就是“猜斜率,再修正”
-
solve_bvp直接处理边界条件 - 最简单的验算对象是一条能手算的抛物线
下一篇我们把时间加回来,看看热量在空间和时间里同时变化时,偏微分方程要怎么落到网格上。
