表达式膨胀 · 符号计算的阿喀琉斯之踵

符号计算最大的软肋,是表达式会爆炸式增长——一个 n×n 符号行列式有 n! 项,中间结果能撑到几百兆字节,把内存和时间一起吃光。这一章看膨胀怎么发生、为什么它是「精确」的必然代价,以及能怎么缓解。

作者 David更新于 第 16 篇(共 18 篇)

表达式膨胀 · 符号计算的阿喀琉斯之踵

前面一路见识了符号计算的威力:精确、给公式、能证明。但它有一个致命软肋,强大到能让一个看起来不大的问题彻底卡死。这个软肋叫表达式膨胀(expression swell),是每个认真用符号计算的人都会撞上的墙。

眼见为实:n! 的爆炸

看一个触目惊心的例子——符号矩阵的行列式。给一个 n×nn\times n、元素全是变量的矩阵求行列式,展开后有多少项?

from sympy import *

for n in [3, 4, 5]:
    M = Matrix(n, n, symbols(f'a0:{n*n}'))
    terms = len(M.det().expand().args)
    print(f'{n}x{n} 行列式展开后有 {terms} 项   (n! = {factorial(n)})')
3x3 行列式展开后有 6 项     (n! = 6)
4x4 行列式展开后有 24 项    (n! = 24)
5x5 行列式展开后有 120 项   (n! = 120)

项数正好是 n!n!——阶乘增长。5×55\times5 才 120 项还好,但往上:10×1010\times10 就是 10!=3 628 80010!=3\,628\,800 项,15×1515\times15 是 15!≈1.3×101215!\approx1.3\times10^{12} 项。一个「才 15 行」的矩阵,它的符号行列式就有上万亿项,任何计算机都存不下。 而同样一个 15×1515\times15 的数值矩阵,求行列式眨眼就完。

更阴险的:中间结果的膨胀

上面是最终结果就很大。更常见、也更阴险的是中间表达式膨胀——最终答案可能不大,但算的过程中,中间表达式会先膨胀成庞然大物:

一个符号计算,输入很小、输出也可能很小,但中间某一步的表达式能撑到几百兆字节,把内存吃光、把 CPU 拖死。 典型的重灾区:

  • Gröbner 基:最坏情况双指数复杂度,中间多项式的系数和项数可以疯长。
  • 符号矩阵求逆 / 特征值:大矩阵的符号逆,每个元素都是一个巨大的分式。
  • 反复代入、积分、化简:每一步都可能让树长大一圈,层层叠加。

这就是为什么符号计算「说不准」:同样一行 simplify 或 integrate,对某个式子瞬间返回,对另一个结构相近的式子却跑上几小时、或直接耗尽内存。符号计算的运行时间和内存,极难预测——这是它与数值计算「时间可控、内存固定」最刺眼的差别。

为什么膨胀是「精确」的必然代价

膨胀不是实现得不好,而是**「精确」这个承诺的直接后果**:

数值计算能「保持小」,靠的是不停地四舍五入——每个数永远压缩成固定的 16 位,再复杂的中间量也被强行截断成一个浮点数。符号计算做不到,因为它承诺精确:它不能丢弃任何一项、不能把 13\frac{1}{3} 近似成 0.3330.333、不能把一个 120 项的式子「抹平」——那样就不精确了。

于是矛盾摆在面前:精确 ⟺ 保留全部信息 ⟺ 表达式可能无限长大。 数值用「可控的误差」换来了「可控的规模」;符号用「爆炸的规模」换来了「零误差」。天下没有免费的午餐——膨胀,是精确性开出的账单。

怎么缓解

膨胀无法根除,但有一批经验手法能拖住它:

  • 别急着展开:保持因式分解 / 未展开的紧凑形式(factor 后的 (x+1)100(x+1)^{100} 只占几字节,expand 后是 101 项)。能不 expand 就不 expand。
  • 早化简、勤化简:在中间步骤及时 cancel、simplify,别让垃圾项滚雪球。
  • 选对变量顺序 / 单项序:Gröbner 基对变量排序极敏感,换个序可能从「算不动」变「秒出」。
  • 该放手就放手:如果符号结果注定是天文数字的表达式,问问自己是不是真的需要那个公式——很多时候你要的只是某些点的数值,那就转数值。

最后这一条最重要,它把我们引向下一章的主题。

小结与下一章

表达式膨胀是符号计算的阿喀琉斯之踵:n×nn\times n 符号行列式有 n!n! 项(阶乘爆炸),更阴险的是中间表达式膨胀——输入输出都不大,中间却撑爆内存(Gröbner、符号求逆是重灾区),导致符号计算运行时间和内存极难预测。根源是精确性的必然代价:不能丢弃任何信息,就可能无限长大。缓解靠不急着展开、勤化简、选好序,以及——该转数值时就转数值。

膨胀这一课,逼我们正视一个根本问题:符号计算和数值计算,到底该用哪个? 它们是对手,更是搭档。下一章给出决策框架,以及一个把两者优点合一的漂亮手法——lambdify。👉 符号 vs 数值:何时用哪个。

参考文献

  • [1] Richard Fateman.「Why Computer Algebra Systems Can't Solve Physics Problems.」—— 表达式膨胀的实战之痛。
  • [2] von zur Gathen & Gerhard.《Modern Computer Algebra》—— 中间膨胀与复杂度分析。
  • [3] SymPy 文档.「Performance / Simplification.」https://docs.sympy.org/latest/explanation/gotchas.html

本页目录