Frequently Asked Questions
Limits of Symbolic Computation
Transforming my function to a symbolic equation has failed. What do I do?
If you see the error:
ERROR: TypeError: non-boolean (Num) used in boolean context
this is likely coming from an algorithm which cannot be traced into a purely symbolic algorithm. Many numerical solvers, for instance, have this property. It shows up when you're doing something like if x < tol
. If x is a number, then this is true or false. If x is a symbol, then it's x < tol
, so Julia just cannot know how many iterations to do and throws an error.
This shows up in adaptive algorithms, for example:
function factorial(x)
out = x
while x > 1
x -= 1
out *= x
end
out
end
factorial (generic function with 1 method)
The number of iterations this algorithm runs for is dependent on the value of x
, and so there is no static representation of the algorithm. If x
is 5, then it's out = x*(x-1)*(x-2)*(x-3)*(x-4)
, while if x
is 3, then it's out = x*(x-1)*(x-2)
. It should thus be no surprise that:
using Symbolics
@variables x
try
factorial(x)
catch e
e
end
TypeError(:if, "", Bool, x > 1)
fails. It's not that there is anything wrong with this code, but it's not going to work because fundamentally this is not a symbolically-representable algorithm.
The space of algorithms which can be turned into symbolic algorithms is what we call quasi-static, that is, there is a way to represent the algorithm as static. Loops are allowed, but the amount of loop iterations should not require that you know the value of the symbol x
. If the algorithm is quasi-static, then Symbolics.jl tracing will produce the static form of the code, unrolling the operations, and generating a flat representation of the algorithm.
What can be done?
If you need to represent this function f
symbolically, then you'll need to make sure it's not traced and instead is directly represented in the underlying computational graph. Just like how sqrt(x)
symbolically does not try to represent the underlying algorithm, this must be done to your f
. This is done by doing @register_symbolic f(x)
. If you have to define things like derivatives to f
, then the function registration documentation.
Equality and set membership tests
Comparing symbols with ==
produces a symbolic equality, not a Bool
. To produce a Bool
, call isequal
.
To test if a symbol is part of a collection of symbols, i.e., a vector, either create a Set
and use in
, e.g.
try
x in [x]
catch e
e
end
TypeError(:if, "", Bool, x == x)
x in Set([x])
true
any(isequal(x), [x])
true
If ==
is used instead, you will receive TypeError: non-boolean (Num) used in boolean context
. What this error is telling you is that the symbolic x == y
expression is being used where a Bool
is required, such as if x == y
, and since the symbolic expression is held lazily this will error because the appropriate branch cannot be selected (since x == y
is unknown for arbitrary symbolic values!). This is why the check isequal(x,y)
is required, since this is a non-lazy check of whether the symbol x
is always equal to the symbol y
, rather than an expression of whether x
and y
currently have the same value.
Understanding the Difference Between the Julia Variable and the Symbolic Variable
In the most basic usage of Symbolics, the name of the Julia variable and the symbolic variable are the same. For example, when we do:
@variables a
\[ \begin{equation} \left[ \begin{array}{c} a \\ \end{array} \right] \end{equation} \]
the name of the symbolic variable is a
and same with the Julia variable. However, we can de-couple these by setting a
to a new symbolic variable, for example:
b = only(@variables(a))
\[ \begin{equation} a \end{equation} \]
Now the Julia variable b
refers to the variable named a
. However, the downside of this current approach is that it requires that the user writing the script knows the name a
that they want to place to the variable. But what if for example we needed to get the variable's name from a file?
To do this, one can interpolate a symbol into the @variables
macro using $
. For example:
a = :c
b = only(@variables($a))
\[ \begin{equation} c \end{equation} \]
In this example, @variables($a)
created a variable named c
, and set this variable to b
.