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