Function Building and Compilation (build_function)

At any time, callable functions can be generated from Symbolics IR by using Symbolics.toexpr. This performs some cleaning to return an expression without extraneous pieces that commonly matches expressions one would write in functions like those for differential equation solvers and optimization libraries. These functions can be automatically parallelized and specialize on Julia types like static arrays and sparse matrices.

The core compilation process of Symbolics IR is build_function. build_function takes an operation or an AbstractArray of operations and generates a compilable version of the model for numerical solvers. The form of this output is dependent on the target. By default, the target outputs Julia code, but other formats, such as C, Stan, and MATLAB are available. For the default Julia target these can be generated as expressions which can then be evaluated into a callable function (expression=Val{false}), or returned as Julia source (expression=Val{true}). Non-Julia targets such as C, Stan, and MATLAB return source code as a String only (expression=Val{true}); they do not compile or return a callable.

build_function

Symbolics.build_function — Function
build_function(ex, args...;
               expression = Val{true},
               target = JuliaTarget(),
               parallel=nothing,
               kwargs...)

Generates a numerically-usable function from a Symbolics Num, a symbolic array (Arr), or an AbstractArray of Nums.

Arguments:

  • ex: The Num, Arr, or AbstractArray of Nums to compile
  • args: The arguments of the function
  • expression: Whether to generate code or whether to generate the compiled form. By default, expression = Val{true}, which means that the code for the function is returned. If Val{false}, then the returned value is compiled.

Return Value:

  • If ex is a scalar, a single function f(args...) is returned which computes the value of the expression.
  • If ex is an AbstractArray or an Arr, a 2-tuple of functions (f, f!) is returned:
    • f(args...): the out-of-place function, which computes and returns the output array.
    • f!(out, args...): the in-place function, which mutates out with the result and returns it.

When expression = Val{true}, these are returned as Exprs which can be evaled into functions. When expression = Val{false}, the returned functions are compiled RuntimeGeneratedFunctions which can be called directly, e.g.:

f, f! = build_function(ex, args...; expression = Val{false})
f(args)        # out-of-place evaluation
f!(out, args)  # in-place evaluation

Keyword Arguments:

  • target: The output target of the compilation process. Possible options are:
    • JuliaTarget: Generates a Julia function
    • CTarget: Generates a C function
    • StanTarget: Generates a function for compiling with the Stan probabilistic programming language
    • MATLABTarget: Generates an anonymous function for use in MATLAB and Octave environments
  • parallel: The kind of parallelism to use in the generated function. Defaults to SerialForm(), i.e. no parallelism, if ex is a single expression or an array containing <= 1500 non-zero expressions. If ex is an array of > 1500 non-zero expressions, then ShardedForm(80, 4) is used. See below for more on ShardedForm. Note that the parallel forms are not exported and thus need to be chosen like Symbolics.SerialForm(). The choices are:
    • SerialForm(): Serial execution.
    • ShardedForm(cutoff, ncalls): splits the output function into sub-functions which contain at most cutoff number of output rhss. These sub-functions are called by the top-level function that buildfunction returns. This helps in reducing the compile time of the generated function.
    • MultithreadedForm(): Multithreaded execution with a static split, evenly splitting the number of expressions per thread.
  • fname: Used by some targets for the name of the function in the target space.
  • iip_config: A 2-tuple of Bools (oop, iip) selecting whether the out-of-place and in-place functions are generated. Defaults to (true, true). A variant that is turned off is still returned as a stub so that the 2-tuple shape is kept, and calling the stub throws an ArgumentError. Only applies when ex is an array (scalar ex returns a single function).

Note that not all build targets support the full compilation interface. Check the individual target documentation for details.

source

Low-Level Code Generation

Consumers that already maintain a SymbolicUtils.IRStructure can generate Julia function expressions directly with codegen_function.

Symbolics.CodegenFunctionOptions — Type
CodegenFunctionOptions(; kwargs...)

Bundle of options controlling code generation in codegen_function.

Historically codegen_function accepted these as a kwargs... splat. Threading a kwargs... bundle means every function that forwards codegen options gets specialized (and recompiled) once per distinct set of keyword arguments, even when the values it forwards are irrelevant to it. CodegenFunctionOptions is a single, non-parametric concrete type: no matter which options are set, its type is always CodegenFunctionOptions, so functions that thread it through only need to be compiled once.

The keyword constructor mirrors the historical keyword arguments of codegen_function exactly, including their defaults. Unknown keyword arguments are ignored (as they were previously silently dropped by codegen_function), preserving backwards compatibility.

Fields:

  • nanmath: rewrite math functions to their NaN-safe variants.
  • wrap_code: a 2-tuple of transformations applied to the out-of-place and in-place Funcs respectively.
  • checkbounds: emit bounds checks (when false, generated code is wrapped in @inbounds).
  • iip_config: a 2-tuple of Bools indicating whether to generate the out-of-place and in-place functions respectively.
  • sort_addmul: sort the arguments of +/* for deterministic codegen.
  • optimize: optimization rules to apply, or nothing.
  • similarto: the array type (or name) to generate for array outputs, or nothing to infer it.
  • outputidxs: explicit output indices for in-place array codegen, or nothing.
  • skipzeros: skip writing structural zeros in in-place array codegen.
  • scalarize_single_element_regions: when false, a single-element region of an ArrayMaker output that holds an array expression is written through a view of the output instead of being scalarized. Arrays that disallow scalar indexing, such as GPU arrays, need false.
  • fuse_broadcasts: when true, each elementwise array expression is generated as one fused broadcast, slices of arguments are read through views, and ArrayMaker regions are written in place, so a GPU array takes one kernel per region rather than one per operation. Each fused expression is compiled separately, so this is off by default.
  • fuse_unalias: when false, fused ArrayMaker regions do not copy inputs that might share memory with the output. Set it only when no region reads memory that a region writes, except elements read at the same index they are written, as in an in-place update u[r] .= u[r] .+ dt .* f.
  • fuse_output_eltype: when true, floating-point scalar arguments of fused ArrayMaker regions, such as literals, are converted to the output's element type once per call, so a Float32 output is computed in Float32 arithmetic. This rounds them to that type, so it is off by default.
  • use_literal_pow: Whether to lower x ^ n for a constant literal n to Base.literal_pow(^, x, Val(n)). This improves the runtime performance of the generated code, but may be incompatible with symbolic tracing. Defaults to true.
source
Symbolics.codegen_function — Function
codegen_function(ir, expr, args[, options])
codegen_function(ir, expr, args; kwargs...)

Generate out-of-place and in-place Julia function expressions for expr using a SymbolicUtils.IRStructure. args is a vector whose elements describe each generated function argument as either a scalar symbolic value or a collection of symbolic values.

Pass a CodegenFunctionOptions as options, or pass the same settings as keyword arguments. The result is a 2-tuple containing the out-of-place and in-place function expressions. The requested iip_config and the shape of expr determine which variants are implemented.

This is the low-level code-generation interface for consumers that already maintain a SymbolicUtils.IRStructure. Prefer build_function when starting from ordinary symbolic expressions.

source

Target-Specific Definitions

MATLAB

Symbolics.MATLABTarget — Type
MATLABTarget()

A build_function target that generates an anonymous function expression for MATLAB and GNU Octave.

Arguments

None.

Keywords

None. Pass this target as target to build_function.

Examples

julia> @variables x
1-element Vector{Num}:
 x

julia> build_function(x^2, x; target = MATLABTarget()) isa String
true
source
Symbolics._build_function — Method
_build_function(target::JuliaTarget, rhss::AbstractArray, args...;
                   conv=toexpr,
                   expression = Val{true},
                   expression_module = @__MODULE__(),
                   checkbounds = false,
                   postprocess_fbody=ex -> ex,
                   linenumbers = false,
                   outputidxs=nothing,
                   skipzeros = false,
                   force_SA = false,
                   wrap_code = (nothing, nothing),
                   fillzeros = skipzeros && !(rhss isa SparseMatrixCSC),
                   states = LazyState(),
                   iip_config = (true, true),
                   parallel=nothing, cse = false, kwargs...)

Build function target: JuliaTarget

_build_function(target::JuliaTarget, rhss, args...;
                conv = toexpr,
                expression = Val{true},
                checkbounds = false,
                linenumbers = false,
                headerfun = addheader, outputidxs=nothing,
                convert_oop = true, force_SA = false,
                skipzeros = outputidxs===nothing,
                fillzeros = skipzeros && !(typeof(rhss)<:SparseMatrixCSC),
                parallel=SerialForm(), kwargs...)

Generates a Julia function which can then be utilized for further evaluations. If expression=Val{false}, the return is a Julia function which utilizes RuntimeGeneratedFunctions.jl to be free of world-age issues.

If the rhss is a scalar, the generated function is a function with a scalar output. Otherwise, if it's an AbstractArray, the output is two functions, one for out-of-place AbstractArray output and a second which is a mutating function. The outputted functions match the given argument order, i.e., f(u,p,args...) for the out-of-place and scalar functions and f!(du,u,p,args..) for the in-place version.

Special Keyword Arguments:

  • parallel: The kind of parallelism to use in the generated function. Defaults to SerialForm(), i.e. no parallelism. Note that the parallel forms are not exported and thus need to be chosen like Symbolics.SerialForm(). The choices are:
    • SerialForm(): Serial execution.
    • ShardedForm(cutoff, ncalls): splits the output function into sub-functions which contain at most cutoff number of output rhss. These sub-functions are called by the top-level function that buildfunction returns.
    • MultithreadedForm(): Multithreaded execution with a static split, evenly splitting the number of expressions per thread.
  • conv: The conversion function of symbolic types to Expr. By default, this uses the toexpr function.
  • checkbounds: For whether to enable bounds checking inside the generated function. Defaults to false, meaning that @inbounds is applied.
  • linenumbers: Determines whether the generated function expression retains the line numbers. Defaults to true.
  • convert_oop: Determines whether the OOP version should try to convert the output to match the type of the first input. This is useful for cases like LabelledArrays or other array types that carry extra information. Defaults to true.
  • force_SA: Forces the output of the OOP version to be a StaticArray. Defaults to false, and outputs a static array when the first argument is a static array.
  • similarto: An AbstractArray subtype which controls the type of the returned array for the OOP version. If provided, it ignores the value of force_SA.
  • skipzeros: Whether to skip filling zeros in the in-place version if the filling function is 0.
  • fillzeros: Whether to perform fill(out,0) before the calculations to ensure safety with skipzeros.
  • iip_config: A 2-tuple of Bools (oop, iip) selecting whether the out-of-place and in-place functions are generated. Defaults to (true, true). A variant that is turned off is still returned as a stub so that the 2-tuple shape is kept, and calling the stub throws an ArgumentError. Only applies when rhss is an array (scalar rhss returns a single function).
source
Symbolics._build_function — Method

Build function target: CTarget

_build_function(target::CTarget, eqs::Array{<:Equation}, args...;
                conv = toexpr, expression = Val{true},
                fname = :diffeqf,
                lhsname=:du,rhsnames=[Symbol("RHS$i") for i in 1:length(args)],
                libpath=tempname(), compiler=:gcc)

This builds an in-place C function. Only works on arrays of equations. expression must be Val{true} (the default); the return value is C source code as a String. expression=Val{false} is not supported because CTarget emits C, not Julia, and Symbolics cannot guarantee a C compiler or library path. Pass expression=Val{true} (not a Bool such as true). The keyword arguments libpath and compiler are accepted for API compatibility but are unused.

source
Symbolics._build_function — Method

Build function target: StanTarget

_build_function(target::StanTarget, eqs::Array{<:Equation}, vs, ps, iv;
                conv = toexpr, expression = Val{true},
                fname = :diffeqf, lhsname=:internal_var___du,
                rhsnames=[:internal_var___u,:internal_var___p,:internal_var___t])

This builds an in-place Stan function compatible with the Stan differential equation solvers. Unlike other build targets, this one requires (vs, ps, iv) as the function arguments. Only allowed on arrays of equations.

source
Symbolics._build_function — Method

Build function target: MATLABTarget

_build_function(target::MATLABTarget, eqs::Array{<:Equation}, args...;
                conv = toexpr, expression = Val{true},
                lhsname=:internal_var___du,
                rhsnames=[:internal_var___u,:internal_var___p,:internal_var___t])

This builds an out of place anonymous function @(t,rhsnames[1]) to be used in MATLAB. Compatible with the MATLAB differential equation solvers. Only allowed on expressions, and arrays of expressions.

source

Limitations

build_function