自动化简的边界 · 为什么 sin²+cos² 不自动等于 1

x+x 自动变成 2x,sin²+cos² 却不自动变成 1——这不是 SymPy 偷懒,而是一个深刻的事实:判断一个表达式是否等于零,在一般情况下是「不可判定」的。这一章讲清自动化简的边界、「最简」的模糊,以及为什么 simplify 只能是一个尽力而为的启发式操作。

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

自动化简的边界 · 为什么 sin²+cos² 不自动等于 1

新手常有个期待:输入一个式子,CAS 就该「自动化到最简」。现实却微妙得多——有些化简自动发生,有些死活不发生。理解这条边界在哪、为什么在那,是用好符号计算的关键,也是我们第一次触到「计算的本质」。

有些化简,自动发生

SymPy 在你构造表达式的同时,就会做一批廉价、且永远正确的化简:

from sympy import *
x = symbols('x')

print(x + x)                  # 2*x
print(2*x + 3*x)              # 5*x
print(x / x)                  # 1
print(sqrt(8) - 2*sqrt(2))    # 0     —— √8 规范成 2√2,于是相减为 0
print(exp(x) * exp(-x))       # 1     —— 同底幂相乘,指数相加为 0

这些叫自动化简(automatic simplification)。它们有个共同点:又快又无脑地对——合并同类项、约分、同底幂相加,这些规则任何时候都成立,而且代价极低。SymPy 用它们把表达式维持在一个规范形式(canonical form),好让 x+xx+x 和 2x2x 被认成同一棵树。

有些化简,死活不自动发生

但下面这些,SymPy 不会主动帮你做:

print((x + 1)**2)               # (x + 1)**2     —— 不自动展开
print(sin(x)**2 + cos(x)**2)    # sin(x)**2 + cos(x)**2   —— 不自动等于 1
print(log(x**2))                # log(x**2)      —— 不自动变 2*log(x)

sin⁡2x+cos⁡2x\sin^2x+\cos^2x 明明等于 11,为什么不自动化简?两个原因,一浅一深。

浅层原因:「最简」根本没有定义

先看 (x+1)2(x+1)^2。它不自动展开成 x2+2x+1x^2+2x+1——可反过来,x2+2x+1x^2+2x+1 也不自动因式分解成 (x+1)2(x+1)^2。哪个更简单?

「最简形式」是个模糊、甚至自相矛盾的概念。 (x+1)2(x+1)^2 和 x2+2x+1x^2+2x+1 哪个简单?看你要干什么:想求根,因式分解的 (x+1)2(x+1)^2 更好;想求导或看各项系数,展开的 x2+2x+1x^2+2x+1 更方便。没有放之四海皆「最简」的形式。所以 CAS 不敢替你擅自决定朝哪个方向化——它把选择权交给你,用有明确方向的操作:expand(展开)、factor(因式分解)、collect(合并)、trigsimp(三角化简)……

print(expand((x + 1)**2))        # x**2 + 2*x + 1
print(factor(x**2 + 2*x + 1))    # (x + 1)**2

你要展开还是因式分解?你说了算,而不是系统猜。

深层原因:判断「是否等于零」是不可判定的

sin⁡2x+cos⁡2x=1\sin^2x+\cos^2x=1 需要一条三角恒等式——这不像「合并同类项」那样无脑,得搜索该用哪条恒等式。而搜索是昂贵的:如果每构造一个式子都自动跑一遍所有可能的恒等式,符号计算会慢到无法使用。所以这类化简被挪出「自动」范畴,交给一个你显式调用的操作:

print(simplify(sin(x)**2 + cos(x)**2))   # 1   —— 你得开口问

但代价高只是表面。真正深刻的原因是:

一个「完美的化简算法」根本不存在——这是数学上被证明的不可能。 化简的核心是判断「两个式子是否相等」,等价于判断「它们的差是否恒等于零」。而 Richardson 定理(1968) 证明:对于包含 π\pi、指数、对数、sin⁡\sin、绝对值等的一大类表达式,判断一个表达式是否恒等于零,是不可判定的——不存在任何算法,能对所有这类式子给出「是零 / 非零」的正确回答。

这意味着:没有、也不可能有一个 simplify 能把任何式子都化到真正的最简。它只能是一个启发式:试一堆常见变换,挑个看起来最简的返回——大多数时候好用,但有时化不动、甚至越化越复杂。这不是 SymPy 或 Mathematica 不够聪明,而是问题本身就没有完美解。我们在上一章说精确数消灭了误差;这里则看到,精确的世界自有它无法逾越的边界——而这条边界,直指「计算」的极限。

所以:用有方向的工具,别指望「一键最简」

认清边界后,实践策略很清楚:别指望一个万能的 simplify,而是根据目的选对准的工具。 常用的一批:

你想要用例子
乘开括号expand(x+1)2→x2+2x+1(x+1)^2 \to x^2+2x+1
因式分解factorx2−1→(x−1)(x+1)x^2-1 \to (x-1)(x+1)
按某变量合并collect整理成 xx 的多项式
三角化简trigsimpsin⁡2+cos⁡2→1\sin^2+\cos^2 \to 1
拆对数expand_loglog⁡(x2)→2log⁡x\log(x^2)\to 2\log x(需 x>0x>0)
通用启发式simplify不确定时的兜底,可能慢

simplify 是「不知道用啥就先试它」的兜底,但生产代码里更推荐用有明确方向的操作——它们快、可预期,不会给你一个「我也不知道为啥长这样」的结果。

顺带一提 log(x**2) 那个:它不自动变 2log⁡x2\log x,是因为当 x<0x<0 时这一步并不成立(log⁡\log 的定义域)!CAS 不敢在不知道 xx 正负时乱化——这也是它「谨慎」的一面。你可以用假设告诉它 x 为正,它就放心化了(假设系统是符号计算的重要一环)。

小结与下一站

自动化简只做廉价且永远正确的规范化(x+x→2xx+x\to2x、约分、同底幂);不做有方向的变换((x+1)2(x+1)^2 不自动展开)和需要搜索恒等式的化简(sin⁡2+cos⁡2\sin^2+\cos^2 不自动等于 1)。深层原因是 Richardson 定理:判断表达式是否恒为零不可判定,所以完美的 simplify 不存在,它只能是启发式。实践中用 expand/factor/trigsimp 等有方向的工具。

地基就位:表达式是树、数是精确的、化简有其边界。接下来进入内核——符号计算到底怎么「算」?下一章你会发现,无论求导、化简还是解方程,底层都是同一个动作:在树里找到一个模式,把它替换掉。👉 替换与模式匹配。

参考文献

  • [1] Daniel Richardson.「Some Undecidable Problems Involving Elementary Functions of a Real Variable.」J. Symbolic Logic, 1968. —— 零判定不可判定性。
  • [2] Joel S. Cohen.《Computer Algebra and Symbolic Computation》—— 自动化简与规范形式。
  • [3] SymPy 文档.「Simplify.」https://docs.sympy.org/latest/tutorials/intro-tutorial/simplification.html

本页目录