符号和模糊布尔值¶
本页描述了SymPy中的符号 Boolean
是什么,以及它与SymPy许多部分中使用的三值模糊布尔值的关系。还讨论了在使用三值逻辑编写代码时出现的常见问题,以及如何正确处理这些问题。
符号布尔值 vs 三值布尔值¶
假设查询如 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`
不能将 None
用作 Piecewise
的条件,因为与不等式 x > 0
不同,它不提供任何信息。对于不等式,一旦知道 x
的值,就可以在将来决定条件是否可能为 True
或 False
。None
的值不能以这种方式使用,因此被拒绝。
备注
我们可以在 Piecewise
中使用 True
,因为 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
可以简化代码,并且由于代码看起来更像普通二进制逻辑的情况,因此还可以减少引入逻辑错误的机会。
带有符号布尔值的三值逻辑¶
当使用符号 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
,而这两种情况在本例中都无法确定。
当使用 and
、or
或 not
与符号 Boolean
时,会出现类似的问题。解决方法是使用 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)
现在在这个版本中,我们不会遇到任何异常,如果结果是不确定的,我们将得到一个符号化的 Boolean
,它表示在什么条件下陈述“a
和 b
都是正的”会是真的:
>>> 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
可以为你处理逻辑。
脚注