# Symbolic and fuzzy booleans¶

This page described what a symbolic `Boolean`

in SymPy is and also
how that relates to three-valued fuzzy-bools that are used in many parts of
SymPy. It also discusses some common problems that arise when writing code
that uses three-valued logic and how to handle them correctly.

## Symbolic Boolean vs three valued bool¶

Assumptions queries like `x.ispositive`

give fuzzy-bool `True`

,
`False`

or `None`

results 1. These are low-level Python objects
rather than SymPy’s symbolic `Boolean`

expressions.

```
>>> 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
```

A `None`

result as a fuzzy-bool should be interpreted as meaning “maybe” or
“unknown”.

An example of a symbolic `Boolean`

class in SymPy can be found when
using inequalities. When an inequality is not known to be true or false a
`Boolean`

can represent indeterminate results symbolically:

```
>>> xpos > 0
True
>>> xneg > 0
False
>>> x > 0
x > 0
>>> type(x > 0)
<class 'sympy.core.relational.StrictGreaterThan'>
```

The last example shows what happens when an inequality is indeterminate: we
get an instance of `StrictGreaterThan`

which represents the
inequality as a symbolic expression. Internally when attempting to evaluate an
inequality like `a > b`

SymPy will compute `(a - b).is_extended_positive`

.
If the result is `True`

or `False`

then SymPy’s symbolic `S.true`

or
`S.false`

will be returned. If the result is `None`

then an unevaluated
`StrictGreaterThan`

is returned as shown for `x > 0`

above.

It is not obvious that queries like `xpos > 0`

return `S.true`

rather than
`True`

because both objects display in the same way but we can check this
using the Python `is`

operator:

```
>>> 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
```

There is no general symbolic analogue of `None`

in SymPy. In the cases where
a low-level assumptions query gives `None`

the symbolic query will result in
an unevaluated symbolic `Boolean`

(e.g, `x > 0`

). We can use a
symbolic `Boolean`

as part of a symbolic expression such as a
`Piecewise`

:

```
>>> from sympy import Piecewise
>>> p = Piecewise((1, x > 0), (2, True))
>>> p
Piecewise((1, x > 0), (2, True))
>>> p.subs(x, 3)
1
```

Here `p`

represents an expression that will be equal to `1`

if `x > 0`

or otherwise it will be equal to `2`

. The unevaluated `Boolean`

inequality
`x > 0`

represents the condition for deciding the value of the expression
symbolically. When we substitute a value for `x`

the inequality will resolve
to `S.true`

and then the `Piecewise`

can evaluate to `1`

or `2`

.

The same will not work when using a fuzzy-bool instead of a symbolic
`Boolean`

:

```
>>> p2 = Piecewise((1, x.is_positive), (2, True))
Traceback (most recent call last):
...
TypeError: Second argument must be a Boolean, not `NoneType`
```

The `Piecewise`

can not use `None`

as the condition because unlike the
inequality `x > 0`

it gives no information. With the inequality it is
possible to decide in future if the condition might `True`

or `False`

once a value for `x`

is known. A value of `None`

can not be used in that
way so it is rejected.

Note

We can use `True`

in the `Piecewise`

because `True`

sympifies
to `S.true`

. Sympifying `None`

just gives `None`

again which
is not a valid symbolic SymPy object.

There are many other symbolic `Boolean`

types in SymPy. The same
considerations about the differences between fuzzy bool and symbolic
`Boolean`

apply to all other SymPy `Boolean`

types. To give
a different example there is `Contains`

which represents the
statement that an object is contained in a set:

```
>>> 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
```

The Python operator corresponding to `Contains`

is `in`

. A quirk of
`in`

is that it can only evaluate to a `bool`

(`True`

or `False`

) so
if the result is indeterminate then an exception will be raised:

```
>>> 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)
```

The exception can be avoided by using `Contains(x, Reals)`

or
`Reals.contains(x)`

rather than `x in Reals`

.

## Three-valued logic with fuzzy bools¶

Whether we use the fuzzy-bool or symbolic `Boolean`

we always need to be
aware of the possibility that a query might be indeterminate. How to write
code that handles this is different in the two cases though. We will look at
fuzzy-bools first.

Consider the following function:

```
>>> 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
```

The `both_positive`

function is supposed to tell us whether or not `a`

and
`b`

are both positive. However the `both_positive`

function will fail if
either of the `is_positive`

queries gives `None`

:

```
>>> 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
```

Note

We need to sympify the arguments to this function using `S`

because the assumptions are only defined on SymPy objects and not
regular Python `int`

objects.

Here `False`

is incorrect because it is *possible* that `x`

is positive in
which case both arguments would be positive. We get `False`

here because
`x.is_positive`

gives `None`

and Python will treat `None`

as “falsey”.

In order to handle all possible cases correctly we need to separate the logic
for identifying the `True`

and `False`

cases. An improved function might
be:

```
>>> 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
```

This function now can handle all cases of `True`

, `False`

or `None`

for
both `a`

and `b`

and will always return a fuzzy bool representing whether
the statement “`a`

and `b`

are both positive” is true, false or unknown:

```
>>> 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
```

Another case that we need to careful of when using fuzzy-bools is negation
with Python’s `not`

operator e.g.:

```
>>> x = Symbol('x')
>>> print(x.is_positive)
None
>>> not x.is_positive
True
```

The correct negation of a fuzzy bool `None`

is `None`

again. If we do not
know whether the statement “`x`

is positive” is `True`

or `False`

then
we also do not know whether its negation “`x`

is not positive” is `True`

or `False`

. The reason we get `True`

instead is again because `None`

is
considered “falsey”. When `None`

is used with a logical operator such as
`not`

it will first be converted to a `bool`

and then negated:

```
>>> bool(None)
False
>>> not bool(None)
True
>>> not None
True
```

The fact that `None`

is treated as falsey can be useful if used correctly.
For example we may want to do something only if `x`

is known to positive in
which case we can do

```
>>> 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
```

Provided we understand that an alternate condition branch refers to two cases
(`False`

and `None`

) then this can be a useful way of writing
conditionals. When we really do need to distinguish all cases then we need to
use things like `x.is_positive is False`

. What we need to be careful of
though is using Python’s binary logic operators like `not`

or `and`

with
fuzzy bools as they will not handle the indeterminate case correctly.

In fact SymPy has internal functions that are designed to handle fuzzy-bools correctly:

```
>>> 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
```

Using the `fuzzy_and`

function we can write the `both_positive`

function
more simply:

```
>>> def both_positive_best(a, b):
... """ask whether a and b are both positive"""
... return fuzzy_and([a.is_positive, b.is_positive])
```

Making use of `fuzzy_and`

, `fuzzy_or`

and `fuzzy_not`

leads to simpler
code and can also reduce the chance of introducing a logic error because the
code can look more like it would in the case of ordinary binary logic.

## Three-valued logic with symbolic Booleans¶

When working with symbolic `Boolean`

rather than fuzzy-bool the issue of
`None`

silently being treated as falsey does not arise so it is easier not
to end up with a logic error. However instead the indeterminate case will
often lead to an exception being raised if not handled carefully.

We will try to implement the `both_positive`

function this time using
symbolic `Boolean`

:

```
>>> 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
```

The first difference is that we return the symbolic `Boolean`

objects
`S.true`

and `S.false`

rather than `True`

and `False`

. The second
difference is that we test e.g. `a > 0`

rather than `a.is_positive`

.
Trying this out we get

```
>>> 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
```

What happens now is that testing `x > 0`

gives an exception when `x`

is
not known to be positive or not positive. More precisely `x > 0`

does not
give an exception but `if x > 0`

does and that is because the `if`

statement implicitly calls `bool(x > 0)`

which raises.

```
>>> 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
```

The Python expression `x > 0`

creates a SymPy `Boolean`

. Since in this
case the `Boolean`

can not evaluate to `True`

or `False`

we get an
unevaluated `StrictGreaterThan`

. Attempting to force that into a
`bool`

with `bool(x > 0)`

raises an exception. That is because a regular
Python `bool`

must be either `True`

or `False`

and neither of those
are known to be correct in this case.

The same kind of issue arises when using `and`

, `or`

or `not`

with
symbolic `Boolean`

. The solution is to use SymPy’s symbolic
`And`

, `Or`

and `Not`

or equivalently Python’s
bitwise logical operators `&`

, `|`

and `~`

:

```
>>> 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
```

As before we can make a better version of `both_positive`

if we avoid
directly using a SymPy `Boolean`

in an `if`

, `and`

, `or`

, or `not`

.
Instead we can test whether or not the `Boolean`

has evaluated to `S.true`

or `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)
```

Now with this version we don’t get any exceptions and if the result is
indeterminate we will get a symbolic `Boolean`

representing the conditions
under which the statement “`a`

and `b`

are both positive” would be true:

```
>>> 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
```

The last case shows that actually using the `And`

with a condition that is
known to be true simplifies the `And`

. In fact we have

```
>>> And(x > 0, 3 > 0)
x > 0
>>> And(4 > 0, 3 > 0)
True
>>> And(-1 > 0, 3 > 0)
False
```

What this means is that we can improve `both_positive_better`

. The
different cases are not needed at all. Instead we can simply return the
`And`

and let it simplify if possible:

```
>>> def both_positive_best(a, b):
... """ask whether a and b are both positive"""
... return And(a > 0, b > 0)
```

Now this will work with any symbolic real objects and produce a symbolic result. We can also substitute into the result to see how it would work for particular values:

```
>>> 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
```

The idea when working with symbolic `Boolean`

objects is as much as possible
to avoid trying to branch on them with `if/else`

and other logical operators
like `and`

etc. Instead think of computing a condition and passing it around
as a variable. The elementary symbolic operations like `And`

,
`Or`

and `Not`

can then take care of the logic for you.

Footnotes

- 1
Note that what is referred to in SymPy as a “fuzzy bool” is really about using three-valued logic. In normal usage “fuzzy logic” refers to a system where logical values are continuous in between zero and one which is something different from three-valued logic.