Graph Framework
|
A tutorial for creating new operations.
In most cases, physics problems can be generated from combinations of graph nodes. For instance, the graph::tan nodes are built from \(\frac{\sin\left(x\right)}{\cos\left(x\right)}\). However, some problems will call for adding new operations. This page provides a basic example of how to impliment a new operator \(foo\left(x\right)\) in the graph_framework.
All graph nodes are subclasses of graph::leaf_node or subclasses of other nodes. In the case of our \(foo\left(x\right)\) example we can sublass the graph::straight_node since these assume single arguments. If there are two or three operands you can subclass
In this case, the graph::straight_node (Along with graph::branch_node, graph::triple_node) have no reduction assumputions. For this case since our operation \(foo\left(x\right)\) takes one argument, we will subclass the graph::straight_node.
The basics of subclassing a node, start with a subclass and a constructor.
The static to_string
method provices an idenifier that can be used to generate a hash for the node. This hash will be used later in a factory function to exsure nodes only exist once.
A factory function constructs a node then immedately reduces it. The reduced node is then checked if it already exists in the graph::leaf_node::caches_t::nodes. If the node is a new node, we add it to the cache and return it. Otherwise we discard the node and return the cached node in it's place.
To aid in introspection we also need a function to cast a generic graph::shared_leaf back to the specific node type. For convience, we also define a type alias for shared type.
To subclass a graph::leaf_node there are several methods that need to be provided.
To start, lets provide a way to evalute the node. The first step to evaluate a node is to evaluate the nodes argument.
backend::buffer are quick ways we can evalute the node on the host before needing to generate device kernels and is used by the graph::leaf_node::reduce method to precompute constant values. We can extend the backend::buffer class with a new method to evaluate foo or you can use the existing operators. In this case lets assume \(foo\left(x\right)=x^{2}\).
This methiod checks if the node matches another node. The first thing to check is if the pointers match. Then we can check if the structure of the graphs match. This is important for the factory function. When checking for cached nodes, two graphs can be identical but have different pointer values. Checking the structure of the graphs ensures that we catch identical graphs.
Lets add a simple reduction method. When the argument \(x \) is a constant we can reduce this node down to a single constant by pre evaluating it.
In this example we first check if the argument can be cast to a constant. If it was castable, we evalute this node and create a new constant to return in its place. Otherwise we return the current node unchanged.
Auto differentiation is provided by returning the derivative expression. \(\frac{\partial}{\partial y}foo\left(x\right)=2x\frac{\partial x}{\partial y}\). However, in this framework it is also possible to take a derivative with respect to itself \(\frac{\partial foo\left(x\right)}{\partial foo\left(x\right)}=1 \).
Here we made use of the graph::leaf_node::df_cache to avoid needing to rebuild expressions everytime the same derivative is taken.
The graph::leaf_node::compile_preamble method provides ways to include header files or define functions. Lets use this method to define a function that can be called from the kernel.
The compile methods generate kernel source code. In this case we created a function in the preamble to evaluate foo. Since we only want this create this preamble once, we first check if this node has already been visited. The build system option SHOW_USE_COUNT
tracks the number of times a node is used in the kernel. When this option is set we need to increment it's usage count.
The compile method writes a line of source code to the kernel. Here we can use the function defined in the preamble.
Kernels are created by assuming infinite registers. In this case, a register is a temporary variable. To provide a unquie name, the node pointer value is converted into a string. Since we only want to evaluate this once, we check if the register has already been created.
This method returns the code to generate the \(\LaTeX \) expression for the node.
This provides information for other nodes about how this works for reduction methods. In this care we need to set this to true. If this node did not act like a power, this method can be ignored.
Return the base of the power node. This provides information for other nodes about how this works for reduction methods. In this case the power base is the function argument.
Return the exponent of the power node. This provides information for other nodes about how this works for reduction methods. In this case, the power exponent is \(2 \).
Return the node with pseduo variables removed. graph::pseudo_variable_node are used to end derivatives construction by treating a sub graph as a pseduo variable. Before graphs can be evaluated, these graph::pseudo_variable_node need to be removed.
Generates a vizgraph node for visualization.