### Node and Tree Operations Example Source: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/examples/base_operations Demonstrates basic operations on nodes and trees within the DynamicExpressions.jl framework. This example is crucial for understanding how to build and manipulate symbolic expressions. ```julia include("../src/DynamicExpressions.jl") using .DynamicExpressions # Example usage for node and tree operations would go here. ``` -------------------------------- ### StructuredExpression Example Source: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/examples/base_operations Provides examples of using `StructuredExpression` in DynamicExpressions.jl. This covers more complex expression structures and their operations. ```julia include("../src/DynamicExpressions.jl") using .DynamicExpressions # Example usage for StructuredExpression would go here. ``` -------------------------------- ### Expression Example Source: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/examples/base_operations Illustrates the usage of the `Expression` type in DynamicExpressions.jl. This section focuses on creating, manipulating, and evaluating symbolic expressions. ```julia include("../src/DynamicExpressions.jl") using .DynamicExpressions # Example usage for Expression would go here. ``` -------------------------------- ### DynamicExpressions Node Manipulation Example Source: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/api Demonstrates how to use generic node accessors to get, set, and replace children of nodes, including transforming to different arities. ```Julia using DynamicExpressions # Define operators including ternary my_ternary(x, y, z) = x + y * z operators = OperatorEnum(((sin,), (+, *), (my_ternary,))) # (unary, binary, ternary) tree = Node{Float64,3}(; op=1, children=(Node{Float64,3}(; val=1.0), Node{Float64,3}(; val=2.0))) new_child = Node{Float64,3}(; val=3.0) left_child = get_child(tree, 1) right_child = get_child(tree, 2) set_child!(tree, new_child, 1) left, right = get_children(tree, Val(2)) # type stable # Transform to ternary operation child1, child2, child3 = Node{Float64,3}(; val=4.0), Node{Float64,3}(; val=5.0), Node{Float64,3}(; val=6.0) set_children!(tree, (child1, child2, child3)) tree.op = 1 # my_ternary tree.degree = 3 ``` -------------------------------- ### GraphNode Usage Example 2 Source: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/api Illustrates creating multiple shared nodes and how modifications to a shared node propagate to all its references, as shown in the output. ```julia operators = OperatorEnum(1 => (cos, sin, exp), 2 => (+, -, *)) x1, x2 = GraphNode(feature=1), GraphNode(feature=2) y = sin(x1) + 1.5 z = exp(y) + y ``` -------------------------------- ### GraphNode Usage Example 1 Source: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/api Demonstrates the creation and usage of GraphNode, highlighting how shared nodes are indicated by curly braces '{}' in the output string representation. ```julia operators = OperatorEnum(1 => (cos, sin), 2 => (+, -, *)) x = GraphNode(feature=1) y = sin(x) + x cos(y) * y ``` -------------------------------- ### Julia Structured Expression Example Source: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/api Example of defining a custom function for a structured expression type in Julia, demonstrating how to dispatch on specific expression structures. ```julia my_factory(nt) = nt.f + nt.g Base.show(io::IO, e::StructuredExpression{T,typeof(my_factory)}) where {T} = ... ``` -------------------------------- ### Get the Expression Tree Source: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/examples/expression Extracts the underlying tree structure from an Expression object. ```Julia tree = get_tree(parsed_expr) ``` -------------------------------- ### Accessing and Modifying Node Children Source: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/api Lists the functions available for interacting with the children of a `Node` in an expression tree. These include getting, setting, and retrieving all children. ```APIDOC get_child(node, i) set_child!(node, i, child_node) get_children(node) set_children!(node, children_nodes) ``` -------------------------------- ### Get Expression Contents Source: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/examples/expression Retrieves the raw contents of an expression, typically its tree structure. ```Julia get_contents(parsed_expr) ``` -------------------------------- ### Get Expression Metadata Source: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/examples/expression Fetches the metadata associated with an expression, including operators and variable names. ```Julia get_metadata(parsed_expr) ``` -------------------------------- ### DynamicExpressions.jl Expression Manipulation Functions Source: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/api Provides functions to get and set the contents and metadata of AbstractExpression objects. These utilities are crucial for manipulating expression trees and their associated data. ```APIDOC DynamicExpressions.ExpressionModule.get_contents(ex::AbstractExpression) - Get the contents of the expression. - Returns: The expression's content, which could be an AbstractExpressionNode or other data. DynamicExpressions.ExpressionModule.with_contents(ex::AbstractExpression, tree::AbstractExpressionNode) DynamicExpressions.ExpressionModule.with_contents(ex::AbstractExpression, tree::AbstractExpression) - Create a new expression based on 'ex' but with a different 'tree'. DynamicExpressions.ExpressionModule.get_metadata(ex::AbstractExpression) - Get the metadata of the expression. - Returns: The expression's metadata, which could be a NamedTuple or other data. DynamicExpressions.ExpressionModule.with_metadata(ex::AbstractExpression, metadata) DynamicExpressions.ExpressionModule.with_metadata(ex::AbstractExpression; metadata...) - Create a new expression based on 'ex' but with different metadata. DynamicExpressions.ExpressionModule.get_tree(ex::AbstractExpression) - Extract the expression tree from AbstractExpression. - Returns: An AbstractExpressionNode representing the expression tree. ``` -------------------------------- ### DynamicExpressions Generic Node Accessors Source: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/api Functions for accessing and modifying children of arbitrary arity nodes. Includes getting individual children, all children, and setting children. ```APIDOC DynamicExpressions.NodeModule.get_child(node::AbstractNode, i::Integer) - Returns the i-th child of a node (1-indexed). - Source: https://github.com/SymbolicML/DynamicExpressions.jl/blob/b80be2a3158e8175752ae15972962a5d1cc8a53c/src/Node.jl#L208-L212 DynamicExpressions.NodeModule.set_child!(node::AbstractNode, child::AbstractNode, i::Integer) - Replaces the i-th child of a node (1-indexed) with the given child node. - Returns the new child. - Updates the children tuple in-place. - Source: https://github.com/SymbolicML/DynamicExpressions.jl/blob/b80be2a3158e8175752ae15972962a5d1cc8a53c/src/Node.jl#L217-L222 DynamicExpressions.NodeModule.get_children(node::AbstractNode, n::Integer) DynamicExpressions.NodeModule.get_children(node::AbstractNode, ::Val{n}) - Returns a tuple of exactly n children of the node. - Use the `.degree` field of a node to determine the number of children. - Typically used within a `Base.Cartesian.@nif` statement for type stability. - Source: https://github.com/SymbolicML/DynamicExpressions.jl/blob/b80be2a3158e8175752ae15972962a5d1cc8a53c/src/Node.jl#L191-L199 DynamicExpressions.NodeModule.set_children!(node::AbstractNode, children::Tuple) - Replaces all children of a node with the given tuple. - If fewer children are provided than the node's maximum degree, remaining slots are filled with poison nodes. - Source: https://github.com/SymbolicML/DynamicExpressions.jl/blob/b80be2a3158e8175752ae15972962a5d1cc8a53c/src/Node.jl#L228-L233 ``` -------------------------------- ### Creating and Using StructuredExpression Source: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/examples/structured_expression Demonstrates the creation of basic Expression objects, their composition into a StructuredExpression, and evaluation on sample data. It shows how StructuredExpressions allow for predefined structures that are evaluated dynamically. ```julia using DynamicExpressions, Random ``` ```julia operators = OperatorEnum(1 => (cos, exp), 2 => (+, -, *, /)) variable_names = ["x", "y"] x = Expression(Node{Float64}(; feature=1); operators, variable_names) y = Expression(Node{Float64}(; feature=2); operators, variable_names) ``` ```julia f = x * x - cos(2.5f0 * y + -0.5f0) g = exp(2.0 - y * y) ex = StructuredExpression( (; f, g); structure=nt -> nt.f + nt.g, operators, variable_names ) ``` ```julia length(get_tree(ex)) ``` ```julia rng = Random.MersenneTwister(0) X = randn(rng, Float64, 2, 5) ex(X) ``` ```julia f(X) + g(X) ``` -------------------------------- ### Import DynamicExpressions and Random Source: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/examples/expression Imports necessary libraries for working with DynamicExpressions and random number generation. ```Julia using DynamicExpressions, Random ``` -------------------------------- ### DynamicExpressions.jl API Reference Source: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/examples/base_operations Comprehensive API documentation for the DynamicExpressions.jl library, detailing functions, types, and modules available for symbolic computation. ```APIDOC Module: DynamicExpressions Core Functionality: - `Node`: Represents a node in a symbolic expression tree. - `Expression`: Represents a symbolic expression, typically a tree of `Node`s. - `StructuredExpression`: Represents a structured symbolic expression. Key Functions: - `eval_tree(tree, params)`: Evaluates a symbolic expression tree with given parameters. - `string_tree(tree)`: Converts a symbolic expression tree to its string representation. - `Node(operator, children...)`: Constructor for creating a new Node. - `Expression(node)`: Constructor for creating an Expression from a Node. Utilities: - Functions for expression simplification, manipulation, and analysis. Examples: - Refer to the examples section for practical usage. Dependencies: - Requires Julia programming language. Related Packages: - SymbolicRegression.jl ``` -------------------------------- ### Define Operators and Variable Names Source: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/examples/expression Sets up the operator and variable name mappings required for creating mathematical expressions. ```Julia operators = OperatorEnum(1 => (sin, cos, exp), 2 => (+, -, *, /)) ``` ```Julia variable_names = ["x", "y"] ``` -------------------------------- ### Sample Node using NodeSampler Instance Source: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/utils Samples a node from a tree based on the configuration provided by a `NodeSampler` instance. This allows for custom sampling logic defined by the sampler. ```APIDOC Base.rand(rng::AbstractRNG, sampler::NodeSampler) Sample a node from a tree according to the sampler `sampler`. ``` -------------------------------- ### DynamicExpressions.jl API Reference Source: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/examples/expression This section details the API for the DynamicExpressions.jl package, outlining available functions, structures, and their usage. ```APIDOC DynamicExpressions.jl API: This API provides functionalities for creating, manipulating, and evaluating dynamic expressions in Julia. Core Components: * `Expression`: Represents a symbolic expression tree. - Constructors: - `Expression(op, args...)`: Creates an expression with a given operator and arguments. - Methods: - `Node(expr)`: Returns the root node of the expression. - `operands(expr)`: Returns the operands of an expression. - `operator(expr)`: Returns the operator of an expression. * `StructuredExpression`: A more structured representation of expressions, potentially with metadata. - Constructors: - `StructuredExpression(op, args...)`: Creates a structured expression. Examples: * Node and Tree Operations: Demonstrates basic operations on expression nodes and trees. URL: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/examples/base_operations/ * `Expression` example: Illustrates the creation and manipulation of `Expression` objects. URL: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/examples/expression/ * `StructuredExpression` example: Shows how to use `StructuredExpression` for more complex scenarios. URL: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/examples/structured_expression/ Eval Module: * Provides functions for evaluating expressions. URL: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/eval/ Utils Module: * Contains utility functions for working with expressions. URL: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/utils/ Version Information: * Stable Version: v2.0.0 * Development Version: v2.0 Source Code: * GitHub Repository: https://github.com/SymbolicML/DynamicExpressions.jl * Edit Source: https://github.com/SymbolicML/DynamicExpressions.jl/blob/master/test/test_expressions.jl ``` -------------------------------- ### Constructing and Evaluating Expressions Source: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/examples/expression Shows how to construct a new expression by combining existing ones using arithmetic operations and the exponential function. It then demonstrates evaluating this expression with a given input matrix. ```Julia ex = xs[1] * 2.1 - exp(3 * xs[2]) X = randn(rng, 5, 2) ex(X) ``` -------------------------------- ### DynamicExpressions.jl API Documentation Source: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/examples/structured_expression API documentation for DynamicExpressions.jl, detailing its various components and functionalities. ```APIDOC DynamicExpressions.jl API Reference: - **Home:** https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/ - **Examples:** - Node and Tree Operations: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/examples/base_operations/ - `Expression` example: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/examples/expression/ - `StructuredExpression` example: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/examples/structured_expression/ - **Eval:** https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/eval/ - **Utils:** https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/utils/ - **API:** https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/api/ **Version:** v2.0.0 stable, v2.0 dev **Source Code:** https://github.com/SymbolicML/DynamicExpressions.jl ``` -------------------------------- ### Basic Expression Creation and Evaluation Source: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/index Demonstrates how to define custom operators, create expressions using these operators, and evaluate them with input data. It highlights the performance benefits compared to naive evaluation. ```Julia using DynamicExpressions operators = OperatorEnum(1 => (cos,), 2 => (+, -, *)) variable_names = ["x1", "x2"] x1 = Expression(Node{Float64}(feature=1); operators, variable_names) x2 = Expression(Node{Float64}(feature=2); operators, variable_names) expression = x1 * cos(x2 - 3.2) X = randn(Float64, 2, 100); expression(X) # 100-element Vector{Float64} ``` ```Julia @btime eval(:(X[1, :] .* cos.(X[2, :] .- 3.2))) ``` ```Julia @btime expression(X) ``` -------------------------------- ### DynamicExpressions.StructuredExpressionModule.StructuredExpression Type and Usage Source: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/api An expression type that allows combining multiple expressions in a predefined way using a structure function. It is a subtype of `AbstractExpression` and provides an example of creating and combining expressions. ```APIDOC DynamicExpressions.StructuredExpressionModule.StructuredExpression{T,F,N,E,TS,D} <: AbstractStructuredExpression{T,F,N,E,D} <: AbstractExpression{T,N} This expression type allows you to combine multiple expressions together in a predefined way. **Parameters** * `T`: The numeric value type of the expressions. * `F`: The type of the structure function, which combines each expression into a single expression. * `N`: The type of the nodes inside expressions. * `E`: The type of the expressions. * `TS`: The type of the named tuple containing those inner expressions. * `D`: The type of the metadata, another named tuple. **Usage** For example, we can create two expressions, `f`, and `g`, and then combine them together in a new expression, `f_plus_g`, using a constructor function that simply adds them together: ```julia kws = (; binary_operators=[+, -, *, /], unary_operators=[-, cos, exp], variable_names=["x", "y"], ) f = parse_expression(:(x * x - cos(2.5f0 * y + -0.5f0)); kws...) g = parse_expression(:(exp(-(y * y))); kws...) f_plus_g = StructuredExpression((; f, g); structure=nt -> nt.f + nt.g) ``` ``` -------------------------------- ### Sample Node from Tree using Default Sampler Source: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/utils Samples a node from a given tree using the default `NodeSampler` configuration. This method is a convenience wrapper for sampling directly from a tree. ```APIDOC Base.rand(rng::AbstractRNG, tree::AbstractNode) Sample a node from a tree according to the default sampler `NodeSampler(; tree)`. ``` -------------------------------- ### Create an Expression Tree Source: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/examples/base_operations Constructs an expression tree using defined nodes and operators. Demonstrates type stability. ```julia tree = (x + y) * const_1 - sin(x) typeof(tree), typeof(x) ``` -------------------------------- ### Differentiating Expressions with Zygote Source: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/examples/expression Demonstrates how to differentiate an expression with respect to its variables using the Zygote.jl package. It also shows how to differentiate with respect to constants. ```Julia using Zygote ex'(X) ex'(X; variable=Val(false)) ``` -------------------------------- ### Manually Create a Simple Expression Source: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/examples/expression Constructs a basic 'x' expression using the Node and Expression types. ```Julia x = Node{Float64}(; feature=1) x_expr = Expression(x; operators, variable_names) ``` -------------------------------- ### Evaluating Expression Trees Source: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/eval Demonstrates how to create an expression tree using Node and OperatorEnum, and then evaluate it with sample data. The evaluation process is automatically handled by overloaded methods when using OperatorEnum. ```julia using DynamicExpressions operators = OperatorEnum(1 => (cos, sin), 2 => (+, -, *, /)) tree = Node(; feature=1) * cos(Node(; feature=2) - 3.2) tree([1 2 3; 4 5 6.], operators) ``` -------------------------------- ### Create Expression Tree with Scalars Source: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/examples/base_operations Creates an expression tree using scalar values directly, demonstrating flexibility. ```julia tree2 = 2x - sin(x) ``` -------------------------------- ### DynamicExpressions API Documentation Source: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/eval Provides API documentation for key components of the DynamicExpressions.jl library, including EvalOptions for controlling evaluation behavior and eval_tree_array for evaluating expression trees with generic operators. ```APIDOC DynamicExpressions.EvaluateModule.EvalOptions — Type This holds options for expression evaluation, such as evaluation backend. **Fields** * `turbo::Val{T}=Val(false)`: If `Val{true}`, use LoopVectorization.jl for faster evaluation. * `bumper::Val{B}=Val(false)`: If `Val{true}`, use Bumper.jl for faster evaluation. * `early_exit::Val{E}=Val(true)`: If `Val{true}`, any element of any step becoming `NaN` or `Inf` will terminate the computation. For `eval_tree_array`, this will result in the second return value, the completion flag, being `false`. For calling an expression using `tree(X)`, this will result in `NaN`s filling the entire buffer. This early exit is performed to avoid wasting compute cycles. Setting `Val{false}` will continue the computation as usual and thus result in `NaN`s only in the elements that actually have `NaN`s. * `buffer::Union{ArrayBuffer,Nothing}`: If not `nothing`, use this buffer for evaluation. This should be an instance of `ArrayBuffer` which has an `array` field and an `index` field used to iterate which buffer slot to use. [source](https://github.com/SymbolicML/DynamicExpressions.jl/blob/b80be2a3158e8175752ae15972962a5d1cc8a53c/src/Evaluate.jl#L77-L97) DynamicExpressions.EvaluateModule.eval_tree_array — Method ``` eval_tree_array(tree::AbstractExpressionNode, cX::AbstractMatrix, operators::GenericOperatorEnum; throw_errors::Bool=true) ``` Evaluate a generic binary tree (equation) over a given input data, whatever that input data may be. The `operators` enum contains all of the operators used. Unlike `eval_tree_array` with the normal `OperatorEnum`, the array `cX` is sliced only along the first dimension. i.e., if `cX` is a vector, then the output of a feature node will be a scalar. If `cX` is a 3D tensor, then the output of a feature node will be a 2D tensor. Note also that `tree.feature` will index along the first axis of `cX`. However, there is no requirement about input and output types in general. You may set up your tree such that some operator nodes work on tensors, while other operator nodes work on scalars. `eval_tree_array` will simply return `nothing` if a given operator is not defined for the given input type. 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)) ``` **Arguments** * `tree::AbstractExpressionNode`: The root node of the tree to evaluate. * `cX::AbstractArray`: The input data to evaluate the tree on. * `operators::GenericOperatorEnum`: The operators used in the tree. ``` -------------------------------- ### DynamicExpressions.jl API Documentation Source: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/utils This section provides detailed API documentation for the DynamicExpressions.jl package. It covers various utilities for node and tree operations, expression handling, evaluation, printing, sampling, and internal functionalities. The documentation is structured to help users understand and utilize the package's capabilities effectively. ```APIDOC Node utilities: Provides functions for manipulating nodes within expression trees. Expression example: Demonstrates the usage of the `Expression` type for symbolic manipulation. StructuredExpression example: Illustrates how to work with `StructuredExpression` for more complex expression structures. Eval: Details the evaluation capabilities of the DynamicExpressions.jl package. Utils: Base: - Provides fundamental utility functions. Printing: - Functions for pretty-printing expressions. Sampling: - Utilities for sampling from expression structures. Internal utilities: - Internal helper functions not intended for direct user interaction. ``` -------------------------------- ### Performance Benchmarks Source: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/index Compares the execution time of a direct expression evaluation with a more complex, manually constructed expression using basic operations. Benchmarks are performed using the @btime macro. ```julia @btime expression(X) # Output: 461.086 ns (13 allocations: 448 bytes) @btime eval(:(vec_add(vec_add(vec_square(X[1]), [1.0, 2.0, 3.0]), 0.0))) # Output: 115,000 ns ``` -------------------------------- ### DynamicExpressions.jl API Reference Source: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/index Provides an overview of the core components and functionalities of the DynamicExpressions.jl library, including operator definition, expression structure, evaluation kernels, and derivative computation. ```APIDOC DynamicExpressions.jl API Overview: Core Functionalities: 1. Operator Definition: Define custom operators using `OperatorEnum`. - Example: `operators = OperatorEnum(1 => (cos,), 2 => (+, -, *))` 2. Expression Structure: Utilizes a lightweight, type-stable data structure (`Node`) for arbitrary expressions. - Reference: [https://symbolicml.org/DynamicExpressions.jl/dev/types/#DynamicExpressions.NodeModule.Node](https://symbolicml.org/DynamicExpressions.jl/dev/types/#DynamicExpressions.NodeModule.Node) 3. Evaluation Kernels: Generates specialized kernels for efficient expression evaluation. - Reference: [https://github.com/SymbolicML/DynamicExpressions.jl/blob/fe8e6dfa160d12485fb77c226d22776dd6ed697a/src/EvaluateEquation.jl#L29-L66](https://github.com/SymbolicML/DynamicExpressions.jl/blob/fe8e6dfa160d12485fb77c226d22776dd6ed697a/src/EvaluateEquation.jl#L29-L66) 4. Derivative Kernels: Generates kernels for first-order derivatives using Zygote.jl. - Reference: [https://github.com/SymbolicML/DynamicExpressions.jl/blob/fe8e6dfa160d12485fb77c226d22776dd6ed697a/src/EvaluateEquationDerivative.jl#L139-L175](https://github.com/SymbolicML/DynamicExpressions.jl/blob/fe8e6dfa160d12485fb77c226d22776dd6ed697a/src/EvaluateEquationDerivative.jl#L139-L175) 5. Generic Types: Operates on various data types including vectors, tensors, symbols, strings, and unions. 6. Import/Export: Functionality for importing and exporting expressions with SymbolicUtils.jl. Key Components: - `OperatorEnum`: Defines the set of available operators for expressions. - `Expression`: Represents a dynamic expression. - `Node`: The underlying data structure for expression nodes. Related Projects: - SymbolicRegression.jl - PySR ``` -------------------------------- ### Enzyme.jl Integration for Automatic Differentiation Source: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/eval Demonstrates the experimental integration of Enzyme.jl for reverse-mode automatic differentiation with DynamicExpressions.jl. This allows for efficient gradient computation of expression trees. It's recommended to verify gradients with finite differences due to the experimental nature. ```julia using DynamicExpressions operators = OperatorEnum(1 => (cos, sin), 2 => (+, -, *, /)) x1 = Node{Float64}(feature=1) x2 = Node{Float64}(feature=2) tree = 0.5 * x1 + cos(x2 - 0.2) ``` ```julia X = [1.0 2.0 3.0; 4.0 5.0 6.0] # 2x3 matrix (2 features, 3 rows) tree(X, operators) ``` ```julia using Enzyme function my_loss_function(tree, X, operators) # Get the outputs y = tree(X, operators) # Sum them (so we can take a gradient, rather than a jacobian) return sum(y) end dX = begin storage=zero(X) autodiff( Reverse, my_loss_function, Active, ## Actual arguments to function: Const(tree), Duplicated(X, storage), Const(operators), ) storage end ``` -------------------------------- ### Gradient Computation with Zygote Source: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/index Demonstrates how to compute gradients of dynamically defined expressions using Zygote.jl. Shows the performance of gradient calculation and how it remains consistent even when the expression changes at runtime. ```julia using Zygote using DynamicExpressions operators = OperatorEnum(1 => (cos,), 2 => (+, -, *)) variable_names = ["x1", "x2"] x1, x2 = (Expression(Node{Float64}(; feature=i); operators, variable_names) for i in 1:2) expression = x1 * cos(x2 - 3.2) # Compute gradient with respect to inputs grad = expression'(X) @btime expression'(X) # Dynamic gradient computation @btime ex'(X) setup=(ex = copy(expression); ex.tree.op = rand(1:3)) # Compute derivative with respect to constants result, grad, did_finish = eval_grad_tree_array(expression, X; variable=false) ``` -------------------------------- ### Expression Simplification Source: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/examples/expression Demonstrates simplifying a complex mathematical expression using the `combine_operators` function. It shows the original and simplified forms of an expression. ```Julia simplified_expr = combine_operators(copy(complex_expr)) println("Original: ", complex_expr) println("Simplified: ", simplified_expr) ``` -------------------------------- ### Create a Node Source: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/examples/base_operations Creates a Node object to reference a feature in the dataset. It asserts that the created node is of the expected type. ```julia using DynamicExpressions, Random, Test x = Node{Float64}(; feature=1) @test x isa Node{Float64,2} ``` -------------------------------- ### Manually Create a Complex Expression Source: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/examples/expression Builds a more complex expression involving multiple operators and nodes. ```Julia y = Node{Float64}(; feature=2) c = Node{Float64}(; val=2.0) complex_node = Node(; op=3, l=x, r=Node(; op=1, l=y, r=c)) complex_expr = Expression(complex_node; operators, variable_names) ``` -------------------------------- ### Simplify an Expression Source: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/examples/expression Creates a simplified expression by combining constants. ```Julia complex_expr = parse_expression( :((2.0 + x) + 3.0); operators=operators, variable_names=["x"] ) ``` -------------------------------- ### Implementing the Expression Interface Source: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/api Demonstrates how to declare a custom type as implementing the Expression Interface in Julia using the `Interface.jl` package. This includes adding all optional methods and testing the implementation. ```Julia using DynamicExpressions: ExpressionInterface, all_ei_methods_except using Interface: @implements, Arguments, Interface # Add all optional methods: valid_optional_methods = all_ei_methods_except(()) @implements ExpressionInterface{valid_optional_methods} MyCustomExpression [Arguments()] @test Interface.test(ExpressionInterface, MyCustomExpression, [ex::MyCustomExpression]) ``` -------------------------------- ### DynamicExpressions Node Interface Source: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/api Defines the interface for AbstractExpressionNode, covering node creation, child retrieval, copying, hashing, equality checks, and tree-specific operations like map-reduce and node manipulation. ```APIDOC DynamicExpressions.InterfacesModule.NodeInterface: Mandatory components: (:create_node, :get_children, :copy, :hash, :any, :equality, :preserve_sharing, :constructorof, :eltype, :with_type_parameters, :with_max_degree, :default_allocator, :set_node!, :count_nodes, :tree_mapreduce) Optional components: (:copy_into!, :leaf_copy, :leaf_copy_into!, :leaf_convert, :leaf_hash, :leaf_equal, :branch_copy, :branch_copy_into!, :branch_convert, :branch_hash, :branch_equal, :count_depth, :is_node_constant, :count_constant_nodes, :filter_map, :has_constants, :get_scalar_constants, :set_scalar_constants!, :index_constant_nodes, :has_operators) - create_node: Creates a new instance of the node type. - get_children: Returns the children of the node. - copy: Returns a copy of the tree. - hash: Returns the hash of the tree. - any: Checks if any element of the tree satisfies a condition. - equality: Checks equality of the tree with itself and its copy. - preserve_sharing: Checks if the node type preserves sharing. - constructorof: Gets the constructor function for a type. - eltype: Returns the element type of the node. - with_type_parameters: Returns the node with specified type parameters. - with_max_degree: Returns the node with a specified maximum degree. - default_allocator: Returns the default allocator for the node. - set_node!: Sets a node in the tree. - count_nodes: Counts the number of nodes in the tree. - tree_mapreduce: Applies a function across the tree. - copy_into!: Copies a node into a preallocated container. - leaf_copy: Returns a copy of a leaf node. - leaf_copy_into!: Copies a leaf node into a preallocated container. - leaf_convert: Converts a leaf node to a different type. - leaf_hash: Returns the hash of a leaf node. - leaf_equal: Checks equality of a leaf node. - branch_copy: Returns a copy of a branch node. - branch_copy_into!: Copies a branch node into a preallocated container. - branch_convert: Converts a branch node to a different type. - branch_hash: Returns the hash of a branch node. - branch_equal: Checks equality of a branch node. - count_depth: Calculates the depth of the node tree. - is_node_constant: Checks if the node is constant. - count_constant_nodes: Counts the number of constant nodes in the node tree. - filter_map: Filters and maps nodes in the tree. - has_constants: Checks if the node has constants. - get_scalar_constants: Gets constants from the node tree. - set_scalar_constants!: Sets constants in the node tree. - index_constant_nodes: Indexes constants in the node tree. - has_operators: Checks if the node has operators. ``` -------------------------------- ### DynamicExpressions.jl API Reference Source: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/api This section details the core components and functionalities of the DynamicExpressions.jl library. It includes definitions for Operators, Nodes, Expressions, and related utilities for graph manipulation and abstract types. ```APIDOC Operator Enum: Represents the set of available operators for symbolic expressions. Nodes: AbstractNode: Base type for all nodes in an expression tree. Node: Represents a basic symbolic expression node. SymbolicUtils.Symbolic{T}: Generic node type for symbolic expressions. Generic Node Accessors: get_expr(node): Retrieves the expression associated with a node. get_sym(node): Retrieves the symbolic representation of a node. Graph Nodes: GraphNode: Represents a node within a computational graph. Abstract Types: AbstractExpression: Base type for all expression representations. Expressions: Expression: Represents a symbolic expression. StructuredExpression: Represents a structured symbolic expression. Interfaces: Provides interfaces for interacting with expression structures and nodes. ``` -------------------------------- ### Optional Expression Interface Methods Source: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/api A comprehensive set of optional methods that extend the functionality of the Expression Interface, covering advanced operations like copying, converting, hashing, comparing nodes, and managing constants within the expression tree. ```APIDOC copy_into!: Copies a node into a preallocated container. leaf_copy: Copies a leaf node. leaf_copy_into!: Copies a leaf node in-place. leaf_convert: Converts a leaf node. leaf_hash: Computes the hash of a leaf node. leaf_equal: Checks equality of two leaf nodes. branch_copy: Copies a branch node. branch_copy_into!: Copies a branch node in-place. branch_convert: Converts a branch node. branch_hash: Computes the hash of a branch node. branch_equal: Checks equality of two branch nodes. count_depth: Calculates the depth of the tree. is_node_constant: Checks if the node is a constant. count_constant_nodes: Counts the number of constant nodes. filter_map: Applies a filter and map function to the tree. has_constants: Checks if the tree has constants. get_scalar_constants: Gets constants from the tree, returning a tuple of: (1) a flat vector of the constants, and (2) a reference object that can be used by `set_scalar_constants!` to efficiently set them back. set_scalar_constants!: Sets constants in the tree, given: (1) a flat vector of constants, (2) the tree, and (3) the reference object produced by `get_scalar_constants`. index_constant_nodes: Indexes constants in the tree. has_operators: Checks if the tree has operators. ``` -------------------------------- ### Operator Overloading and Expression Combination Source: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/examples/expression Illustrates how `AbstractExpression` types overload operators to combine expressions. It shows creating a list of expressions and combining two of them, then printing the type of the resulting expression. ```Julia xs = [Expression(Node{Float64}(; feature=i); operators, variable_names) for i in 1:5] xs[1] + xs[2] typoeof(xs[1] + xs[2]) ``` -------------------------------- ### Evaluate Tree with Batched Data Source: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/examples/base_operations Evaluates an expression tree over batched input data using a provided random number generator. ```julia rng = Random.MersenneTwister(0) tree2(randn(rng, Float64, 2, 5), operators) ``` -------------------------------- ### Compare Operators Source: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/examples/base_operations Compares the operators found in the tree with a predefined list of binary operators. This helps in identifying the types of operations present. ```julia operators.binops ``` -------------------------------- ### AbstractNode Definition Source: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/api Julia code snippet showing the definition of the AbstractNode type. ```Julia AbstractNode{D} ``` -------------------------------- ### Evaluate Expression Source: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/index Demonstrates how to evaluate a symbolic expression with given input values. If an operator is not defined for the input type, 'nothing' is returned. ```julia expression(X) # Example output: [2.0, 29.04, 3.01] ``` -------------------------------- ### DynamicExpressions Node Manipulation Functions Source: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/api Documents utility functions for managing nodes within expression trees, including `set_node!` for in-place updates and `copy_node` for creating duplicates. ```APIDOC DynamicExpressions.NodeModule.set_node!(tree::AbstractExpressionNode{T}, new_tree::AbstractExpressionNode{T}) where {T} Set every field of `tree` equal to the corresponding field of `new_tree`. [source](https://github.com/SymbolicML/DynamicExpressions.jl/blob/b80be2a3158e8175752ae15972962a5d1cc8a53c/src/Node.jl#L467-L471) ``` ```APIDOC copy_node(node::AbstractExpressionNode) Create a copy of a node. ``` -------------------------------- ### Parse an Expression from String Source: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/examples/expression Creates an Expression object by parsing a mathematical expression string. ```Julia parsed_expr = parse_expression( :(sin(2.0 * x + exp(y + 5.0))); operators=operators, variable_names=variable_names ) ``` -------------------------------- ### Create a Constant Node Source: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/examples/base_operations Creates a Node object representing a constant value. ```julia const_1 = Node{Float64}(; val=1.0) ``` -------------------------------- ### DynamicExpressions Node Copying Source: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/api Provides a function to efficiently copy a node and its children. Allows breaking sharing within the copied tree. ```APIDOC DynamicExpressions.NodeModule.copy_node(tree::AbstractExpressionNode; break_sharing::Val{BS}=Val(false)) where {BS} - Copies a node and recursively copies all children nodes. - More efficient than the built-in copy. - Parameters: - tree: The node to copy. - break_sharing: If set to Val(true), sharing in a tree will be ignored. - Source: https://github.com/SymbolicML/DynamicExpressions.jl/blob/b80be2a3158e8175752ae15972962a5d1cc8a53c/src/base.jl#L488-L495 ``` -------------------------------- ### DynamicExpressions with Tensors Source: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/index Demonstrates the use of DynamicExpressions.jl with tensors (e.g., `Vector{Float64}`). Shows how to define tensor-specific operators using multiple dispatch and construct expressions involving scalar and vector constants. ```julia using DynamicExpressions using DynamicExpressions: @declare_expression_operator T = Union{Float64, Vector{Float64}} vec_add(x, y) = x .+ y vec_square(x) = x .* x @declare_expression_operator(vec_add, 2) @declare_expression_operator(vec_square, 1) operators = GenericOperatorEnum(1 => (vec_square,), 2 => (vec_add,)) variable_names = ["x1"] c1 = Expression(Node{T}(; val=0.0); operators, variable_names) # Scalar constant c2 = Expression(Node{T}(; val=[1.0, 2.0, 3.0]); operators, variable_names) # Vector constant x1 = Expression(Node{T}(; feature=1); operators, variable_names) expression = vec_add(vec_add(vec_square(x1), c2), c1) X = [[-1.0, 5.2, 0.1], [0.0, 0.0, 0.0]] # Example evaluation (output not shown in snippet) # expression(X) ``` -------------------------------- ### Core Expression Interface Methods Source: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/api These are the fundamental methods required for the Expression Interface, enabling basic operations on expression trees such as accessing node properties, modifying types, and traversing the tree. ```APIDOC constructorof: Gets the constructor function for a node type. eltype: Gets the element type of the node. with_type_parameters: Applies type parameters to the node type. with_max_degree: Changes the maximum degree of a node type. default_allocator: Gets the default allocator for the node type. set_node!: Sets the node's value. count_nodes: Counts the number of nodes in the tree. tree_mapreduce: Applies a function across the tree. ``` -------------------------------- ### Compare Root Node Source: https://ai.damtp.cam.ac.uk/dynamicexpressions/stable/examples/base_operations Compares the root node of the tree with the first element obtained after collecting all nodes. This verifies the order of collection. ```julia tree == first(collect(tree)) ```