/
InterfaceDynamicExpressions.jl
217 lines (177 loc) · 7.37 KB
/
InterfaceDynamicExpressions.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
module InterfaceDynamicExpressionsModule
import DynamicExpressions:
Node,
eval_tree_array,
eval_diff_tree_array,
eval_grad_tree_array,
symbolic_to_node,
node_to_symbolic,
print_tree,
string_tree,
differentiable_eval_tree_array
using SymbolicUtils: SymbolicUtils
using DynamicExpressions: DynamicExpressions
import ..CoreModule: Options
"""
eval_tree_array(tree::Node, X::AbstractArray, options::Options; kws...)
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.
# Arguments
- `tree::Node`: The root node of the tree to evaluate.
- `X::AbstractArray`: The input data to evaluate the tree on.
- `options::Options`: Options used to define the operators used in the tree.
# Returns
- `(output, complete)::Tuple{AbstractVector, Bool}`: the result,
which is a 1D array, as well as if the evaluation completed
successfully (true/false). A `false` complete means an infinity
or nan was encountered, and a large loss should be assigned
to the equation.
"""
function eval_tree_array(tree::Node, X::AbstractArray, options::Options; kws...)
return eval_tree_array(tree, X, options.operators; turbo=options.turbo, kws...)
end
"""
eval_diff_tree_array(tree::Node, X::AbstractArray, options::Options, direction::Int)
Compute the forward derivative of an expression, using a similar
structure and optimization to eval_tree_array. `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.
- `X::AbstractArray`: The data matrix, with each column being a data point.
- `options::Options`: The options containing the operators used to create the `tree`.
`enable_autodiff` must be set to `true` when creating the options.
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, AbstractVector, Bool}`: the normal evaluation,
the derivative, and whether the evaluation completed as normal (or encountered a nan or inf).
"""
function eval_diff_tree_array(
tree::Node, X::AbstractArray, options::Options, direction::Int
)
return eval_diff_tree_array(tree, X, options.operators, direction)
end
"""
eval_grad_tree_array(tree::Node, X::AbstractArray, options::Options; variable::Bool=false)
Compute the forward-mode derivative of an expression, using a similar
structure and optimization to eval_tree_array. `variable` specifies whether
we should take derivatives with respect to features (i.e., `X`), or with respect
to every constant in the expression.
# Arguments
- `tree::Node`: The expression tree to evaluate.
- `X::AbstractArray`: The data matrix, with each column being a data point.
- `options::Options`: The options containing the operators used to create the `tree`.
`enable_autodiff` must be set to `true` when creating the options.
This is needed to create the derivative operations.
- `variable::Bool`: Whether to take derivatives with respect to features (i.e., `X` - with `variable=true`),
or with respect to every constant in the expression (`variable=false`).
# Returns
- `(evaluation, gradient, complete)::Tuple{AbstractVector, AbstractArray, Bool}`: the normal evaluation,
the gradient, and whether the evaluation completed as normal (or encountered a nan or inf).
"""
function eval_grad_tree_array(tree::Node, X::AbstractArray, options::Options; kws...)
return eval_grad_tree_array(tree, X, options.operators; kws...)
end
"""
differentiable_eval_tree_array(tree::Node, X::AbstractArray, options::Options)
Evaluate an expression tree in a way that can be auto-differentiated.
"""
function differentiable_eval_tree_array(
tree::Node, X::AbstractArray, options::Options; kws...
)
return differentiable_eval_tree_array(tree, X, options.operators; kws...)
end
"""
string_tree(tree::Node, options::Options; kws...)
Convert an equation to a string.
# Arguments
- `tree::Node`: The equation to convert to a string.
- `options::Options`: The options holding the definition of operators.
- `varMap::Union{Array{String, 1}, Nothing}=nothing`: what variables
to print for each feature.
"""
function string_tree(tree::Node, options::Options; kws...)
return string_tree(tree, options.operators; kws...)
end
"""
print_tree(tree::Node, options::Options; kws...)
Print an equation
# Arguments
- `tree::Node`: The equation to convert to a string.
- `options::Options`: The options holding the definition of operators.
- `varMap::Union{Array{String, 1}, Nothing}=nothing`: what variables
to print for each feature.
"""
function print_tree(tree::Node, options::Options; kws...)
return print_tree(tree, options.operators; kws...)
end
function print_tree(io::IO, tree::Node, options::Options; kws...)
return print_tree(io, tree, options.operators; kws...)
end
"""
convert(::Type{Node{T}}, tree::Node, options::Options; kws...)
Convert an equation to a different base type `T`.
"""
function Base.convert(::Type{Node{T}}, tree::Node, options::Options) where {T}
return convert(Node{T}, tree, options.operators)
end
function Base.convert(
s::typeof(SymbolicUtils.Symbolic), tree::Node, options::Options; kws...
)
return convert(s, tree, options.operators; kws...)
end
function Base.convert(
n::typeof(Node), x::Union{Number,SymbolicUtils.Symbolic}, options::Options; kws...
)
return convert(n, x, options.operators; kws...)
end
"""
node_to_symbolic(tree::Node, options::Options; kws...)
Convert an expression to SymbolicUtils.jl form.
"""
function node_to_symbolic(tree::Node, options::Options; kws...)
return node_to_symbolic(tree, options.operators; kws...)
end
"""
node_to_symbolic(eqn::T, options::Options; kws...) where {T}
Convert a SymbolicUtils.jl expression to SymbolicRegression.jl's `Node` type.
"""
function symbolic_to_node(
eqn::T, options::Options; kws...
) where {T<:SymbolicUtils.Symbolic}
return symbolic_to_node(eqn, options.operators; kws...)
end
"""
@extend_operators options
Extends all operators defined in this options object to work on the
`Node` type. While by default this is already done for operators defined
in `Base` when you create an options and pass `define_helper_functions=true`,
this does not apply to the user-defined operators. Thus, to do so, you must
apply this macro to the operator enum in the same module you have the operators
defined.
"""
macro extend_operators(options)
operators = :($(esc(options)).operators)
type_requirements = Options
quote
if !isa($(esc(options)), $type_requirements)
error("You must pass an options type to `@extend_operators`.")
end
DynamicExpressions.@extend_operators $operators
end
end
end