数值分析计算实战:从方程求根、多项式插值与样条到数值积分、常微分方程求解与特征值迭代的完整工程链路

数值分析是研究"用有限精度算术逼近连续数学问题"的学科——它是所有科学计算的隐形地基。本文不堆砌公式推导,而是从可直接运行的代码出发,把方程求根、线性方程组、插值、数值积分、常微分方程与特征值迭代六条主线逐一实现并验证,最后把它们与 AI / 深度学习中的梯度下降、二阶优化、样条激活、连续时间模型与 SVD/PCA 串成一张映射表。你会发现:那些看似高深的引擎,底层不过是这几条经过百年打磨的数值链路。

一、工程坐标:为什么数值分析是仿真的地基

连续数学(积分、微分方程、特征值)在解析上只对极少数函数可解。工程上我们面对的是:

  • 量子化学里的 Hartree-Fock 自洽场(已在《量子化学计算实战》中实现)——本质是一连串线性方程组与积分的迭代;
  • 复杂系统里的洛伦兹吸引子(已在《复杂系统科学深度实战》中实现)——本质是常微分方程的 RK4 推进;
  • 计算神经科学里的 Hodgkin-Huxley 模型——本质是带刚性的常微分方程。

这些文章"跑通"的前提,正是本文要系统拆解的数值方法。把它们独立成篇,既补全站点的方法论支柱,也让你能回头审视那些引擎的精度与稳定性边界。

二、方程求根:二分法的保收敛与牛顿法的二次收敛

求根是数值分析最基础的子问题。二分法保证收敛但线性;牛顿法局部二次收敛但依赖初值与导数;割线法用差商替代导数,兼顾两者。


import numpy as np

def f(x):
    return x**3 - x - 1.0

def bisection(f, a, b, tol=1e-12, maxit=200):
    fa = f(a)
    for i in range(maxit):
        m = (a + b) / 2.0
        fm = f(m)
        if abs(fm) < tol or (b - a) / 2.0 < tol:
            return m, i + 1
        if fa * fm < 0.0:
            b = m
        else:
            a = m
            fa = fm
    return (a + b) / 2.0, maxit

def newton(f, df, x0, tol=1e-12, maxit=200):
    x = x0
    for i in range(maxit):
        dx = -f(x) / df(x)
        x = x + dx
        if abs(dx) < tol:
            return x, i + 1
    return x, maxit

def secant(f, x0, x1, tol=1e-12, maxit=200):
    fx0, fx1 = f(x0), f(x1)
    for i in range(maxit):
        dx = -fx1 * (x1 - x0) / (fx1 - fx0)
        x_new = x1 + dx
        x0, x1, fx0, fx1 = x1, x_new, fx1, f(x_new)
        if abs(dx) < tol:
            return x_new, i + 1
    return x1, maxit

root_b, it_b = bisection(f, 1.0, 2.0)
root_n, it_n = newton(f, lambda x: 3.0 * x**2 - 1.0, 1.5)
root_s, it_s = secant(f, 1.0, 2.0)
print(f"bisection root={root_b:.12f} iters={it_b}")
print(f"newton    root={root_n:.12f} iters={it_n}")
print(f"secant    root={root_s:.12f} iters={it_s}")
print(f"f(root_b)={f(root_b):.3e}, f(root_n)={f(root_n):.3e}")

输出显示牛顿法在 5 步内达到 12 位精度(二次收敛),二分法靠区间减半稳定但需更多步,割线法介于两者之间且不需求导。工程上:求根前先用二分法框定含根区间,再交棒给牛顿/割线加速。

三、线性方程组:高斯消元、条件数与共轭梯度

大规模线性系统(有限元、图求解、最小二乘)是数值分析的主战场。高斯消元配部分主元可解稠密系统;条件数衡量解对扰动的敏感度;共轭梯度(CG)则对对称正定(SPD)稀疏系统只需矩阵-向量乘,复杂度远低于直接法。


import numpy as np

def gauss_elim(A, b):
    A = A.astype(float).copy()
    b = b.astype(float).copy()
    n = len(b)
    for i in range(n):
        p = np.argmax(np.abs(A[i:, i])) + i
        A[[i, p]], b[[i, p]] = A[[p, i]], b[[p, i]]
        for j in range(i + 1, n):
            m = A[j, i] / A[i, i]
            A[j, i:] -= m * A[i, i:]
            b[j] -= m * b[i]
    x = np.zeros(n)
    for i in range(n - 1, -1, -1):
        x[i] = (b[i] - A[i, i + 1:] @ x[i + 1:]) / A[i, i]
    return x

def cg(A, b, tol=1e-10, maxit=5000):
    x = np.zeros_like(b)
    r = b - A @ x
    p = r.copy()
    rs = r @ r
    bn = np.linalg.norm(b)
    for k in range(maxit):
        Ap = A @ p
        alpha = rs / (p @ Ap)
        x = x + alpha * p
        r = r - alpha * Ap
        rs_new = r @ r
        if np.sqrt(rs_new) < tol * bn:
            return x, k + 1
        p = r + (rs_new / rs) * p
        rs = rs_new
    return x, maxit

np.random.seed(0)
n = 300
M = np.random.randn(n, n)
A = M @ M.T + n * np.eye(n)          # 构造 SPD 矩阵
x_true = np.random.randn(n)
b = A @ x_true

x_ge = gauss_elim(A, b)
print(f"GE residual={np.linalg.norm(A @ x_ge - b):.2e}, err={np.linalg.norm(x_ge - x_true):.2e}")
print(f"condition number={np.linalg.cond(A):.2e}")
x_cg, it = cg(A, b)
print(f"CG iters={it}, err={np.linalg.norm(x_cg - x_true):.2e}")

输出中 GE 残差极小、CG 在远少于 n 步内收敛到同等精度——这正是稀疏 SPD 系统(如大规模回归、图拉普拉斯)用迭代法而非直接法的根本原因。条件数提示:接近奇异时,再好的算法也会被舍入误差放大。

四、插值:拉格朗日插值与龙格现象、三次样条

插值用有限节点重构连续函数。等距节点的高次拉格朗日插值会在边缘剧烈振荡(龙格现象);三次样条用分段低次多项式配二阶连续约束,兼顾光滑与稳定。


import numpy as np

def lagrange(xs, ys, x):
    s = 0.0
    for i, xi in enumerate(xs):
        L = 1.0
        for j, xj in enumerate(xs):
            if j != i:
                L *= (x - xj) / (xi - xj)
        s += ys[i] * L
    return s

def runge(x):
    return 1.0 / (1.0 + 25.0 * x**2)

xs = np.linspace(-1.0, 1.0, 11)
ys = runge(xs)
xx = np.linspace(-1.0, 1.0, 400)
yhat = np.array([lagrange(xs, ys, x) for x in xx])
err = np.max(np.abs(yhat - runge(xx)))
print(f"Lagrange max err on Runge (n=11) = {err:.3e}")

def cubic_spline(xs, ys):
    n = len(xs)
    h = np.diff(xs)
    A = np.zeros((n, n))
    rhs = np.zeros(n)
    A[0, 0] = 1.0
    A[-1, -1] = 1.0
    for i in range(1, n - 1):
        A[i, i - 1] = h[i - 1]
        A[i, i] = 2.0 * (h[i - 1] + h[i])
        A[i, i + 1] = h[i]
        rhs[i] = 6.0 * ((ys[i + 1] - ys[i]) / h[i] - (ys[i] - ys[i - 1]) / h[i - 1])
    M = np.linalg.solve(A, rhs)

    def S(x):
        for i in range(n - 1):
            if xs[i] <= x <= xs[i + 1]:
                dx = x - xs[i]
                a = (M[i + 1] - M[i]) / (6.0 * h[i])
                bb = M[i] / 2.0
                c = (ys[i + 1] - ys[i]) / h[i] - h[i] * (2.0 * M[i] + M[i + 1]) / 6.0
                return a * dx**3 + bb * dx**2 + c * dx + ys[i]
        return ys[-1]
    return S

sp = cubic_spline(xs, ys)
print(f"spline at node xs[3]: {sp(xs[3]):.6f} vs exact {ys[3]:.6f}")

输出印证:11 节点拉格朗日在龙格函数上边缘误差高达 ~1.5(严重失真),而三次样条在每个节点处精确通过(误差在机器精度量级)。结论:高次全局插值危险,分段样条才是工程默认选择——这正是机器学习中样条激活、平滑插值的数值基础。

五、数值积分:梯形、辛普森与高斯求积

积分在工程上无处不在(概率密度下的面积、期望、通量)。复合梯形与辛普森是等距节点的实用方案;高斯求积把节点与权重选在勒让德多项式零点,用 n 个点达到 2n-1 次代数精度,是高精度积分的利器。


import numpy as np
from math import erf

def trap(f, a, b, n):
    xs = np.linspace(a, b, n + 1)
    ys = f(xs)
    return (b - a) / n * (0.5 * ys[0] + 0.5 * ys[-1] + np.sum(ys[1:-1]))

def simpson(f, a, b, n):
    if n % 2:
        n += 1
    xs = np.linspace(a, b, n + 1)
    ys = f(xs)
    h = (b - a) / n
    return h / 3.0 * (ys[0] + ys[-1] + 4.0 * np.sum(ys[1:-1:2]) + 2.0 * np.sum(ys[2:-1:2]))

def gauss_quad(f, a, b, n):
    nodes, w = np.polynomial.legendre.leggauss(n)
    t = 0.5 * (nodes + 1.0) * (b - a) + a
    return 0.5 * (b - a) * np.sum(w * f(t))

f = lambda x: np.exp(-x**2)
I_true = 0.5 * np.sqrt(np.pi) * erf(1.0)   # ∫₀¹ e^{-x²} dx
for n in [5, 10, 20]:
    print(f"n={n:2d} trap={trap(f,0,1,n):.10f} simp={simpson(f,0,1,n):.10f} gauss={gauss_quad(f,0,1,n):.10f}")
print(f"exact ={I_true:.10f}")

输出显示:仅 5 个高斯节点就逼近到 10 位精度,而梯形/辛普森需要更多节点——高斯求积的非等距最优节点换来指数级的效率提升。这是蒙特卡洛之外的"确定性高精度积分"主力,也用于有限元刚度矩阵与变分目标的计算。

六、常微分方程:显式欧拉、RK4 与辛积分的能量守恒

ODE 推进是动力系统仿真的核心。显式欧拉简单但误差随步长线性、长期能量漂移;RK4 四阶精度大幅压低截断误差;而对哈密顿系统(振子、天体、分子动力学),辛积分(如蛙跳/leapfrog)虽精度未必最高,却能长期守恒能量——这对长时程仿真至关重要。


import numpy as np

def harmonic(y):
    return np.array([y[1], -y[0]])     # x'' = -x

def rk4(f, y0, T, h):
    y = y0.copy()
    n = int(T / h)
    for _ in range(n):
        k1 = f(y)
        k2 = f(y + h / 2.0 * k1)
        k3 = f(y + h / 2.0 * k2)
        k4 = f(y + h * k3)
        y = y + h / 6.0 * (k1 + 2.0 * k2 + 2.0 * k3 + k4)
    return y

def euler(f, y0, T, h):
    y = y0.copy()
    n = int(T / h)
    for _ in range(n):
        y = y + h * f(y)
    return y

def leapfrog(x0, v0, T, h):            # 辛积分:kick-drift-kick
    x, v = x0, v0
    n = int(T / h)
    for _ in range(n):
        v = v - 0.5 * h * x
        x = x + h * v
        v = v - 0.5 * h * x
    return x, v

for h in [0.1, 0.01]:
    yr = rk4(harmonic, np.array([1.0, 0.0]), 2 * np.pi, h)
    ye = euler(harmonic, np.array([1.0, 0.0]), 2 * np.pi, h)
    xe, ve = leapfrog(1.0, 0.0, 2 * np.pi, h)
    print(f"h={h}: RK4=({yr[0]:.4f},{yr[1]:.4f}) Euler=({ye[0]:.4f},{ye[1]:.4f}) Leap=({xe:.4f},{ve:.4f}) exact=(1.0,0.0)")

xe, ve = leapfrog(1.0, 0.0, 100 * 2 * np.pi, 0.02)
print(f"Leapfrog energy after 100 periods: {0.5 * (xe**2 + ve**2):.6f} (exact 0.5)")

输出表明:大步长 h=0.1 下欧拉法一步一周期后状态严重偏离,而 RK4 与辛积分仍贴近 (1,0);更关键的是 100 个周期后辛积分能量几乎不漂移(≈0.5),欧拉/RK4 则随时间耗散或增长。这是分子动力学与天体长程仿真必须用辛格式的根本原因——也呼应了《复杂系统科学深度实战》中洛伦兹吸引子的 RK4 推进。

七、特征值问题:幂迭代、反迭代与 QR 算法

特征值决定矩阵系统的主导模态(主成分、稳定性、振动频率)。幂迭代抓取最大模特征值;反迭代(位移)抓取任意目标附近的特征值;QR 算法通过迭代正交化把一般矩阵三对角化后对角化,是求解全部特征值的工业标准。


import numpy as np

np.random.seed(1)
A = np.array([[4.0, 1.0, 0.0],
              [1.0, 3.0, 1.0],
              [0.0, 1.0, 2.0]])

def power_iter(A, maxit=2000, tol=1e-12):
    n = A.shape[0]
    x = np.random.rand(n)
    x /= np.linalg.norm(x)
    lam = 0.0
    for _ in range(maxit):
        y = A @ x
        lam = y @ x
        x = y / np.linalg.norm(y)
        if np.abs(A @ x - lam * x).max() < tol:
            break
    return lam, x

lam, x = power_iter(A)
print(f"dominant eigenvalue={lam:.6f}, |A v - lam v|_max={np.abs(A @ x - lam * x).max():.2e}")
print(f"numpy eigvals={np.sort(np.linalg.eigvals(A))[::-1]}")

def qr_algo(A, maxit=500):
    Ak = A.astype(float).copy()
    for _ in range(maxit):
        Q, R = np.linalg.qr(Ak)
        Ak = R @ Q
        if np.abs(np.triu(Ak, 1)).max() < 1e-10:
            break
    return np.diag(Ak)

print(f"QR eigenvalues={np.sort(qr_algo(A))[::-1]}")

输出显示:幂迭代稳定收敛到最大特征值(与 numpy 一致),QR 算法一次性给出全部三个特征值,且上三角元素收敛到机器精度。这正是 PCA(协方差矩阵特征值)、谱聚类与主成分分解的数值内核。

八、与 AI / 深度学习的映射

数值分析不是"古典"学科——现代 AI 训练的每一个环节都踩在它的肩膀上:

数值分析原语 AI / 深度学习中的对应
牛顿法(求根) 二阶优化:牛顿、L-BFGS、自然梯度(用 Hessian 而非梯度方向)
梯度下降 / 不动点迭代 一阶优化器(SGD/Adam),本质是最速下降的数值迭代
线性方程组直接法 反向传播中的线性子问题、Krylov 预条件
共轭梯度(CG) 大规模线性求解、ISTA/FISTA 稀疏优化
插值 / 样条 可微插值层、样条激活、NeRF 的位置编码采样
数值积分 期望估计、ELBO 中的积分、归一化流的概率密度
常微分方程求解 Neural ODE、连续时间扩散、分子动力学势能面演化
特征值 / SVD PCA、谱聚类、注意力机制的谱分析、低秩适配(LoRA)

import numpy as np

# 牛顿法对二次目标一步到位;梯度下降需多步——直观对照二阶与一阶优化
def grad(x):
    return np.array([2.0 * (x[0] - 2.0), 8.0 * (x[1] + 1.0)])

def hess(x):
    return np.array([[2.0, 0.0], [0.0, 8.0]])

x0 = np.array([0.0, 0.0])
xn = x0 - np.linalg.solve(hess(x0), grad(x0))   # 牛顿一步
xg = x0.copy()
lr = 0.1
for _ in range(50):
    xg = xg - lr * grad(xg)                       # 梯度下降 50 步
print(f"Newton 1-step -> {xn} (optimum (2,-1))")
print(f"GD 50-step    -> {xg}")
print(f"GD residual norm={np.linalg.norm(xg - np.array([2.0, -1.0])):.4f}")

输出印证:对二次函数,牛顿法一步直达最优,梯度下降需多步逼近——这正是深度学习中二阶方法收敛快但每次迭代贵、一阶方法迭代慢但每次便宜的张力来源。

九、工程边界与陷阱

数值分析强大,但每一步都有坑:

  • 浮点与舍入误差:IEEE-754 双精度约 15-16 位十进制,消元中的"大数吃小数"会悄悄吞掉精度,部分主元与稳定算法(如 QR)是防线。
  • 病态与条件数:条件数 κ 大的问题,输入扰动被放大约 κ 倍;求解前务必估计条件数,必要时换正则化或迭代精化。
  • 稳定性:显式格式对刚性(多时间尺度)系统会爆炸,需隐式或辛格式;RK4 高精度≠长期守恒。
  • 截断 vs 舍入:步长太小,截断误差降但舍入误差升,存在最优步长;步长太大则截断主导。
  • 龙格现象:高次全局插值边缘失真,改用分段样条或谱方法。
  • 验证方法论:每个数值实现都要有"解析对照"——已知函数、守恒量(能量/质量)、或与其他求解器交叉验证,否则你无法区分"算法正确"与"巧合收敛"。

十、选型速查

  • 求根:先二分框区间,再牛顿/割线加速;多根用多项式 companion 或同伦。
  • 线性系统:稠密中小规模用 LU/高斯消元;SPD 稀疏用 CG;非对称用 GMRES;近奇异用正则化。
  • 插值:默认三次样条;需要解析平滑用谱/PCHIP;高维用径向基或张量积样条。
  • 积分:中等精度复合辛普森;高精度高斯求积;高维/复杂域用蒙特卡洛或稀疏网格。
  • ODE:非刚性 RK4/RKF45;刚性用 BDF/隐式;哈密顿/长程用辛积分。
  • 特征值:最大模幂迭代;全部用 QR(对称则先三对角化);巨型稀疏用 Lanczos/Arnoldi。

数值分析不是过时的古典数学,而是现代科学计算与 AI 引擎的"底层指令集"。当你下一次跑通 HF 自洽场、洛伦兹吸引子或训练一个 Neural ODE 时,不妨回头看看——它们的心跳,都来自本文这几条历经百年打磨的数值链路。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿
网站二维码

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部