Supported types and dispatch in Symbolics

There is a tension between types as representation of expression trees and types that are subtype of types already present in Julia.

We want to be able to deal with expression trees in a unified way and not constrain expression trees themselves to be under an abstract type in Julia's type hierarchy. (For example, if we said that all expression trees are subtype of Real, then we couldn't represent array operations using the same expression tree.). But we also want to be able to pass in expression trees into places in existing code that accept Real values.

We accomplish this by wrapping expression trees in a simple wrapper type which is a subtype of our desired abstract type. For example, we wrap expression trees in the type Num which is a subtype of Real to make it behave like a Real number.

The methods on Num objects are forwarded to the wrapped expression tree. And care is taken so that an expression tree never internally contains Num – this is both for performance and separation of concerns.

User-facing APIs in Symbolics always take wrapped objects like Num, they are then internally unwrapped for expression tree manipulation.

Due to it requiring such wrappers, we only fully support limited number of types as both the types of expression trees and the type as Julia sees them.

These types are

  • Real numbers (wrapped using Num)
  • complex numbers (stored as Complex{Num} where Complex is from Base Julia)
  • arrays of Real and complex numbers (wrapped using Arr, so Arr{Num} or Arr{Complex{Num}})

@variables and types

Use the syntax @variables x::T to create a symbol named x of symbolic type T. If T is a subtype of any of the above listed types which support a wrapper, the resulting variable will be wrapped in that type. As seen in the examples below, x,z,X,Z all have a suitable wrapper type, hence their types are shown. However, s being of symbolic type String does not have a corresponding wrapper supported by Symbolics. Hence it returns a Sym{String} object. This is the trivial expression tree of a single variable without a wrapper, and is not a subtype of String or AbstractString.

julia> @variables x::Real z::Complex{Real} (X::Real)[1:10, 1:10] (Z::Complex{Real})[1:10] s::String
4-element Vector{Any}:

julia> typeof(x)

julia> typeof(z)

julia> typeof(X)
Symbolics.Arr{Num, 2}

julia> typeof(Z)
Symbolics.Arr{Complex{Num}, 1}

julia> typeof(s)
SymbolicUtils.Sym{String, Nothing}