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