假设

本页概述了 SymPy 中的核心假设系统。它解释了核心假设系统是什么,如何使用假设系统以及不同的假设谓词的含义。

注意

本页描述了核心假设系统,也常被称为“旧假设”系统。还存在一个“新假设”系统,将在其他地方进行描述。请注意,这里描述的系统实际上是 SymPy 中广泛使用的系统。“新假设”系统实际上在 SymPy 中还没有任何应用,并且不会删除“旧假设”系统。在撰写本文时(SymPy 1.7),仍然建议用户使用旧假设系统。

首先,我们考虑对诸如 \(2\)\(-2\) 之类的具体整数的平方进行平方根时会发生什么

>>> from sympy import sqrt
>>> sqrt(2**2)
2
>>> sqrt((-2)**2)
2
>>> x = 2
>>> sqrt(x**2)
2
>>> sqrt(x**2) == x
True
>>> y = -2
>>> sqrt(y**2) == y
False
>>> sqrt(y**2) == -y
True

这些示例表明,对于正数 \(x\),我们有 \(\sqrt{x^2} = x\),而对于负数,我们则有 \(\sqrt{x^2} = -x\)。这似乎很明显,但在使用符号而不是显式数字时,情况可能会更加令人惊讶。例如

>>> from sympy import Symbol, simplify
>>> x = Symbol('x')
>>> sqrt(x**2)
sqrt(x**2)

看起来这应该化简为 x,但即使使用 simplify() 也不行

>>> simplify(sqrt(x**2))
sqrt(x**2)

这是因为 SymPy 拒绝简化此表达式,除非简化对于 x所有可能值都有效。默认情况下,符号 x 被认为仅表示类似于任意复数的东西,而这里明显的简化仅对正实数有效。由于 x 未知是正数还是实数,因此无法简化此表达式。

我们可以在创建符号时告诉 SymPy 一个符号表示正实数,然后简化将自动发生。

>>> y = Symbol('y', positive=True)
>>> sqrt(y**2)
y

这就是 SymPy 中“假设”的含义。如果符号 y 是用 positive=True 创建的,那么 SymPy 将假设它表示一个正实数,而不是任意复数或可能是无限的数。这个假设可以使简化表达式成为可能,或者可能允许其他操作起作用。在创建符号时,尽可能精确地说明符号的假设通常是一个好主意。

(旧的)假设系统

假设系统有两个方面。第一方面是,我们可以在创建符号时声明对符号的假设。另一方面是,我们可以使用相应的 is_* 属性查询任何表达式的假设。例如

>>> x = Symbol('x', positive=True)
>>> x.is_positive
True

我们可以查询任何表达式的假设,而不仅仅是一个符号

>>> x = Symbol('x', positive=True)
>>> expr = 1 + x**2
>>> expr
x**2 + 1
>>> expr.is_positive
True
>>> expr.is_negative
False

在假设查询中给出的值使用三值“模糊”逻辑。任何查询都可以返回 TrueFalseNone,其中 None 应该被解释为结果未知

>>> x = Symbol('x')
>>> y = Symbol('y', positive=True)
>>> z = Symbol('z', negative=True)
>>> print(x.is_positive)
None
>>> print(y.is_positive)
True
>>> print(z.is_positive)
False

注意

在上面的示例中,我们需要使用 print,因为特殊值 None 在 Python 解释器中默认不显示。

假设查询可能给出 None 的原因有很多。查询可能不可知,如上面的 x 的情况。由于 x 没有声明任何假设,因此它大致表示一个任意复数。一个任意复数可能是一个正实数,但也可能不是。如果没有进一步的信息,无法解析查询 x.is_positive

假设查询可能给出 None 的另一个原因是,在许多情况下,确定一个表达式是否例如为正数的问题是不可判定的。这意味着不存在用于一般性地回答查询的算法。在某些情况下,算法或至少简单的检查是可能的,但尚未实现,尽管可以添加到 SymPy 中。

假设查询可能给出 None 的最后一个原因是,假设系统并没有尽力回答复杂的查询。该系统旨在快速,并使用简单的启发式方法来推断 TrueFalse 答案在常见情况下。例如,任何正项之和都是正的,因此

>>> from sympy import symbols
>>> x, y = symbols('x, y', positive=True)
>>> expr = x + y
>>> expr
x + y
>>> expr.is_positive
True

最后一个示例特别简单,因此假设系统能够给出明确的答案。如果该和涉及正项和负项的混合,则它将是一个更难的查询。

>>> x = Symbol('x', real=True)
>>> expr = 1 + (x - 2)**2
>>> expr
(x - 2)**2 + 1
>>> expr.is_positive
True
>>> expr2 = expr.expand()
>>> expr2
x**2 - 4*x + 5
>>> print(expr2.is_positive)
None

理想情况下,最后一个示例将给出 True 而不是 None,因为表达式对于 x 的任何实数值始终为正(并且 x 被假定为实数)。但是,假设系统旨在高效:预计许多更复杂的查询将无法完全解决。这是因为假设查询主要在 SymPy 内部用作低级计算的一部分。使系统更加全面会减慢 SymPy 的速度。

请注意,在模糊逻辑中,给出不确定的结果 None 从来不是矛盾。如果在解析查询时能够推断出明确的 TrueFalse 结果,那么这比返回 None 更好。但是,结果为 None 不是错误。任何使用假设系统的代码都需要准备好处理任何查询的所有三种情况,并且不应假定总是会给出明确的答案。

假设系统不仅仅用于符号或复杂表达式。它也可以用于简单的 SymPy 整数和其他对象。假设谓词在 Basic 的任何实例上都可用,它是大多数 SymPy 对象类别的超类。一个普通的 Python int 不是 Basic 实例,不能用于查询假设谓词。我们可以使用 sympify()S (SingletonRegistry) 将常规 Python 对象“符号化”为 SymPy 对象,然后可以使用假设系统。

>>> from sympy import S
>>> x = 2
>>> x.is_positive
Traceback (most recent call last):
...
AttributeError: 'int' object has no attribute 'is_positive'
>>> x = S(2)
>>> type(x)
<class 'sympy.core.numbers.Integer'>
>>> x.is_positive
True

陷阱:具有不同假设的符号

在 SymPy 中,可以声明两个具有不同名称的符号,它们在结构相等性下将被隐式地视为相等。

>>> x1 = Symbol('x')
>>> x2 = Symbol('x')
>>> x1
x
>>> x2
x
>>> x1 == x2
True

但是,如果符号具有不同的假设,则它们将被认为表示不同的符号。

>>> x1 = Symbol('x', positive=True)
>>> x2 = Symbol('x')
>>> x1
x
>>> x2
x
>>> x1 == x2
False

简化表达式的另一种方法是使用 posify() 函数,该函数将表达式中的所有符号替换为具有假设 positive=True 的符号(除非这与符号的任何现有假设相矛盾)。

>>> from sympy import posify, exp
>>> x = Symbol('x')
>>> expr = exp(sqrt(x**2))
>>> expr
exp(sqrt(x**2))
>>> posify(expr)
(exp(_x), {_x: x})
>>> expr2, rep = posify(expr)
>>> expr2
exp(_x)

posify() 函数返回所有符号都被替换的表达式(这可能会导致简化),以及一个字典,该字典将新符号映射到旧符号,这些旧符号可以与 subs() 一起使用。这很有用,因为否则具有新符号的表达式,这些新符号具有 positive=True 假设,将不会与旧表达式比较相等。

>>> expr2
exp(_x)
>>> expr2 == exp(x)
False
>>> expr2.subs(rep)
exp(x)
>>> expr2.subs(rep) == exp(x)
True

将假设应用于字符串输入

我们已经了解了如何在 Symbolsymbols() 中显式地设置假设。一个自然的问题是,我们可以在哪些其他情况下将假设分配给对象?

用户通常使用字符串作为 SymPy 函数的输入(尽管 SymPy 开发人员普遍认为应该避免这种做法),例如

>>> from sympy import solve
>>> solve('x**2 - 1')
[-1, 1]

在显式地创建符号时,可以分配会影响 solve() 行为的假设。

>>> x = Symbol('x', positive=True)
>>> solve(x**2 - 1)
[1]

当使用字符串输入时,SymPy 将创建表达式并隐式地创建所有符号,因此出现了如何指定假设的问题?答案是,与其依赖于隐式字符串转换,不如显式地使用 parse_expr() 函数,然后可以为符号提供假设,例如

>>> from sympy import parse_expr
>>> parse_expr('x**2 - 1')
x**2 - 1
>>> eq = parse_expr('x**2 - 1', {'x':Symbol('x', positive=True)})
>>> solve(eq)
[1]

注意

solve() 函数作为高级 API 不同寻常,因为它实际上检查任何输入符号(未知数)上的假设,并使用它来调整其输出。否则,假设系统会影响低级评估,但不会被高级 API 显式地处理。

谓词

有很多不同的谓词可以被假设为一个符号,或者可以被查询为一个表达式。在创建符号时,可以组合多个谓词。谓词使用and 逻辑地组合,因此如果一个符号是用 positive=True 以及 integer=True 声明的,那么它既是正的又是整数的。

>>> x = Symbol('x', positive=True, integer=True)
>>> x.is_positive
True
>>> x.is_integer
True

可以使用 assumptions0 属性访问符号的所有已知谓词。

>>> x.assumptions0
{'algebraic': True,
 'commutative': True,
 'complex': True,
 'extended_negative': False,
 'extended_nonnegative': True,
 'extended_nonpositive': False,
 'extended_nonzero': True,
 'extended_positive': True,
 'extended_real': True,
 'finite': True,
 'hermitian': True,
 'imaginary': False,
 'infinite': False,
 'integer': True,
 'irrational': False,
 'negative': False,
 'noninteger': False,
 'nonnegative': True,
 'nonpositive': False,
 'nonzero': True,
 'positive': True,
 'rational': True,
 'real': True,
 'transcendental': False,
 'zero': False}

我们可以看到,列出的谓词比用于创建 x 的两个谓词要多。这是因为假设系统可以从其他谓词的组合中推断出一些谓词。例如,如果一个符号用 positive=True 声明,那么可以推断它应该具有 negative=False,因为一个正数永远不可能是负数。类似地,如果一个符号是用 integer=True 创建的,那么可以推断它应该具有 rational=True,因为每个整数都是一个有理数。

下面给出了所有可能的谓词及其定义的完整表格。

(旧的)假设的假设谓词

谓词

定义

含义

commutative

交换表达式。一个 commutative 表达式在乘法下与所有其他表达式交换。如果表达式 a 具有 commutative=True,则对于任何其他表达式 b(即使 b 不是 commutative),都有 a * b == b * a。与所有其他假设谓词不同,commutative 必须始终为 TrueFalse,永远不能为 None。此外,与所有其他谓词不同,commutative 在例如 Symbol('x') 中默认为 True[commutative]

无穷大

一个无穷表达式,例如 oo-oozoo[infinite]

== !finite

有限

一个有限表达式。任何不是 infinite 的表达式都被认为是 finite[infinite]

== !infinite

厄米

厄米算符域中的一个元素。 [antihermitian]

反厄米

反厄米算符域中的一个元素。 [antihermitian]

复数

一个复数,\(z\in\mathbb{C}\)。任何形式为 \(x + iy\) 的数,其中 \(x\)\(y\)real,且 \(i = \sqrt{-1}\)。所有 complex 数都是 finite。包括所有 real 数。 [complex]

-> commutative
-> finite

代数

一个代数数,\(z\in\overline{\mathbb{Q}}\)。任何为非零多项式 \(p(z)\in\mathbb{Q}[z]\)(具有有理系数)的根的数。所有 algebraic 数都是 complex。一个 algebraic 数可以是也可以不是 real。包括所有 rational 数。 [algebraic]

-> complex

超越

一个不是代数的复数,\(z\in\mathbb{C}-\overline{\mathbb{Q}}\)。所有 transcendental 数都是 complex。一个 transcendental 数可以是也可以不是 real,但永远不能是 rational[transcendental]

== (complex & !algebraic)

扩展实数

扩展实数轴上的一个元素,\(x\in\overline{\mathbb{R}}\),其中 \(\overline{\mathbb{R}}=\mathbb{R}\cup\{-\infty,+\infty\}\)。一个 extended_real 数是 real 数或 \(\pm\infty\)。关系运算符 <<=>=> 仅为 extended_real 表达式定义。 [extended_real]

-> commutative

实数

一个实数,\(x\in\mathbb{R}\)。所有 real 数都是 finitecomplex(实数集是复数集的子集)。包括所有 rational 数。一个 real 数是 negativezeropositive[real]

-> complex
== (extended_real & finite)
== (negative | zero | positive)
-> hermitian

虚数

一个虚数,\(z\in\mathbb{I}-\{0\}\)。一个形式为 \(z=yi\) 的数,其中 \(y\) 为实数,\(y\ne 0\),且 \(i=\sqrt{-1}\)。所有 imaginary 数都是 complex,而不是 real。特别注意,zero 在 SymPy 中 \(not\) 被认为是 imaginary[imaginary]

-> complex
-> antihermitian
-> !extended_real

有理数

一个有理数,\(q\in\mathbb{Q}\)。任何形式为 \(\frac{a}{b}\) 的数,其中 \(a\)\(b\) 为整数,且 \(b \ne 0\)。所有 rational 数都是 realalgebraic。包括所有 integer 数。 [rational]

-> real
-> algebraic

无理数

一个不是有理数的实数,\(x\in\mathbb{R}-\mathbb{Q}\)[irrational]

== (real & !rational)

整数

一个整数,\(a\in\mathbb{Z}\)。所有整数都是 rational。包括 zero 和所有 primecompositeevenodd 数。 [integer]

-> rational

非整数

一个不是整数的扩展实数,\(x\in\overline{\mathbb{R}}-\mathbb{Z}\)

== (extended_real & !integer)

偶数

一个偶数,\(e\in\{2k: k\in\mathbb{Z}\}\)。所有 even 数都是 integer 数。包括 zero[parity]

-> integer
-> !odd

奇数

一个奇数,\(o\in\{2k + 1: k\in\mathbb{Z}\}\)。所有 odd 数都是 integer 数。 [parity]

-> integer
-> !even

质数

一个质数,\(p\in\mathbb{P}\)。所有 prime 数都是 positiveinteger[prime]

-> integer
-> positive

合数

一个合数,\(c\in\mathbb{N}-(\mathbb{P}\cup\{1\})\)。一个正整数,它是两个或多个质数的乘积。一个 composite 数始终是一个 positive integer,并且不是 prime[composite]

-> (integer & positive & !prime)
!composite -> (!positive | !even | prime)

数字 \(0\)。一个具有 zero=True 的表达式代表数字 0,它是一个 integer[zero]

-> even & finite
== (extended_nonnegative & extended_nonpositive)
== (nonnegative & nonpositive)

非零

一个非零实数,\(x\in\mathbb{R}-\{0\}\)。一个 nonzero 数始终是 real,且不能是 zero

-> real
== (extended_nonzero & finite)

扩展非零

一个不是零的扩展实数,\(x\in\overline{\mathbb{R}}-\{0\}\)

== (extended_real & !zero)

正数

一个正实数,\(x\in\mathbb{R}, x>0\)。所有 positive 数都是 finite,因此 oo 不是 positive[positive]

== (nonnegative & nonzero)
== (extended_positive & finite)

非负数

非负实数,\(x\in\mathbb{R}, x\ge 0\)。所有 nonnegative 数都是 finite 数,所以 oo 不是 nonnegative 数。 [正数]

== (real & !negative)
== (extended_nonnegative & finite)

负数

负实数,\(x\in\mathbb{R}, x<0\)。所有 negative 数都是 finite 数,所以 -oo 不是 negative 数。 [负数]

== (nonpositive & nonzero)
== (extended_negative & finite)

非正数

非正实数,\(x\in\mathbb{R}, x\le 0\)。所有 nonpositive 数都是 finite 数,所以 -oo 不是 nonpositive 数。 [负数]

== (real & !positive)
== (extended_nonpositive & finite)

扩展正数

扩展正实数,\(x\in\overline{\mathbb{R}}, x>0\)。扩展正数是 positiveoo[扩展实数]

== (extended_nonnegative & extended_nonzero)

扩展非负数

扩展非负实数,\(x\in\overline{\mathbb{R}}, x\ge 0\)。扩展非负数是 nonnegativeoo[扩展实数]

== (extended_real & !extended_negative)

扩展负数

扩展负实数,\(x\in\overline{\mathbb{R}}, x<0\)。扩展负数是 negative-oo[扩展实数]

== (extended_nonpositive & extended_nonzero)

扩展非正数

扩展非正实数,\(x\in\overline{\mathbb{R}}, x\le 0\)。扩展非正数是 nonpositive-oo[扩展实数]

== (extended_real & !extended_positive)

以上定义的参考资料

推论

假设系统使用推理规则推断新谓词,这些谓词超出了在创建符号时直接指定的谓词。

>>> x = Symbol('x', real=True, negative=False, zero=False)
>>> x.is_positive
True

尽管 x 没有明确声明为 positive,但可以从明确给出的谓词推断出来。具体来说,其中一个推理规则是 real == negative | zero | positive,所以如果 realTrue,并且 negativezero 都是 False,那么 positive 必须为 True

在实践中,假设推理规则意味着没有必要包含冗余谓词,例如,正实数可以简单地声明为正数。

>>> x1 = Symbol('x1', positive=True, real=True)
>>> x2 = Symbol('x2', positive=True)
>>> x1.is_real
True
>>> x2.is_real
True
>>> x1.assumptions0 == x2.assumptions0
True

组合不一致的谓词将导致错误。

>>> x = Symbol('x', commutative=False, real=True)
Traceback (most recent call last):
...
InconsistentAssumptions: {
      algebraic: False,
      commutative: False,
      complex: False,
      composite: False,
      even: False,
      extended_negative: False,
      extended_nonnegative: False,
      extended_nonpositive: False,
      extended_nonzero: False,
      extended_positive: False,
      extended_real: False,
      imaginary: False,
      integer: False,
      irrational: False,
      negative: False,
      noninteger: False,
      nonnegative: False,
      nonpositive: False,
      nonzero: False,
      odd: False,
      positive: False,
      prime: False,
      rational: False,
      real: False,
      transcendental: False,
      zero: False}, real=True

谓词的解释

虽然谓词在上面的表格中定义,但花点时间思考如何解释它们是值得的。首先,许多由谓词名称指代的概念,如“零”、“质数”、“有理数”等,在数学中都有基本意义,但也可以有更一般的意义。例如,在处理矩阵时,一个全零矩阵可能被称为“零”。假设系统中的谓词不允许任何此类概括。谓词 zero 严格保留用于普通数字 \(0\)。相反,矩阵具有一个 is_zero_matrix() 属性来实现此目的(尽管该属性严格来说不是假设系统的一部分)。

>>> from sympy import Matrix
>>> M = Matrix([[0, 0], [0, 0]])
>>> M.is_zero
False
>>> M.is_zero_matrix
True

类似地,整数有推广,如高斯整数,它们有不同的质数概念。假设系统中的 prime 谓词不包括这些,并且严格地仅指标准质数 \(\mathbb{P} = \{2, 3, 5, 7, 11, \cdots\}\)。同样,integer 仅指整数的标准概念 \(\mathbb{Z} = \{0, \pm 1, \pm 2, \cdots\}\)rational 仅指有理数的标准概念 \(\mathbb{Q}\),等等。

谓词建立了子集方案,如从复数开始的链,复数被视为实数的超集,实数又是有理数的超集,等等。子集链

\[\mathbb{Z} \subset \mathbb{Q} \subset \mathbb{R} \subset \mathbb{C}\]

对应于假设系统中的蕴含链

integer -> rational -> real -> complex

一个没有显式附加假设的“普通”符号,不知道属于这些集合中的任何一个,甚至不知道是有限的。

>>> x = Symbol('x')
>>> x.assumptions0
{'commutative': True}
>>> print(x.is_commutative)
True
>>> print(x.is_rational)
None
>>> print(x.is_complex)
None
>>> print(x.is_real)
None
>>> print(x.is_integer)
None
>>> print(x.is_finite)
None

对于 SymPy 来说,很难知道如何处理这样一个甚至不知道是有限还是复数的符号,所以通常最好为符号显式给出一些假设。SymPy 的许多部分将隐式地将这样的符号视为复数,在某些情况下,SymPy 将允许进行严格来说在给定 x 不知道是有限的情况下无效的操作。然而,从形式意义上来说,关于普通符号,我们知道的很少,这使得涉及它的操作变得困难。

定义符号的某些内容可能会产生很大的影响。例如,如果我们声明符号是一个整数,那么这意味着会有一系列其他谓词,这些谓词将有助于进一步操作。

>>> n = Symbol('n', integer=True)
>>> n.assumptions0
{'algebraic': True,
 'commutative': True,
 'complex': True,
 'extended_real': True,
 'finite': True,
 'hermitian': True,
 'imaginary': False,
 'infinite': False,
 'integer': True,
 'irrational': False,
 'noninteger': False,
 'rational': True,
 'real': True,
 'transcendental': False}

这些假设可能会导致非常显著的简化,例如 integer=True 给出

>>> from sympy import sin, pi
>>> n1 = Symbol('n1')
>>> n2 = Symbol('n2', integer=True)
>>> sin(n1 * pi)
sin(pi*n1)
>>> sin(n2 * pi)
0

将整个表达式替换为 \(0\) 是关于简化可以达到的最优效果了!

通常建议对任何符号设置尽可能多的假设,以便表达式可以尽可能地简化。一个常见的误解会导致使用 False 谓词定义符号,例如:

>>> x = Symbol('x', negative=False)
>>> print(x.is_negative)
False
>>> print(x.is_nonnegative)
None
>>> print(x.is_real)
None
>>> print(x.is_complex)
None
>>> print(x.is_finite)
None

如果意图是说 x 是一个非正实数,那么需要明确说明。在已知符号为实数的情况下,谓词 positive=False 会更有意义。

>>> x = Symbol('x', real=True, negative=False)
>>> print(x.is_negative)
False
>>> print(x.is_nonnegative)
True
>>> print(x.is_real)
True
>>> print(x.is_complex)
True
>>> print(x.is_finite)
True

声明为 Symbol('x', real=True, negative=False) 的符号等同于声明为 Symbol('x', nonnegative=True) 的符号。仅仅将符号声明为 Symbol('x', positive=False) 不会让假设系统得出太多关于它的结论,因为一个普通符号并不被认为是有限的,甚至不一定是复数。

Symbol('x', complex=True)Symbol('x', real=False) 相关的另一个困惑。通常当使用其中任何一个时,实际上都不想要它们。首先要明白,所有实数都是复数,因此用 real=True 创建的符号也将具有 complex=True,而用 complex=True 创建的符号将不会具有 real=False。如果意图是创建一个不是实数的复数,那么它应该是 Symbol('x', complex=True, real=False)。另一方面,仅仅声明 real=False 并不足以得出 complex=True 的结论,因为知道它不是实数并不告诉我们它是否有限,或者它是否与复数完全不同类型的对象。

一个普通符号的定义是不确定它是否 finite 等,但没有明确的定义它实际上应该代表什么。人们很容易把它想象成一个“任意复数,或者可能是无穷大之一”,但无法查询任意(非符号)表达式以确定它是否符合这些条件。重要的是要记住,在 SymPy 代码库中,以及可能在下游库中,可以找到许多其他类型的数学对象,它们也可能具有 commutative=True,同时与普通数字大不相同(在这种情况下,即使 SymPy 的标准无穷大也被认为是“普通”)。

默认情况下,对符号应用的唯一谓词是 commutative。我们还可以声明一个符号为 *非交换*,例如:

>>> x, y = symbols('x, y', commutative=False)
>>> z = Symbol('z')  # defaults to commutative=True
>>> x*y + y*x
x*y + y*x
>>> x*z + z*x
2*z*x

这里要注意,由于 xy 都是非交换的,xy 不交换,因此 x*y != y*x。另一方面,由于 z 是交换的,xz 交换,并且 x*z == z*x,即使 x 是非交换的。

对普通符号代表什么含义的解释尚不清楚,但对具有 commutative=False 的表达式的解释完全不清楚。这样的表达式一定不是复数、扩展实数或任何标准无穷大(即使 zoo 也是交换的)。我们几乎无法说出这样的表达式 *确实* 代表什么。

其他 is_* 属性

SymPy 中有许多属性,它们的名字以 is_ 开头,看起来与(旧的)假设系统中使用的属性类似,但实际上并不属于假设系统。其中一些具有与假设系统中类似的含义和用法,例如上面显示的 is_zero_matrix() 属性。另一个例子是集合的 is_empty 属性。

>>> from sympy import FiniteSet, Intersection
>>> S1 = FiniteSet(1, 2)
>>> S1
{1, 2}
>>> print(S1.is_empty)
False
>>> S2 = Intersection(FiniteSet(1), FiniteSet(Symbol('x')))
>>> S2
Intersection({1}, {x})
>>> print(S2.is_empty)
None

is_empty 属性给出了一个模糊布尔值,指示一个 Set 是否为空集。在 S2 的例子中,在不知道 x 是否等于 1 的情况下,不可能知道该集合是否为空,因此 S2.is_empty 给出了 None。集合的 is_empty 属性在假设系统中扮演着类似于数字的 is_zero 属性的角色:is_empty 通常仅对 EmptySet 对象为 True,但能够区分 is_empty=Falseis_empty=None 的情况仍然很有用。

虽然 is_zero_matrix()is_empty 用于与 is_zero 等假设属性类似的目的,但它们并不属于(旧的)假设系统。没有关联的推理规则连接例如 Set.is_emptySet.is_finite_set,因为推理规则是(旧的)假设系统的一部分,该系统只处理上面表格中列出的谓词。不可能用例如 zero_matrix=False 声明一个 MatrixSymbol,并且没有 SetSymbol 类,但如果有的话,它将没有用于理解像 empty=False 这样的谓词的系统。

is_zero_matrix()is_empty 属性类似于假设系统中的属性,因为它们涉及表达式的 *语义* 方面。还有大量的其他属性侧重于 *结构* 方面,例如 is_Numberis_number()is_comparable()。由于这些属性指的是表达式的结构方面,因此它们总是给出 TrueFalse,而不是一个模糊的布尔值,它也可能为 None。大写的属性,例如 is_Number,通常是 isinstance 检查的简写,例如:

>>> from sympy import Number, Rational
>>> x = Rational(1, 2)
>>> isinstance(x, Number)
True
>>> x.is_Number
True
>>> y = Symbol('y', rational=True)
>>> isinstance(y, Number)
False
>>> y.is_Number
False

Number 类是 IntegerRationalFloat 的超类,因此 Number 的任何实例都代表一个具有已知值的具体数字。声明为 rational=True 的符号,例如 y,可能代表与 x 相同的值,但它不是一个具有已知值的具体数字,因此这是一个结构上的区别,而不是语义上的区别。像 is_Number 这样的属性有时在 SymPy 中用作例如 isinstance(obj, Number) 的替代,因为它们不会出现循环导入的问题,并且检查 x.is_Number 可能比调用 isinstance 速度更快。

is_number (小写)属性与 is_Number 非常不同。 is_number 属性对于任何可以用 evalf() 数值计算为浮点复数的表达式都为 True

>>> from sympy import I
>>> expr1 = I + sqrt(2)
>>> expr1
sqrt(2) + I
>>> expr1.is_number
True
>>> expr1.evalf()
1.4142135623731 + 1.0*I
>>> x = Symbol('x')
>>> expr2 = 1 + x
>>> expr2
x + 1
>>> expr2.is_number
False
>>> expr2.evalf()
x + 1.0

检查 expr.is_number 的主要原因是预测调用 evalf() 是否会完全评估。属性 is_comparable()is_number() 类似,区别在于如果 is_comparable 返回 True,那么表达式保证会数值评估为一个实数 Float。当 a.is_comparableb.is_comparable 时,不等式 a < b 应该可以解析为类似 a.evalf() < b.evalf() 的结果。

SymPy 中所有 is_* 属性、特性和方法的完整集合非常庞大。但需要注意的是,只有上述谓词表中列出的那些才真正属于假设系统的一部分。只有这些属性参与到机制中,该机制实现了下面解释的假设系统。

实现假设处理程序

现在我们将通过一个如何实现 SymPy 符号函数的例子来讲解,以便了解旧的假设是如何在内部使用的。SymPy 已经有一个 exp 函数,该函数针对所有复数定义,但我们将定义一个 expreal 函数,该函数仅限于实数参数。

>>> from sympy import Function
>>> from sympy.core.logic import fuzzy_and, fuzzy_or
>>>
>>> class expreal(Function):
...     """exponential function E**x restricted to the extended reals"""
...
...     is_extended_nonnegative = True
...
...     @classmethod
...     def eval(cls, x):
...         # Validate the argument
...         if x.is_extended_real is False:
...             raise ValueError("non-real argument to expreal")
...         # Evaluate for special values
...         if x.is_zero:
...             return S.One
...         elif x.is_infinite:
...             if x.is_extended_negative:
...                 return S.Zero
...             elif x.is_extended_positive:
...                 return S.Infinity
...
...     @property
...     def x(self):
...         return self.args[0]
...
...     def _eval_is_finite(self):
...         return fuzzy_or([self.x.is_real, self.x.is_extended_nonpositive])
...
...     def _eval_is_algebraic(self):
...         if fuzzy_and([self.x.is_rational, self.x.is_nonzero]):
...             return False
...
...     def _eval_is_integer(self):
...         if self.x.is_zero:
...             return True
...
...     def _eval_is_zero(self):
...         return fuzzy_and([self.x.is_infinite, self.x.is_extended_negative])

方法 Function.eval 用于拾取函数的特殊值,以便我们能够返回一个不同的对象,如果它是一个简化结果。当调用 expreal(x) 时,expreal.__new__ 类方法(在超类 Function 中定义)将调用 expreal.eval(x)。如果 expreal.eval 返回的内容不是 None,那么它将被返回,而不是未评估的 expreal(x)

>>> from sympy import oo
>>> expreal(1)
expreal(1)
>>> expreal(0)
1
>>> expreal(-oo)
0
>>> expreal(oo)
oo

请注意,expreal.eval 方法没有使用 == 来比较参数。特殊值是使用假设系统来查询参数的属性进行验证的。这意味着 expreal 方法还可以评估具有匹配属性的不同形式的表达式,例如:

>>> x = Symbol('x', extended_negative=True, infinite=True)
>>> x
x
>>> expreal(x)
0

当然,假设系统只能解析有限数量的特殊值,因此大多数 eval 方法也会使用 == 检查一些特殊值,但最好检查例如 x.is_zero 而不是 x==0

另外需要注意的是,expreal.eval 方法验证了参数是实数。我们希望允许 \(\pm\infty\) 作为 expreal 的参数,因此我们检查 extended_real 而不是 real。如果参数不是扩展实数,那么我们将引发错误。

>>> expreal(I)
Traceback (most recent call last):
...
ValueError: non-real argument to expreal

重要的是,我们检查 x.is_extended_real is False 而不是 not x.is_extended_real,这意味着我们只在参数绝对不是扩展实数时才拒绝它:如果 x.is_extended_real 返回 None,那么参数将不会被拒绝。允许 x.is_extended_real=None 的第一个原因是为了让普通符号可以与 expreal 一起使用。第二个原因是,即使在参数绝对是实数的情况下,假设查询也始终可以返回 None,例如:

>>> x = Symbol('x')
>>> print(x.is_extended_real)
None
>>> expreal(x)
expreal(x)
>>> expr = (1 + I)/sqrt(2) + (1 - I)/sqrt(2)
>>> print(expr.is_extended_real)
None
>>> expr.expand()
sqrt(2)
>>> expr.expand().is_extended_real
True
>>> expreal(expr)
expreal(sqrt(2)*(1 - I)/2 + sqrt(2)*(1 + I)/2)

expreal.eval 中验证参数确实意味着当传递 evaluate=False 时,它不会被验证,但实际上没有更好的地方来执行验证。

>>> expreal(I, evaluate=False)
expreal(I)

类属性 extended_nonnegativeexpreal 类上的 _eval_is_* 方法实现了针对 expreal 实例的假设系统查询。

>>> expreal(2)
expreal(2)
>>> expreal(2).is_finite
True
>>> expreal(2).is_integer
False
>>> expreal(2).is_rational
False
>>> expreal(2).is_algebraic
False
>>> z = expreal(-oo, evaluate=False)
>>> z
expreal(-oo)
>>> z.is_integer
True
>>> x = Symbol('x', real=True)
>>> expreal(x)
expreal(x)
>>> expreal(x).is_nonnegative
True

假设系统使用相应的处理程序 expreal._eval_is_finite 以及还有隐含规则来解析像 expreal(2).is_finite 这样的查询。例如,众所周知 expreal(2).is_rationalFalse,因为 expreal(2)._eval_is_algebraic 返回 False,并且存在一个隐含规则 rational -> algebraic。这意味着在这种情况下,_eval_is_algebraic 处理程序可以解析 is_rational 查询。实际上,最好不要为每个可能的谓词实现假设处理程序,而是尝试识别出一个最小的处理程序集,该集合可以使用最少的检查来解析尽可能多的查询。

需要注意的另一点是,_eval_is_* 方法只对参数 x 进行假设查询,不会对 self 进行任何假设查询。对同一对象的递归假设查询将干扰假设隐含解析器,可能导致非确定性行为,因此不应使用它们(在 SymPy 代码库中存在此类示例,但应将其删除)。

许多 expreal 方法隐式地返回 None。这是假设系统中的常见模式。eval 方法和 _eval_is_* 方法都可以返回 None,并且通常会返回。没有到达 return 语句的 Python 函数将隐式地返回 None。我们通过省略 if 语句中的许多 else 子句,并允许 None 被隐式返回来利用这一点。在跟踪这些方法的控制流程时,首先要记住任何查询的属性都可以返回 TrueFalseNone,还要记住如果所有条件都不满足,任何函数都将隐式返回 None

假设系统的机制

注意

本节描述了可能在 SymPy 未来版本中发生更改的内部细节。

本节将解释假设系统的内部工作原理。重要的是要理解这些内部工作原理是实现细节,并且可能在 SymPy 的不同版本之间发生变化。此解释是在 SymPy 1.7 的基础上编写的。虽然(旧的)假设系统存在许多局限性(将在下一节中讨论),但它是一个成熟的系统,在 SymPy 中得到广泛使用,并且针对其当前使用情况进行了优化。假设系统被隐式地用于大多数 SymPy 操作中,以控制基本表达式的评估。

在 SymPy 进程中,假设系统的实现中包含多个阶段,最终导致对假设系统中单个查询的评估。简而言之,这些阶段是:

  1. 在导入时,在 sympy/core/assumptions.py 中定义的假设规则被处理为规范形式,以便高效地应用隐含规则。这会在 SymPy 导入后,甚至在定义 Basic 类之前,发生一次。

  2. 方法 Basic.__init_subclass__ 将对每个 Basic 子类进行后处理,以添加假设查询所需的相关属性。这也将属性 default_assumptions 添加到类中。这在每次定义 Basic 子类(当导入其包含的模块时)时发生。

  3. 每个 Basic 实例最初使用类属性 default_assumptions。当第一次对 Basic 实例进行假设查询时,查询将从类的 default_assumptions 中获得答案。

  4. 如果在类的 default_assumptions 中没有为假设查询缓存值,则会复制默认假设以创建实例的假设缓存。然后,调用 _ask() 函数来解析查询,该函数首先会调用相关的实例处理程序 _eval_is 方法。如果处理程序返回非 None,则结果将被缓存并返回。

  5. 如果处理程序不存在或返回 None,则会尝试使用隐含解析器。这将枚举(以随机顺序)所有可能用于根据隐含规则解析查询的谓词组合。在每种情况下,都会调用处理程序 _eval_is 方法,以查看它是否返回非 None。如果任何处理程序和隐含规则的组合导致查询的明确结果,则该结果将被缓存到实例缓存中并返回。

  6. 最后,如果隐含解析器未能解析查询,则查询将被视为不可解析。查询的 None 值将被缓存到实例缓存中并返回。

sympy/core/assumptions.py 中定义的假设规则以 real ==  negative | zero | positive 形式给出。当导入此模块时,它们会被转换为名为 _assume_rulesFactRules 实例。这会将隐含规则预处理为可以用于隐含解析器的“A”和“B”规则形式。这在 sympy/core/facts.py 中的代码中进行了解释。我们可以直接访问此内部对象,例如(省略完整输出)

>>> from sympy.core.assumptions import _assume_rules
>>> _assume_rules.defined_facts   
{'algebraic',
 'antihermitian',
 'commutative',
 'complex',
 'composite',
 'even',
 ...
>>> _assume_rules.full_implications   
defaultdict(set,
            {('extended_positive', False): {('composite', False),
  ('positive', False),
  ('prime', False)},
 ('finite', False): {('algebraic', False),
  ('complex', False),
  ('composite', False),
  ...

Basic.__init_subclass__ 方法会检查每个 Basic 类的属性,以查看是否定义了任何与假设相关的属性。这些属性的一个例子是 expreal 类中定义的 is_extended_nonnegative = True 属性。任何此类属性的隐含意义将被用于预先计算任何静态可知的假设。例如,is_extended_nonnegative=True 意味着 real=True 等。会为该类创建一个 StdFactKB 实例,用于存储在此阶段已知值的假设。该 StdFactKB 实例被分配为类属性 default_assumptions。我们可以通过以下方法查看这一点

>>> from sympy import Expr
...
>>> class A(Expr):
...     is_positive = True
...
...     def _eval_is_rational(self):
...         # Let's print something to see when this method is called...
...         print('!!! calling _eval_is_rational')
...         return True
...
>>> A.is_positive
True
>>> A.is_real  # inferred from is_positive
True

尽管在类 A 中仅定义了 is_positive,但它也具有从 is_positive 推断出的属性,例如 is_real。类 A 的所有此类假设集可以在 default_assumptions 中看到,它看起来像一个 dict,但实际上是一个 StdFactKB 实例

>>> type(A.default_assumptions)
<class 'sympy.core.assumptions.StdFactKB'>
>>> A.default_assumptions
{'commutative': True,
 'complex': True,
 'extended_negative': False,
 'extended_nonnegative': True,
 'extended_nonpositive': False,
 'extended_nonzero': True,
 'extended_positive': True,
 'extended_real': True,
 'finite': True,
 'hermitian': True,
 'imaginary': False,
 'infinite': False,
 'negative': False,
 'nonnegative': True,
 'nonpositive': False,
 'nonzero': True,
 'positive': True,
 'real': True,
 'zero': False}

当创建任何 Basic 子类的实例时,Basic.__new__ 会分配其 _assumptions 属性,该属性最初将引用 cls.default_assumptions,在同一类的所有实例之间共享。实例将使用它来解析任何假设查询,直到该查询无法给出明确结果,此时会创建 cls.default_assumptions 的副本并将其分配给实例的 _assumptions 属性。副本将用作缓存,用于存储其 _eval_is 处理程序为实例计算的任何结果。

_assumptions 属性无法给出相关结果时,就该调用 _eval_is 处理程序了。此时会调用 _ask() 函数。 _ask() 函数最初会尝试通过调用相应方法(即 _eval_is_rational)来解析查询,例如 is_rational。如果它返回非 None,则结果将存储在 _assumptions 中,并且还会计算并存储该结果的任何隐含意义。此时查询已解决,并返回该值。

>>> a = A()
>>> a._assumptions is A.default_assumptions
True
>>> a.is_rational
!!! calling _eval_is_rational
True
>>> a._assumptions is A.default_assumptions
False
>>> a._assumptions   # rational now shows as True
{'algebraic': True,
 'commutative': True,
 'complex': True,
 'extended_negative': False,
 'extended_nonnegative': True,
 'extended_nonpositive': False,
 'extended_nonzero': True,
 'extended_positive': True,
 'extended_real': True,
 'finite': True,
 'hermitian': True,
 'imaginary': False,
 'infinite': False,
 'irrational': False,
 'negative': False,
 'nonnegative': True,
 'nonpositive': False,
 'nonzero': True,
 'positive': True,
 'rational': True,
 'real': True,
 'transcendental': False,
 'zero': False}

如果例如 _eval_is_rational 不存在或返回 None,则 _ask() 将尝试使用隐含规则和任何其他处理程序方法(例如 _eval_is_integer_eval_is_algebraic 等)的所有可能性,这些方法可能能够给出对原始查询的答案。如果任何方法导致原始查询的明确结果已知,则返回该结果。否则,一旦所有使用处理程序和隐含规则来解析查询的可能性都已耗尽,则 None 将被缓存并返回。

>>> b = A()
>>> b.is_algebraic    # called _eval_is_rational indirectly
!!! calling _eval_is_rational
True
>>> c = A()
>>> print(c.is_prime)   # called _eval_is_rational indirectly
!!! calling _eval_is_rational
None
>>> c._assumptions   # prime now shows as None
{'algebraic': True,
 'commutative': True,
 'complex': True,
 'extended_negative': False,
 'extended_nonnegative': True,
 'extended_nonpositive': False,
 'extended_nonzero': True,
 'extended_positive': True,
 'extended_real': True,
 'finite': True,
 'hermitian': True,
 'imaginary': False,
 'infinite': False,
 'irrational': False,
 'negative': False,
 'nonnegative': True,
 'nonpositive': False,
 'nonzero': True,
 'positive': True,
 'prime': None,
 'rational': True,
 'real': True,
 'transcendental': False,
 'zero': False}

注意

_ask() 函数中,以随机顺序调用处理程序,这意味着此处的执行可能是非确定性的。只要所有不同的处理程序方法都一致(即没有错误),最终结果仍然是确定性的。但是,如果两个处理程序不一致,则可能导致非确定性行为,因为这种随机化可能会导致在多次运行同一程序时以不同的顺序调用处理程序。

限制

使用或组合谓词

在旧的假设中,我们可以在创建符号时轻松地使用 *and* 组合谓词,例如

>>> x = Symbol('x', integer=True, positive=True)
>>> x.is_positive
True
>>> x.is_integer
True

我们还可以轻松地查询两个条件是否同时满足

>>> fuzzy_and([x.is_positive, x.is_integer])
True
>>> x.is_positive and x.is_integer
True

但是,在旧的假设中,无法使用 *or* 创建具有假设谓词组合的 Symbol。例如,如果我们想说“x 为正或 x 为整数”,那么无法创建具有这些假设的 Symbol

也不可能基于 *or* 提出假设查询,例如“表达式是否为正数或整数”。我们可以使用例如

>>> fuzzy_or([x.is_positive, x.is_integer])
True

但是,如果 x 的所有已知信息是它可能是正数,否则为负整数,则 x.is_positivex.is_integer 都会解析为 None。这意味着查询变为

>>> fuzzy_or([None, None])

它也会返回 None

不同符号之间的关系

旧的假设系统的一个基本限制是,所有显式假设都是单个符号的属性。在这个系统中,无法对两个符号之间的 *关系* 做出假设。最常见的请求之一是能够假设类似 x < y 的内容,但在旧的假设中甚至无法指定这一点。

新的假设具有理论上的能力,可以指定关系假设。但是,利用这些信息的算法尚未实现,并且指定关系假设的精确 API 尚未确定。