假设¶
本页概述了 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
在假设查询中给出的值使用三值“模糊”逻辑。任何查询都可以返回 True
、False
或 None
,其中 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
的最后一个原因是,假设系统并没有尽力回答复杂的查询。该系统旨在快速,并使用简单的启发式方法来推断 True
或 False
答案在常见情况下。例如,任何正项之和都是正的,因此
>>> 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
从来不是矛盾。如果在解析查询时能够推断出明确的 True
或 False
结果,那么这比返回 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
将假设应用于字符串输入¶
我们已经了解了如何在 Symbol
或 symbols()
中显式地设置假设。一个自然的问题是,我们可以在哪些其他情况下将假设分配给对象?
用户通常使用字符串作为 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
,因为每个整数都是一个有理数。
下面给出了所有可能的谓词及其定义的完整表格。
谓词 |
定义 |
含义 |
---|---|---|
|
交换表达式。一个 |
|
|
一个无穷表达式,例如 |
== !finite |
|
一个有限表达式。任何不是 |
== !infinite |
|
厄米算符域中的一个元素。 [antihermitian] |
|
|
反厄米算符域中的一个元素。 [antihermitian] |
|
|
一个复数,\(z\in\mathbb{C}\)。任何形式为 \(x + iy\) 的数,其中 \(x\) 和 \(y\) 为 |
-> commutative -> finite |
|
一个代数数,\(z\in\overline{\mathbb{Q}}\)。任何为非零多项式 \(p(z)\in\mathbb{Q}[z]\)(具有有理系数)的根的数。所有 |
-> complex |
|
一个不是代数的复数,\(z\in\mathbb{C}-\overline{\mathbb{Q}}\)。所有 |
== (complex & !algebraic) |
|
扩展实数轴上的一个元素,\(x\in\overline{\mathbb{R}}\),其中 \(\overline{\mathbb{R}}=\mathbb{R}\cup\{-\infty,+\infty\}\)。一个 |
-> commutative |
|
一个实数,\(x\in\mathbb{R}\)。所有 |
-> complex == (extended_real & finite) == (negative | zero | positive) -> hermitian |
|
一个虚数,\(z\in\mathbb{I}-\{0\}\)。一个形式为 \(z=yi\) 的数,其中 \(y\) 为实数,\(y\ne 0\),且 \(i=\sqrt{-1}\)。所有 |
-> complex -> antihermitian -> !extended_real |
|
一个有理数,\(q\in\mathbb{Q}\)。任何形式为 \(\frac{a}{b}\) 的数,其中 \(a\) 和 \(b\) 为整数,且 \(b \ne 0\)。所有 |
-> real -> algebraic |
|
一个不是有理数的实数,\(x\in\mathbb{R}-\mathbb{Q}\)。 [irrational] |
== (real & !rational) |
|
一个整数,\(a\in\mathbb{Z}\)。所有整数都是 |
-> rational |
|
一个不是整数的扩展实数,\(x\in\overline{\mathbb{R}}-\mathbb{Z}\)。 |
== (extended_real & !integer) |
|
一个偶数,\(e\in\{2k: k\in\mathbb{Z}\}\)。所有 |
-> integer -> !odd |
|
一个奇数,\(o\in\{2k + 1: k\in\mathbb{Z}\}\)。所有 |
-> integer -> !even |
|
一个质数,\(p\in\mathbb{P}\)。所有 |
-> integer -> positive |
|
一个合数,\(c\in\mathbb{N}-(\mathbb{P}\cup\{1\})\)。一个正整数,它是两个或多个质数的乘积。一个 |
-> (integer & positive & !prime) !composite -> (!positive | !even | prime) |
|
数字 \(0\)。一个具有 |
-> even & finite == (extended_nonnegative & extended_nonpositive) == (nonnegative & nonpositive) |
|
一个非零实数,\(x\in\mathbb{R}-\{0\}\)。一个 |
-> real == (extended_nonzero & finite) |
|
一个不是零的扩展实数,\(x\in\overline{\mathbb{R}}-\{0\}\)。 |
== (extended_real & !zero) |
|
一个正实数,\(x\in\mathbb{R}, x>0\)。所有 |
== (nonnegative & nonzero) == (extended_positive & finite) |
|
非负实数,\(x\in\mathbb{R}, x\ge 0\)。所有 |
== (real & !negative) == (extended_nonnegative & finite) |
|
负实数,\(x\in\mathbb{R}, x<0\)。所有 |
== (nonpositive & nonzero) == (extended_negative & finite) |
|
非正实数,\(x\in\mathbb{R}, x\le 0\)。所有 |
== (real & !positive) == (extended_nonpositive & finite) |
|
扩展正实数,\(x\in\overline{\mathbb{R}}, x>0\)。扩展正数是 |
== (extended_nonnegative & extended_nonzero) |
|
扩展非负实数,\(x\in\overline{\mathbb{R}}, x\ge 0\)。扩展非负数是 |
== (extended_real & !extended_negative) |
|
扩展负实数,\(x\in\overline{\mathbb{R}}, x<0\)。扩展负数是 |
== (extended_nonpositive & extended_nonzero) |
|
扩展非正实数,\(x\in\overline{\mathbb{R}}, x\le 0\)。扩展非正数是 |
== (extended_real & !extended_positive) |
以上定义的参考资料¶
推论¶
假设系统使用推理规则推断新谓词,这些谓词超出了在创建符号时直接指定的谓词。
>>> x = Symbol('x', real=True, negative=False, zero=False)
>>> x.is_positive
True
尽管 x
没有明确声明为 positive
,但可以从明确给出的谓词推断出来。具体来说,其中一个推理规则是 real == negative | zero | positive
,所以如果 real
为 True
,并且 negative
和 zero
都是 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}\),等等。
谓词建立了子集方案,如从复数开始的链,复数被视为实数的超集,实数又是有理数的超集,等等。子集链
对应于假设系统中的蕴含链
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
这里要注意,由于 x
和 y
都是非交换的,x
和 y
不交换,因此 x*y != y*x
。另一方面,由于 z
是交换的,x
和 z
交换,并且 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=False
和 is_empty=None
的情况仍然很有用。
虽然 is_zero_matrix()
和 is_empty
用于与 is_zero
等假设属性类似的目的,但它们并不属于(旧的)假设系统。没有关联的推理规则连接例如 Set.is_empty
和 Set.is_finite_set
,因为推理规则是(旧的)假设系统的一部分,该系统只处理上面表格中列出的谓词。不可能用例如 zero_matrix=False
声明一个 MatrixSymbol
,并且没有 SetSymbol
类,但如果有的话,它将没有用于理解像 empty=False
这样的谓词的系统。
is_zero_matrix()
和 is_empty
属性类似于假设系统中的属性,因为它们涉及表达式的 *语义* 方面。还有大量的其他属性侧重于 *结构* 方面,例如 is_Number
、is_number()
、is_comparable()
。由于这些属性指的是表达式的结构方面,因此它们总是给出 True
或 False
,而不是一个模糊的布尔值,它也可能为 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
类是 Integer
、Rational
和 Float
的超类,因此 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_comparable
和 b.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_rational
是 False
,因为 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
被隐式返回来利用这一点。在跟踪这些方法的控制流程时,首先要记住任何查询的属性都可以返回 True
、False
或 None
,还要记住如果所有条件都不满足,任何函数都将隐式返回 None
。
假设系统的机制¶
注意
本节描述了可能在 SymPy 未来版本中发生更改的内部细节。
本节将解释假设系统的内部工作原理。重要的是要理解这些内部工作原理是实现细节,并且可能在 SymPy 的不同版本之间发生变化。此解释是在 SymPy 1.7 的基础上编写的。虽然(旧的)假设系统存在许多局限性(将在下一节中讨论),但它是一个成熟的系统,在 SymPy 中得到广泛使用,并且针对其当前使用情况进行了优化。假设系统被隐式地用于大多数 SymPy 操作中,以控制基本表达式的评估。
在 SymPy 进程中,假设系统的实现中包含多个阶段,最终导致对假设系统中单个查询的评估。简而言之,这些阶段是:
在导入时,在
sympy/core/assumptions.py
中定义的假设规则被处理为规范形式,以便高效地应用隐含规则。这会在 SymPy 导入后,甚至在定义Basic
类之前,发生一次。方法
Basic.__init_subclass__
将对每个Basic
子类进行后处理,以添加假设查询所需的相关属性。这也将属性default_assumptions
添加到类中。这在每次定义Basic
子类(当导入其包含的模块时)时发生。每个
Basic
实例最初使用类属性default_assumptions
。当第一次对Basic
实例进行假设查询时,查询将从类的default_assumptions
中获得答案。如果在类的
default_assumptions
中没有为假设查询缓存值,则会复制默认假设以创建实例的假设缓存。然后,调用_ask()
函数来解析查询,该函数首先会调用相关的实例处理程序_eval_is
方法。如果处理程序返回非 None,则结果将被缓存并返回。如果处理程序不存在或返回 None,则会尝试使用隐含解析器。这将枚举(以随机顺序)所有可能用于根据隐含规则解析查询的谓词组合。在每种情况下,都会调用处理程序
_eval_is
方法,以查看它是否返回非 None。如果任何处理程序和隐含规则的组合导致查询的明确结果,则该结果将被缓存到实例缓存中并返回。最后,如果隐含解析器未能解析查询,则查询将被视为不可解析。查询的
None
值将被缓存到实例缓存中并返回。
在 sympy/core/assumptions.py
中定义的假设规则以 real == negative | zero | positive
形式给出。当导入此模块时,它们会被转换为名为 _assume_rules
的 FactRules
实例。这会将隐含规则预处理为可以用于隐含解析器的“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_positive
和 x.is_integer
都会解析为 None
。这意味着查询变为
>>> fuzzy_or([None, None])
它也会返回 None
。
不同符号之间的关系¶
旧的假设系统的一个基本限制是,所有显式假设都是单个符号的属性。在这个系统中,无法对两个符号之间的 *关系* 做出假设。最常见的请求之一是能够假设类似 x < y
的内容,但在旧的假设中甚至无法指定这一点。
新的假设具有理论上的能力,可以指定关系假设。但是,利用这些信息的算法尚未实现,并且指定关系假设的精确 API 尚未确定。