Code generation
Note: this feature is experimental and the API might change frequently
toexpr(ex) turns any expression (ex) into the equivalent Expr which is suitable for eval. The SymbolicUtils.Code module provides some combinators which provides the ability to construct more complex expressions than just function calls. These include:
- Let blocks
- Functions with arguments and keyword arguments
- Functions with arguments which are to be de-structured
- Expressions that set array elements in-place
- Expressions that create an array similar in type to a reference array (currently supports
Array,StaticArrays.SArray, andLabelledArrays.SLArray) - Expressions that create sparse arrays
Do using SymbolicUtils.Code to get the following bindings
toexpr
SymbolicUtils.Code.toexpr — Functiontoexpr(ex, [st,])Convert a symbolic expression into an Expr, suitable to be passed into eval.
For example,
julia> @syms a b
(a, b)
julia> toexpr(a+b)
:((+)(a, b))
julia> toexpr(a+b) |> dump
Expr
head: Symbol call
args: Array{Any}((3,))
1: + (function of type typeof(+))
2: Symbol a
3: Symbol bNote that the function is an actual function object.
For more complex expressions, see other code-related combinators,
Namely Assignment, Let, Func, SetArray, MakeArray, MakeSparseArray and MakeTuple.
To make your own type convertible to Expr using toexpr define toexpr(x, st) and forward the state st in internal calls to toexpr. st is state used to know when to leave something like y(t) as it is or when to make it var"y(t)". E.g. when y(t) is itself the argument of a function rather than y.
Code Combinators
These are all exported when you do using SymbolicUtils.Code
SymbolicUtils.Code.Assignment — TypeAssignment(lhs, rhs)An assignment expression. Shorthand lhs ← rhs (\leftarrow)
SymbolicUtils.Code.Let — TypeLet(assignments, body[, let_block])A Let block.
assignmentsis a vector ofAssignmentsbodyis the body of the let blocklet_blockboolean (default=true) – do not create a let block if false.
SymbolicUtils.Code.Func — TypeFunc(args, kwargs, body[, pre])A function.
argsis a vector of expressionskwargsis a vector ofAssignmentsbodyis the body of the functionprea vector of expressions to be prepended to the function body, for example, it could be[Expr(:meta, :inline), Expr(:meta, :propagate_inbounds)]to create an@inline @propagate_inboundsfunction definition.
Special features in args:
- args can contain
DestructuredArgs - call expressions
For example,
julia> @syms a b c t f(d) x(t) y(t) z(t)
(a, b, c, t, f(::Number)::Number, x(::Number)::Number, y(::Number)::Number, z(::Number)::Number)
julia> func = Func([a,x(t), DestructuredArgs([b, y(t)]), f], # args
[c ← 2, z(t) ← 42], # kwargs
f((a + b + c) / x(t) + y(t) + z(t)));
julia> toexpr(func)
:(function (a, var"x(t)", var"##arg#255", f; c = 2, var"z(t)" = 42)
let b = var"##arg#255"[1], var"y(t)" = var"##arg#255"[2]
f((+)(var"y(t)", var"z(t)", (*)((+)(a, b, c), (inv)(var"x(t)"))))
end
end)- the second argument is a
DestructuredArgs, in theExprform, it is given a random name, and is expected to receive a vector or tuple of size 2 containing the values ofbandy(t). The let block that is automatically generated "destructures" these arguments. x(t)andy(t)have been replaced withvar"x(t)"andvar"y(t)"symbols throughout
the generated Expr. This makes sure that we are not actually calling the expressions x(t) or y(t) but instead passing the right values in place of the whole expression.
fis also a function-like symbol, same asxandy, but since theargsarray containsfas itself rather than as say,f(t), it does not become avar"f(t)". The generated function expects a function of one argument to be passed in the position off.
An example invocation of this function is:
julia> executable = eval(toexpr(func))
#10 (generic function with 1 method)
julia> executable(1, 2.0, [2,3.0], x->string(x); var"z(t)" = sqrt(42))
"11.98074069840786"SymbolicUtils.Code.SpawnFetch — TypeSpawnFetch{ParallelType}(funcs [, args], reduce)Run every expression in funcs in its own task, the expression should be a Func object and is passed to Threads.Task(f). If Func takes arguments, then the arguments must be passed in as args–a vector of vector of arguments to each function in funcs. We don't use @spawn in order to support RuntimeGeneratedFunctions which disallow closures, instead we interpolate these functions or closures as smaller RuntimeGeneratedFunctions.
reduce function is used to combine the results of executing exprs. A SpawnFetch expression returns the reduced result.
Use Symbolics.MultithreadedForm ParallelType from the Symbolics.jl package to get the RuntimeGeneratedFunction version SpawnFetch.
ParallelType can be used to define more parallelism types SymbolicUtils supports Multithreaded type. Which spawns threaded tasks.
SymbolicUtils.Code.SetArray — TypeSetArray(inbounds::Bool, arr, elems[, return_arr::Bool])An expression representing setting of elements of arr.
By default, every element of elems is copied over to arr,
but if elems contains AtIndex(i, val) objects, then arr[i] = val is performed in its place.
inbounds is a boolean flag, true surrounds the resulting expression in an @inbounds.
return_arr is a flag which controls whether the generated begin..end block returns the arr. Defaults to false, in which case the block returns nothing.
SymbolicUtils.Code.MakeArray — TypeMakeArray(elems, similarto, [output_eltype=nothing])An expression which constructs an array.
elemsis the output arraysimilartocan either be a type, or some symbol that is an array whose type needs to be emulated. Ifsimilartois a StaticArrays.SArray, then the output array is also created as anSArray, similarly, anArraywill result in anArray, and aLabelledArrays.SLArraywill result in a labelled static array.output_eltype: if set, then forces the element type of the output array to be this. by default, the output type is inferred automatically.
You can define:
@inline function create_array(A::Type{<:MyArray},a
::Nothing, d::Val{dims}, elems...) where dims
# and
@inline function create_array(::Type{<:MyArray}, T, ::Val{dims}, elems...) where dimswhich creates an array of size dims using the elements elems and eltype T, to allow MakeArray to create arrays similarto MyArrays.
SymbolicUtils.Code.MakeSparseArray — TypeMakeSpaseArray(array)An expression which creates a SparseMatrixCSC or a SparseVector.
The generated expression contains the sparsity information of array,
it only creates the nzval field at run time.
SymbolicUtils.Code.MakeTuple — TypeMakeTuple(tup)Make a Tuple from a tuple of expressions.
SymbolicUtils.Code.LiteralExpr — TypeLiteralExpr(ex)Literally ex, an Expr. toexpr on LiteralExpr recursively calls toexpr on any interpolated symbolic expressions.
SymbolicUtils.Code.ForLoop — TypeForLoop(itervar, range, body)Generate a for loop of the form
for itervar in range
body
endOptimizations
Common Subexpression Elimination (CSE)
SymbolicUtils can perform CSE on symbolic expressions, and codegen primitives composed of the above "Code Combinators". This ensures that common subexpressions in the expression are only computed once. Note that this assumes that all functions called within the expression are pure. SymbolicUtils can and will change the number and order of function calls.
SymbolicUtils.Code.cse — Functioncse(expr) -> Any
Perform common subexpression elimination on an expression.
This optimization identifies repeated subexpressions and replaces them with variables to avoid redundant computation.
Arguments
expr: The expression to optimize
Returns
An optimized expression with common subexpressions eliminated
Examples
julia> expr = :(sin(x) + sin(x) * cos(y))
julia> cse(expr) # sin(x) is computed only onceSymbolicUtils.Code.cse_inside_expr — Functioncse_inside_expr(sym, f) -> Bool
Return true if CSE should descend inside sym, which has operation f.