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 微分方程(四):边值问题,边界条件在两头时怎么办?

在知乎查看原文

阅读 知乎未公开 收藏 2 点赞 2

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

前几篇我们一直在做一类题:

给一个起点,往后推一条曲线。

这一篇换一种更麻烦、也更常见的题:

如果题目不是给你一个起点,而是把条件放在两头,你还怎么把中间那条曲线找出来?

先给一句人话答案:

这类题不能只从左边往右走,因为你走到右边以后还得刚好满足右端条件。

这就是边值问题的核心。

边值问题的英文是 boundary value problem,简称 BVP。
先别急着背这个缩写,它只是说:

你要满足的不止是起点,还有边界。

这一节的 takeaway:初值问题是“从起点出发”,边值问题是“两头都要对”。

0. 先看最小问题

想象一根长度固定的细杆,两端温度都被固定成 0。
杆中间有一点加热,所以温度在中间会鼓起来,往两边慢慢降下去。

我们想问的不是“它会怎么随时间变化”,而是:

在稳态下,整根杆的温度曲线长什么样?

这类题很常见:

  • 两端固定温度的导热棒
  • 两端固定位置的绳子
  • 两头都定死条件的梁挠度

这一节的 takeaway:有些题不是“时间演化题”,而是“空间上怎么连起来”的题。

1. 变量和方程

我们把空间位置记成 x,温度曲线记成 u(x)。

稳态情况下,最简单的模型可以写成:

 \frac{d^2u}{dx^2} = -1

边界条件是:

 u(0)=0,\qquad u(1)=0

这里的意思是:

  • 左端温度固定为 0
  • 右端温度固定为 0
  • 中间有均匀的“抬高”作用,所以曲线会鼓起来

为什么这个模型好?

因为它足够简单,但已经包含了边值问题的全部难点:

方程在中间,条件在两头。

这一节的 takeaway:边值问题的难点不在公式长,而在条件分散在不同位置。

2. 为什么不能直接像 solve_ivp 那样算

如果这是初值问题,我们只需要知道:

  • 起点在哪里
  • 起点斜率是多少

然后就能一路往前推。

可边值问题不一样。
你虽然知道 u(0)=0,但你不知道 u'(0) 应该是多少。
而右端又要求 u(1)=0。

所以如果你只在左边随便猜一个斜率,曲线大概率会在右边偏掉。

这就逼出了一个很自然的办法:

先猜左边斜率,再看右边对不对,不对就改猜。

这就是射击法(shooting method)的直觉。

这一节的 takeaway:边值问题不是不能算,而是不能一次把所有条件都同时从左边带过去。

3. 射击法的想法

射击法的人话版本是:

  1. 先猜一个左端斜率
  2. 把它当成初值问题往右算
  3. 看右端边界有没有命中
  4. 没命中就改斜率,再来一次

这很像往靶子上射箭:

  • 你知道靶子在哪里
  • 但你不知道第一箭该抬多高
  • 所以你得试几次

数学上,我们把二阶方程改成一阶方程组:

 \begin{aligned} u' &= v, \\ v' &= -1 \end{aligned}

这样就能用 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. 最小可用例子

这道题其实有解析解:

 u(x) = \frac{x(1-x)}{2}

你可以直接代回去验证:

 u''(x) = -1,\qquad u(0)=0,\qquad u(1)=0

这很好,因为它能拿来检验数值结果有没有跑偏。

如果 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. 一个实用判断

遇到边值问题时,可以先问自己三个问题:

  1. 条件是在起点,还是在两头
  2. 我是不是把问题误当成了初值问题
  3. 有没有一个简单曲线能先用来验算

只要这三件事想清楚,solve_bvp 和射击法就不再神秘。

这一节的 takeaway:边值问题最怕的不是难,而是把它当成初值问题硬推。

9. 小结

这一篇压成一句话就是:

边值问题不是从一个起点往前推,而是要让整条曲线同时满足两头的条件。

如果只记五句话,就记这五句:

  • BVP 是 boundary value problem
  • 条件放在两头时,不能只从左边一路算到底
  • 射击法就是“猜斜率,再修正”
  • solve_bvp 直接处理边界条件
  • 最简单的验算对象是一条能手算的抛物线

下一篇我们把时间加回来,看看热量在空间和时间里同时变化时,偏微分方程要怎么落到网格上。

参考


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