符号布尔值和模糊布尔值¶
本页描述了 SymPy 中的符号 Boolean
以及它与 SymPy 许多部分中使用的三值模糊布尔值之间的关系。它还讨论了在编写使用三值逻辑的代码时出现的一些常见问题以及如何正确处理这些问题。
符号布尔值与三值布尔值¶
假设查询(如 x.ispositive
)会返回模糊布尔值 True
、False
或 None
结果 [1]。这些是低级 Python 对象,而不是 SymPy 的符号 Boolean
表达式。
>>> from sympy import Symbol, symbols
>>> xpos = Symbol('xpos', positive=True)
>>> xneg = Symbol('xneg', negative=True)
>>> x = Symbol('x')
>>> print(xpos.is_positive)
True
>>> print(xneg.is_positive)
False
>>> print(x.is_positive)
None
作为模糊布尔值的 None
结果应解释为“可能”或“未知”。
在 SymPy 中使用不等式时,可以找到符号 Boolean
类的示例。当不等式的真假未知时,Boolean
可以用符号的方式表示不确定的结果。
>>> xpos > 0
True
>>> xneg > 0
False
>>> x > 0
x > 0
>>> type(x > 0)
<class 'sympy.core.relational.StrictGreaterThan'>
最后一个示例显示了当不等式不确定时会发生什么:我们得到了 StrictGreaterThan
的一个实例,它用符号表达式表示不等式。在内部,当试图计算类似 a > b
的不等式时,SymPy 会计算 (a - b).is_extended_positive
。如果结果是 True
或 False
,那么 SymPy 的符号 S.true
或 S.false
将被返回。如果结果是 None
,那么将返回一个未计算的 StrictGreaterThan
,如上所示 x > 0
。
并不明显的是,类似 xpos > 0
的查询返回 S.true
而不是 True
,因为两个对象都以相同的方式显示,但我们可以使用 Python 的 is
运算符来检查这一点。
>>> from sympy import S
>>> xpos.is_positive is True
True
>>> xpos.is_positive is S.true
False
>>> (xpos > 0) is True
False
>>> (xpos > 0) is S.true
True
在 SymPy 中,没有 None
的通用符号类比。在底层假设查询给出 None
的情况下,符号查询将导致一个未计算的符号 Boolean
(例如,x > 0
)。我们可以使用符号 Boolean
作为符号表达式的部分,例如 Piecewise
。
>>> from sympy import Piecewise
>>> p = Piecewise((1, x > 0), (2, True))
>>> p
Piecewise((1, x > 0), (2, True))
>>> p.subs(x, 3)
1
这里 p
代表一个表达式,如果 x > 0
,该表达式将等于 1
,否则将等于 2
。未计算的 Boolean
不等式 x > 0
表示符号决定表达式值的条件。当我们用一个值替换 x
时,不等式将解析为 S.true
,然后 Piecewise
可以计算为 1
或 2
。
使用模糊布尔值而不是符号 Boolean
将无法正常工作。
>>> p2 = Piecewise((1, x.is_positive), (2, True))
Traceback (most recent call last):
...
TypeError: Second argument must be a Boolean, not `NoneType`
Piecewise
不能使用 None
作为条件,因为与不等式 x > 0
不同,它没有提供任何信息。对于不等式,我们可以在将来确定条件是否可能为 True
或 False
,一旦知道 x
的值。None
的值不能以这种方式使用,因此它被拒绝。
注意
我们可以使用 True
在 Piecewise
中,因为 True
会象征化为 S.true
。象征化 None
只会再次给出 None
,它不是有效的符号 SymPy 对象。
在 SymPy 中有许多其他符号 Boolean
类型。关于模糊布尔值和符号 Boolean
之间的区别的相同考虑因素适用于所有其他 SymPy Boolean
类型。为了给出不同的示例,有 Contains
,它表示一个对象包含在一个集合中的语句。
>>> from sympy import Reals, Contains
>>> x = Symbol('x', real=True)
>>> y = Symbol('y')
>>> Contains(x, Reals)
True
>>> Contains(y, Reals)
Contains(y, Reals)
>>> Contains(y, Reals).subs(y, 1)
True
对应于 Contains
的 Python 运算符是 in
。in
的一个怪癖是它只能计算为一个 bool
(True
或 False
),因此如果结果不确定,则会引发异常。
>>> from sympy import I
>>> 2 in Reals
True
>>> I in Reals
False
>>> x in Reals
True
>>> y in Reals
Traceback (most recent call last):
...
TypeError: did not evaluate to a bool: (-oo < y) & (y < oo)
可以通过使用 Contains(x, Reals)
或 Reals.contains(x)
而不是 x in Reals
来避免异常。
带有模糊布尔值的三个值逻辑¶
无论我们使用模糊布尔值还是符号 Boolean
,我们总是需要意识到查询可能不确定的可能性。然而,在两种情况下如何编写处理此问题的代码是不同的。我们将首先关注模糊布尔值。
考虑以下函数。
>>> def both_positive(a, b):
... """ask whether a and b are both positive"""
... if a.is_positive and b.is_positive:
... return True
... else:
... return False
both_positive
函数应该告诉我们 a
和 b
是否都为正。但是,如果任一 is_positive
查询给出 None
,则 both_positive
函数将失败。
>>> print(both_positive(S(1), S(1)))
True
>>> print(both_positive(S(1), S(-1)))
False
>>> print(both_positive(S(-1), S(-1)))
False
>>> x = Symbol('x') # may or may not be positive
>>> print(both_positive(S(1), x))
False
注意
我们需要使用 S
对该函数的参数进行象征化,因为假设只在 SymPy 对象上定义,而不是在普通的 Python int
对象上定义。
这里 False
是不正确的,因为 x
可能为正,在这种情况下,两个参数都将为正。我们在这里得到 False
是因为 x.is_positive
给出 None
,并且 Python 会将 None
视为“假”。
为了正确处理所有可能的情况,我们需要将识别 True
和 False
情况的逻辑分开。改进后的函数可能是
>>> def both_positive_better(a, b):
... """ask whether a and b are both positive"""
... if a.is_positive is False or b.is_positive is False:
... return False
... elif a.is_positive is True and b.is_positive is True:
... return True
... else:
... return None
此函数现在可以处理 a
和 b
的所有 True
、False
或 None
情况,并且将始终返回一个模糊布尔值,表示语句“a
和 b
都为正”是真、假还是未知。
>>> print(both_positive_better(S(1), S(1)))
True
>>> print(both_positive_better(S(1), S(-1)))
False
>>> x = Symbol('x')
>>> y = Symbol('y', positive=True)
>>> print(both_positive_better(S(1), x))
None
>>> print(both_positive_better(S(-1), x))
False
>>> print(both_positive_better(S(1), y))
True
当使用模糊布尔值时,我们还需要注意使用 Python 的 not
运算符进行否定,例如
>>> x = Symbol('x')
>>> print(x.is_positive)
None
>>> not x.is_positive
True
模糊布尔值 None
的正确否定再次是 None
。如果我们不知道语句“x
为正”是 True
还是 False
,那么我们也不知道它的否定“x
不为正”是 True
还是 False
。我们得到 True
的原因再次是因为 None
被认为是“假值”。当 None
与逻辑运算符(如 not
)一起使用时,它将首先被转换为 bool
,然后被否定。
>>> bool(None)
False
>>> not bool(None)
True
>>> not None
True
None
被视为假值这一事实如果使用正确会很有用。例如,我们可能希望仅当 x
已知为正时才执行某些操作,在这种情况下,我们可以执行以下操作。
>>> x = Symbol('x', positive=True)
>>> if x.is_positive:
... print("x is definitely positive")
... else:
... print("x may or may not be positive")
x is definitely positive
只要我们理解替代条件分支是指两种情况(False
和 None
),那么这可能是一种有用的编写条件语句的方式。当我们确实需要区分所有情况时,我们需要使用诸如 x.is_positive is False
之类的东西。但是,我们需要谨慎的是,不要将 Python 的二元逻辑运算符(如 not
或 and
)与模糊布尔值一起使用,因为它们无法正确处理不确定情况。
事实上,SymPy 具有旨在正确处理模糊布尔值的内部函数。
>>> from sympy.core.logic import fuzzy_not, fuzzy_and
>>> print(fuzzy_not(True))
False
>>> print(fuzzy_not(False))
True
>>> print(fuzzy_not(None))
None
>>> print(fuzzy_and([True, True]))
True
>>> print(fuzzy_and([True, None]))
None
>>> print(fuzzy_and([False, None]))
False
使用 fuzzy_and
函数,我们可以更简单地编写 both_positive
函数。
>>> def both_positive_best(a, b):
... """ask whether a and b are both positive"""
... return fuzzy_and([a.is_positive, b.is_positive])
利用 fuzzy_and
、fuzzy_or
和 fuzzy_not
可以生成更简单的代码,并且还可以减少引入逻辑错误的可能性,因为代码看起来更像是普通二元逻辑中的代码。
带符号布尔的 3 值逻辑¶
在使用符号 Boolean
而不是模糊布尔值时,不会出现 None
被静默地视为假值的问题,因此更容易避免出现逻辑错误。但是,不确定情况往往会导致异常被抛出,如果处理不当的话。
我们将尝试使用符号 Boolean
实现 both_positive
函数。
>>> def both_positive(a, b):
... """ask whether a and b are both positive"""
... if a > 0 and b > 0:
... return S.true
... else:
... return S.false
第一个区别是,我们返回符号 Boolean
对象 S.true
和 S.false
,而不是 True
和 False
。第二个区别是,我们测试例如 a > 0
而不是 a.is_positive
。尝试一下,我们得到
>>> both_positive(1, 2)
True
>>> both_positive(-1, 1)
False
>>> x = Symbol('x') # may or may not be positive
>>> both_positive(x, 1)
Traceback (most recent call last):
...
TypeError: cannot determine truth value of Relational
现在发生的事情是,当 x
未知为正或不为正时,测试 x > 0
会抛出异常。更准确地说,x > 0
不会抛出异常,但 if x > 0
会,这是因为 if
语句隐式地调用了 bool(x > 0)
,这会抛出异常。
>>> x > 0
x > 0
>>> bool(x > 0)
Traceback (most recent call last):
...
TypeError: cannot determine truth value of Relational
>>> if x > 0:
... print("x is positive")
Traceback (most recent call last):
...
TypeError: cannot determine truth value of Relational
Python 表达式 x > 0
创建了一个 SymPy Boolean
。由于在这种情况下,Boolean
无法计算为 True
或 False
,因此我们得到了一个未计算的 StrictGreaterThan
。尝试用 bool(x > 0)
将其强制转换为 bool
会抛出异常。这是因为普通的 Python bool
必须是 True
或 False
,而在这两种情况下,都没有被认为是正确的。
当使用符号 Boolean
时,也会出现类似的问题,例如使用 and
、or
或 not
。解决方案是使用 SymPy 的符号 And
、Or
和 Not
,或者等效地使用 Python 的按位逻辑运算符 &
、|
和 ~
。
>>> from sympy import And, Or, Not
>>> x > 0
x > 0
>>> x > 0 and x < 1
Traceback (most recent call last):
...
TypeError: cannot determine truth value of Relational
>>> And(x > 0, x < 1)
(x > 0) & (x < 1)
>>> (x > 0) & (x < 1)
(x > 0) & (x < 1)
>>> Or(x < 0, x > 1)
(x > 1) | (x < 0)
>>> Not(x < 0)
x >= 0
>>> ~(x < 0)
x >= 0
和以前一样,如果我们避免直接在 if
、and
、or
或 not
中使用 SymPy Boolean
,我们可以制作一个更好的 both_positive
版本。相反,我们可以测试 Boolean
是否已计算为 S.true
或 S.false
。
>>> def both_positive_better(a, b):
... """ask whether a and b are both positive"""
... if (a > 0) is S.false or (b > 0) is S.false:
... return S.false
... elif (a > 0) is S.true and (b > 0) is S.true:
... return S.true
... else:
... return And(a > 0, b > 0)
现在使用这个版本,我们不会遇到任何异常,如果结果是不确定的,我们会得到一个表示语句“a
和 b
都为正”为真的条件的符号 Boolean
。
>>> both_positive_better(S(1), S(2))
True
>>> both_positive_better(S(1), S(-1))
False
>>> x, y = symbols("x, y")
>>> both_positive_better(x, y + 1)
(x > 0) & (y + 1 > 0)
>>> both_positive_better(x, S(3))
x > 0
最后一种情况表明,实际上使用 And
以及已知为真的条件会简化 And
。实际上,我们有
>>> And(x > 0, 3 > 0)
x > 0
>>> And(4 > 0, 3 > 0)
True
>>> And(-1 > 0, 3 > 0)
False
这意味着我们可以改进 both_positive_better
。根本不需要不同的情况。相反,我们可以简单地返回 And
,并让它尽可能地简化。
>>> def both_positive_best(a, b):
... """ask whether a and b are both positive"""
... return And(a > 0, b > 0)
现在,这将适用于任何符号实数对象,并产生符号结果。我们还可以将结果代入,以了解它在特定值的情况下如何工作。
>>> both_positive_best(2, 1)
True
>>> both_positive_best(-1, 2)
False
>>> both_positive_best(x, 3)
x > 0
>>> condition = both_positive_best(x/y, x + y)
>>> condition
(x + y > 0) & (x/y > 0)
>>> condition.subs(x, 1)
(1/y > 0) & (y + 1 > 0)
>>> condition.subs(x, 1).subs(y, 2)
True
使用符号 Boolean
对象时的想法是,尽可能避免尝试使用 if/else
和其他逻辑运算符(如 and
等)对它们进行分支。相反,可以考虑计算一个条件并将其作为变量传递。然后,基本符号运算(如 And
、Or
和 Not
)可以为您处理逻辑。
脚注