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 iscall objects) return an expression that iscall. 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.

Types

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 iscall 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.iscall to check if arguments and operation is defined.

Symbolics.@variablesMacro

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}}:
 x[1:3]

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

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

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

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

julia> Symbolics.scalarize(z)
3-element Vector{Num}:
 (z(t))[1]
 (z(t))[2]
 (z(t))[3]

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> length(@variables t $a $b(t) $c(t)[1:3])
4
source
Symbolics.variableFunction
variable(name::Symbol, idx::Integer...; T=Real)

Create a variable with the given name along with subscripted indices with the symtype=T. When T=FnType, it creates a symbolic function.

julia> Symbolics.variable(:x, 4, 2, 0)
x₄ˏ₂ˏ₀

julia> Symbolics.variable(:x, 4, 2, 0, T=Symbolics.FnType{Tuple{Real}, Real, Nothing})
x₄ˏ₂ˏ₀⋆

Also see variables.

source
Symbolics.variablesFunction
variables(name::Symbol, indices...)

Create a multi-dimensional array of individual variables named with subscript notation. Use @variables instead to create symbolic array variables (as opposed to array of variables). See variable to create one variable with subscripts.

julia> Symbolics.variables(:x, 1:3, 3:6)
3×4 Matrix{Num}:
 x₁ˏ₃  x₁ˏ₄  x₁ˏ₅  x₁ˏ₆
 x₂ˏ₃  x₂ˏ₄  x₂ˏ₅  x₂ˏ₆
 x₃ˏ₃  x₃ˏ₄  x₃ˏ₅  x₃ˏ₆
source
Symbolics.EquationType
struct Equation

An equality relationship between two expressions.

Fields

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

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

source
Base.:~Method
~(lhs, rhs) -> Any

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

Examples

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
A ~ B

julia> A .~ 3x
(broadcast(~, A, 3x))[1:3,1:3]
source
Symbolics.InequalityType
struct Inequality

An inequality relationship between two expressions.

Fields

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

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

  • relational_op: The relational operator of the inequality.

source
Symbolics.:≲Function
≲(lhs, rhs) -> Any

Create an Inequality out of two Num instances, or an Num and a Number. Unicode can be typed by writing \lesssim then pressing tab in the Julia REPL, and in many editors.

Examples

julia> using Symbolics

julia> @variables x y;

julia> x ≲ y
x ≲ y

julia> x - y ≲ 0
x - y ≲ 0
source
Symbolics.:≳Function
≳(lhs, rhs) -> Any

Create an Inequality out of two Num instances, or an Num and a Number. Unicode can be typed by writing \gtrsim then pressing tab in the Julia REPL, and in many editors.

Examples

julia> using Symbolics

julia> @variables x y;

julia> x ≳ y
x ≳ y

julia> x - y ≳ 0
x - y ≳ 0
source

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 937 methods)
Symbolics.operation(Symbolics.value(z))

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

Symbolics.arguments(Symbolics.value(x + y))
2-element ReadOnlyArrays.ReadOnlyVector{SymbolicUtils.BasicSymbolicImpl.var"typeof(BasicSymbolicImpl)"{SymReal}, SymbolicUtils.ArgsT{SymReal}}:
 y
 x

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
    end
end
f(t)

\[ \begin{equation} 1 + \left( \frac{2}{3} + \frac{4}{5} ~ \pi \right) ~ t \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 way:

Base.ifelseMethod
ifelse(cond::Num, x, y)

Symbolic conditional expression. Returns x if cond evaluates to true, and y if cond evaluates to false. This allows encoding conditional logic in symbolic expressions.

Examples

@variables a b c
ifelse(a > b, c, 0)  # Returns c if a > b, otherwise 0
source

ifelse is the default conditional. Two further variants pin how the conditional is lowered by build_function: ifelse_eager always evaluates both branches, while ifelse_branching emits an if/else so the untaken branch is never evaluated (useful when a branch is only valid when its condition holds). They build the same kind of symbolic expression as ifelse and differ only at code generation.

These functions are provided by SymbolicUtils and reexported by Symbolics. Their contracts and examples are documented in the [SymbolicUtils conditional API] (https://symbolicutils.juliasymbolics.org/api/#SymbolicUtils.ifelse_eager).

Inspection Functions

The expression-tree interface is owned by TermInterface. See its documentation for the contracts of iscall, operation, and arguments.

Variable Utilities

Symbolics.get_variables!Function
get_variables!(buffer, e; kwargs...)
get_variables!(buffer, e, varlist; is_atomic = SymbolicUtils.default_is_atomic, kwargs...)

Append the symbolic variables found in e to the supplied mutable buffer and return buffer. The expression is unwrapped before traversal, so the returned variables are not wrapped in Num.

Arguments

  • buffer: a mutable collection accepted by SymbolicUtils.search_variables!.
  • e: the symbolic expression to traverse.
  • varlist: an optional collection restricting which expressions are treated as atomic variables.

Keywords

Examples

julia> using Symbolics

julia> @variables x y

julia> buffer = Set{SymbolicUtils.BasicSymbolic}()
Set{SymbolicUtils.BasicSymbolic}()

julia> Symbolics.get_variables!(buffer, x + y) === buffer
true

julia> sort!(collect(buffer), by = string)
2-element Vector{SymbolicUtils.BasicSymbolic}:
 x
 y
source
Symbolics.map_subscriptsFunction
map_subscripts(indices)

Convert the decimal characters in an index to the Unicode subscript characters used in symbolic variable names.

Arguments

  • indices: an integer or other value whose string representation consists of characters in -0123456789.

Examples

julia> Symbolics.map_subscripts(-12)
"₋₁₂"
source

Variable Parsing

For implementing custom variable-creating macros:

Symbolics._parse_varsFunction
_parse_vars(macroname, type, x) -> Expr
_parse_vars(macroname, type, x, transform) -> Expr

The worker function for parse_vars. This returns the expanded code, exactly as it should be run. In other words, this does not require sanitization and the result should be passed through esc before returning from the macro. parse_vars does this automatically. This function also guarantees that the last expression in the returned Expr(:block) is an Expr(:vect) of the identifiers for the created variables.

source
Symbolics.parse_varsFunction
parse_vars(macroname, type, x) -> Expr
parse_vars(macroname, type, x, transform) -> Expr

Parse variables using the syntax expected by @variables. Used for implementing custom macros similar to @variables. macroname refers to the name of the macro creating the variables. This is stored in the VariableSource metadata of created variables. type is the default type of created variables. x is the tuple of expressions passed to the macro. transform is an optional function that takes constructed variables and performs custom postprocessing to them, returning the created variables. This function returns the Expr for constructing the parsed variables.

See also: _parse_vars.

source