# 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`

— Type`OperatorEnum`

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.

Construct this operator specification as follows:

`DynamicExpressions.OperatorEnumModule.OperatorEnum`

— Method```
OperatorEnum(; binary_operators=[], unary_operators=[],
define_helper_functions::Bool=true,
empty_old_operators::Bool=true)
```

Construct an `OperatorEnum`

object, defining the possible expressions. This will also redefine operators for `AbstractExpressionNode`

types, as well as `show`

, `print`

, and `(::AbstractExpressionNode)(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.`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.`empty_old_operators::Bool=true`

: Whether to clear the old operators.

This is just for scalar operators. However, you can use the following for more general operators:

`DynamicExpressions.OperatorEnumModule.GenericOperatorEnum`

— Method```
GenericOperatorEnum(; binary_operators=[], unary_operators=[],
define_helper_functions::Bool=true, empty_old_operators::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 `AbstractExpressionNode`

types, as well as `show`

, `print`

, and `(::AbstractExpressionNode)(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.`empty_old_operators::Bool=true`

: Whether to clear the old operators.

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 [kws...]`

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.NodeModule.Node`

— Type`Node{T} <: AbstractExpressionNode{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::UInt8`

: 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. If`degree==0`

, and`constant==true`

, this is the value of the constant. It has a type specified by the overall type of the`Node`

(e.g.,`Float64`

).`feature::UInt16`

: Index of the feature to use in the case of a feature node. Only used if`degree==0`

and`constant==false`

. Only defined if`degree == 0 && constant == false`

.`op::UInt8`

: If`degree==1`

, this is the index of the operator in`operators.unaops`

. If`degree==2`

, this is the index of the operator in`operators.binops`

. In other words, this is an enum of the operators, and is dependent on the specific`OperatorEnum`

object. Only defined if`degree >= 1`

`l::Node{T}`

: Left child of the node. Only defined if`degree >= 1`

. Same type as the parent node.`r::Node{T}`

: Right child of the node. Only defined if`degree == 2`

. Same type as the parent node. This is to be passed as the right argument to the binary operator.

**Constructors**

```
Node([T]; val=nothing, feature=nothing, op=nothing, l=nothing, r=nothing, children=nothing, allocator=default_allocator)
Node{T}(; val=nothing, feature=nothing, op=nothing, l=nothing, r=nothing, children=nothing, allocator=default_allocator)
```

Create a new node in an expression tree. If `T`

is not specified in either the type or the first argument, it will be inferred from the value of `val`

passed or `l`

and/or `r`

. If it cannot be inferred from these, it will default to `Float32`

.

The `children`

keyword can be used instead of `l`

and `r`

and should be a tuple of children. This is to permit the use of splatting in constructors.

You may also construct nodes via the convenience operators generated by creating an `OperatorEnum`

.

You may also choose to specify a default memory allocator for the node other than simply `Node{T}()`

in the `allocator`

keyword argument.

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`

— Method`convert(::Type{<:AbstractExpressionNode{T1}}, n::AbstractExpressionNode{T2}) where {T1,T2}`

Convert a `AbstractExpressionNode{T2}`

to a `AbstractExpressionNode{T1}`

. This will recursively convert all children nodes to `AbstractExpressionNode{T1}`

, using `convert(T1, tree.val)`

at constant nodes.

**Arguments**

`::Type{AbstractExpressionNode{T1}}`

: Type to convert to.`tree::AbstractExpressionNode{T2}`

: AbstractExpressionNode to convert.

You can set a `tree`

(in-place) with `set_node!`

:

`DynamicExpressions.NodeModule.set_node!`

— Function`set_node!(tree::AbstractExpressionNode{T}, new_tree::AbstractExpressionNode{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.NodeModule.copy_node`

— Function`copy_node(tree::AbstractExpressionNode; break_sharing::Val=Val(false))`

Copy a node, recursively copying all children nodes. This is more efficient than the built-in copy.

If `break_sharing`

is set to `Val(true)`

, sharing in a tree will be ignored.

## Graph-Like Equations

You can describe an equation as a *graph* rather than a tree by using the `GraphNode`

type:

`DynamicExpressions.NodeModule.GraphNode`

— Type`GraphNode{T} <: AbstractExpressionNode{T}`

Exactly the same as `Node{T}`

, but with the assumption that some nodes will be shared. All copies of this graph-like structure will be performed with this assumption, to preserve structure of the graph.

**Examples**

```
julia> operators = OperatorEnum(;
binary_operators=[+, -, *], unary_operators=[cos, sin]
);
julia> x = GraphNode(feature=1)
x1
julia> y = sin(x) + x
sin(x1) + {x1}
julia> cos(y) * y
cos(sin(x1) + {x1}) * {(sin(x1) + {x1})}
```

Note how the `{}`

indicates a node is shared, and this is the same node as seen earlier in the string.

This has the same constructors as `Node{T}`

. Shared nodes are created simply by using the same node in multiple places when constructing or setting properties.

This makes it so you can have multiple parents for a given node, and share parts of an expression. For example:

```
julia> operators = OperatorEnum(;
binary_operators=[+, -, *], unary_operators=[cos, sin, exp]
);
julia> x1, x2 = GraphNode(feature=1), GraphNode(feature=2)
(x1, x2)
julia> y = sin(x1) + 1.5
sin(x1) + 1.5
julia> z = exp(y) + y
exp(sin(x1) + 1.5) + {(sin(x1) + 1.5)}
```

Here, the curly braces `{}`

indicate that the node is shared by another (or more) parent node.

This means that we only need to change it once to have changes propagate across the expression:

```
julia> y.r.val *= 0.9
1.35
julia> z
exp(sin(x1) + 1.35) + {(sin(x1) + 1.35)}
```

This also means there are fewer nodes to describe an expression:

```
julia> length(z)
6
julia> length(convert(Node, z))
10
```

where we have converted the `GraphNode`

to a `Node`

type, which breaks shared connections into separate nodes.

## Abstract Types

Both the `Node`

and `GraphNode`

types are subtypes of the abstract type:

`DynamicExpressions.NodeModule.AbstractExpressionNode`

— Type`AbstractExpressionNode{T} <: AbstractNode`

Abstract type for nodes that represent an expression. Along with the fields required for `AbstractNode`

, this additionally must have fields for:

`constant::Bool`

: Whether the node is a constant.`val::T`

: Value of the node. If`degree==0`

, and`constant==true`

, this is the value of the constant. It has a type specified by the overall type of the`Node`

(e.g.,`Float64`

).`feature::UInt16`

: Index of the feature to use in the case of a feature node. Only used if`degree==0`

and`constant==false`

. Only defined if`degree == 0 && constant == false`

.`op::UInt8`

: If`degree==1`

, this is the index of the operator in`operators.unaops`

. If`degree==2`

, this is the index of the operator in`operators.binops`

. In other words, this is an enum of the operators, and is dependent on the specific`OperatorEnum`

object. Only defined if`degree >= 1`

```

which can be used to create additional expression-like types. The supertype of this abstract type is the `AbstractNode`

type, which is more generic but does not have all of the same methods:

`DynamicExpressions.NodeModule.AbstractNode`

— Type`AbstractNode`

Abstract type for binary trees. Must have the following fields:

`degree::Integer`

: Degree of the node. Either 0, 1, or 2. If 1, then`l`

needs to be defined as the left child. If 2, then`r`

also needs to be defined as the right child.`l::AbstractNode`

: Left child of the current node. Should only be defined if`degree >= 1`

; otherwise, leave it undefined (see the the constructors of`Node{T}`

for an example). Don't use`nothing`

to represent an undefined value as it will incur a large performance penalty.`r::AbstractNode`

: Right child of the current node. Should only be defined if`degree == 2`

.