5.12 渐近分析与特殊函数
前面的内容处理的是精确问题:求极限、求积分、求极值。实际工作中另一类问题出现得更频繁:参数很大或很小时,精确解无法求得,但解的主项可以写成简单的显式表达式。例如
三条线索构成本节。Laplace 方法处理形如
5.12.1 Laplace 方法
主项的来源
考虑积分
则
把积分区间从
端点最大值:
多个最大值:
最大值处
修正项
把
实验一第二组的数据验证这条展开:
第一组的数据给出指数级精度的例子:
鞍点法与最陡下降
把积分推广到复平面上的围道积分
鞍点法的应用范围超出纯数学:统计力学中的配分函数、组合计数中的系数提取(生成函数的系数
5.12.2 Euler-Maclaurin 公式
公式
Euler-Maclaurin 公式把求和与积分联系起来:
其中
实验二的数据给出四项的完整误差阶梯:只用
这组数据把「渐近展开」的准确含义写清楚了:每一项的系数可以精确预测,误差与下一项同阶。同时要注意展开式的发散性:Bernoulli 数
应用
三条常见用法。
级数求和:把
渐近展开的推导:Stirling 公式的对数形式
离散与连续的桥:Euler-Maclaurin 公式是「离散和 ↔ 连续积分」的定量版本,在数值积分(梯形公式的误差展开)、数论(
5.12.3 特殊函数的渐近与递推
三类特殊函数
Gamma 函数
误差函数
这条展开在统计中直接对应「正态分布尾概率」的估计(第二章的尾概率与第六章的假设检验),也是大偏差原理的具体形态:
Bessel 函数
即在远处表现为振幅按
递推关系的稳定性
Bessel 函数满足三项递推
它在理论上对一切整数
实验三的数据(
这条对照给出常微分方程与递推计算的一般原则:递推的方向应当与解的衰减方向一致(向前递推用于增长解,向后递推用于衰减解)。同一条原则出现在很多场合:三对角矩阵的追赶法(向前消元对应稳定的方向)、卡尔曼滤波的协方差更新(用 Joseph 形式保持正定)、偏微分方程的迎风格式(实验四)。判断稳定性的方法是检查递推的放大因子与解的增长/衰减趋势是否匹配,而不是只看公式是否成立。
5.12.4 奇异摄动与边界层
问题的结构
考虑含小参数的微分方程
外解:在远离边界的区域,忽略
内解:在
匹配:内解在
复合展开:把外解与内解相加再减去公共部分(匹配值),得一致有效的近似
(外解中保留一阶修正
实验四的数据验证这条构造:只用外解时最大误差是
边界层与数值振荡
渐近分析解决了「解的形状」,但数值求解仍然困难:边界层的宽度
实验四的数值部分给出三种格式的完整对比(
中心差分:方程为
迎风格式:一阶项改用后向差分(对流方向来自左侧),系数变为
指数拟合格式(Scharfetter-Gummel):利用单元内方程
三个系数
这组数据的方法论含义超出这个方程:当解在某个区域急剧变化时,格式的单调性与稳定性比形式上的精度阶更重要。中心差分的二阶精度只在
5.12.5 渐近分析在算法与统计中的用法
三条常见的应用形态。
算法复杂度:Euler-Maclaurin 公式给出求和量的精确展开,例如二叉堆建堆的代价
大偏差与尾概率:
统计中的近似:Stirling 公式用于二项系数的估计(第四章的组合数与熵),
5.12.6 数值实验
实验一:Laplace 方法的主项与修正项
两组积分分别对应端点最大值与内部最大值的情形,用精确值核对主项与第一修正项。
import math
from scipy.special import erf, i0
print("(a) I(n) = int_0^1 exp(-n x^2) dx,精确值 = sqrt(pi)/(2 sqrt(n)) * erf(sqrt(n))")
print(f"{'n':>6} {'精确值':>16} {'主项 sqrt(pi/n)/2':>20} {'相对误差':>14} "
f"{'误差 * e^n * sqrt(pi n)':>26}")
for n in [1, 2, 3, 5, 10, 20]:
exact = math.sqrt(math.pi) / (2 * math.sqrt(n)) * erf(math.sqrt(n))
lead = math.sqrt(math.pi) / (2 * math.sqrt(n))
rel = abs(lead - exact) / exact
print(f"{n:>6} {exact:>16.10f} {lead:>20.10f} {rel:>14.3e} "
f"{rel * math.exp(n) * math.sqrt(math.pi * n):>26.6f}")
print("(b) I(a) = int_{-pi}^{pi} exp(a cos x) dx = 2 pi I_0(a),最大值在 x = 0")
print(" Laplace 展开:I(a) = e^a sqrt(2 pi / a) (1 + 1/(8a) + 9/(128 a^2) + ...)")
print(f"{'a':>6} {'精确值 2 pi I_0(a)':>22} {'主项':>20} {'主项相对误差':>16} {'加 1/(8a) 后':>16}")
for a in [1, 2, 5, 10, 20]:
exact = 2 * math.pi * float(i0(a))
lead = math.exp(a) * math.sqrt(2 * math.pi / a)
lead2 = lead * (1 + 1 / (8 * a))
print(f"{a:>6} {exact:>22.6f} {lead:>20.6f} {abs(lead - exact) / exact:>16.3e} "
f"{abs(lead2 - exact) / exact:>16.3e}")输出:
(a) I(n) = int_0^1 exp(-n x^2) dx,精确值 = sqrt(pi)/(2 sqrt(n)) * erf(sqrt(n))
n 精确值 主项 sqrt(pi/n)/2 相对误差 误差 * e^n * sqrt(pi n)
1 0.7468241328 0.8862269255 1.867e-01 0.899337
2 0.5981440067 0.6266570687 4.767e-02 0.882911
3 0.5043435602 0.5116633540 1.451e-02 0.894935
5 0.3957123096 0.3963327298 1.568e-03 0.922229
10 0.2802473905 0.2802495608 7.744e-06 0.956094
20 0.1981663648 0.1981663649 2.540e-10 0.976674
(b) I(a) = int_{-pi}^{pi} exp(a cos x) dx = 2 pi I_0(a),最大值在 x = 0
Laplace 展开:I(a) = e^a sqrt(2 pi / a) (1 + 1/(8a) + 9/(128 a^2) + ...)
a 精确值 2 pi I_0(a) 主项 主项相对误差 加 1/(8a) 后
1 7.954927 6.813722 1.435e-01 3.639e-02
2 14.323057 13.096761 8.562e-02 2.847e-02
5 171.153162 166.370891 2.794e-02 3.640e-03
10 17691.669349 17459.618631 1.312e-02 7.803e-04
20 273684760.984164 271934665.877386 6.395e-03 1.845e-04(a) 端点最大值的指数级精度。
(b) 内部最大值的幂次修正。主项的相对误差按
实验二:Euler-Maclaurin 公式求和
用四项修正计算调和数
import math
print("H_N = sum_{k=1}^N 1/k = ln N + gamma + 1/(2N) - 1/(12 N^2) + 1/(120 N^4) - ...")
gamma = 0.5772156649015329
rows = []
for N in [10, 100, 1000, 10 ** 4, 10 ** 6]:
s = math.fsum(1.0 / k for k in range(1, N + 1))
lN = math.log(N)
v1 = lN + gamma
v2 = v1 + 1.0 / (2 * N)
v3 = v2 - 1.0 / (12 * N * N)
v4 = v3 + 1.0 / (120 * N ** 4)
rows.append((N, s, v1, v2, v3, v4))
print(f"{'N':>8} {'|ln N + gamma|':>18} {'加 1/(2N)':>16} {'减 1/(12N^2)':>18} "
f"{'加 1/(120N^4)':>18}")
for N, s, v1, v2, v3, v4 in rows:
print(f"{N:>8} {abs(v1 - s):>18.3e} {abs(v2 - s):>16.3e} {abs(v3 - s):>18.3e} "
f"{abs(v4 - s):>18.3e}")输出:
H_N = sum_{k=1}^N 1/k = ln N + gamma + 1/(2N) - 1/(12 N^2) + 1/(120 N^4) - ...
N |ln N + gamma| 加 1/(2N) 减 1/(12N^2) 加 1/(120N^4)
10 4.917e-02 8.325e-04 8.294e-07 3.927e-09
100 4.992e-03 8.333e-06 8.333e-11 3.553e-15
1000 4.999e-04 8.333e-08 8.882e-15 8.882e-16
10000 5.000e-05 8.333e-10 1.776e-15 1.776e-15
1000000 5.000e-07 8.349e-14 0.000e+00 0.000e+00四列的误差衰减速度依次是
math.fsum 精确求和,误差只剩舍入误差)。这条现象给出渐近展开的实用边界:展开的项数应当取到误差与舍入误差相当为止,继续增加项数没有收益(对发散的渐近级数还会使误差回升)。
实验三:特殊函数的递推稳定性
用前向与后向递推计算 Bessel 函数
from scipy.special import jv
x = 1.0
NMAX = 20
j0 = float(jv(0, x))
j1 = float(jv(1, x))
fw = [j0, j1]
for n in range(1, NMAX):
fw.append((2 * n / x) * fw[n] - fw[n - 1]) # 前向递推
exact = [float(jv(n, x)) for n in range(NMAX + 1)]
Nstart = 60
b = [0.0] * (Nstart + 2)
b[Nstart] = 1e-30
for n in range(Nstart, 0, -1):
b[n - 1] = (2 * n / x) * b[n] - b[n + 1] # 后向递推(Miller 算法)
norm = b[0] + 2 * sum(b[2::2]) # 归一化:J_0 + 2 sum J_2k = 1
bw = [v / norm for v in b]
print(f"{'n':>4} {'精确值':>16} {'前向递推':>18} {'前向相对误差':>16} "
f"{'后向递推':>16} {'后向相对误差':>16}")
for n in [0, 2, 5, 8, 10, 12, 15, 18, 20]:
fwe = abs(fw[n] - exact[n]) / abs(exact[n])
bwe = abs(bw[n] - exact[n]) / abs(exact[n])
print(f"{n:>4} {exact[n]:>16.6e} {fw[n]:>18.6e} {fwe:>16.3e} "
f"{bw[n]:>16.6e} {bwe:>16.3e}")输出:
n 精确值 前向递推 前向相对误差 后向递推 后向相对误差
0 7.651977e-01 7.651977e-01 0.000e+00 7.651977e-01 1.451e-16
2 1.149035e-01 1.149035e-01 0.000e+00 1.149035e-01 1.208e-16
5 2.497577e-04 2.497577e-04 8.030e-12 2.497577e-04 1.085e-15
8 9.422344e-08 9.422672e-08 3.480e-05 9.422344e-08 4.214e-16
10 2.630615e-10 1.199871e-09 3.561e+00 2.630615e-10 3.931e-16
12 4.999718e-13 4.101109e-07 8.203e+05 4.999718e-13 4.039e-15
15 2.297532e-17 7.130539e-03 3.104e+14 2.297532e-17 1.744e-15
18 5.880345e-22 2.320072e+02 3.945e+23 5.880345e-22 2.879e-15
20 3.873503e-25 3.168944e+05 8.181e+29 3.873503e-25 1.897e-15两列误差的走向完全相反。前向递推的相对误差按指数增长:
失效的机制可以从递推的两个解看出来。
实验四:奇异摄动与边界层
比较外解、复合展开与精确解,并对比三种有限差分格式在边界层问题上的表现。
import math
import numpy as np
print("eps*y'' + y' = x,y(0) = 0,y(1) = 2")
print("精确解 y = x^2/2 - eps*x + A - A e^(-x/eps),A = (1.5 + eps)/(1 - e^(-1/eps))")
print("外解 y_out = x^2/2 + 1.5 + eps*(1 - x),复合展开 y_comp = y_out - (1.5 + eps) e^(-x/eps)")
def exact_sol(xq, eps):
e = math.exp(-1.0 / eps)
A = (1.5 + eps) / (1.0 - e)
return 0.5 * xq ** 2 - eps * xq + A - A * np.exp(-xq / eps)
def outer(xq, eps):
return 0.5 * xq ** 2 + 1.5 + eps * (1 - xq)
def comp(xq, eps):
return outer(xq, eps) - (1.5 + eps) * np.exp(-xq / eps)
xq = np.linspace(0, 1, 200001)
print(f"{'eps':>8} {'外解的最大误差':>18} {'复合展开的最大误差':>20}")
for eps in [0.1, 0.05, 0.02, 0.01]:
ex = exact_sol(xq, eps)
print(f"{eps:>8} {float(np.max(np.abs(outer(xq, eps) - ex))):>18.6f} "
f"{float(np.max(np.abs(comp(xq, eps) - ex))):>20.3e}")
def solve_fd(eps, N, scheme):
h = 1.0 / N
A = np.zeros((N + 1, N + 1))
b = np.zeros(N + 1)
A[0, 0] = 1.0
A[N, N] = 1.0
b[N] = 2.0
c = eps / h ** 2
beta = math.exp(-h / eps)
for i in range(1, N):
xi = i * h
if scheme == "central":
A[i, i - 1] = c - 1.0 / (2 * h)
A[i, i] = -2 * c
A[i, i + 1] = c + 1.0 / (2 * h)
b[i] = xi
elif scheme == "upwind":
A[i, i - 1] = c - 1.0 / h
A[i, i] = -2 * c + 1.0 / h
A[i, i + 1] = c
b[i] = xi
else: # 指数拟合(Scharfetter-Gummel)
A[i, i - 1] = c * beta
A[i, i] = -c * (1 + beta)
A[i, i + 1] = c
b[i] = xi * (eps / h) * (1 - beta)
return np.linspace(0, 1, N + 1), np.linalg.solve(A, b), beta
eps = 0.01
print(f"{'N':>6} {'h':>8} {'h/(2 eps)':>10} {'中心差分误差':>14} {'斜率变号次数':>14} "
f"{'指数拟合误差':>14} {'迎风误差':>12}")
for N in [20, 50, 100, 500, 2000]:
h = 1.0 / N
xg, uc, _ = solve_fd(eps, N, "central")
xg, ug, beta = solve_fd(eps, N, "sg")
try:
xg, uw, _ = solve_fd(eps, N, "upwind")
uw_err = f"{float(np.max(np.abs(uw - exact_sol(xg, eps)))):>12.6f}"
except np.linalg.LinAlgError:
uw_err = f"{'矩阵奇异':>12}"
ex = exact_sol(xg, eps)
du = np.diff(uc)
osc = int(np.sum(du[:-1] * du[1:] < 0))
print(f"{N:>6} {h:>8.4f} {h / (2 * eps):>10.2f} "
f"{float(np.max(np.abs(uc - ex))):>14.6f} {osc:>14} "
f"{float(np.max(np.abs(ug - ex))):>14.6f} {uw_err}")输出:
eps*y'' + y' = x,y(0) = 0,y(1) = 2
精确解 y = x^2/2 - eps*x + A - A e^(-x/eps),A = (1.5 + eps)/(1 - e^(-1/eps))
外解 y_out = x^2/2 + 1.5 + eps*(1 - x),复合展开 y_comp = y_out - (1.5 + eps) e^(-x/eps)
eps 外解的最大误差 复合展开的最大误差
0.1 1.600000 7.264e-05
0.05 1.550000 3.195e-09
0.02 1.520000 4.441e-16
0.01 1.510000 4.441e-16
N h h/(2 eps) 中心差分误差 斜率变号次数 指数拟合误差 迎风误差
20 0.0500 2.50 0.657317 6 0.014469 1.857500
50 0.0200 1.00 0.204356 0 0.002948 矩阵奇异
100 0.0100 0.50 0.052165 0 0.000773 0.550548
500 0.0020 0.10 0.001860 0 0.000031 0.060039
2000 0.0005 0.03 0.000116 0 0.000002 0.014026第一张表验证渐近展开的构造。外解的最大误差是
第二张表给出格式选择的三条结论。中心差分在
三者的对照给出边界层问题的实践规则:当网格无法分辨层的宽度时,格式的单调性比形式精度更重要。中心差分的二阶精度只在
5.12.7 本节小结
渐近分析的三层内容
本节把渐近分析与特殊函数组织为三层。渐近方法层给出三件工具。Laplace 方法处理
渐近分析给出了「参数极端时解的主项」,下一节作为选读把常微分方程与动力系统的视角补齐:解的存在唯一性、相平面与稳定性分析、数值方法的稳定性与刚性方程。这些内容与本章的其余部分关系密切:存在性用压缩映射原理(5.1 节),稳定性分析用线性化与 Jacobian 的谱(5.8 节),数值方法的稳定性用与本节相同的单调性与放大因子分析。
练习题
第 1 题 概念推导
(a) 用 Laplace 方法求
参考答案
(a) 写成
精确值可以用不完全
(b) 忽略
(c) 递推
代价是后向递推的起始值是任意的(不知道
第 2 题 计算推理
(a) 用 Euler-Maclaurin 公式估计
参考答案
(a) 取
计算各项:
(常数项
(b)
简化:
数值核对:
(c) 把被积函数写成
主项取到
第 3 题 代码验证
用本节代码做三组实验并解释:(a) 在实验一里把积分换成
参考答案
(a) 作换元
主项从
(b) 递推的放大因子是
(c)
第 4 题 综合应用
某数值程序需要计算
参考答案
分析积分结构:
路线一:数值积分(自适应 Gauss)。用自适应求积(例如 Gauss-Kronrod 的 G7-K15 对,误差由两个结果的差估计)在
路线二:级数展开(渐近级数)。把
而
截到
路线三:换元 + 数值积分。把积分变量改成
推荐方案:分段实施。
常见错误
错误 1 · 在参数不够大时使用渐近公式
原因:渐近展开的每一项都假设「展开参数是小量」,参数不够大时误差很大,甚至主项的符号或量级都可能错。本节实验一第二组的数据给出量化对照:
解决:使用渐近公式前检查参数是否落在适用区间(一般要求展开参数
错误 2 · 把发散的渐近级数当作收敛级数求和
原因:渐近级数的系数(Bernoulli 数、阶乘项)增长很快,级数对固定的参数最终发散。本节实验二的数据给出了边界现象:
解决:遵循「截到最小项为止」的原则:逐项累加并在误差开始回升时停止(与 5.4 节数值微分的最优步长是同一类权衡)。用余项的估计给出误差界(对交错型的渐近级数,误差小于第一个被舍弃项)。对需要高精度的场合,改用收敛的展开(如 Taylor 级数、连分式、Padé 逼近)或者数值方法。报告中说明所用的项数与误差估计,不要报告「加到第
错误 3 · 选择与解的衰减方向相反的递推方向
原因:三项递推有两个解,一个增长一个衰减,递推方向决定了误差的放大或压缩。本节实验三的数据给出极端例子:Bessel 函数
解决:先判断递推的两个解中哪个是你需要的、哪个是增长的(比较递推系数的量级);若要得到衰减解,用后向递推加归一化(Miller 算法);若两者都不衰减(临界情形),需要更细致的分析(例如用 Chebyshev 型的方法或改写成稳定的二阶形式)。实现时做「方向对照实验」:用两个方向各算一次,看结果是否一致,不一致说明方向选错。对已经算出的结果,用已知恒等式或特殊点上的已知值核对。
错误 4 · 在边界层问题中使用常规差分格式而不检查网格条件
原因:含小参数的微分方程在边界层内的解变化尺度是
解决:先估计解的变化尺度(由方程的小参数与系数决定,通常是