假设

本页概述了 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 ,因为在 Python 解释器中,特殊值 None 默认不会显示。

有几个原因可能导致假设查询返回 None 。可能查询是 不可知的 ,就像上面的 x 的情况。由于 x 没有任何假设声明,它大致代表一个任意的复数。一个任意的复数 可能 是一个正实数,但也 可能 不是。在没有进一步信息的情况下,无法解析查询 x.is_positive

另一个假设查询可能返回 None 的原因是,在许多情况下,确定一个表达式是否为正(例如)是 不可判定的。这意味着不存在一个算法来普遍回答这个查询。对于某些情况,可能存在一个算法或至少一个简单的检查,尽管它尚未在 SymPy 中实现,但可以添加到 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 的结果并不是一个 bug。任何使用假设系统的代码都需要准备好处理任何查询的所有三种情况,并且不应该假设总是会给出确定答案。

假设系统不仅适用于符号或复杂表达式。它也可以用于普通的 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,那么它既是正数 and 整数:

>>> 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 表达式在乘法下与其他所有表达式交换。如果一个表达式 acommutative=True ,那么对于任何其他表达式 b (即使 b 不是 commutative ), a * b == b * a 。与其他所有假设谓词不同, commutative 必须始终为 TrueFalse ,而不能为 None 。同样与其他所有谓词不同, commutative 在例如 Symbol('x') 中默认值为 True[commutative]

infinite

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

== !finite

finite

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

== !infinite

hermitian

Hermitian 算子领域的一个元素。 [antihermitian]

antihermitian

反厄米算子领域的一个元素。 [antihermitian]

complex

复数,\(z\in\mathbb{C}\)。任何形如 \(x + iy\) 的数,其中 \(x\)\(y\)实数,且 \(i = \sqrt{-1}\)。所有 复数 都是 有限的。包含所有 实数[complex]

-> 可交换
-> 有限

algebraic

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

-> 复杂

transcendental

一个不是代数的复数,\(z\in\mathbb{C}-\overline{\mathbb{Q}}\)。所有``超越``数都是``复数``。一个``超越``数可能是也可能不是``实数``,但永远不可能是``有理数``。 [transcendental]

== (复杂 & !代数)

extended_real

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

-> 可交换

real

一个实数,\(x\in\mathbb{R}\)。所有“实”数都是“有限”且“复”的(实数集是复数集的子集)。包括所有“有理”数。一个“实”数要么是“负”的,要么是“零”,要么是“正”的。 [real]

-> 复杂
== (extended_real & finite)
== (负数 | | 正数)
-> hermitian

imaginary

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

-> 复杂
-> 反厄米
-> !extended_real

rational

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

-> 实数
-> 代数

irrational

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

== (实数 & !有理数)

integer

一个整数,\(a\in\mathbb{Z}\)。所有整数都是 有理数。包括 和所有 质数合数偶数奇数[integer]

-> 理性

noninteger

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

== (extended_real & !integer)

even

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

-> 整数
-> !odd

odd

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

-> 整数
-> !even

prime

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

-> 整数
-> 正向

composite

一个合数,\(c\in\mathbb{N}-(\mathbb{P}\cup\{1\})\)。一个正整数,是两个或更多素数的乘积。一个``合数``总是``正````整数``,并且不是``素数``。 [composite]

-> (整数 & 正数 & 非质数)
!composite -> (!positive | !even | prime)

zero

数字 \(0\)。带有 zero=True 的表达式表示数字 0,这是一个 整数[zero]

-> 甚至 & 有限
== (extended_nonnegative & extended_nonpositive)
== (非负 & 非正)

nonzero

一个非零实数,\(x\in\mathbb{R}-\{0\}\)。一个``非零``数总是``实``数,并且不能是``零``。

-> 实数
== (extended_nonzero & finite)

extended_nonzero

扩展实数中的一个非零元素,\(x\in\overline{\mathbb{R}}-\{0\}\)

== (extended_real & !zero)

positive

一个正实数,\(x\in\mathbb{R}, x>0\)。所有“正”数都是“有限”的,因此“无穷大”不是“正”的。 [positive]

== (非负且非零)
== (extended_positive & finite)

nonnegative

一个非负实数,\(x\in\mathbb{R}, x\ge 0\)。所有``非负``数都是``有限``的,因此``oo``不是``非负``的。 [positive]

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

negative

一个负实数,\(x\in\mathbb{R}, x<0\)。所有“负”数都是“有限”的,因此“-oo”不是“负”的。 [negative]

== (非正数 & 非零)
== (extended_negative & finite)

nonpositive

非正实数,\(x\in\mathbb{R}, x\le 0\)。所有``非正``数都是``有限``的,因此``-oo``不是``非正``的。[negative]

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

extended_positive

一个正的扩展实数,\(x\in\overline{\mathbb{R}}, x>0\)。一个``extended_positive`` 数要么是``positive``,要么是``oo``。 [extended_real]

== (extended_nonnegative & extended_nonzero)

extended_nonnegative

非负扩展实数,\(x\in\overline{\mathbb{R}}, x\ge 0\)。一个``extended_nonnegative`` 数要么是``nonnegative``,要么是``oo``。[extended_real]

== (extended_real & !extended_negative)

extended_negative

一个负的扩展实数,\(x\in\overline{\mathbb{R}}, x<0\)。一个``extended_negative`` 数要么是``negative`` 要么是``-oo``。 [extended_real]

== (extended_nonpositive & extended_nonzero)

extended_nonpositive

一个非正的扩展实数,\(x\in\overline{\mathbb{R}}, x\le 0\)。一个``extended_nonpositive``数要么是``nonpositive``,要么是``-oo``。[extended_real]

== (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 是非交换的,也有 x*z == z*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_matrixis_empty 用于与假设属性(如 is_zero)类似的目的,但它们不属于(旧)假设系统。没有关联的推理规则连接例如 Set.is_emptySet.is_finite_set,因为推理规则是(旧)假设系统的一部分,该系统仅处理上表中列出的谓词。无法声明一个带有例如 zero_matrix=FalseMatrixSymbol,并且没有 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

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

The is_number (小写) 属性与 is_Number 非常不同。is_number 属性对于任何可以数值评估为浮点复数的表达式为 True,使用 evalf() 方法:

>>> 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_nonnegative 类属性和 expreal 类上的 _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。这意味着在这种情况下,is_rational 查询可以通过 _eval_is_algebraic 处理程序来解析。实际上,最好不要为每个可能的谓词实现假设处理程序,而是尝试识别一组最小的处理程序,这些处理程序可以用尽可能少的检查来解析尽可能多的查询。

另一个需要注意的点是,_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_real 这样的属性,这些属性是从 is_positive 推断出来的。类 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

然而,在旧的假设中,无法创建一个带有 组合假设谓词的 Symbol。例如,如果我们想要表示“x 是正数或 x 是整数”,那么就无法创建一个带有这些假设的 Symbol

基于 的假设查询也是不可能的,例如“expr 是一个正表达式或一个整数”。我们可以使用例如

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

然而,如果关于 x 的所有已知信息是它可能是正数或否则是负整数,那么两个查询 x.is_positivex.is_integer 都将解析为 None。这意味着查询变为

>>> fuzzy_or([None, None])

这也会返回 None

不同符号之间的关系

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

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