5.11 曲线曲面几何与变分法初步
前一节处理积分的理论基础,本节回到几何与优化之间的交界处。前面的内容处理的都是「给定函数求积分或导数」,变分法处理的问题形式不同:在所有函数中找使某个泛函取极值的那一个。最短路径、最小表面积、最小能量、最速下降曲线都属于这一类,「求一个函数」比「求一个数」高一个层次,因此需要新的工具。
两类工具构成本节的主线。第一类是微分几何:曲线与曲面的局部形状由曲率、度量张量(第一基本形式)与曲率张量(第二基本形式)描述,这些量决定了「长度」「面积」「弯曲程度」这些几何量如何计算。第二类是变分原理:泛函的一阶变分为零给出 Euler-Lagrange 方程,它把无穷维的极值问题化为一个微分方程;反过来,物理与几何中的大量微分方程都可以写成变分形式(最小作用量原理、极小曲面方程、测地线方程)。两条线索的交点是测地线:曲面上长度最短的曲线,它的方程既可以用曲率刻画(测地曲率为零),也可以用变分法导出(度量给出的 Euler-Lagrange 方程)。
解析求解 Euler-Lagrange 方程通常只能处理少数情形(线性方程、可分离的方程、有守恒量的方程)。实际问题的求解依靠直接方法:在有限维子空间中寻找泛函的极小值,把问题化为线性代数或非线性规划。这条路线在现代计算中最成功的产物是有限元方法,它的理论基础是弱形式与 Sobolev 空间,本节会给出弱形式的来源、
5.11.1 曲线、弧长与曲率
参数化与弧长
平面曲线通常用参数表示:
弧长是曲线的内在量,与参数化方式无关(换参数时速度与
曲率
曲线的曲率
分子是「速度与加速度的外积」(二维时的叉积),分母是速度模长的三次方,量纲上是长度分之一次方。
三条基本结论。
直线的曲率恒为零(
圆的曲率恒为半径的倒数
曲率与参数化无关:换参数后分子分母同时变化,比值不变(这一点可以把上式写成
Frenet 标架把曲线的局部几何完整描述下来。在弧长参数下定义切向量
这两条Frenet 公式说明曲率是「标架转动」的唯一速率。三维情形多一个挠率
例子:椭圆
5.11.2 曲面、基本形式与测地线
参数曲面与第一基本形式
曲面用两个参数表示:
它的系数矩阵
这与 5.9 节的
第二基本形式与曲率
第二基本形式度量曲面如何偏离切平面:
其中
法曲率:曲面沿切方向
主曲率
高斯曲率
三类曲面的分类:
测地线
曲面上两点之间的最短曲线称测地线。它有两条等价的刻画。
变分刻画:在连接两点的所有曲线中使长度最小,即长度泛函的变分取零。球面上的测地线是大圆(过两点的平面与球面的交线)。
几何刻画:测地线的「测地曲率」为零,即它的加速度向量没有切向分量(加速度只沿法向):
第二种刻画把测地线方程写成
平面上的测地线是直线(曲率为零),球面上的测地线是大圆(曲率等于球面半径的倒数),圆柱面(
5.11.3 变分法与 Euler-Lagrange 方程
泛函与变分
泛函是把函数映到实数的映射,例如
其中
变分是把「函数的变化」形式化:取扰动
看成
Euler-Lagrange 方程
计算
第二项含
边界项因
由
三个要点。第一,变分问题(无穷维)被化成一个微分方程(含二阶导数),这是变分法最核心的一步。第二,边界项消失用的是边界条件给出的约束(
这条恒等式在求最速降线、极小旋转曲面等问题的显式解时是关键工具。
三个经典例子
最短路径:
最速降线:求一条曲线使质点从
下滑时间
等周问题:在给定周长的闭曲线中找面积最大的一条。用弧长参数化并写成变分问题,EL 方程给出曲率恒为常数,即圆。实验四的数据用等周不等式的形式核对:
单位圆取等号(比值
5.11.4 变分问题的直接方法与弱解
从经典解到弱解
Euler-Lagrange 方程要求解有二阶导数(方程中出现
这个等式只含
线性情形:若
对应的经典方程是
Sobolev 空间
弱形式需要一个新的函数空间:
其中
一维的嵌入定理:
Ritz 方法与 Galerkin 方法
直接方法的核心思想是把无穷维的极小化问题限制在有限维子空间:取
Galerkin 正交性给出误差估计的基础:真解
有限元方法
有限元方法把基函数取为分片多项式:在网格上取分片线性(一维)或分片线性/双线性(高维)的「帽函数」,每个基函数在一个节点附近支集很小。三条优点:刚度矩阵稀疏(带状或分块带状),局部基函数使装配可以逐单元进行,且基函数的逼近性质与函数的二阶导数有关,收敛率可以证明。
一维分片线性的收敛率:设
节点值的超收敛是本节数据的另一条信息:节点最大误差按每次网格加倍降到约
5.11.5 变分方法的其他用途
变分表述的价值不限于求解经典问题,它提供了一种「把目标写成泛函再求极值」的建模方式。
极小曲面:面积泛函
变分推断:把后验分布
正则化:把目标写成「数据项 + 正则项」的泛函,例如图像去噪中的总变差模型
最优控制:状态方程约束下的目标极小化,Euler-Lagrange 方程推广为Pontryagin 极大值原理(引入协态变量,与 Lagrange 乘子同源)。这条路线在机器人轨迹规划、强化学习的最优控制表述中都出现。
守恒律:Noether 定理把泛函的对称性翻译为守恒量(时间平移对称给出能量守恒、空间平移给出动量守恒、旋转对称给出角动量守恒)。这条定理把「找守恒量」这件在物理中很困难的事化归为「找对称性」,是变分表述在理论物理中最有力的应用。
5.11.6 数值实验
实验一:Euler-Lagrange 方程与直接离散化
求解变分问题
import math
import numpy as np
def u_exact(x):
return np.sin(math.pi * x) / math.pi ** 2
print(f"{'N':>8} {'节点最大误差':>16} {'误差比值':>10} {'真 L2 误差':>16} {'L2 比值':>10}")
prev_i = prev_l = None
gp = np.array([-0.906179845938664, -0.538469310105683, 0.0,
0.538469310105683, 0.906179845938664])
gw = np.array([0.236926885056189, 0.478628670499366, 0.568888888888889,
0.478628670499366, 0.236926885056189])
for N in [4, 8, 16, 32, 64, 128]:
h = 1.0 / N
nodes = np.linspace(0, 1, N + 1)
fv = np.sin(math.pi * nodes)
A = np.zeros((N - 1, N - 1))
for i in range(1, N):
A[i - 1, i - 1] = 2.0 / h
if i - 1 >= 1:
A[i - 1, i - 2] = -1.0 / h
if i + 1 <= N - 1:
A[i - 1, i] = -1.0 / h
b = h * fv[1:-1] # 梯形法:端点处 y = 0,权重 1/2 的项为零
u = np.zeros(N + 1)
u[1:-1] = np.linalg.solve(A, b)
err_i = float(np.max(np.abs(u - u_exact(nodes))))
e2 = 0.0
for e in range(N): # 真 L2 误差:逐单元 5 点 Gauss
a0, b0 = nodes[e], nodes[e + 1]
for q in range(5):
xi = 0.5 * (a0 + b0) + 0.5 * h * gp[q]
uh = u[e] + (u[e + 1] - u[e]) * (xi - a0) / h
e2 += 0.5 * h * gw[q] * (uh - float(u_exact(xi))) ** 2
err_l = math.sqrt(e2)
ri = "-" if prev_i is None else f"{prev_i / err_i:.2f}"
rl = "-" if prev_l is None else f"{prev_l / err_l:.2f}"
print(f"{N:>8} {err_i:>16.3e} {ri:>10} {err_l:>16.3e} {rl:>10}")
prev_i, prev_l = err_i, err_l输出:
N 节点最大误差 误差比值 真 L2 误差 L2 比值
4 5.373e-03 - 1.770e-03 -
8 1.312e-03 4.09 4.193e-04 4.22
16 3.261e-04 4.02 1.034e-04 4.06
32 8.142e-05 4.01 2.576e-05 4.01
64 2.035e-05 4.00 6.436e-06 4.00
128 5.086e-06 4.00 1.609e-06 4.00两条误差列的比值都稳定在
实验二:最速降线与摆线
比较摆线(EL 解)与其他曲线,并用直接离散化 + 优化求数值极小。
import math
import numpy as np
from scipy.optimize import minimize
g = 9.8
a = 0.5
x1 = a * (math.pi - math.sin(math.pi))
y1 = a * (1 - math.cos(math.pi))
T_cyc = math.sqrt(a / g) * math.pi
print(f"终点:x1 = {x1:.10f}, y1 = {y1:.10f}")
print(f"摆线(解析):T = sqrt(a/g)*theta1 = {T_cyc:.10f} s")
def T_of(x, y, M=400001):
"""改用 y 作积分变量并令 y = u^2,消除 x = 0 处的奇性"""
x = np.asarray(x, dtype=float)
y = np.asarray(y, dtype=float)
yg = np.linspace(0.0, y[-1], M)
xg = np.interp(yg, y, x)
dxdy = np.gradient(xg, yg)
u = np.sqrt(yg)
integrand = 2.0 * np.sqrt(1.0 + dxdy ** 2) / math.sqrt(2.0 * g)
return float(np.trapezoid(integrand, u))
thn = np.linspace(0, math.pi, 400001)
print(f"摆线(数值积分):T = {T_of(a * (thn - np.sin(thn)), a * (1 - np.cos(thn))):.10f} s")
xs = np.linspace(0, x1, 400001)
m = y1 / x1
for m0 in [m, 0.7 * m, 0.4 * m]:
yv = m0 * xs + (y1 - m0 * x1) * (xs / x1) ** 2
T = T_of(xs, yv)
print(f"起始斜率 {m0 / m:.1f} 倍:T = {T:.10f} s,比摆线多 {(T / T_cyc - 1) * 100:.4f}%")
def T_element(yv, h):
"""分段线性曲线的下滑时间(单元内解析积分)"""
yv = np.asarray(yv)
d = np.diff(yv) / h
K = (1.0 + d ** 2) / (2.0 * g)
sa = np.sqrt(np.maximum(yv[:-1], 1e-300))
sb = np.sqrt(np.maximum(yv[1:], 1e-300))
ad = np.abs(d)
seg = np.where(ad < 1e-12, h / sa, 2.0 * np.abs(sb - sa) / np.maximum(ad, 1e-300))
return float(np.sum(np.sqrt(K) * seg))
def cycloid_y(xq):
th = np.linspace(0, math.pi, 400001)
return np.interp(xq, a * (th - np.sin(th)), a * (1 - np.cos(th)))
for N in [20, 40, 80, 160, 320]:
h = x1 / N
xg = np.linspace(0, x1, N + 1)
y0 = 0.7 * xg + (y1 - 0.7 * x1) * (xg / x1) ** 2
res = minimize(lambda z: T_element(np.concatenate([[0.0], z, [y1]]), h),
y0[1:-1], method="L-BFGS-B", bounds=[(1e-9, 3.0)] * (N - 1),
options={"maxiter": 200000, "maxfun": 200000, "ftol": 1e-20, "gtol": 1e-16})
yfull = np.concatenate([[0.0], res.x, [y1]])
print(f"N = {N:>4}: T = {T_element(yfull, h):.10f},"
f"与摆线的最大偏差 = {float(np.max(np.abs(yfull - cycloid_y(xg)))):.6f}")输出:
终点:x1 = 1.5707963268, y1 = 1.0000000000
摆线(解析):T = sqrt(a/g)*theta1 = 0.7096134476 s
摆线(数值积分):T = 0.7096134480 s
起始斜率 1.0 倍:T = 0.8412091759 s,比摆线多 18.5447%
起始斜率 0.7 倍:T = 0.9192494657 s,比摆线多 29.5423%
起始斜率 0.4 倍:T = 1.0628828407 s,比摆线多 49.7834%
N = 20: T = 0.7126498323,与摆线的最大偏差 = 0.025965
N = 40: T = 0.7110892891,与摆线的最大偏差 = 0.016090
N = 80: T = 0.7103384053,与摆线的最大偏差 = 0.010024
N = 160: T = 0.7099719115,与摆线的最大偏差 = 0.006267
N = 320: T = 0.7097965189,与摆线的最大偏差 = 0.003466第一段数据有三条信息。摆线的解析值与数值积分值相差
第二段数据给出直接离散化的收敛行为。离散最小值从
实验三:一维有限元方法
用分片线性有限元求解
import math
import numpy as np
def u_exact(x):
return np.sin(math.pi * x) / math.pi ** 2
gp3 = np.array([-math.sqrt(3.0 / 5), 0.0, math.sqrt(3.0 / 5)])
gw3 = np.array([5.0 / 9, 8.0 / 9, 5.0 / 9])
gp5 = np.array([-0.906179845938664, -0.538469310105683, 0.0,
0.538469310105683, 0.906179845938664])
gw5 = np.array([0.236926885056189, 0.478628670499366, 0.568888888888889,
0.478628670499366, 0.236926885056189])
for N in [4, 8, 16, 32, 64, 128]:
h = 1.0 / N
nodes = np.linspace(0, 1, N + 1)
A = np.zeros((N + 1, N + 1))
b = np.zeros(N + 1)
for e in range(N): # 逐单元装配刚度矩阵与载荷向量
a0, b0 = nodes[e], nodes[e + 1]
ke = (1.0 / h) * np.array([[1.0, -1.0], [-1.0, 1.0]])
fe = np.zeros(2)
for q in range(3):
xi = 0.5 * (a0 + b0) + 0.5 * h * gp3[q]
w = 0.5 * h * gw3[q]
phi = np.array([(b0 - xi) / h, (xi - a0) / h])
fe += w * math.sin(math.pi * xi) * phi
for i in range(2):
for j in range(2):
A[e + i, e + j] += ke[i, j]
b[e + i] += fe[i]
free = np.arange(1, N) # 齐次 Dirichlet 边界条件
u = np.zeros(N + 1)
u[free] = np.linalg.solve(A[np.ix_(free, free)], b[free])
err_i = float(np.max(np.abs(u - u_exact(nodes))))
e2 = 0.0
for e in range(N):
a0, b0 = nodes[e], nodes[e + 1]
for q in range(5):
xi = 0.5 * (a0 + b0) + 0.5 * h * gp5[q]
uh = u[e] + (u[e + 1] - u[e]) * (xi - a0) / h
e2 += 0.5 * h * gw5[q] * (uh - float(u_exact(xi))) ** 2
print(f"N = {N:>4}: 节点最大误差 = {err_i:.3e}, L2 误差 = {math.sqrt(e2):.3e}")输出:
N = 4: 节点最大误差 = 6.075e-08, L2 误差 = 3.980e-03
N = 8: 节点最大误差 = 9.284e-10, L2 误差 = 1.005e-03
N = 16: 节点最大误差 = 1.443e-11, L2 误差 = 2.519e-04
N = 32: 节点最大误差 = 2.253e-13, L2 误差 = 6.302e-05
N = 64: 节点最大误差 = 4.455e-15, L2 误差 = 1.576e-05
N = 128: 节点最大误差 = 9.437e-16, L2 误差 = 3.940e-06两列误差的收敛速度截然不同。
节点值的超收敛可以用一句话解释:在这个一维问题中,若刚度矩阵与载荷向量都用精确积分装配,有限元解的节点值与精确解的节点值完全相等(节点精确性),因此观测到的节点误差只来自载荷积分的数值求积误差,而三点 Gauss 公式的误差是
实验四:曲率与等周不等式
用周期中心差计算闭曲线的周长、面积、曲率范围与等周比值,并核对摆线的曲率公式。
import math
import numpy as np
def curve_stats(xt, yt, t):
"""闭曲线的周期中心差分(t 上首尾相接)"""
h = t[1] - t[0]
ux = (np.roll(xt, -1) - np.roll(xt, 1)) / (2 * h)
uy = (np.roll(yt, -1) - np.roll(yt, 1)) / (2 * h)
uxx = (np.roll(xt, -1) - 2 * xt + np.roll(xt, 1)) / h ** 2
uyy = (np.roll(yt, -1) - 2 * yt + np.roll(yt, 1)) / h ** 2
speed = np.sqrt(ux ** 2 + uy ** 2)
kappa = np.abs(ux * uyy - uy * uxx) / np.maximum(speed ** 3, 1e-300)
L = float(np.sum(speed) * h)
A = 0.5 * float(np.sum(xt * uy - yt * ux) * h)
return L, abs(A), kappa
t = np.linspace(0, 2 * math.pi, 200000, endpoint=False)
tests = [
("圆(半径 1)", np.cos(t), np.sin(t)),
("椭圆 a=1.5, b=1", 1.5 * np.cos(t), np.sin(t)),
("椭圆 a=2, b=1", 2.0 * np.cos(t), np.sin(t)),
("超椭圆 |x|^4+|y|^4=1",
np.sign(np.cos(t)) * np.abs(np.cos(t)) ** 0.5,
np.sign(np.sin(t)) * np.abs(np.sin(t)) ** 0.5),
]
for name, xt, yt in tests:
L, A, k = curve_stats(xt, yt, t)
print(f"{name:>22}: L = {L:.6f}, A = {A:.6f}, "
f"L^2/(4 pi A) = {L ** 2 / (4 * math.pi * A):.6f}, "
f"曲率 [{np.min(k):.4f}, {np.max(k):.4f}]")
a = 0.5
th = np.linspace(1e-3, math.pi - 1e-3, 400001)
xt = a * (th - np.sin(th))
yt = a * (1 - np.cos(th))
dx = np.gradient(xt, th); dy = np.gradient(yt, th)
d2x = np.gradient(dx, th); d2y = np.gradient(dy, th)
kappa = np.abs(dx * d2y - dy * d2x) / (dx ** 2 + dy ** 2) ** 1.5
for tv in [math.pi / 2, math.pi / 2 + 1.0, math.pi - 0.02]:
i = int(np.argmin(np.abs(th - tv)))
print(f"theta = {th[i]:.6f}: 数值曲率 = {kappa[i]:.8f}, "
f"理论值 = {1 / (4 * a * math.sin(th[i] / 2)):.8f}")输出:
圆(半径 1): L = 6.283185, A = 3.141593, L^2/(4 pi A) = 1.000000, 曲率 [1.0000, 1.0000]
椭圆 a=1.5, b=1: L = 7.932720, A = 4.712389, L^2/(4 pi A) = 1.062657, 曲率 [0.4444, 1.5000]
椭圆 a=2, b=1: L = 9.688448, A = 6.283185, L^2/(4 pi A) = 1.188827, 曲率 [0.2500, 2.0000]
超椭圆 |x|^4+|y|^4=1: L = 7.017698, A = 3.708149, L^2/(4 pi A) = 1.056872, 曲率 [0.0000, 2.5227]
theta = 1.570796: 数值曲率 = 0.70710657, 理论值 = 0.70710678
theta = 2.570796: 数值曲率 = 0.52107780, 理论值 = 0.52107783
theta = 3.121590: 数值曲率 = 0.50002407, 理论值 = 0.50002501第一段数据同时验证了几何量的计算与等周不等式。圆的三个量都落在理论值上(
超椭圆一行的曲率范围是
第二段数据核对摆线的曲率公式
5.11.7 本节小结
几何与变分的四层内容
本节把曲线曲面几何与变分法组织为四层。曲线层给出弧长
变分法处理的是「在所有函数中找极值」,本节给出了它的骨架与两类求解路线。下一节处理另一类工具:当问题中的参数变得很大或很小时,函数的形状出现极端(尖峰、陡峭的过渡层、快速振荡),此时需要渐近分析:不求精确解,而是求主项与修正项的显式表达式。Stirling 公式、Laplace 方法与边界层理论都属于这一类,它们给出的近似往往比数值解的公式更直观。
练习题
第 1 题 概念推导
(a) 用 Euler-Lagrange 方程求最短路径问题的解,并说明为什么结果是直线。(b) 写出摆线
参考答案
(a) 最短路径问题的泛函是
Euler-Lagrange 方程给出
这条推导的结论与平面几何的直觉一致,但路径不同:几何直觉给出最短性,变分推导给出必要条件(驻点),要确认是极小还需要二阶变分或凸性论证(本例中
(b) 用参数形式的曲率公式
代入分子:
最后一步用了半角公式
(c) 经典方程(Euler-Lagrange 方程或
等式两边只含
第 2 题 计算推理
(a) 用 Beltrami 恒等式求极小旋转曲面的母线方程(泛函
参考答案
(a) 泛函是
计算
即
悬链线。因此旋转极小曲面是悬链面,这条曲线在建筑与力学中也出现(均匀悬链的形状、拱的设计)。这个例子说明 Beltrami 恒等式的作用:一阶守恒量把二阶方程降为一阶,解可以直接分离变量。
(b) 曲率恒为
积分给出圆心在
(c) 由等周不等式
等号在圆处成立,此时
第 3 题 代码验证
用本节代码做三组实验并解释:(a) 在实验一里把
参考答案
(a)
(b)
(c) 把三点 Gauss 换成中点公式后,载荷向量的误差从
第 4 题 综合应用
某工程优化问题需要求解一条曲线的形状,使某个泛函取极值(泛函的被积函数只依赖
参考答案
路线一:解析求解 Euler-Lagrange 方程。步骤是写出被积函数
路线二:直接离散化 + 数值优化。步骤是把区间划分为
推荐:两条路线配合使用。先用解析路线(重点是 Beltrami 恒等式)尝试求出闭式解或至少弄清解在奇点附近的行为(例如确定
常见错误
错误 1 · 把 Euler-Lagrange 方程的驻点直接当作极小点
原因:一阶变分为零只给出驻点(极值点的必要条件),驻点可能是极小、极大或鞍点。变分问题中这种模糊性比有限维更常见:泛函可能不凸(例如泛函
解决:求出 EL 方程的驻点后检查二阶变分(
错误 2 · 在被积函数有奇性时直接用常规求积公式
原因:变分问题的被积函数常在端点或解的特殊点处有奇性(最速降线在起点的
解决:先做奇性分析(找出发散点的位置与阶数),再选择处理方式:变量替换(把积分变量移到使被积函数光滑的那一侧,如实验二用
错误 3 · 用同一分辨率的结果判断离散解是否收敛
原因:直接离散化的误差依赖网格尺寸,单次计算只能给出「当前网格下的近似」,无法区分「已经收敛」与「恰好在这一分辨率下看起来合理」。本节实验二的数据给出警示:
解决:至少用两到三级网格计算,报告误差随网格的变化与估计的收敛阶(用
错误 4 · 混淆弱解与经典解,或忽略边界条件的作用
原因:弱形式只要求一阶(弱)导数可积,因此弱解可能有拐角、kink,甚至导数无界(本节实验二的
解决:明确所用的是弱解还是经典解,报告中说明解的正则性(在哪些点可能不光滑)。求导数时事先判断解的光滑程度:需要二阶导数时先做后处理(例如用导数恢复技术把分段常数导数光滑化),或者改用混合元方法把导数本身作为未知量求解。边界条件的处理逐条核对:Dirichlet 条件体现在试探空间与试验空间的取法上,Neumann 与 Robin 条件体现在弱形式的边界项上(当边界项不为零时把它移到右端作为载荷的一部分)。