Types
Operator Enum
All equations are represented as a tree of operators. Each node in this tree specifies its operator with an integer - which indexes an enum
of operators. This enum
is defined as follows:
DynamicExpressions.OperatorEnumModule.OperatorEnum
— TypeOperatorEnum
Defines an enum over operators, along with their derivatives.
Fields
binops
: A tuple of binary operators. Scalar input type.unaops
: A tuple of unary operators. Scalar input type.diff_binops
: A tuple of Zygote-computed derivatives of the binary operators.diff_unaops
: A tuple of Zygote-computed derivatives of the unary operators.
Construct this operator specification as follows:
DynamicExpressions.OperatorEnumModule.OperatorEnum
— MethodOperatorEnum(; binary_operators=[], unary_operators=[], enable_autodiff::Bool=false, define_helper_functions::Bool=true)
Construct an OperatorEnum
object, defining the possible expressions. This will also redefine operators for Node
types, as well as show
, print
, and (::Node)(X)
. It will automatically compute derivatives with Zygote.jl
.
Arguments
binary_operators::Vector{Function}
: A vector of functions, each of which is a binary operator.unary_operators::Vector{Function}
: A vector of functions, each of which is a unary operator.enable_autodiff::Bool=false
: Whether to enable automatic differentiation.define_helper_functions::Bool=true
: Whether to define helper functions for creating and evaluating node types. Turn this off when doing precompilation. Note that these are not needed for the package to work; they are purely for convenience.
This is just for scalar operators. However, you can use the following for more general operators:
DynamicExpressions.OperatorEnumModule.GenericOperatorEnum
— MethodGenericOperatorEnum(; binary_operators=[], unary_operators=[], define_helper_functions::Bool=true)
Construct a GenericOperatorEnum
object, defining possible expressions. Unlike OperatorEnum
, this enum one will work arbitrary operators and data types. This will also redefine operators for Node
types, as well as show
, print
, and (::Node)(X)
.
Arguments
binary_operators::Vector{Function}
: A vector of functions, each of which is a binary operator.unary_operators::Vector{Function}
: A vector of functions, each of which is a unary operator.define_helper_functions::Bool=true
: Whether to define helper functions for creating and evaluating node types. Turn this off when doing precompilation. Note that these are not needed for the package to work; they are purely for convenience.
By default, these operators will define helper functions for constructing trees, so that you can write Node(;feature=1) + Node(;feature=2)
instead of Node(1, Node(;feature=1), Node(;feature=2))
(assuming +
is the first operator). You can turn this off with define_helper_functions=false
.
For other operators not found in Base
, including user-defined functions, you may use the @extend_operators
macro:
DynamicExpressions.OperatorEnumConstructionModule.@extend_operators
— Macro@extend_operators operators
Extends all operators defined in this operator enum to work on the Node
type. While by default this is already done for operators defined in Base
when you create an enum 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.
This will extend the operators you have passed to work with Node
types, so that it is easier to construct expression trees.
Note that you are free to use the Node
constructors directly. This is a more robust approach, and should be used when creating libraries which use DynamicExpressions.jl
.
Equations
Equations are specified as binary trees with the Node
type, defined as follows:
DynamicExpressions.EquationModule.Node
— TypeNode{T}
Node defines a symbolic expression stored in a binary tree. A single Node
instance is one "node" of this tree, and has references to its children. By tracing through the children nodes, you can evaluate or print a given expression.
Fields
degree::Int
: Degree of the node. 0 for constants, 1 for unary operators, 2 for binary operators.constant::Bool
: Whether the node is a constant.val::T
: Value of the node. Ifdegree==0
, andconstant==true
, this is the value of the constant. It has a type specified by the overall type of theNode
(e.g.,Float64
).feature::Int
(optional): Index of the feature to use in the case of a feature node. Only used ifdegree==0
andconstant==false
. Only defined ifdegree == 0 && constant == false
.op::Int
: Ifdegree==1
, this is the index of the operator inoperators.unaops
. Ifdegree==2
, this is the index of the operator inoperators.binops
. In other words, this is an enum of the operators, and is dependent on the specificOperatorEnum
object. Only defined ifdegree >= 1
l::Node{T}
: Left child of the node. Only defined ifdegree >= 1
. Same type as the parent node.r::Node{T}
: Right child of the node. Only defined ifdegree == 2
. Same type as the parent node. This is to be passed as the right argument to the binary operator.
There are a variety of constructors for Node
objects, including:
DynamicExpressions.EquationModule.Node
— MethodNode([::Type{T}]; val=nothing, feature::Int=nothing) where {T}
Create a leaf node: either a constant, or a variable.
Arguments:
::Type{T}
, optionally specify the type of the node, if not already given by the type ofval
.val
, if you are specifying a constant, pass the value of the constant here.feature::Integer
, if you are specifying a variable, pass the index of the variable here.
Node(op::Int, l::Node)
Apply unary operator op
(enumerating over the order given) to Node
l
Node(op::Int, l::Node, r::Node)
Apply binary operator op
(enumerating over the order given) to Node
s l
and r
DynamicExpressions.EquationModule.Node
— MethodNode(op::Int, l::Node)
Apply unary operator op
(enumerating over the order given) to Node
l
DynamicExpressions.EquationModule.Node
— MethodNode(op::Int, l::Node, r::Node)
Apply binary operator op
(enumerating over the order given) to Node
s l
and r
DynamicExpressions.EquationModule.Node
— MethodNode(var_string::String)
Create a variable node, using the format "x1"
to mean feature 1
When you create an Options
object, the operators passed are also re-defined for Node
types. This allows you use, e.g., t=Node(; feature=1) * 3f0
to create a tree, so long as *
was specified as a binary operator.
When using these node constructors, types will automatically be promoted. You can convert the type of a node using convert
:
Base.convert
— Methodconvert(::Type{Node{T1}}, n::Node{T2}) where {T1,T2}
Convert a Node{T2}
to a Node{T1}
. This will recursively convert all children nodes to Node{T1}
, using convert(T1, tree.val)
at constant nodes.
Arguments
::Type{Node{T1}}
: Type to convert to.tree::Node{T2}
: Node to convert.
You can set a tree
(in-place) with set_node!
:
DynamicExpressions.EquationModule.set_node!
— Methodset_node!(tree::Node{T}, new_tree::Node{T}) where {T}
Set every field of tree
equal to the corresponding field of new_tree
.
You can create a copy of a node with copy_node
:
DynamicExpressions.EquationModule.copy_node
— Methodcopy_node(tree::Node; preserve_sharing::Bool=false)
Copy a node, recursively copying all children nodes. This is more efficient than the built-in copy. With preserve_sharing=true
, this will also preserve linkage between a node and multiple parents, whereas without, this would create duplicate child node copies.
id_map is a map from objectid(tree)
to copy(tree)
. We check against the map before making a new copy; otherwise we can simply reference the existing copy. Thanks to Ted Hopp.
Note that this will not preserve loops in graphs.