Evaluation
Given an expression tree specified with a Node
type, you may evaluate the expression over an array of data with the following command:
DynamicExpressions.EvaluateEquationModule.eval_tree_array
— Methodeval_tree_array(tree::Node, cX::AbstractMatrix{T}, operators::OperatorEnum)
Evaluate a binary tree (equation) over a given input data matrix. The operators contain all of the operators used. This function fuses doublets and triplets of operations for lower memory usage.
This function can be represented by the following pseudocode:
function eval(current_node)
if current_node is leaf
return current_node.value
elif current_node is degree 1
return current_node.operator(eval(current_node.left_child))
else
return current_node.operator(eval(current_node.left_child), eval(current_node.right_child))
The bulk of the code is for optimizations and pre-emptive NaN/Inf checks, which speed up evaluation significantly.
Returns
(output, complete)::Tuple{AbstractVector{T}, Bool}
: the result, which is a 1D array, as well as if the evaluation completed successfully (true/false). Afalse
complete means an infinity or nan was encountered, and a large loss should be assigned to the equation.
Assuming you are only using a single OperatorEnum
, you can also use the following short-hand by using the expression as a function:
operators = OperatorEnum(; binary_operators=[+, -, *], unary_operators=[cos])
tree = Node(; feature=1) * cos(Node(; feature=2) - 3.2)
tree(X)
This is possible because when you call OperatorEnum
, it automatically re-defines (::Node)(X)
to call the evaluation operation with the given operators loaded. It also re-defines
print,
show, and the various operators, to work with the
Node` type.
The Node
type does not know about which OperatorEnum
you used to create it. Thus, if you define an expression with one OperatorEnum
, and then try to evaluate it or print it with a different OperatorEnum
, you will get undefined behavior!
You can also work with arbitrary types, by defining a GenericOperatorEnum
instead. The notation is the same for eval_tree_array
, though it will return nothing
when it can't find a method, and not do any NaN checks:
Missing docstring for eval_tree_array(tree, cX::AbstractArray{T,N}, operators::GenericOperatorEnum) where {T,N}
. Check Documenter's build log for details.
Derivatives
DynamicExpressions.jl
can efficiently compute first-order derivatives of expressions with respect to variables or constants. This is done using either eval_diff_tree_array
, to compute derivative with respect to a single variable, or with eval_grad_tree_array
, to compute the gradient with respect all variables (or, all constants). Both use forward-mode automatic, but use Zygote.jl
to compute derivatives of each operator, so this is very efficient.
DynamicExpressions.EvaluateEquationDerivativeModule.eval_diff_tree_array
— Methodeval_diff_tree_array(tree::Node{T}, cX::AbstractMatrix{T}, operators::OperatorEnum, direction::Int)
Compute the forward derivative of an expression, using a similar structure and optimization to evaltreearray. direction
is the index of a particular variable in the expression. e.g., direction=1
would indicate derivative with respect to x1
.
Arguments
tree::Node
: The expression tree to evaluate.cX::AbstractMatrix{T}
: The data matrix, with each column being a data point.operators::OperatorEnum
: The operators used to create thetree
. Note thatoperators.enable_autodiff
must betrue
. This is needed to create the derivative operations.direction::Int
: The index of the variable to take the derivative with respect to.
Returns
(evaluation, derivative, complete)::Tuple{AbstractVector{T}, AbstractVector{T}, Bool}
: the normal evaluation, the derivative, and whether the evaluation completed as normal (or encountered a nan or inf).
DynamicExpressions.EvaluateEquationDerivativeModule.eval_grad_tree_array
— Methodeval_grad_tree_array(tree::Node{T}, cX::AbstractMatrix{T}, operators::OperatorEnum; variable::Bool=false)
Compute the forward-mode derivative of an expression, using a similar structure and optimization to evaltreearray. variable
specifies whether we should take derivatives with respect to features (i.e., cX), or with respect to every constant in the expression.
Arguments
tree::Node{T}
: The expression tree to evaluate.cX::AbstractMatrix{T}
: The data matrix, with each column being a data point.operators::OperatorEnum
: The operators used to create thetree
. Note thatoperators.enable_autodiff
must betrue
. This is needed to create the derivative operations.variable::Bool
: Whether to take derivatives with respect to features (i.e.,cX
- withvariable=true
), or with respect to every constant in the expression (variable=false
).
Returns
(evaluation, gradient, complete)::Tuple{AbstractVector{T}, AbstractMatrix{T}, Bool}
: the normal evaluation, the gradient, and whether the evaluation completed as normal (or encountered a nan or inf).
Alternatively, you can compute higher-order derivatives by using ForwardDiff
on the function differentiable_eval_tree_array
, although this will be slower.
DynamicExpressions.EvaluateEquationModule.differentiable_eval_tree_array
— Methoddifferentiable_eval_tree_array(tree::Node, cX::AbstractMatrix, operators::OperatorEnum)
Evaluate an expression tree in a way that can be auto-differentiated.
Printing
You can also print a tree as follows:
DynamicExpressions.EquationModule.string_tree
— Methodstring_tree(tree::Node, operators::AbstractOperatorEnum; kws...)
Convert an equation to a string.
Arguments
varMap::Union{Array{String, 1}, Nothing}=nothing
: what variables to print for each feature.
When you define an OperatorEnum
, the standard show
and print
methods will be overwritten to use string_tree
.