Example
This is an illustrative example of a OptiGraph program.
To run this code, create a file named 'HelloGraph.scala' forge/apps/OptiGraph/src
and then compile it using the instructions on our getting started page.
import optigraph.compiler._
import optigraph.library._
import optigraph.shared._
// This object lets us run the Delite version of the code
object HelloGraphCompiler extends OptiGraphApplicationCompiler with HelloGraph
// This object lets us run the Scala library version of the code
object HelloGraphInterpreter extends OptiGraphApplicationInterpreter
with HelloGraph
trait HelloGraph extends OptiGraphApplication {
def main() = {
val srcNodes = NodeData.fromFunction(10,i => i)
val dstNodes = NodeData.fromFunction(10,i => 9-i)
val edgeList = srcNodes.pack(dstNodes)
val dg = directedGraphFromEdgeList(edgeList)
val dt = sumOverNodes(dg.nodes){ n =>
sumOverNeighbors(dg.inNeighbors(n)){ nbr =>
if(nbr >= n) 1
else 0
}
}
println("nbr >= n: " + dt)
val dupSrcNodes = srcNodes.concat(dstNodes)
val dupDstNodes = dstNodes.concat(srcNodes)
val dupEdgeList = dupSrcNodes.pack(dupDstNodes)
val ug = undirectedGraphFromEdgeList(dupEdgeList)
val ut = sumOverNodes(ug.nodes){ n =>
sumOverNeighbors(ug.neighbors(n)){ nbr =>
if(nbr >= n) 1
else 0
}
}
println("nbr >= n: " + ut)
}
}
This code is mostly boilerplate, and is needed to setup the application. First, we import the DSL code. Then, we create both the HelloGraphInterpreter and HelloGraphCompiler objects. HelloGraphInterpreter produces an object that can be run in interpreter mode. HelloGraphCompiler will produce our high performance Delite code. Both objects extend the trait HelloGraph which will hold our application example application. This is an idiom commonly used in Forge code.
import optigraph.compiler._
import optigraph.library._
import optigraph.shared._
// This object lets us run the Delite version of the code
object HelloGraphCompiler extends OptiGraphApplicationCompiler with HelloGraph
// This object lets us run the Scala library version of the code
object HelloGraphInterpreter extends OptiGraphApplicationInterpreter
with HelloGraph
trait HelloGraph extends OptiGraphApplication {
def main() = {
This is where we create the variables used in the problem. In this case we declare two NodeData variables srcNodes
and dstNodes
. The NodeData trait is akin to a Vector
object for graphs. We pack these NodeData objects together to create an edgeList
of integer tuples. The edgeList
variable holds pairs of src-dst nodes to create our graph.
val srcNodes = NodeData.fromFunction(10,i => i)
val dstNodes = NodeData.fromFunction(10,i => 9-i)
val edgeList = srcNodes.pack(dstNodes)
Here the call to directedGraphFromEdgeList
does exactly what it suggests. We
take an edge list and create a DSL graph object from it. We just created our first graph!
Typically graphs are created from .txt
files that are laid out as an adjacency list
or an edge list. See the OptiGraph code documentation for specialized file parsers for OptiGraph.
val dg = directedGraphFromEdgeList(edgeList)
This code introduces us to our first parallel graph operations sumOverNodes
and sumOverNodes
. I'd like to think the names are descriptive enough.
Effectively what we accomplish here is traversing all of the nodes in the graph and counting
the number of neighbors of that node on incoming edges that have an id larger than the
current node id. These sum over methods boil down to a parallel mapreduce under the covers.
val dt = sumOverNodes(dg.nodes){ n =>
sumOverNeighbors(dg.inNeighbors(n)){ nbr =>
if(nbr >= n) 1
else 0
}
}
The next chunk of code is very similar but you will notice some slight differences. Namely
we are operating on an undirected graph now. First we do some manipulation on the NodeData
objects in order to prepare an edgelist with undirected edges. Namely we have to make sure
we have duplicate edges! After creating an undirected graph you should notice that the operations
on the undirected graph are very similar to the directed graph. Incoming edges and outgoing edges
are really the same thing here so we just access neighbors
now. You should notice that
our final count doubles. We have twice the number of edges now!
val dupSrcNodes = srcNodes.concat(dstNodes)
val dupDstNodes = dstNodes.concat(srcNodes)
val dupEdgeList = dupSrcNodes.pack(dupDstNodes)
val ug = undirectedGraphFromEdgeList(dupEdgeList)
val ut = sumOverNodes(ug.nodes){ n =>
sumOverNeighbors(ug.neighbors(n)){ nbr =>
if(nbr >= n) 1
else 0
}
}
Congrats you have build your first OptiGraph application!