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.@variables — Macro
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])
4Symbolics.variable — Function
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.
Symbolics.variables — Function
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₃ˏ₆Symbolics.Equation — Type
struct EquationAn 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.
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]Symbolics.Inequality — Type
struct InequalityAn 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.
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 ≲ 0Symbolics.:≳ — 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 ≳ 0Symbolics.value — Function
value(x) -> Any
Equivalent to unwrap_const ∘ unwrap.
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
xNote that Julia converts irrationals — like π and ℯ — to Float64 whenever they are involved in arithmetic with other numbers, including integers. An expression like 2π 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.ifelse — Method
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 0ifelse 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 bySymbolicUtils.search_variables!.e: the symbolic expression to traverse.varlist: an optional collection restricting which expressions are treated as atomic variables.
Keywords
is_atomic: predicate used with thevarlistform to select variables.kwargs...: keyword arguments forwarded toSymbolicUtils.search_variables!.
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
ySymbolics.map_subscripts — Function
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)
"₋₁₂"Variable Parsing
For implementing custom variable-creating macros:
Symbolics._parse_vars — Function
_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.
Symbolics.parse_vars — Function
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.