Dolang's compiler

Dolang's compiler

The second main component of Dolang is a compiler that leverages its Symbolic manipulation routines to produce efficient Julia functions to evaluate equations and systems of equations.

FunctionFactory

The compiler mainly operates through one main type, the FunctionFactory.

FunctionFactory{T4}([dispatch::Type{T4}], eqs::Vector{Expr},
                     args::ArgType, params::ParamType; targets=Symbol[],
                     defs=Dict{Symbol,Any}(), funname::Symbol=:anon)

Construct a FunctionFactory that evaluates eqs using args and params.

args and params can either be flat Vector of Dolang symbols (not just julia Symbols), or an associative mapping from a grouped argument name, to a list of symbols in that group. See examples below:

# if ...
args = Dict(:x => [(:a, 0), (:b, 0)], :X => [(:a, 1)])
params = [:beta, :delta]

# compiled function would have arguments ...
# funname(..., x, X, p)
# where length(x) = 2, length(X) = 1, length(p) = 2
# if ...
args = [(:a, 0), (:b, 0), (:a, 1)]
params = [:beta, :delta]

# compiled function would have arguments ...
# funname(..., V, p)
# where length(V) = 3, length(p) = 2

Optional function arguments have the following purposes:

  • funname: instruct the Dolang compiler that the compiled function should have a particular name

  • targets: If non-empty and the symbols listed in targets and eqs contains statements of the form lhs = rhs

  • defs: Recursively substitute definitions into eqs (see csubs for more info)

  • dispatch: If this argument is passed, then the Dolang compiler will generate code for a function whose first argument must be an instance of type T4. This can be used to compile functions with the same funname, but different behavior based on the type of dispatch. Note that the argument to FunctionFactory must be the name of a type, not an instance of a type (e.g. Float64 instead of 1.0), but when calling the compiled code you must pass an instance instead of the name of the type (e.g. funname(1.0, ...) not funname(Float64, ...))

source

make_function

Once an instance of FunctionFactory is created, a single function is used to compile Julia a function with various methods for evaluating different orders of derivative of the factory's expressions. This function is named make_function and is called

make_function(ff::FunctionFactory)

Compile a function using data in ff; with methods for

  • various order of derivative

  • Allocating output arguments

  • Non-allocating functions that mutate the input argument

  • (partially-)Vectorized evaluation

See FunctionFactory for a description of how the fields of ff impact the generated code.

In non-vectorized evaluation, all function arguments should be vectors and will be unpacked into scalars according to ff.args and ff.params. If any argument is an AbstractMatrix, then each column of the matrix is assumed to be multiple observations of a single variable. All matrix arguments must have the same number of rows. Let this number be n. Any arguments passed as vectors will be implicitly repeated n times and the function will be evaluated with these vectors and the n observations of each matrix argument.

Note

The output will be an @generated function that can be evaluated at arbitrary order of analytical derivative – with derivative computation and function compilation happening at runtime upon the user's first request to evaluate that order derivative.

source

There is also a convenience method of make_function that takes built-in Julia objects as inputs, constructs the FunctionFactory for you, then calls the above method:

make_function(
    eqs::Vector{Expr}, variables::AbstractVector,
    to_diff::AbstractVector=1:length(variables);
    dispatch::DataType=SkipArg,
    targets=Symbol[], name::Symbol=:anon,
    defs=Dict()
)

Compile a Julia function by first constructing a FunctionFactory instance where

  • args is equal to variables[to_diff]

  • params is equal to variables[setdiff(to_diff, 1:length(variables))]

  • eqs, dispatch, defs, targets, and name are passed along to the FunctionFactory as arguments with the same name (except name, which becomes the funname argument to FunctionFactory)

See make_function(ff::FunctionFactory) for more details.

This method is less flexible than constructing the FunctionFactory by hand because you can only create that have one vector for arguments and one vector for symbols. Meaning you cannot construct an associative mapping for args or params that groups symbols together.

source