Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright (c) 2016
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.waioeka.graph
import scala.annotation.tailrec
/**
* Provides method for generating connected (random) DAG.
*/
object Graph {
/** Define our vertex type as an Int. */
type Vertex = Int
/** Edge from a vertex to a vertex. */
case class Edge(from: Vertex, to: Vertex)
/** Graph is an adjacent list of edges. */
case class Graph(numVertices: Int, adjacencySet: Set[Edge]) {
/**
* Return a 'DOT' representation of the graph.
*/
def toDotString : String = {
val builder = StringBuilder.newBuilder
builder.append("digraph {\n")
for (edge <- adjacencySet) {
builder.append(s"${edge.from} -> ${edge.to}\n")
}
builder.append("}\n")
builder.toString()
}
}
/**
* Find the root parents of the graph, i.e. all vertices that have no incoming edges.
*
* @param g the graph.
* @return a set containing all vertices that have no incoming edges.
*/
def parents(g: Graph) = {
val vertices = g.adjacencySet.map(_.to)
g.adjacencySet.filterNot(e => vertices.contains(e.from)).map(_.from)
}
/**
* Find the root children of the graph, i.e. all vertices that have no outgoing edges.
* @param g
* @return
*/
def children(g: Graph) = {
val vertices = g.adjacencySet.map(_.from)
g.adjacencySet.filterNot(e => vertices.contains(e.to)).map(_.to)
}
/**
* Creates a random DAG, It does this by randomly setting entries in a lower diagonal matrix.
*
* n.b.
* * there may be more than one connected component.
* * edges on the diagonal (i.e. edge from a vertex to itself) are filtered out.
*
* Filtering out vertices with edges to themselves should be optional.
* Common to filter out.
*
* @param numVertices the number of vertices.
* @param probOfEdge the probability of an edge.
* @param d the distribution to use for random variables.
* @return a DAG, may have more than one connected component.
*/
private[this] def randomEdges(numVertices: Int, probOfEdge: Double)(implicit d: Distribution[Double]): Graph = {
val edges = for {
i <- 1 to numVertices
j <- 1 to i
if i != j && d.get <= probOfEdge
} yield Edge(j, i)
Graph(numVertices,edges.toSet)
}
/**
* Generates a random DAG with one component. The algorithm is as follows:
*
* 1. Generate random initial set of edges. This is done by iterating over a
* lower diagonal of a matrix of size equal to number of vertices. If the
* random variable at the element is less than a probability, create an edge
* from the column to the row index.
*
* 2. Find the number of components in the graph generated. If the number of
* components = 1, return the graph. Otherwise, connect the components.
*
* @param numVertices the number of vertices in the dag.
* @param probOfEdge the probability of an edge between two nodes in the dag.
* @param d the distribution to use for random number generation.
* @return a connected DAG.
*/
def randomDAG(numVertices: Int, probOfEdge: Double)(implicit d: Distribution[Double]): Graph = {
require(numVertices > 1,"Number of vertices must be greater than one.")
reduce(randomEdges(numVertices, probOfEdge))
}
/**
* Topological sort, code adapted from this source:
*
* https://gist.github.com/ThiporKong/4399695
*
* @param g the graph to sort.
* @return topological sort of the graph or none.
*/
def tsort(g: Graph): Option[Iterable[Vertex]] = {
@tailrec
def tsort0(toPreds: Map[Vertex, Set[Vertex]], done: Iterable[Vertex]): Option[Iterable[Vertex]] = {
val (noPreds, hasPreds) = toPreds.partition { _._2.isEmpty }
if (noPreds.isEmpty) {
if (hasPreds.isEmpty) Some(done) else None
} else {
val found = noPreds.keys
tsort0(hasPreds.mapValues { _ -- found }, done ++ found)
}
}
val toPred = g.adjacencySet.foldLeft(Map[Vertex, Set[Vertex]]()) { (acc, e) =>
acc + (e.from -> acc.getOrElse(e.from, Set())) + (e.to -> (acc.getOrElse(e.to, Set()) + e.from))
}
tsort0(toPred, Seq())
}
/**
* Reduce a graph that may have more than one component to
* a single component DAG.
*
* @param graph the graph to reduce.
* @return a dag that has a single component.
*/
private[this] def reduce(graph: Graph): Graph = {
val disjoint = DisjointSet(graph.numVertices).union(graph.adjacencySet)
def root(vertex: Vertex): Vertex =
if (disjoint.parents(vertex) == vertex) vertex else root(disjoint.parents(vertex))
val connected = disjoint.numComponents match {
case 1 => graph
case _ =>
/*
* Possible join points are the parents, but a vertex may have more
* than one parent. Iterate over the parents and form an edge if and
* only if the resulting graph is acyclic.
*/
val points = (1 to graph.numVertices).map(root).distinct
val start = if (graph.adjacencySet.nonEmpty) graph.adjacencySet.toList
else List(Edge(points.head,points.last))
val edges = points.foldLeft(start)((acc, v) => {
val edge = Edge(acc.head.from, v)
val g0 = Graph(graph.numVertices, graph.adjacencySet + edge)
val update = tsort(g0) match {
case Some(_) => acc :+ Edge(acc.head.from, v)
case None => acc
}
update
})
Graph(graph.numVertices,edges.toSet)
}
connected
}
}