5. Boolean expressions and conditionals#
5.1. Relationals#
In addition to scalar-valued expressions, we can also construct boolean-valued expressions using relationals:
>>> x, y = sym.symbols('x, y')
>>> condition = x < y
>>> type(condition)
pywrenfold.sym.BooleanExpr
>>> print(condition.expression_tree_str())
Relational (<)
├─ Variable (x, unknown)
└─ Variable (y, unknown)
Warning
The __eq__
method in python corresponds to strict equality, not mathematical equivalence.
Hence the ==
operator will directly return True
if the two expression trees are
completely identical, and False
otherwise. To construct an equivalence relational, use
wrenfold.sym.eq()
instead.
>>> x == y
False
>>> sym.eq(x, y)
x == y
5.2. Conditional logic#
Boolean-valued expressions are relevant when creating conditional expressions. A ternary conditional
can be constructed using wrenfold.sym.where()
:
>>> f = sym.where(x > 0, x * y, -x / 5)
>>> f
where(0 < x, x*y, -x/5) # Note that `x > 0` was canonicalized as `0 < x`.
In the example above, \(f\left(x\right)\) is a piecewise function with a discontinuity in the
first derivative at \(x = 0\). At code-generation time, sym.where
will be converted to an
if-else branch. This can serve a number of purposes:
Skipping irrelevant computations.
Avoiding singularities or calculations that would introduce
NaN
values.Implementing piecewise continuous functions.
The result of the conditional is a scalar-valued expression, so it can be differentiated:
# Take the derivative of both branches.
>>> f.diff(x)
where(0 < x, y, -1/5)
# Take the derivative of both branches, twice.
# Since both sides evaluate to zero, the conditional is immediately simplified:
>>> f.diff(x, 2)
0
Note
When taking the derivative of sym.where
expressions, the contribution of the condition
itself is ignored and the two branches are differentiated to create a new piecewise function.
In the example above, the true derivative of the function \(f\left(x\right)\) is infinite
at \(x = 0\). We could insert the Dirac delta function to capture this, but this result
would not be very useful in a numerical evaluation context.
5.3. Casting booleans#
Boolean expressions cannot be multiplied or added like scalars, but they can be “casted” to scalars
using the Iverson bracket. The iverson bracket
evaluates to 1
if the boolean condition is true, and 0
if the condition is false.
>>> condition = x < y
>>> g = sym.iverson(condition)
>>> g
iverson(x < y)
# Substitute values that make the statement true:
>>> g.subs(x, 0.23).subs(y, 4)
1
This can be useful if you want to mask out a value without inserting a conditional:
# We can't multiply (x < 0) by sin(x) directly, but we can do this:
>>> h = sym.iverson(x < 0) * sym.sin(x)
>>> h
sin(x)*iverson(x < 0)
# Will be zero for all positive `x`:
>>> h.subs(x, 0.7)
0