Variable and Equation Types

Symbolics IR mirrors the Julia AST but allows for easy mathematical manipulation by itself following mathematical semantics. The base of the IR is the Sym type, which defines a symbolic variable. Registered (mathematical) functions on Syms (or istree objects) return an expression that istree. For example, op1 = x+y is one symbolic object and op2 = 2z is another, and so op1*op2 is another tree object. Then, at the top, an Equation, normally written as op1 ~ op2, defines the symbolic equality between two operations.


Sym, Term, and FnType are from SymbolicUtils.jl. Note that in Symbolics, we always use Sym{Real}, Term{Real}, and FnType{Tuple{Any}, Real}. To get the arguments of an istree object, use arguments(t::Term), and to get the operation, use operation(t::Term). However, note that one should never dispatch on Term or test isa Term. Instead, one needs to use SymbolicUtils.istree to check if arguments and operation is defined.


Define one or more unknown variables.

@variables t α σ(..) β[1:2]
@variables w(..) x(t) y z(t, α, x)

expr = β[1]* x + y^α + σ(3) * (z - t) - β[2] * w(t - 1)

(..) signifies that the value should be left uncalled.

Symbolics supports creating variables that denote an array of some size.

julia> @variables x[1:3]
1-element Vector{Symbolics.Arr{Num, 1}}:

julia> @variables y[1:3, 1:6] # support for  tensors
1-element Vector{Symbolics.Arr{Num, 2}}:

julia> @variables t z(t)[1:3] # also works for dependent variables
2-element Vector{Any}:

A symbol or expression that represents an array can be turned into an array of symbols or expressions using the scalarize function.

julia> Symbolics.scalarize(z)
3-element Vector{Num}:

Note that @variables returns a vector of all the defined variables.

@variables can also take runtime symbol values by the $ interpolation operator, and in this case, @variables doesn't automatically assign the value, instead, it only returns a vector of symbolic variables. All the rest of the syntax also applies here.

julia> a, b, c = :runtime_symbol_value, :value_b, :value_c
(:runtime_symbol_value, :value_b, :value_c)

julia> vars = @variables t $a $b(t) $c(t)[1:3]
4-element Vector{Any}:

julia> (t, a, b, c)
(t, :runtime_symbol_value, :value_b, :value_c)
struct Equation

An equality relationship between two expressions.


  • lhs: The expression on the left-hand side of the equation.

  • rhs: The expression on the right-hand side of the equation.

~(lhs, rhs) -> Any

Create an Equation out of two Num instances, or an Num and a Number.


julia> using Symbolics

julia> @variables x y;

julia> @variables A[1:3, 1:3] B[1:3, 1:3];

julia> x ~ y
x ~ y

julia> x - y ~ 0
x - y ~ 0

julia> A ~ B
(broadcast(~, A, B))[1:3,1:3]

julia> A .~ 3x
(broadcast(~, A, 3x))[1:3,1:3]

A note about functions restricted to Numbers

Sym and Term objects are NOT subtypes of Number. Symbolics provides a simple wrapper type called Num which is a subtype of Real. Num wraps either a Sym or a Term or any other object, defines the same set of operations as symbolic expressions and forwards those to the values it wraps. You can use Symbolics.value function to unwrap a Num.

By default, the @variables macros return Num-wrapped objects to allow calling functions which are restricted to Number or Real.

using Symbolics
@variables t x y z(t);
Symbolics.operation(Symbolics.value(x + y))
+ (generic function with 542 methods)

\[ \begin{equation} z \end{equation} \]

Symbolics.arguments(Symbolics.value(x + y))
2-element Vector{Any}:

Note that Julia converts irrationals — like π and — to Float64 whenever they are involved in arithmetic with other numbers, including integers. An expression like will be converted to a float immediately, so an expression like 2π * x will leave the symbolic x multiplied by a Float64. It may be preferable to have a symbolic representation of π also, which can be achieved with Num(π). For generic programming, it may be helpful to simply redefine the variable π to be of the same type as some other argument, as in

function f(x)
    let π=oftype(x, π)
        1 + (2//3 + 4π/5) * x

\[ \begin{equation} 1 + t \left( \frac{2}{3} + \frac{4}{5} \mathrm{identity}\left( \pi \right) \right) \end{equation} \]

This will work for any floating-point input, as well as symbolic input.

Symbolic Control Flow

Control flow can be expressed in Symbolics.jl in the following ways:

  • IfElse.ifelse(cond,x,y): this is a dispatch-able version of the ifelse function provided by IfElse.jl which allows for encoding conditionals in the symbolic branches.

Inspection Functions



Returns true if x is a term. If true, operation, arguments must also be defined for x appropriately.



If x is a term as defined by istree(x), operation(x) returns the head of the term if x represents a function call, for example, the head is the function being called.