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
.
Dolang.FunctionFactory
— Type.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 nametargets
: If non-empty and the symbols listed intargets
andeqs
contains statements of the formlhs = rhs
–defs
: Recursively substitute definitions intoeqs
(seecsubs
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 typeT4
. This can be used to compile functions with the samefunname
, but different behavior based on the type ofdispatch
. Note that the argument toFunctionFactory
must be the name of a type, not an instance of a type (e.g.Float64
instead of1.0
), but when calling the compiled code you must pass an instance instead of the name of the type (e.g.funname(1.0, ...)
notfunname(Float64, ...)
)
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
Dolang.make_function
— Method.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.
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:
Dolang.make_function
— 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 tovariables[to_diff]
params
is equal tovariables[setdiff(to_diff, 1:length(variables))]
eqs
,dispatch
,defs
,targets
, andname
are passed along to theFunctionFactory
as arguments with the same name (exceptname
, which becomes thefunname
argument toFunctionFactory
)
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.