5.9 重积分、曲线曲面积分与向量分析
上一节把微分推广到多维,这一节处理多维的积分。多元积分与一元积分共用「分割求和取极限」的定义,但多了两项内容。第一项是换元的复杂性:一元换元只需要一个导数的绝对值,多元换元需要 Jacobian 行列式,而换元的选择决定了计算量,选得恰当可以把复杂的区域或奇性变成简单形式(实验二的数据给出这条对比)。第二项是积分区域与被积对象的多样性:积分可以沿曲线、沿曲面、在区域上进行,被积对象可以是标量也可以是向量场。曲线积分与曲面积分把「沿路径的累积」与「穿过曲面的通量」纳入同一框架,Green、Gauss、Stokes 三个公式把区域内部的积分与边界上的积分联系起来,5.8 节的广义 Stokes 公式把三者归为一条。
本节最后处理数值积分。高维积分有一条硬性的分界线:网格法的代价随维数指数增长,Monte Carlo 的误差始终是
5.9.1 重积分与 Fubini 定理
二重积分的定义
设
其中
几何含义是「曲面
Fubini 定理
直接按定义计算多重积分不可行。Fubini 定理把它化为累次的一元积分:若
内层对
例:计算
5.9.2 换元公式与 Jacobian 行列式
换元公式
设
例:单位圆盘的面积
同样的积分若不用换元,需要处理区域边界
换元的作用不止于简化区域
换元还有第二个作用:改变被积函数的光滑程度,从而影响数值求积的精度。实验二最后一行给出一个鲜明的例子:
5.9.3 曲线积分与曲面积分
第一类与第二类曲线积分
设
第一类曲线积分(对弧长)以弧长为积分变量:
它的值与曲线的定向无关,物理含义是「沿曲线的质量或累积量」(
第二类曲线积分(对坐标)以位移为积分变量:
它的值随定向改变符号,物理含义是向量场
第一类与第二类曲面积分
设
第一类曲面积分(对面积):
面积元
第二类曲面积分(通量):
5.9.4 Green、Gauss 与 Stokes 公式
三个公式共同的形式是「区域内部的积分等于边界上的积分」。
Green 公式:设
Gauss 公式(散度定理):设
Stokes 公式:设
三条公式的关系可以用一句话概括:Green 公式是 Stokes 公式在平面上的特例(取
选择哪个公式:计算闭曲线上的线积分时,若曲线内部的二重积分容易算,用 Green 公式转过去;计算闭曲面上的通量时,若体积分容易算,用 Gauss 公式;计算沿空间曲线的线积分时,若以该曲线为边界的曲面上的旋度通量容易算,用 Stokes 公式。方向相反的使用同样常见:用曲线积分计算不便直接求积的面积(取
5.9.5 数值积分:网格法与 Monte Carlo
维数灾难
一元数值求积用
指数随维数下降,这就是维数灾难:
网格法还有一个实践上的障碍:网格点的个数随维数爆炸。
Monte Carlo 积分
把积分写成期望:
用独立同分布样本
因此标准误是
这条区间是数值结果自带的误差估计,网格法没有同样方便的对应物(网格误差依赖被积函数的高阶导数,需要估计导数界)。误差按
方差缩减
既然误差由
重要性采样:从另一个分布
估计量是
分层采样:把区域分成若干层,每层独立抽样若干点,再按层的体积加权平均。估计量是
层内方差通常小于整体方差,因此方差下降。实验中十层分层把
其他技术:对偶变量(用
实验四的数据给出重要性与分层的量化对比:均匀采样标准误
5.9.6 数值实验
实验一:网格法与 Monte Carlo 的维度对比
固定总采样点数
import math
import numpy as np
rng = np.random.default_rng(42)
n = 10 ** 6
sin1 = math.sin(1.0)
print(f"一维积分 int_0^1 cos(x) dx = sin(1) = {sin1:.10f},d 维真值 = sin(1)^d / 2")
print(f"{'d':>4} {'真值':>16} {'每边点数':>10} {'网格法相对误差':>16} {'MC 相对误差':>14} {'MC 理论标准误':>16}")
for d in [1, 2, 3, 5, 8]:
exact = sin1 ** d / 2.0
per = max(2, int(round(n ** (1.0 / d))))
grids = [np.linspace(0, 1, per + 1)[:-1] + 0.5 / per for _ in range(d)]
mesh = np.meshgrid(*grids, indexing="ij")
pts = np.stack([g.ravel() for g in mesh], axis=1)
gval = float(np.mean(np.prod(np.cos(pts), axis=1) / 2.0))
gerr = abs(gval - exact) / exact
x = rng.random((n, d))
fv = np.prod(np.cos(x), axis=1) / 2.0
merr = abs(float(np.mean(fv)) - exact) / exact
mse = float(np.std(fv) / math.sqrt(n)) / exact
print(f"{d:>4} {exact:>16.10f} {per:>10} {gerr:>16.3e} {merr:>14.3e} {mse:>16.3e}")输出:
一维积分 int_0^1 cos(x) dx = sin(1) = 0.8414709848,d 维真值 = sin(1)^d / 2
d 真值 每边点数 网格法相对误差 MC 相对误差 MC 理论标准误
1 0.4207354924 1000000 4.169e-14 1.523e-06 1.649e-04
2 0.3540367091 1000 8.333e-08 2.624e-04 2.350e-04
3 0.2979116183 100 1.250e-05 2.347e-04 2.895e-04
5 0.2109432979 16 8.142e-04 3.521e-04 3.786e-04
8 0.1256849185 6 9.304e-03 9.070e-04 4.882e-04网格法一列随维数上升:
Monte Carlo 一列始终在
实验二:换元积分与 Jacobian 行列式
四组换元的验证:极坐标求圆盘面积、球坐标求球体积、高维立方体中的球、以及换元对求积精度的影响。
import math
import numpy as np
rng = np.random.default_rng(42)
n = 2 * 10 ** 6
print("(a) 单位圆盘面积:换元到极坐标后 dx dy = r dr dtheta")
r = rng.random(n)
area = 2 * math.pi * float(np.mean(r))
print(f" 面积 = 2*pi*E[r] = {area:.6f},pi = {math.pi:.6f},"
f"相对误差 = {abs(area - math.pi) / math.pi:.2e}")
print("(b) 单位球体积:球坐标体积元 r^2 sin(phi) dr dphi dtheta")
r = rng.random(n)
phi = math.pi * rng.random(n)
vol = 2 * math.pi ** 2 * float(np.mean(r ** 2 * np.sin(phi)))
print(f" V = 2*pi^2*E[r^2 sin(phi)] = {vol:.8f},4*pi/3 = {4 * math.pi / 3:.8f},"
f"相对误差 = {abs(vol - 4 * math.pi / 3) / (4 * math.pi / 3):.2e}")
print("(c) 高维立方体中的球:int_[0,1]^d 1_{|x|<=1} dx")
print(f"{'d':>4} {'真值':>16} {'MC 估计':>16} {'相对误差':>14}")
for d in [2, 3, 5, 8]:
n2 = 2 * 10 ** 5
x = rng.random((n2, d))
inside = (np.sum(x ** 2, axis=1) <= 1.0)
est = float(np.mean(inside))
exact = math.pi ** (d / 2) / math.gamma(d / 2 + 1) / 2 ** d
print(f"{d:>4} {exact:>16.10f} {est:>16.8f} {abs(est - exact) / exact:>14.3e}")
def simpson(f, a, b, m):
x = np.linspace(a, b, m + 1)
y = f(x)
return (b - a) / (3 * m) * float(y[0] + y[-1] + 4 * np.sum(y[1:m:2]) + 2 * np.sum(y[2:m:2]))
print("(d) 含端点奇性的积分 int_0^1 sqrt(1-x^2) dx = pi/4")
ref = math.pi / 4
print(f"{'n':>8} {'直接 Simpson':>18} {'误差':>12} {'换元 x=sin t 后':>18} {'误差':>12}")
for m in [4, 8, 16, 32, 64, 128]:
v1 = simpson(lambda x: np.sqrt(np.maximum(1 - x * x, 0.0)), 0.0, 1.0, m)
v2 = simpson(lambda t: np.cos(t) ** 2, 0.0, math.pi / 2, m)
print(f"{m:>8} {v1:>18.12f} {abs(v1 - ref):>12.3e} {v2:>18.12f} {abs(v2 - ref):>12.3e}")输出:
(a) 单位圆盘面积:换元到极坐标后 dx dy = r dr dtheta
面积 = 2*pi*E[r] = 3.141650,pi = 3.141593,相对误差 = 1.83e-05
(b) 单位球体积:球坐标体积元 r^2 sin(phi) dr dphi dtheta
V = 2*pi^2*E[r^2 sin(phi)] = 4.18995939,4*pi/3 = 4.18879020,相对误差 = 2.79e-04
(c) 高维立方体中的球:int_[0,1]^d 1_{|x|<=1} dx
d 真值 MC 估计 相对误差
2 0.7853981634 0.78537000 3.586e-05
3 0.5235987756 0.52149500 4.018e-03
5 0.1644934067 0.16595500 8.885e-03
8 0.0158543442 0.01591500 3.826e-03
(d) 含端点奇性的积分 int_0^1 sqrt(1-x^2) dx = pi/4
n 直接 Simpson 误差 换元 x=sin t 后 误差
4 0.770898788737 1.450e-02 0.785398163397 0.000e+00
8 0.780297292444 5.101e-03 0.785398163397 0.000e+00
16 0.783599417246 1.799e-03 0.785398163397 0.000e+00
32 0.784763054473 6.351e-04 0.785398163397 0.000e+00
64 0.785173769020 2.244e-04 0.785398163397 1.110e-16
128 0.785318854734 7.931e-05 0.785398163397 0.000e+00(a)(b) 两组验证 Jacobian 因子的正确性。单位圆盘面积用
(c) 高维多变量积分的难度。
(d) 换元改变求积精度。直接对
实验三:Green、Gauss 与 Stokes 公式的数值验证
用线积分、面积分、体积分三种方式计算同一个量,核对公式两端。
import math
import numpy as np
rng = np.random.default_rng(42)
print("(a) Green 公式:取 P = -y, Q = x,两端都等于 2*pi")
print(f"{'折线段数':>10} {'左端(线积分)':>20} {'与 2*pi 的差':>16} {'收敛比':>10}")
prev = None
for m in [100, 200, 400, 800, 1600]:
t = np.linspace(0, 2 * math.pi, m + 1)
x, y = np.cos(t), np.sin(t)
val = float(np.sum((-y)[:-1] * np.diff(x) + x[:-1] * np.diff(y)))
err = abs(val - 2 * math.pi)
ratio = "-" if prev is None else f"{prev / err:.2f}"
print(f"{m:>10} {val:>20.10f} {err:>16.3e} {ratio:>10}")
prev = err
print("(b) Gauss 公式:取 F = (x, 0, 0),右端 = 4*pi/3,左端 = int_S x^2 dS")
print(f"{'n':>10} {'左端估计':>16} {'标准误':>12} {'与 4*pi/3 的差':>18}")
for m in [10 ** 5, 10 ** 6, 10 ** 7]:
ph = np.arccos(1 - 2 * rng.random(m))
tt = 2 * math.pi * rng.random(m)
x = np.sin(ph) * np.cos(tt)
left = 4 * math.pi * float(np.mean(x * x))
se = 4 * math.pi * float(np.std(x * x)) / math.sqrt(m)
print(f"{m:>10} {left:>16.10f} {se:>12.3e} {abs(left - 4 * math.pi / 3):>18.3e}")
print("(c) Stokes 公式:取 F = (-y, x, 0),两端都等于 2*pi")
m = 10 ** 6
right = 4 * math.pi * float(np.mean(rng.random(m)))
print(f" 右端(圆盘上的旋度通量,极坐标 MC)= {right:.10f},差 = {abs(right - 2 * math.pi):.3e}")
t = np.linspace(0, 2 * math.pi, 10001)
x, y = np.cos(t), np.sin(t)
left = float(np.sum((-y)[:-1] * np.diff(x) + x[:-1] * np.diff(y)))
print(f" 左端(圆上的线积分,10000 段折线)= {left:.10f},差 = {abs(left - 2 * math.pi):.3e}")输出:
(a) Green 公式:取 P = -y, Q = x,两端都等于 2*pi
折线段数 左端(线积分) 与 2*pi 的差 收敛比
100 6.2790519529 4.133e-03 -
200 6.2821518156 1.033e-03 4.00
400 6.2829269247 2.584e-04 4.00
800 6.2831207110 6.460e-05 4.00
1600 6.2831691581 1.615e-05 4.00
(b) Gauss 公式:取 F = (x, 0, 0),右端 = 4*pi/3,左端 = int_S x^2 dS
n 左端估计 标准误 与 4*pi/3 的差
100000 4.1915460402 1.186e-02 2.756e-03
1000000 4.1872637313 3.748e-03 1.526e-03
10000000 4.1894366454 1.185e-03 6.464e-04
(c) Stokes 公式:取 F = (-y, x, 0),两端都等于 2*pi
右端(圆盘上的旋度通量,极坐标 MC)= 6.2808673110,差 = 2.318e-03
左端(圆上的线积分,10000 段折线)= 6.2831848938,差 = 4.134e-07(a) Green 公式。取
(b) Gauss 公式。取
(c) Stokes 公式。取
实验四:重要性采样与分层采样的方差缩减
用四种方法估计
import math
import numpy as np
rng = np.random.default_rng(42)
exact_i = 1 - 1 / math.e
n = 200000
print(f"估计 I = int_0^1 e^(-x) dx = 1 - 1/e = {exact_i:.10f},n = {n}")
print(f"{'方法':>34} {'估计值':>14} {'标准误':>12}")
x = rng.random(n)
f = np.exp(-x)
est1 = float(np.mean(f))
se1 = float(np.std(f) / math.sqrt(n))
print(f"{'均匀采样':>34} {est1:>14.10f} {se1:>12.3e}")
u = rng.random(n)
xs = np.clip(np.sqrt(1 + 3 * u) - 1, 0, 1) # q = 2(1+x)/3 的逆变换
w = np.exp(-xs) / (2 * (1 + xs) / 3)
est2, se2 = float(np.mean(w)), float(np.std(w) / math.sqrt(n))
print(f"{'重要性采样 q ~ 2(1+x)/3':>34} {est2:>14.10f} {se2:>12.3e}")
u = rng.random(n)
xs = -np.log(1 - u * (1 - math.exp(-1))) # q = e^(-x)/(1-e^(-1)) 的逆变换
w = np.exp(-xs) / (np.exp(-xs) / (1 - math.exp(-1)))
est3, se3 = float(np.mean(w)), float(np.std(w) / math.sqrt(n))
print(f"{'重要性采样 q ~ e^(-x)(最优)':>34} {est3:>14.10f} {se3:>12.3e}")
L = 10
m = n // L
means, varlist = [], []
for i in range(L):
fi = np.exp(-(rng.random(m) / L + i / L))
means.append(float(np.mean(fi)))
varlist.append(float(np.var(fi, ddof=1)))
est4 = float(np.mean(means))
se4 = math.sqrt(sum(varlist) / (L * m))
print(f"{'分层采样(10 层)':>34} {est4:>14.10f} {se4:>12.3e}")
print(f"相对效率(均匀采样的标准误除以各方法的标准误):"
f"{se1 / se2:.2f}(差提议), {se1 / se4:.2f}(分层)")输出:
估计 I = int_0^1 e^(-x) dx = 1 - 1/e = 0.6321205588,n = 200000
方法 估计值 标准误
均匀采样 0.6318353077 4.045e-04
重要性采样 q ~ 2(1+x)/3 0.6318105270 7.074e-04
重要性采样 q ~ e^(-x)(最优) 0.6321205588 0.000e+00
分层采样(10 层) 0.6321284331 1.340e-04
相对效率(均匀采样的标准误除以各方法的标准误):0.57(差提议), 3.02(分层)四行数据给出三种不同的结果,正好覆盖了方差缩减的三种情形。均匀采样的标准误是
最优提议分布
分层采样把标准误降到
5.9.7 本节小结
多元积分的四层内容
本节把多元积分学组织为四层。定义层给出重积分:分割求和取极限,Fubini 定理把它化为累次一元积分(
多元微积分与向量分析说完了,下一节处理积分的理论基础。Riemann 积分对间断点与换序的限制(可积函数类的刻画、极限与积分交换的条件)在测度论的框架下变得清晰,Lebesgue 积分的三大收敛定理给出更容易验证的换序条件,
练习题
第 1 题 概念推导
(a) 用 Fubini 定理计算
参考答案
(a) 被积函数可分离:
数值上
(b) 球坐标变换
按第三行展开求行列式:
(c) 第一类曲线积分
其中
第 2 题 计算推理
(a) 用极坐标计算
参考答案
(a) 被积函数只依赖
关键在于换元后被积函数与
(b) 取
由 Green 公式
被积函数
(c) 不能随意交换。被积函数
第 3 题 代码验证
用本节代码做三组实验并解释:(a) 在实验一里把被积函数换成
参考答案
(a)
(b) 改用中点格式后,误差的收敛阶会提高到
(c) 分层数的增加带来两方面的变化。理论上标准误按
第 4 题 综合应用
某程序需要计算一个
参考答案
方案一:网格法。误差按
方案二:均匀 Monte Carlo。误差按
方案三:重要性采样 Monte Carlo。被积函数的峰值集中在小区域内,均匀采样的大部分样本落在函数值接近零的地方,贡献小、方差大。用形状接近被积函数的提议分布(例如以峰值位置为中心的多元正态分布,或自适应方法逐步调整)可以把变异系数从
推荐:方案三。依据是两方面的匹配。一是维数
常见错误
错误 1 · 用网格法处理高维积分
原因:网格法的误差按
解决:按维数选择方法。
错误 2 · 不检查 Jacobian 行列式就套用换元公式
原因:换元公式中的
解决:换元后先写清楚三件事:变换公式、Jacobian 行列式的绝对值、新区域的范围,然后再写积分。可以用量纲检查(
错误 3 · 在条件收敛的积分上交换积分次序
原因:Fubini 定理要求积分绝对收敛。本节练习 2(c) 的反例
解决:交换次序前检查绝对可积性(
错误 4 · 用有限样本的方差缩减结果判断方法的优劣
原因:Monte Carlo 的标准误本身是估计量,样本量不足时它的波动很大(相对波动约
解决:比较不同方法时用同一批随机数(公共随机数技术)减少比较的噪声,对每个方法重复多次取平均,报告标准误的估计区间而不只报点估计。使用重要性采样时先检查权重分布(最大权重与平均权重之比、有效样本量