表达式膨胀 · 符号计算的阿喀琉斯之踵
符号计算最大的软肋,是表达式会爆炸式增长——一个 n×n 符号行列式有 n! 项,中间结果能撑到几百兆字节,把内存和时间一起吃光。这一章看膨胀怎么发生、为什么它是「精确」的必然代价,以及能怎么缓解。
作者 David更新于 第 16 篇(共 18 篇)
表达式膨胀 · 符号计算的阿喀琉斯之踵
前面一路见识了符号计算的威力:精确、给公式、能证明。但它有一个致命软肋,强大到能让一个看起来不大的问题彻底卡死。这个软肋叫表达式膨胀(expression swell),是每个认真用符号计算的人都会撞上的墙。
眼见为实: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)项数正好是 ——阶乘增长。 才 120 项还好,但往上: 就是 项, 是 项。一个「才 15 行」的矩阵,它的符号行列式就有上万亿项,任何计算机都存不下。 而同样一个 的数值矩阵,求行列式眨眼就完。
更阴险的:中间结果的膨胀
上面是最终结果就很大。更常见、也更阴险的是中间表达式膨胀——最终答案可能不大,但算的过程中,中间表达式会先膨胀成庞然大物:
为什么膨胀是「精确」的必然代价
膨胀不是实现得不好,而是**「精确」这个承诺的直接后果**:
怎么缓解
膨胀无法根除,但有一批经验手法能拖住它:
- 别急着展开:保持因式分解 / 未展开的紧凑形式(
factor后的 只占几字节,expand后是 101 项)。能不expand就不expand。 - 早化简、勤化简:在中间步骤及时
cancel、simplify,别让垃圾项滚雪球。 - 选对变量顺序 / 单项序:Gröbner 基对变量排序极敏感,换个序可能从「算不动」变「秒出」。
- 该放手就放手:如果符号结果注定是天文数字的表达式,问问自己是不是真的需要那个公式——很多时候你要的只是某些点的数值,那就转数值。
最后这一条最重要,它把我们引向下一章的主题。
小结与下一章
表达式膨胀是符号计算的阿喀琉斯之踵: 符号行列式有 项(阶乘爆炸),更阴险的是中间表达式膨胀——输入输出都不大,中间却撑爆内存(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