├── .gitignore ├── .mill-version ├── Examples ├── src │ └── scala │ │ └── net │ │ └── walend │ │ └── disentangle │ │ └── examples │ │ ├── BradnesImplicitsExample.scala │ │ ├── BrandesExample.scala │ │ ├── DijkstraExample.scala │ │ ├── DijkstraLeastWeightsExample.scala │ │ ├── FloydWarshallExample.scala │ │ └── package.scala └── test │ └── src │ └── net │ └── walend │ └── disentangle │ └── examples │ ├── BrandesExampleTest.scala │ ├── DijkstraExampleTest.scala │ ├── DijkstraLeastWeightsExampleTest.scala │ └── FloydWarshallExampleTest.scala ├── Experiments ├── src │ └── scala │ │ └── net │ │ └── walend │ │ └── disentangle │ │ └── graph │ │ └── cluster │ │ └── Agglomerate.scala └── test │ └── src │ └── net │ └── walend │ └── disentangle │ └── graph │ └── cluster │ └── AgglomerateTest.scala ├── Graph ├── src │ └── scala │ │ └── net │ │ └── walend │ │ └── disentangle │ │ ├── graph │ │ ├── AdjacencyDigraph.scala │ │ ├── AdjacencyLabelDigraph.scala │ │ ├── AdjacencyLabelUndigraph.scala │ │ ├── AdjacencyUndigraph.scala │ │ ├── Digraph.scala │ │ ├── DigraphFactory.scala │ │ ├── Graph.scala │ │ ├── IndexedSet.scala │ │ ├── LabelDigraph.scala │ │ ├── LabelUndigraph.scala │ │ ├── Undigraph.scala │ │ ├── mutable │ │ │ ├── MatrixLabelDigraph.scala │ │ │ └── MutableLabelDigraph.scala │ │ ├── package.scala │ │ └── semiring │ │ │ ├── AllPathsFirstSteps.scala │ │ │ ├── Brandes.scala │ │ │ ├── Dijkstra.scala │ │ │ ├── FewestNodes.scala │ │ │ ├── FloydWarshall.scala │ │ │ ├── LeastWeights.scala │ │ │ ├── MostProbable.scala │ │ │ ├── OnePathFirstStep.scala │ │ │ ├── SemiringSupport.scala │ │ │ ├── TransitiveClosure.scala │ │ │ └── package.scala │ │ └── heap │ │ ├── FibonacciHeap.scala │ │ └── Heap.scala └── test │ ├── resources │ ├── Enron2000Apr.json │ └── contiguous-usa.dat.txt │ └── src │ └── net │ └── walend │ └── disentangle │ ├── graph │ ├── SomeGraph.scala │ └── semiring │ │ ├── AllPathsFirstStepsTest.scala │ │ ├── BrandesTest.scala │ │ ├── FewestNodesTest.scala │ │ ├── LeastWeightsTest.scala │ │ ├── MostProbableTest.scala │ │ ├── OnePathFirstStepTest.scala │ │ ├── TransitiveClosureTest.scala │ │ └── UndirectedGraphTest.scala │ └── heap │ └── FibonacciHeapTest.scala ├── GraphJvm ├── src │ └── scala │ │ └── net │ │ └── walend │ │ └── disentangle │ │ └── graph │ │ ├── par │ │ └── ParIndexedSet.scala │ │ └── semiring │ │ └── par │ │ ├── ParBrandes.scala │ │ ├── ParDijkstra.scala │ │ └── package.scala └── test │ ├── resources │ └── contiguous-usa.dat.txt │ └── src │ └── net │ └── walend │ └── disentangle │ └── graph │ └── semiring │ └── par │ ├── BrandesJungTest.scala │ └── ParTests.scala ├── LICENSE ├── README.md ├── benchmark ├── build.sbt ├── results │ └── v0.2.0 │ │ ├── c4.8xlarge │ │ ├── dijkstra.csv │ │ ├── linearPlot.svg │ │ ├── logPlot.svg │ │ └── parDijkstra.csv │ │ ├── laptop │ │ ├── brandes.csv │ │ ├── dijkstra.csv │ │ ├── jung.csv │ │ ├── parBrandes.csv │ │ ├── parBrandesProfile.csv │ │ └── parDijkstra.csv │ │ ├── r3.8xlarge │ │ ├── brandes.csv │ │ ├── brandesLinear.png │ │ ├── brandesLog.png │ │ ├── dijkstra.csv │ │ ├── dijkstraLinear.png │ │ ├── dijkstraLog.png │ │ ├── floydWarshall.csv │ │ ├── parBrandes.csv │ │ └── parDijkstra.csv │ │ └── t2.micro │ │ ├── dijkstra.csv │ │ └── parDijkstra.csv ├── src │ ├── html │ │ └── plot.html │ ├── js │ │ ├── d3.v3.js │ │ ├── plot.js │ │ └── queue.js │ └── scala │ │ └── net │ │ └── walend │ │ └── disentangle │ │ └── graph │ │ └── semiring │ │ └── benchmark │ │ ├── BrandesTiming.scala │ │ ├── DijkstraTiming.scala │ │ ├── FloydWarshallTiming.scala │ │ ├── JungDijkstraTiming.scala │ │ ├── ParBrandesTiming.scala │ │ ├── ParDijkstraTiming.scala │ │ ├── TimingStudies.scala │ │ └── TimingStudy.scala └── test │ └── src │ └── net │ └── walend │ └── disentangle │ └── graph │ └── semiring │ └── benchmark │ └── TimingStudiesTest.scala ├── build.sbt ├── build.sc ├── millw ├── project ├── build.properties ├── build.sbt └── plugins.sbt ├── todo.txt └── version.sbt /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | *.log 3 | 4 | #mill specific 5 | out/* 6 | 7 | #bsp 8 | .bsp/* 9 | 10 | # sbt specific 11 | dist/* 12 | target/ 13 | lib_managed/ 14 | src_managed/ 15 | project/boot/ 16 | project/plugins/project/ 17 | 18 | # Scala-IDE specific 19 | .scala_dependencies 20 | 21 | # IntellijIdea 22 | .idea 23 | .idea_modules 24 | 25 | # From Activator 26 | /RUNNING_PID 27 | /logs/ 28 | /project/*-shim.sbt 29 | /project/project/ 30 | /project/target/ 31 | /target/ 32 | -------------------------------------------------------------------------------- /.mill-version: -------------------------------------------------------------------------------- 1 | 0.10.2 -------------------------------------------------------------------------------- /Examples/src/scala/net/walend/disentangle/examples/BradnesImplicitsExample.scala: -------------------------------------------------------------------------------- 1 | package net.walend.disentangle.examples 2 | 3 | import net.walend.disentangle.graph.semiring.Brandes.BrandesSteps 4 | import net.walend.disentangle.graph.semiring.LabelUndigraphSemiringAlgorithms 5 | import net.walend.disentangle.graph.{AdjacencyLabelUndigraph, NodePair} 6 | 7 | /** 8 | * Use Brandes' algorithms to find least paths and betweenness for a directed graph. 9 | * 10 | * @author dwalend 11 | * @since v0.2.1 12 | */ 13 | object BrandesImplicitsExample { 14 | 15 | /** 16 | * Edges are just a Seq of Tuple3[Node,Node,Edge] 17 | */ 18 | lazy val edges: Seq[(NodePair[String], String)] = Seq( 19 | (NodePair("A","B"),"ab"), 20 | (NodePair("B","C"),"bc"), 21 | (NodePair("C","D"),"cd"), 22 | (NodePair("D","E"),"de"), 23 | (NodePair("E","F"),"ef"), 24 | (NodePair("E","B"),"eb"), 25 | (NodePair("E","H"),"eh"), 26 | (NodePair("H","C"),"hc") 27 | ) 28 | 29 | /** 30 | * The labels from Brandes use node indexes from a directed graph, so it's best to control those via the optional nodeOrder parameter 31 | */ 32 | lazy val nodeOrder = Array("A","B","C","D","E","F","H") 33 | 34 | val graph = AdjacencyLabelUndigraph(edges,nodeOrder) 35 | 36 | lazy val brandesResults = graph.allLeastPathsAndBetweenness() 37 | 38 | lazy val nextStepsAndCosts: IndexedSeq[(String, String, Option[BrandesSteps[String, Int]])] = brandesResults._1 39 | 40 | lazy val betweennessValues: Map[String, Double] = brandesResults._2 41 | 42 | } 43 | -------------------------------------------------------------------------------- /Examples/src/scala/net/walend/disentangle/examples/BrandesExample.scala: -------------------------------------------------------------------------------- 1 | package net.walend.disentangle.examples 2 | 3 | import net.walend.disentangle.examples.BrandesExample.support 4 | import net.walend.disentangle.graph.{AdjacencyLabelDigraph, AdjacencyLabelUndigraph, NodePair} 5 | import net.walend.disentangle.graph.semiring.Brandes.BrandesSteps 6 | import net.walend.disentangle.graph.semiring.Brandes 7 | import net.walend.disentangle.graph.semiring.par.ParBrandes 8 | 9 | import scala.collection.immutable.Seq 10 | import scala.collection.parallel.immutable.{ParMap, ParSeq} 11 | 12 | /** 13 | * Use Brandes' algorithms to find least paths and betweenness for a directed graph. 14 | * 15 | * @author dwalend 16 | * @since v0.2.0 17 | */ 18 | object BrandesExample { 19 | 20 | /** 21 | * Edges are just a Seq of Tuple3[Node,Node,Edge] 22 | */ 23 | lazy val edges: Seq[(String, String, String)] = Seq( 24 | ("A","B","ab"), 25 | ("B","C","bc"), 26 | ("C","D","cd"), 27 | ("D","E","de"), 28 | ("E","F","ef"), 29 | ("E","B","eb"), 30 | ("E","H","eh"), 31 | ("H","C","hc") 32 | ) 33 | 34 | /** 35 | * The labels from Brandes use node indexes from a directed graph, so it's best to control those via the optional nodeOrder parameter 36 | */ 37 | lazy val nodeOrder: Array[String] = Array("A","B","C","D","E","F","H") 38 | 39 | /** 40 | * Find shortest paths and betweenness for the graph 41 | */ 42 | lazy val shortestPathsAndBetweenness: (IndexedSeq[(String, String, Option[BrandesSteps[String, Int]])], Map[String, Double]) = Brandes.allLeastPathsAndBetweenness(edges,nodeOrder) 43 | 44 | /** 45 | * Find shortest paths and betweenness for the graph in parallel 46 | */ 47 | lazy val shortestPathsAndBetweennessFromPar: (ParSeq[(String, String, Option[BrandesSteps[String, Int]])], ParMap[String, Double]) = ParBrandes.parAllLeastPathsAndBetweenness(edges,nodeOrder) 48 | 49 | /** 50 | * The first item in the tuple holds edges labels. 51 | */ 52 | lazy val shortPathLabels: IndexedSeq[(String, String, Option[BrandesSteps[String, Int]])] = shortestPathsAndBetweenness._1 53 | 54 | /** 55 | * The second item in the tuple holds the betweenness for the graph. 56 | */ 57 | lazy val betweennesses: Map[String, Double] = shortestPathsAndBetweenness._2 58 | 59 | /** 60 | * BrandesSupport has some helper methods to generate the shortest paths. 61 | */ 62 | lazy val support: Brandes.BrandesSupport[String, Int, Int] = Brandes.BrandesSupport[String]() 63 | 64 | /** 65 | * The helper methods in AllPathsFirstSteps need a directed graph. 66 | * Use AllPathsFirstSteps.semiring's annihilator - None - for noEdgeExistsValue. 67 | */ 68 | lazy val labelDigraph: AdjacencyLabelDigraph[String, support.Label] = AdjacencyLabelDigraph(edges = shortPathLabels, 69 | nodes = nodeOrder, 70 | noEdgeExistsValue = support.semiring.O) 71 | 72 | /** 73 | * Get a subgraph that holds all the possible shortest paths 74 | */ 75 | lazy val subgraph: Set[labelDigraph.InnerEdgeType] = support.subgraphEdges(labelDigraph,"E","D") 76 | 77 | /** 78 | * Or just get the shortest paths 79 | */ 80 | lazy val paths = support.allLeastPaths(labelDigraph,"E","D") //todo add type annotation back Seq[Seq[labelDigraph.InnerNodeType]] 81 | } 82 | 83 | -------------------------------------------------------------------------------- /Examples/src/scala/net/walend/disentangle/examples/DijkstraExample.scala: -------------------------------------------------------------------------------- 1 | package net.walend.disentangle.examples 2 | 3 | import net.walend.disentangle.graph.semiring.par.ParDijkstra 4 | import net.walend.disentangle.graph.{AdjacencyLabelDigraph, IndexedLabelDigraph} 5 | import net.walend.disentangle.graph.semiring.{AllPathsFirstSteps, Dijkstra, FewestNodes, FirstStepsTrait} 6 | 7 | import scala.collection.parallel.immutable.ParSeq 8 | 9 | /** 10 | * Use Dijkstra's algorithms to find either single-source or all-pairs shortest paths using the default semiring. 11 | * 12 | * @author dwalend 13 | * @since v0.2.0 14 | */ 15 | object DijkstraExample { 16 | 17 | /** 18 | * Edges are just a Seq of Tuple3[Node,Node,Edge] 19 | */ 20 | lazy val edges: Seq[(String, String, String)] = Seq( 21 | ("A","B","ab"), 22 | ("B","C","bc"), 23 | ("C","D","cd"), 24 | ("D","E","de"), 25 | ("E","F","ef"), 26 | ("E","B","eb"), 27 | ("E","H","eh"), 28 | ("H","C","hc") 29 | ) 30 | 31 | /** 32 | * Generate all the shortest paths in the graph 33 | */ 34 | lazy val simpleShortPathLabels: Seq[(String, String, Option[FirstStepsTrait[String, Int]])] = Dijkstra.allPairsShortestPaths(edges) 35 | 36 | /** 37 | * The simplest API call finds paths with the fewest nodes, and supplies possible first steps to follow those paths. 38 | * AllPathsFirstSteps has some helper methods to generate the shortest paths. 39 | * 40 | * AllPathsFirstSteps takes a type parameter for Node's type, so you'll need to create a new one for your use. 41 | */ 42 | lazy val support: AllPathsFirstSteps[String, Int, Int] = Dijkstra.defaultSupport[String] 43 | 44 | 45 | /** 46 | * Generate all the shortest paths in the graph in parallel 47 | */ 48 | lazy val simpleShortPathLabelsFromPar: ParSeq[(String, String, Option[FirstStepsTrait[String, Int]])] = ParDijkstra.parAllPairsShortestPaths(edges) 49 | 50 | /** 51 | * The helper methods in AllPathsFirstSteps need a directed graph. 52 | * Use AllPathsFirstSteps.semiring's annihilator - None - for noEdgeExistsValue. 53 | */ 54 | lazy val labelDigraph: AdjacencyLabelDigraph[String, support.Label] = AdjacencyLabelDigraph(edges = simpleShortPathLabels,noEdgeExistsValue = support.semiring.O) 55 | 56 | /** 57 | * Get a subgraph that holds all the possible shortest paths 58 | */ 59 | lazy val subgraph: Set[labelDigraph.InnerEdgeType] = support.subgraphEdges(labelDigraph,"E","D") 60 | 61 | /** 62 | * Or just get the shortest paths 63 | */ 64 | lazy val paths: Seq[Seq[labelDigraph.InnerNodeType]] = support.allLeastPaths(labelDigraph,"E","D") 65 | 66 | /** 67 | * To get all shortest paths from a single source (or sink), first create the initial label digraph. 68 | * You'll want to reuse this graph for different sources and sinks. 69 | */ 70 | lazy val initialLabelDigraph: IndexedLabelDigraph[String, support.Label] = Dijkstra.createLabelDigraph(edges,support,support.convertEdgeToLabel(FewestNodes.convertEdgeToLabel)) 71 | 72 | /** 73 | * Use the initialLabelDigraph to create the labels for the shortest paths from the source. 74 | */ 75 | lazy val shortPathLabelsFromA: Seq[(String, String, support.Label)] = Dijkstra.dijkstraSingleSource(initialLabelDigraph,support)(initialLabelDigraph.innerNode("A").getOrElse(throw new IllegalStateException("A is not in this graph. How?"))) 76 | 77 | } 78 | -------------------------------------------------------------------------------- /Examples/src/scala/net/walend/disentangle/examples/DijkstraLeastWeightsExample.scala: -------------------------------------------------------------------------------- 1 | package net.walend.disentangle.examples 2 | 3 | import net.walend.disentangle.graph.semiring.par.ParDijkstra 4 | import net.walend.disentangle.graph.{AdjacencyLabelDigraph, IndexedLabelDigraph} 5 | import net.walend.disentangle.graph.semiring.{AllPathsFirstSteps, Dijkstra, LeastWeights} 6 | 7 | import scala.collection.parallel.immutable.ParSeq 8 | 9 | /** 10 | * Use Dijkstra's algorithms to find either single-source or all-pairs shortest paths using a custom semiring, LeastWeights. 11 | * 12 | * @author dwalend 13 | * @since v0.2.0 14 | */ 15 | object DijkstraLeastWeightsExample { 16 | 17 | /** 18 | * Edges are just a Seq of Tuple3[Node,Node,Edge] 19 | */ 20 | lazy val edges: Seq[(String, String, String)] = Seq( 21 | ("A","B","ab"), 22 | ("B","C","bc"), 23 | ("C","D","cd"), 24 | ("D","E","de"), 25 | ("E","F","ef"), 26 | ("E","B","eb"), 27 | ("E","H","eh"), 28 | ("H","C","hc") 29 | ) 30 | 31 | /** 32 | * A semiring support instance that uses double-valued labels to find the shortest paths. 33 | */ 34 | lazy val support: AllPathsFirstSteps[String, Double, Double] = AllPathsFirstSteps(LeastWeights) 35 | 36 | /** 37 | * This time, we'll need to supply a function that can convert from a String to a Double to build up the initial graph 38 | * of edges. You'll probably have something more significant than this hack. 39 | */ 40 | def stringToDouble(fromNode:String,toNode:String,edge:String):Double = edge.map(_.hashCode().toDouble).product 41 | 42 | /** 43 | * Build on AllPathsFirstSteps' convert method 44 | */ 45 | lazy val labelForEdge: (String, String, String) => support.Label = support.convertEdgeToLabel[String](stringToDouble) 46 | 47 | /** 48 | * Generate the first steps for all paths in the graph 49 | */ 50 | lazy val leastPathLabels: Seq[(String, String, support.Label)] = Dijkstra.allPairsLeastPaths(edges,support,labelForEdge) 51 | 52 | /** 53 | * Generate the first steps for all paths in the graph in parallel 54 | */ 55 | lazy val leastPathLabelsFromPar: ParSeq[(String, String, support.Label)] = ParDijkstra.parAllPairsLeastPaths(edges,support,labelForEdge) 56 | 57 | /** 58 | * The helper methods in AllPathsFirstSteps need a directed graph. 59 | * Use AllPathsFirstSteps.semiring's annihilator - None - for noEdgeExistsValue. 60 | */ 61 | lazy val labelDigraph: AdjacencyLabelDigraph[String, support.Label] = AdjacencyLabelDigraph(edges = leastPathLabels,noEdgeExistsValue = support.semiring.O) 62 | 63 | /** 64 | * Get a subgraph that holds all the possible shortest paths 65 | */ 66 | lazy val subgraph = support.subgraphEdges(labelDigraph,"E","D") 67 | 68 | /** 69 | * Or just get the shortest paths 70 | */ 71 | lazy val paths: Seq[Seq[AdjacencyLabelDigraph[String, support.Label]#InnerNode]] = support.allLeastPaths(labelDigraph,"E","D") 72 | 73 | /** 74 | * To get all shortest paths from a single source (or sink), first create the initial label digraph. 75 | * You'll want to reuse this graph for different sources and sinks. 76 | */ 77 | lazy val initialLabelDigraph: IndexedLabelDigraph[String, support.Label] = Dijkstra.createLabelDigraph(edges,support,labelForEdge) 78 | 79 | /** 80 | * Use the initialLabelDigraph to create the labels for the shortest paths from the source 81 | */ 82 | lazy val shortPathLabelsFromA: Seq[(String, String, support.Label)] = Dijkstra.dijkstraSingleSource(initialLabelDigraph,support)(initialLabelDigraph.innerNode("A").getOrElse(throw new IllegalStateException("A is not in this graph. How?"))) 83 | 84 | } 85 | -------------------------------------------------------------------------------- /Examples/src/scala/net/walend/disentangle/examples/FloydWarshallExample.scala: -------------------------------------------------------------------------------- 1 | package net.walend.disentangle.examples 2 | 3 | import net.walend.disentangle.graph.IndexedLabelDigraph 4 | import net.walend.disentangle.graph.semiring.{FloydWarshall, AllPathsFirstSteps, FirstStepsTrait} 5 | 6 | /** 7 | * Use Dijkstra's algorithms to find all-pairs shortest paths using the default semiring. 8 | * 9 | * @author dwalend 10 | * @since v0.2.0 11 | */ 12 | object FloydWarshallExample { 13 | 14 | /** 15 | * Edges are just a Seq of Tuple3[Node,Node,Edge] 16 | */ 17 | lazy val edges: Seq[(String, String, String)] = Seq( 18 | ("A","B","ab"), 19 | ("B","C","bc"), 20 | ("C","D","cd"), 21 | ("D","E","de"), 22 | ("E","F","ef"), 23 | ("E","B","eb"), 24 | ("E","H","eh"), 25 | ("H","C","hc") 26 | ) 27 | 28 | /** 29 | * Generate all the shortest paths in the graph 30 | */ 31 | lazy val simpleShortPathGraph: IndexedLabelDigraph[String, Option[FirstStepsTrait[String, Int]]] = FloydWarshall.allPairsShortestPaths(edges) 32 | 33 | /** 34 | * The simplest API call finds paths with the fewest nodes, and supplies possible first steps to follow those paths. 35 | * AllPathsFirstSteps has some helper methods to generate the shortest paths. 36 | * 37 | * AllPathsFirstSteps takes a type parameter for Node's type, so you'll need to create a new one for your use. 38 | */ 39 | lazy val support: AllPathsFirstSteps[String, Int, Int] = FloydWarshall.defaultSupport[String] 40 | 41 | /** 42 | * Get a subgraph that holds all the possible shortest paths 43 | */ 44 | lazy val subgraph: Set[simpleShortPathGraph.InnerEdgeType] = support.subgraphEdges(simpleShortPathGraph,"E","D") 45 | 46 | /** 47 | * Or just get the shortest paths 48 | */ 49 | lazy val paths: Seq[Seq[simpleShortPathGraph.InnerNodeType]] = support.allLeastPaths(simpleShortPathGraph,"E","D") 50 | 51 | } 52 | -------------------------------------------------------------------------------- /Examples/src/scala/net/walend/disentangle/examples/package.scala: -------------------------------------------------------------------------------- 1 | package net.walend.disentangle 2 | 3 | /** 4 | * Example code for the Disentangle project 5 | * 6 | * @author dwalend 7 | * @since v0.2.0 8 | */ 9 | package object examples { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /Examples/test/src/net/walend/disentangle/examples/BrandesExampleTest.scala: -------------------------------------------------------------------------------- 1 | package net.walend.disentangle.examples 2 | 3 | import munit.FunSuite 4 | import net.walend.disentangle.graph.AdjacencyLabelDigraph 5 | import net.walend.disentangle.graph.semiring.Brandes.BrandesSteps 6 | 7 | /** 8 | * 9 | * 10 | * @author dwalend 11 | * @since v0.2.0 12 | */ 13 | class BrandesExampleTest extends FunSuite { 14 | import net.walend.disentangle.graph.SomeGraph.* 15 | 16 | test("The Brandes example should produce expected results") { 17 | 18 | val expectedShortestPaths = Vector( 19 | (A,A,Some(BrandesSteps(0,1,List()))), 20 | (A,B,Some(BrandesSteps(1,1,List(1)))), 21 | (B,B,Some(BrandesSteps(0,1,List()))), 22 | (C,B,Some(BrandesSteps(3,1,List(3)))), 23 | (D,B,Some(BrandesSteps(2,1,List(4)))), 24 | (E,B,Some(BrandesSteps(1,1,List(1)))), 25 | (H,B,Some(BrandesSteps(4,1,List(2)))), 26 | (A,C,Some(BrandesSteps(2,1,List(1)))), 27 | (B,C,Some(BrandesSteps(1,1,List(2)))), 28 | (C,C,Some(BrandesSteps(0,1,List()))), 29 | (D,C,Some(BrandesSteps(3,2,List(4)))), 30 | (E,C,Some(BrandesSteps(2,2,List(1, 6)))), 31 | (H,C,Some(BrandesSteps(1,1,List(2)))), 32 | (A,D,Some(BrandesSteps(3,1,List(1)))), 33 | (B,D,Some(BrandesSteps(2,1,List(2)))), 34 | (C,D,Some(BrandesSteps(1,1,List(3)))), 35 | (D,D,Some(BrandesSteps(0,1,List()))), 36 | (E,D,Some(BrandesSteps(3,2,List(1, 6)))), 37 | (H,D,Some(BrandesSteps(2,1,List(2)))), 38 | (A,E,Some(BrandesSteps(4,1,List(1)))), 39 | (B,E,Some(BrandesSteps(3,1,List(2)))), 40 | (C,E,Some(BrandesSteps(2,1,List(3)))), 41 | (D,E,Some(BrandesSteps(1,1,List(4)))), 42 | (E,E,Some(BrandesSteps(0,1,List()))), 43 | (H,E,Some(BrandesSteps(3,1,List(2)))), 44 | (A,F,Some(BrandesSteps(5,1,List(1)))), 45 | (B,F,Some(BrandesSteps(4,1,List(2)))), 46 | (C,F,Some(BrandesSteps(3,1,List(3)))), 47 | (D,F,Some(BrandesSteps(2,1,List(4)))), 48 | (E,F,Some(BrandesSteps(1,1,List(5)))), 49 | (F,F,Some(BrandesSteps(0,1,List()))), 50 | (H,F,Some(BrandesSteps(4,1,List(2)))), 51 | (A,H,Some(BrandesSteps(5,1,List(1)))), 52 | (B,H,Some(BrandesSteps(4,1,List(2)))), 53 | (C,H,Some(BrandesSteps(3,1,List(3)))), 54 | (D,H,Some(BrandesSteps(2,1,List(4)))), 55 | (E,H,Some(BrandesSteps(1,1,List(6)))), 56 | (H,H,Some(BrandesSteps(0,1,List()))) 57 | ) 58 | 59 | val expectedBetweennesses: Map[String, Double] = Map(E -> 13.0, F -> 0.0, A -> 0.0, B -> 6.5, C -> 13.0, H -> 1.5, D -> 13.0) 60 | 61 | val shortestPathsAndBetweennesses: (IndexedSeq[(String, String, Option[BrandesSteps[String, Int]])], Map[String, Double]) = BrandesExample.shortestPathsAndBetweenness 62 | 63 | assert(shortestPathsAndBetweennesses._1 == expectedShortestPaths) 64 | assertEquals(shortestPathsAndBetweennesses._2 ,expectedBetweennesses) 65 | /* 66 | val shortestPathsAndBetweennessesFromPar = BrandesExample.shortestPathsAndBetweennessFromPar 67 | 68 | shortestPathsAndBetweennessesFromPar._1 should be (expectedShortestPaths) 69 | shortestPathsAndBetweennessesFromPar._2 should be (expectedBetweennesses) 70 | */ 71 | val labelDigraph = BrandesExample.labelDigraph 72 | 73 | val expectedSubgraphEdges: Set[labelDigraph.InnerEdge] = Set( 74 | labelDigraph.edge(H,C), 75 | labelDigraph.edge(E,B), 76 | labelDigraph.edge(C,D), 77 | labelDigraph.edge(E,H), 78 | labelDigraph.edge(B,C) 79 | ).filter(_.isDefined).map(_.get) 80 | 81 | val subgraph = BrandesExample.subgraph 82 | 83 | assert(subgraph == expectedSubgraphEdges) 84 | 85 | val expectedPaths: Seq[List[labelDigraph.InnerNode]] = List( 86 | List(labelDigraph.innerNode(E).get, labelDigraph.innerNode(B).get, labelDigraph.innerNode(C).get, labelDigraph.innerNode(D).get), 87 | List(labelDigraph.innerNode(E).get, labelDigraph.innerNode(H).get, labelDigraph.innerNode(C).get, labelDigraph.innerNode(D).get) 88 | ) 89 | 90 | val paths = BrandesExample.paths 91 | assert(paths == expectedPaths) 92 | } 93 | 94 | test("The BrandesImplicitsExample should produce expected results") { 95 | 96 | val expectedBetweenesses = Map( 97 | E -> 7.5, 98 | F -> 0.0, 99 | A -> 0.0, 100 | B -> 5.666666666666667, 101 | C -> 2.5, 102 | H -> 0.6666666666666666, 103 | D -> 0.6666666666666666 104 | ) 105 | val betweenesses = BrandesImplicitsExample.betweennessValues 106 | 107 | assertEquals(betweenesses ,expectedBetweenesses) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /Examples/test/src/net/walend/disentangle/examples/DijkstraExampleTest.scala: -------------------------------------------------------------------------------- 1 | package net.walend.disentangle.examples 2 | 3 | import munit.FunSuite 4 | 5 | /** 6 | * 7 | * 8 | * @author dwalend 9 | * @since v0.2.0 10 | */ 11 | class DijkstraExampleTest extends FunSuite { 12 | 13 | test("The Dijkstra example should produce expected results") { 14 | val edges = DijkstraExample.edges 15 | 16 | val simpleShortPathLabels = DijkstraExample.simpleShortPathLabels 17 | 18 | //todo val simpleShortPathLabelsFromPar = DijkstraExample.simpleShortPathLabelsFromPar 19 | 20 | val labelDigraph = DijkstraExample.labelDigraph 21 | 22 | val subgraph = DijkstraExample.subgraph 23 | 24 | val paths = DijkstraExample.paths 25 | 26 | val shortPathLabelsFromA = DijkstraExample.shortPathLabelsFromA 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Examples/test/src/net/walend/disentangle/examples/DijkstraLeastWeightsExampleTest.scala: -------------------------------------------------------------------------------- 1 | package net.walend.disentangle.examples 2 | 3 | import munit.FunSuite 4 | 5 | /** 6 | * 7 | * 8 | * @author dwalend 9 | * @since v0.2.0 10 | */ 11 | class DijkstraLeastWeightsExampleTest extends FunSuite { 12 | 13 | test("The Dijkstra with least weights example should produce expected results"){ 14 | 15 | val shortPathLabels = DijkstraLeastWeightsExample.leastPathLabels 16 | 17 | //todo val shortPathLabelsFromPar = DijkstraLeastWeightsExample.leastPathLabelsFromPar 18 | 19 | val labelDigraph = DijkstraLeastWeightsExample.labelDigraph 20 | 21 | val subgraph = DijkstraLeastWeightsExample.subgraph 22 | 23 | val paths = DijkstraLeastWeightsExample.paths 24 | 25 | val shortPathLabelsFromA = DijkstraLeastWeightsExample.shortPathLabelsFromA 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Examples/test/src/net/walend/disentangle/examples/FloydWarshallExampleTest.scala: -------------------------------------------------------------------------------- 1 | package net.walend.disentangle.examples 2 | 3 | import munit.FunSuite 4 | import net.walend.disentangle.graph.IndexedLabelDigraph 5 | import net.walend.disentangle.graph.mutable.MatrixLabelDigraph 6 | import net.walend.disentangle.graph.semiring.{AllPathsFirstSteps, FirstStepsTrait, FloydWarshall} 7 | 8 | /** 9 | * 10 | * 11 | * @author dwalend 12 | * @since v0.2.0 13 | */ 14 | class FloydWarshallExampleTest extends FunSuite { 15 | import net.walend.disentangle.graph.SomeGraph.* 16 | 17 | val support: AllPathsFirstSteps[String, Int, Int] = FloydWarshall.defaultSupport[String] 18 | 19 | test("The Floyd-Warshall example should produce expected results") { 20 | 21 | val expectedShortPathGraph = MatrixLabelDigraph( 22 | edges = Vector((A,A,Some(support.FirstSteps(0,Set()))), 23 | (A,B,Some(support.FirstSteps(1,Set(B)))), 24 | (A,C,Some(support.FirstSteps(2,Set(B)))), 25 | (A,D,Some(support.FirstSteps(3,Set(B)))), 26 | (A,E,Some(support.FirstSteps(4,Set(B)))), 27 | (A,H,Some(support.FirstSteps(5,Set(B)))), 28 | (A,F,Some(support.FirstSteps(5,Set(B)))), 29 | (B,B,Some(support.FirstSteps(0,Set()))), 30 | (B,C,Some(support.FirstSteps(1,Set(C)))), 31 | (B,D,Some(support.FirstSteps(2,Set(C)))), 32 | (B,E,Some(support.FirstSteps(3,Set(C)))), 33 | (B,H,Some(support.FirstSteps(4,Set(C)))), 34 | (B,F,Some(support.FirstSteps(4,Set(C)))), 35 | (C,B,Some(support.FirstSteps(3,Set(D)))), 36 | (C,C,Some(support.FirstSteps(0,Set()))), 37 | (C,D,Some(support.FirstSteps(1,Set(D)))), 38 | (C,E,Some(support.FirstSteps(2,Set(D)))), 39 | (C,H,Some(support.FirstSteps(3,Set(D)))), 40 | (C,F,Some(support.FirstSteps(3,Set(D)))), 41 | (D,B,Some(support.FirstSteps(2,Set(E)))), 42 | (D,C,Some(support.FirstSteps(3,Set(E)))), 43 | (D,D,Some(support.FirstSteps(0,Set()))), 44 | (D,E,Some(support.FirstSteps(1,Set(E)))), 45 | (D,H,Some(support.FirstSteps(2,Set(E)))), 46 | (D,F,Some(support.FirstSteps(2,Set(E)))), 47 | (E,B,Some(support.FirstSteps(1,Set(B)))), 48 | (E,C,Some(support.FirstSteps(2,Set(B, H)))), 49 | (E,D,Some(support.FirstSteps(3,Set(B, H)))), 50 | (E,E,Some(support.FirstSteps(0,Set()))), 51 | (E,H,Some(support.FirstSteps(1,Set(H)))), 52 | (E,F,Some(support.FirstSteps(1,Set(F)))), 53 | (H,B,Some(support.FirstSteps(4,Set(C)))), 54 | (H,C,Some(support.FirstSteps(1,Set(C)))), 55 | (H,D,Some(support.FirstSteps(2,Set(C)))), 56 | (H,E,Some(support.FirstSteps(3,Set(C)))), 57 | (H,H,Some(support.FirstSteps(0,Set()))), 58 | (H,F,Some(support.FirstSteps(4,Set(C)))), 59 | (F,F,Some(support.FirstSteps(0,Set()))) 60 | ), 61 | nodes = Seq(A, B, C, D, E, H, F), 62 | noEdgeExistsValue = None 63 | ) 64 | 65 | 66 | val shortPathGraph: IndexedLabelDigraph[String, Option[FirstStepsTrait[String, Int]]] = FloydWarshallExample.simpleShortPathGraph 67 | 68 | assert(shortPathGraph == expectedShortPathGraph) 69 | 70 | val expectedSubgraphEdges: Set[shortPathGraph.InnerEdgeType] = Set( 71 | shortPathGraph.edge(H,C), 72 | shortPathGraph.edge(E,B), 73 | shortPathGraph.edge(C,D), 74 | shortPathGraph.edge(E,H), 75 | shortPathGraph.edge(B,C) 76 | ).filter(_.isDefined).map(_.get) 77 | 78 | val subgraphEdges = FloydWarshallExample.subgraph 79 | 80 | assert(subgraphEdges == expectedSubgraphEdges) 81 | 82 | val expectedPaths = Vector( 83 | List(shortPathGraph.innerNode(E).get, shortPathGraph.innerNode(B).get, shortPathGraph.innerNode(C).get, shortPathGraph.innerNode(D).get), 84 | List(shortPathGraph.innerNode(E).get, shortPathGraph.innerNode(H).get, shortPathGraph.innerNode(C).get, shortPathGraph.innerNode(D).get) 85 | ) 86 | 87 | val paths = FloydWarshallExample.paths 88 | 89 | assert(paths == expectedPaths) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Experiments/test/src/net/walend/disentangle/graph/cluster/AgglomerateTest.scala: -------------------------------------------------------------------------------- 1 | package net.walend.disentangle.graph.cluster 2 | 3 | import munit.{FunSuite, IgnoreSuite} 4 | import net.walend.disentangle.graph.{AdjacencyUndigraph, NodePair, SomeGraph} 5 | import net.walend.disentangle.graph.cluster.Agglomerate.{Initial as I, *} 6 | 7 | class AgglomerateTest extends FunSuite { 8 | import SomeGraph._ 9 | 10 | test("Testing with SomeGraph should not crash".ignore) { 11 | 12 | val testGraph = SomeGraph.testUndigraph //todo work with the karate school graph 13 | val initialCluster = Agglomerate.initialClusterFromGraph(testGraph) 14 | val clusters: List[ClusterGraph] = Agglomerate.agglomerate(initialCluster) 15 | } 16 | 17 | test("An empty graph should result in a List with an empty graph".ignore) { 18 | val testGraph = AdjacencyUndigraph() 19 | 20 | val initialClusters = Agglomerate.initialClusterFromGraph(testGraph) 21 | val clusters: List[ClusterGraph] = Agglomerate.agglomerate(initialClusters) 22 | 23 | assertEquals(clusters,List(AdjacencyUndigraph())) 24 | } 25 | 26 | 27 | test("A graph with one node should not crash".ignore) { 28 | val testGraph = AdjacencyUndigraph(nodes = List(A)) 29 | 30 | val initialClusters = Agglomerate.initialClusterFromGraph(testGraph) 31 | val clusters: List[ClusterGraph] = Agglomerate.agglomerate(initialClusters) 32 | 33 | assertEquals(clusters,List(AdjacencyUndigraph(nodes = List(Agglomerate.Initial(A))))) 34 | } 35 | 36 | test("A graph with two isolated nodes should not crash".ignore) { 37 | val testGraph = AdjacencyUndigraph(nodes = List(A,B)) 38 | 39 | val initialClusters = Agglomerate.initialClusterFromGraph(testGraph) 40 | val clusters: List[ClusterGraph] = Agglomerate.agglomerate(initialClusters) 41 | 42 | val expectedClusters: List[ClusterGraph] = List(AdjacencyUndigraph(nodes = Seq(I(A), I(B))), AdjacencyUndigraph(nodes = Seq(Isolates(Set(I(A), I(B)),2)))) 43 | 44 | assertEquals(clusters,expectedClusters) 45 | } 46 | 47 | test("A graph with two linked nodes should form a cycle".ignore) { 48 | val testGraph = AdjacencyUndigraph(edges = Seq(NodePair(A,B))) 49 | 50 | val initialClusters = Agglomerate.initialClusterFromGraph(testGraph) 51 | val clusters: List[ClusterGraph] = Agglomerate.agglomerate(initialClusters) 52 | 53 | val expectedInitial: AdjacencyUndigraph[Cluster] = AdjacencyUndigraph(edges = Seq(NodePair(I(A),I(B)))) 54 | val expectedCycle = Cycle(AdjacencyUndigraph(edges = Seq(NodePair(I(A),I(B)))),Seq(I(B),I(A)),2) 55 | val expectedClusters: List[ClusterGraph] = List(expectedInitial, AdjacencyUndigraph(nodes = Seq(expectedCycle))) 56 | 57 | assertEquals(clusters,expectedClusters) 58 | } 59 | 60 | test("A graph with three linked nodes should form a cycle".ignore) { 61 | val testGraph = AdjacencyUndigraph(edges = Seq(NodePair(A,B),NodePair(B,C),NodePair(C,A)),nodes = Seq(A,B,C)) 62 | 63 | val initialClusters = Agglomerate.initialClusterFromGraph(testGraph) 64 | val clusters: List[ClusterGraph] = Agglomerate.agglomerate(initialClusters) 65 | 66 | val expectedInitial: AdjacencyUndigraph[Cluster] = AdjacencyUndigraph(edges = Seq(NodePair(I(A),I(B)),NodePair(I(B),I(C)),NodePair(I(C),I(A)))) 67 | val expectedCycle = Cycle(AdjacencyUndigraph(edges = Seq(NodePair(I(A),I(B)),NodePair(I(B),I(C)),NodePair(I(C),I(A)))),Seq(I(C),I(B),I(A)),2) 68 | val expectedClusters: List[ClusterGraph] = List(expectedInitial, AdjacencyUndigraph(nodes = Seq(expectedCycle))) 69 | 70 | assertEquals(clusters,expectedClusters) 71 | } 72 | 73 | 74 | test("A star graph should form something reasonable".ignore) { 75 | 76 | val edges = Seq((A,B),(B,C)) 77 | 78 | val testGraph = AdjacencyUndigraph.fromPairs(edges = edges,nodes = Seq(A,B,C)) 79 | 80 | val initialClusters = Agglomerate.initialClusterFromGraph(testGraph) 81 | val clusters: List[ClusterGraph] = Agglomerate.agglomerate(initialClusters) 82 | 83 | val expectedSiblings = Sibling(AdjacencyUndigraph(nodes = Seq(I(B), I(C))),I(A),2) 84 | val expectedCaterpillar = Caterpillar(AdjacencyUndigraph(nodes = Seq(I(A))),List(I(A)),expectedSiblings,2) 85 | 86 | val expectedCycle = Cycle( 87 | AdjacencyUndigraph(edges = Seq(NodePair(expectedCaterpillar,expectedSiblings))), 88 | Seq(expectedCaterpillar,expectedSiblings), 89 | 3) 90 | 91 | val expectedClusters: List[ClusterGraph] = List( 92 | AdjacencyUndigraph(edges = Seq(NodePair(I(A),I(B)), NodePair(I(A),I(C)))), 93 | AdjacencyUndigraph(edges = Seq(NodePair(expectedCaterpillar,expectedSiblings))), 94 | AdjacencyUndigraph(nodes = Seq(expectedCycle)) 95 | ) 96 | 97 | // println(clusters) 98 | 99 | // clusters should be(expectedClusters) 100 | 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /Graph/src/scala/net/walend/disentangle/graph/AdjacencyDigraph.scala: -------------------------------------------------------------------------------- 1 | package net.walend.disentangle.graph 2 | 3 | import scala.collection.{Map, Seq, Iterable} 4 | 5 | /** 6 | * Provides constant-time access for successor and predecessor edges of a node. 7 | * 8 | * The constructor is O(n + a ln(n)) 9 | * 10 | * @author dwalend 11 | * @since v0.2.1 12 | */ 13 | class AdjacencyDigraph[Node](outNodes:IndexedSet[Node], //provides the master index values for each node. 14 | outSuccessors:IndexedSet[IndexedSet[(Node,Node)]], // (i) is the successors for node i, (j) is the node,node tuple to reach that second node. 15 | outPredecessors:IndexedSet[IndexedSet[(Node,Node)]] 16 | ) extends Tuple2Digraph[Node] with IndexedGraph[Node] { 17 | override type InnerNodeType = InNode 18 | case class InNode(override val value:Node,override val index:Int) extends this.DigraphInnerNodeTrait with this.InnerIndexedNodeTrait { 19 | 20 | override def successors: IndexedSet[InnerEdgeType] = { 21 | inSuccessors.apply(index) 22 | } 23 | 24 | override def predecessors: IndexedSet[InnerEdgeType] = { 25 | inPredecessors.apply(index) 26 | } 27 | 28 | override def hashCode(): Int = index 29 | 30 | override def equals(thing: Any): Boolean = { 31 | thing match { 32 | case inNode:InNode => inNode.index == index 33 | case _ => false 34 | } 35 | } 36 | } 37 | 38 | override type InnerEdgeType = InnerEdge 39 | case class InnerEdge(from:InNode,to:InNode) extends DigraphInnerEdgeTrait { 40 | override def value: OuterEdgeType = (from.value,to.value) 41 | } 42 | 43 | val inNodes:IndexedSet[InNode] = outNodes.zipWithIndex.map(x => InNode(x._1,x._2)) 44 | val nodeToInNode:Map[Node,InNode] = inNodes.map(x => x.value -> x).toMap 45 | 46 | def neighborSet(indexedSet:IndexedSet[(Node,Node)]):IndexedSet[InnerEdgeType] = { 47 | indexedSet.map(x => InnerEdge(nodeToInNode.get(x._1).get,nodeToInNode.get(x._2).get)) 48 | } 49 | 50 | val inSuccessors:IndexedSet[IndexedSet[InnerEdgeType]] = outSuccessors.map(neighborSet) 51 | val inPredecessors:IndexedSet[IndexedSet[InnerEdgeType]] = outPredecessors.map(neighborSet) 52 | 53 | def nodes = outNodes 54 | 55 | override def nodeCount: Int = outNodes.size 56 | 57 | /** 58 | * O(ln(n)) 59 | * 60 | * @return Some inner node if it exists in the digraph or None 61 | */ 62 | override def innerNode(value: Node): Option[InNode] = { 63 | nodeToInNode.get(value) 64 | } 65 | 66 | /** 67 | * O(1) 68 | * 69 | * @return InnerNode representation of all of the nodes in the graph. 70 | */ 71 | override def innerNodes: IndexedSet[InNode] = inNodes 72 | 73 | 74 | /** 75 | * @return A Traversable of the edges as represented in the graph 76 | */ 77 | override def innerEdges:IndexedSet[InnerEdgeType] = inSuccessors.flatten 78 | 79 | /** 80 | * O(n^2) 81 | * 82 | * @return All of the edges in the graph 83 | */ 84 | override def edges: IndexedSet[OuterEdgeType] = outSuccessors.flatten 85 | 86 | /** 87 | * O(1) 88 | * 89 | * @return 90 | */ 91 | override def node(i: Int): Node = outNodes.apply(i) 92 | 93 | /** 94 | * O(1) 95 | * 96 | * @return 97 | */ 98 | override def innerNodeForIndex(i: Int): InNode = innerNodes.apply(i) 99 | 100 | override def toString:String = { 101 | s"${this.getClass.getSimpleName}(edges = $edges,nodes = $outNodes)" 102 | } 103 | 104 | override def edge(from: InNode, to: InNode): Option[InnerEdgeType] = ??? //todo 105 | } 106 | 107 | /** 108 | * O(n ln(n) + e ln(n)) 109 | */ 110 | object AdjacencyDigraph{ 111 | 112 | def apply[Node](edges:Iterable[(Node,Node)] = Seq.empty, 113 | nodes:Seq[Node] = Seq.empty) = { 114 | 115 | val nodeValues = IndexedSet.from((nodes ++ edges.map(_._1) ++ edges.map(_._2)).distinct) 116 | 117 | val successorMap:Map[Node,Iterable[(Node,Node)]] = edges.groupBy(_._1) 118 | val predecessorMap:Map[Node,Iterable[(Node,Node)]] = edges.groupBy(_._2) 119 | 120 | val successorAdjacencies: IndexedSet[IndexedSet[(Node, Node)]] = nodeValues.map(n => IndexedSet.from(successorMap.getOrElse(n,Vector.empty[(Node,Node)]))) 121 | val predecessorAdjacencies: IndexedSet[IndexedSet[(Node, Node)]] = nodeValues.map(n => IndexedSet.from(predecessorMap.getOrElse(n,Vector.empty[(Node,Node)]))) 122 | 123 | new AdjacencyDigraph(nodeValues,successorAdjacencies,predecessorAdjacencies) 124 | } 125 | 126 | } 127 | -------------------------------------------------------------------------------- /Graph/src/scala/net/walend/disentangle/graph/AdjacencyLabelDigraph.scala: -------------------------------------------------------------------------------- 1 | package net.walend.disentangle.graph 2 | 3 | import scala.collection.immutable.{Map, Seq, Iterable} 4 | 5 | /** 6 | * Provides constant-time access for successor and predecessor edges of a node. 7 | * 8 | * The constructor is O(n + a ln(n)) 9 | * 10 | * @author dwalend 11 | * @since v0.1.0 12 | */ 13 | class AdjacencyLabelDigraph[Node,Label](outNodes:IndexedSet[Node], //provides the master index values for each node. 14 | outSuccessors:Vector[IndexedSet[(Node,Node,Label)]], // (i) is the successors for node i, (j) is the node,node,label tuple to reach that second node. 15 | outPredecessors:Vector[IndexedSet[(Node,Node,Label)]], 16 | val noEdgeExistsLabel:Label //value for no edge 17 | ) extends IndexedLabelDigraph[Node,Label] { 18 | override type InnerNodeType = InnerNode 19 | case class InnerNode(override val value:Node, override val index:Int) extends this.DigraphInnerNodeTrait with this.InnerIndexedNodeTrait { 20 | 21 | override def successors: IndexedSet[InnerEdgeType] = { 22 | inSuccessors(index) 23 | } 24 | 25 | override def predecessors: IndexedSet[InnerEdgeType] = { 26 | inPredecessors(index) 27 | } 28 | 29 | override def hashCode(): Int = index 30 | 31 | override def equals(thing: Any): Boolean = { 32 | thing match { 33 | case inNode:InnerNode => inNode.index == index 34 | case _ => false 35 | } 36 | } 37 | } 38 | 39 | override type InnerEdgeType = InnerEdge 40 | case class InnerEdge(from:InnerNodeType,to:InnerNodeType,label:Label) extends LabelDigraphEdgeTrait { 41 | override def value: OuterEdgeType = (from.value,to.value,label) 42 | } 43 | 44 | val inNodes:IndexedSet[InnerNode] =outNodes.zipWithIndex.map(x => InnerNode(x._1,x._2)) 45 | val nodeToInNode:Map[Node,InnerNode] = inNodes.map(x => x.value -> x).toMap 46 | 47 | def neighborSet(indexedSet:IndexedSet[(Node,Node,Label)]):IndexedSet[InnerEdgeType] = { 48 | indexedSet.map(x => InnerEdge(nodeToInNode(x._1),nodeToInNode(x._2),x._3)) 49 | } 50 | 51 | val inSuccessors:Vector[IndexedSet[InnerEdgeType]] = outSuccessors.map(neighborSet) 52 | val inPredecessors:Vector[IndexedSet[InnerEdgeType]] = outPredecessors.map(neighborSet) 53 | 54 | def nodes: IndexedSet[Node] = outNodes 55 | 56 | override def nodeCount: Int = outNodes.size 57 | 58 | /** 59 | * O(ln(n)) 60 | * 61 | * @return Some inner node if it exists in the digraph or None 62 | */ 63 | override def innerNode(value: Node): Option[InnerNode] = { 64 | nodeToInNode.get(value) 65 | } 66 | 67 | /** 68 | * O(1) 69 | * 70 | * @return InnerNode representation of all of the nodes in the graph. 71 | */ 72 | override def innerNodes: IndexedSet[InnerNode] = inNodes 73 | 74 | /** 75 | * @return A Traversable of the edges as represented in the graph 76 | */ 77 | override def innerEdges:Iterable[InnerEdgeType] = inSuccessors.flatten 78 | 79 | /** 80 | * O(n^2) 81 | * 82 | * @return All of the edges in the graph 83 | */ 84 | override def edges: Iterable[OuterEdgeType] = outSuccessors.flatten 85 | 86 | /** 87 | * O(n) 88 | * 89 | * @return the edge between start and end or noEdgeExistsValue 90 | */ 91 | //todo reconsile with edge() and the other label. 92 | override def label(from: InnerNode, to: InnerNode):Label = { 93 | val indexedSet = inSuccessors(from.index).filter(x => x.to == to) 94 | indexedSet.size match { 95 | case 0 => noEdgeExistsLabel 96 | case 1 => indexedSet.iterator.next().label 97 | case _ => throw new IllegalStateException(s"Multiple edges from $from to $to: "+indexedSet) 98 | } 99 | } 100 | 101 | /** 102 | * O(1) 103 | * 104 | * @return 105 | */ 106 | override def node(i: Int): Node = outNodes.apply(i) 107 | 108 | /** 109 | * O(1) 110 | * 111 | * @return 112 | */ 113 | override def innerNodeForIndex(i: Int): InnerNode = innerNodes.apply(i) 114 | 115 | /** 116 | * O(n) 117 | * 118 | * @return 119 | */ 120 | override def label(i: Int, j: Int): Label = { 121 | val indexedSet = inSuccessors(i).filter(x => x.to == inNodes.apply(j)) 122 | indexedSet.size match { 123 | case 0 => noEdgeExistsLabel 124 | case 1 => indexedSet.iterator.next().label 125 | case _ => throw new IllegalStateException(s"Multiple edges from ${node(i)} to ${node(j)}: "+indexedSet) 126 | } 127 | } 128 | 129 | override def toString:String = { 130 | s"${this.getClass.getSimpleName}(edges = $edges,nodes = $outNodes,noEdgeExistsValue = $noEdgeExistsLabel)" 131 | } 132 | 133 | override def edge(from: InnerNode, to: InnerNode): Option[InnerEdgeType] = { 134 | 135 | val indexedSet = inSuccessors(from.index).filter(x => x.to == to) 136 | 137 | indexedSet.size match { 138 | case 0 => None 139 | case 1 => Some(indexedSet.iterator.next()) 140 | case _ => throw new IllegalStateException(s"Multiple edges from $from to $to: "+indexedSet) 141 | } 142 | } 143 | } 144 | 145 | /** 146 | * O(n ln(n) + e ln(n)) 147 | */ 148 | object AdjacencyLabelDigraph{ 149 | 150 | def apply[Node,Label](edges:Iterable[(Node,Node,Label)] = Seq.empty, 151 | nodes:Seq[Node] = Seq.empty, 152 | noEdgeExistsValue:Label = null): AdjacencyLabelDigraph[Node, Label] = { 153 | 154 | val nodeValues: Vector[Node] = Vector.from((nodes ++ edges.map(_._1) ++ edges.map(_._2)).distinct) 155 | 156 | val successorMap:Map[Node,Iterable[(Node,Node,Label)]] = edges.groupBy(_._1) 157 | val predecessorMap:Map[Node,Iterable[(Node,Node,Label)]] = edges.groupBy(_._2) 158 | 159 | val successorAdjacencies:Vector[IndexedSet[(Node,Node,Label)]] = nodeValues.map(n => IndexedSet.from(successorMap.getOrElse(n,Vector.empty[(Node,Node,Label)]))) 160 | val predecessorAdjacencies:Vector[IndexedSet[(Node,Node,Label)]] = nodeValues.map(n => IndexedSet.from(predecessorMap.getOrElse(n,Vector.empty[(Node,Node,Label)]))) 161 | 162 | new AdjacencyLabelDigraph(IndexedSet.from(nodeValues),successorAdjacencies,predecessorAdjacencies,noEdgeExistsValue) 163 | } 164 | 165 | } -------------------------------------------------------------------------------- /Graph/src/scala/net/walend/disentangle/graph/AdjacencyLabelUndigraph.scala: -------------------------------------------------------------------------------- 1 | package net.walend.disentangle.graph 2 | 3 | import scala.collection.immutable.{Map, Seq, Iterable} 4 | 5 | /** 6 | * Provides constant-time access for edges of a node. 7 | * 8 | * The constructor is O(n + a ln(n)) 9 | * 10 | * @author dwalend 11 | * @since v0.2.1 12 | */ 13 | class AdjacencyLabelUndigraph[Node,Label](outNodes:IndexedSet[Node], //provides the master index values for each node. 14 | outEdges:Vector[IndexedSet[(NodePair[Node],Label)]], // (i) is the edges for node i, (j) is the NodePair[node,node],edge pair to reach that second node. 15 | val noEdgeExistsLabel:Label //value for no edge 16 | ) extends IndexedLabelUndigraph[Node,Label] { 17 | 18 | type InnerEdgeType = InnerEdge 19 | case class InnerEdge(nodePair: NodePair[InNode],label: Label) extends UndigraphInnerEdgeTrait { 20 | override def value: (NodePair[Node],Label) = (NodePair(nodePair._1.value,nodePair._2.value),label) 21 | } 22 | object InnerEdge{ 23 | def apply(_1:InNode, _2:InNode, label: Label): InnerEdge = new InnerEdge(NodePair(_1,_2),label) 24 | } 25 | 26 | type InnerNodeType = InNode 27 | case class InNode(override val value:Node,override val index:Int) extends this.UndigraphInnerNodeTrait with this.InnerIndexedNodeTrait { 28 | 29 | override def innerEdges: IndexedSet[InnerEdgeType] = { 30 | inEdges(index) 31 | } 32 | 33 | override def outerEdges: Set[(NodePair[Node], Label)] = { 34 | outEdges(index) 35 | } 36 | 37 | override def hashCode(): Int = index 38 | 39 | override def equals(thing: Any): Boolean = { 40 | thing match { 41 | case inNode:InNode => inNode.index == index 42 | case _ => false 43 | } 44 | } 45 | } 46 | 47 | val inNodes:IndexedSet[InNode] =outNodes.zipWithIndex.map(x => InNode(x._1,x._2)) 48 | val nodeToInNode:Map[Node,InNode] = inNodes.map(x => x.value -> x).toMap 49 | 50 | //todo really should be a Set, not an IndexedSet 51 | def neighborSet(indexedSet:IndexedSet[OuterEdgeType]):IndexedSet[InnerEdgeType] = { 52 | indexedSet.map(e => InnerEdge(NodePair(nodeToInNode(e._1._1),nodeToInNode(e._1._2)),e._2)) 53 | } 54 | 55 | //todo really should be a Set, not an IndexedSet 56 | val inEdges:Vector[IndexedSet[InnerEdgeType]] = outEdges.map(neighborSet) 57 | 58 | def nodes: IndexedSet[Node] = outNodes 59 | 60 | override def nodeCount: Int = outNodes.size 61 | 62 | /** 63 | * O(ln(n)) 64 | * 65 | * @return Some inner node if it exists in the digraph or None 66 | */ 67 | override def innerNode(value: Node): Option[InNode] = { 68 | nodeToInNode.get(value) 69 | } 70 | 71 | /** 72 | * O(1) 73 | * 74 | * @return InnerNode representation of all of the nodes in the graph. 75 | */ 76 | override def innerNodes: IndexedSet[InNode] = inNodes 77 | 78 | /** 79 | * @return A Traversable of the edges as represented in the graph 80 | */ 81 | override lazy val innerEdges:Vector[InnerEdgeType] = inEdges.flatten.distinct 82 | 83 | /** 84 | * O(n^2) 85 | * 86 | * @return All of the edges in the graph 87 | */ 88 | override def edges: Seq[OuterEdgeType] = outEdges.flatten.distinct 89 | 90 | /** 91 | * O(n) 92 | * 93 | * @return the edge between start and end or noEdgeExistsValue 94 | */ 95 | override def label(between:NodePair[InnerNodeType]):Label = { 96 | 97 | val indexedSet = inEdges(between._1.index).filter(x => x.nodePair.contains(between._2)) 98 | indexedSet.size match { 99 | case 0 => noEdgeExistsLabel 100 | case 1 => indexedSet.iterator.next().label 101 | case _ => throw new IllegalStateException(s"Multiple edges between $between: "+indexedSet) 102 | } 103 | } 104 | 105 | 106 | /** 107 | * @return the Label between a pair of nodes, or noEdgeExistsLable if no edge exists. 108 | * @throws IllegalArgumentException if either node is not in the graph 109 | */ 110 | override def edge(between: _root_.net.walend.disentangle.graph.NodePair[Node]): InnerEdgeType = { 111 | val a: InNode = innerNode(between._1).getOrElse(throw new IllegalArgumentException(s"${between._1} is not in $this")) 112 | val b: InNode = innerNode(between._2).getOrElse(throw new IllegalArgumentException(s"${between._2} is not in $this")) 113 | val nodePair = NodePair(a,b) 114 | InnerEdge(nodePair,label(nodePair)) 115 | } 116 | 117 | /** 118 | * O(1) 119 | * 120 | * @return 121 | */ 122 | override def node(i: Int): Node = outNodes.apply(i) 123 | 124 | /** 125 | * O(1) 126 | * 127 | * @return 128 | */ 129 | override def innerNodeForIndex(i: Int): InNode = innerNodes.apply(i) 130 | 131 | /** 132 | * O(n) 133 | * 134 | * @return 135 | */ 136 | override def label(i: Int, j: Int): Label = { 137 | val indexedSet = inEdges(i).filter(x => x.nodePair._2 == inNodes.apply(j)) 138 | indexedSet.size match { 139 | case 1 => indexedSet.iterator.next().label 140 | case 0 => noEdgeExistsLabel 141 | case _ => throw new IllegalStateException(s"Multiple edges from ${node(i)} to ${node(j)}: "+indexedSet) 142 | } 143 | } 144 | 145 | override def toString:String = { 146 | s"${this.getClass.getSimpleName}(edges = $edges,nodes = $outNodes,noEdgeExistsValue = $noEdgeExistsLabel)" 147 | } 148 | } 149 | 150 | /** 151 | * O(n ln(n) + e ln(n)) 152 | */ 153 | object AdjacencyLabelUndigraph{ 154 | 155 | //noinspection ConvertibleToMethodValue 156 | def apply[Node,Label](edges:Iterable[(NodePair[Node],Label)] = Seq.empty, 157 | nodes:Seq[Node] = Seq.empty, 158 | noEdgeExists:Label = null): AdjacencyLabelUndigraph[Node, Label] = { 159 | 160 | val nodeValues = Vector.from((nodes ++ edges.map(_._1._1) ++ edges.map(_._1._2)).distinct) 161 | 162 | val successorMap:Map[Node,Iterable[(NodePair[Node],Label)]] = edges.groupBy(x => x._1._1) 163 | val predecessorMap:Map[Node,Iterable[(NodePair[Node],Label)]] = edges.groupBy(x => x._1._2) 164 | 165 | def getOrEmpty(n:Node,nodeToTrav:Map[Node,Iterable[(NodePair[Node],Label)]]):IndexedSet[(NodePair[Node],Label)] = { 166 | IndexedSet.from(nodeToTrav.getOrElse(n,Vector.empty[(NodePair[Node],Label)])) 167 | } 168 | 169 | val edgeAdjacencies: Vector[IndexedSet[(NodePair[Node], Label)]] = nodeValues.map(n => getOrEmpty(n,successorMap) ++ getOrEmpty(n,predecessorMap) ) 170 | 171 | new AdjacencyLabelUndigraph(IndexedSet.from(nodeValues),edgeAdjacencies,noEdgeExists) 172 | } 173 | 174 | } -------------------------------------------------------------------------------- /Graph/src/scala/net/walend/disentangle/graph/AdjacencyUndigraph.scala: -------------------------------------------------------------------------------- 1 | package net.walend.disentangle.graph 2 | 3 | import scala.collection.immutable.{Map, Seq, Iterable} 4 | 5 | /** 6 | * Provides constant-time access for edges of a node. 7 | * 8 | * The constructor is O(n + a ln(n)) 9 | * 10 | * @author dwalend 11 | * @since v0.2.1 12 | */ 13 | case class AdjacencyUndigraph[Node](outNodes:IndexedSet[Node], //provides the master index values for each node. 14 | adjacencyMatrix:Vector[IndexedSet[NodePair[Node]]] // (i) is the edges for node i, (j) is the NodePair[node,node] pair to reach that second node. 15 | ) extends IndexedUndigraph[Node] { 16 | 17 | type InnerNodeType = InNode 18 | case class InNode(override val value:Node,override val index:Int) extends this.InnerIndexedNodeTrait { 19 | 20 | override def innerEdges: IndexedSet[InnerEdgeType] = { 21 | inAdjacencyMatrix(index) 22 | } 23 | 24 | override def outerEdges: Set[NodePair[Node]] = { 25 | adjacencyMatrix(index) 26 | } 27 | 28 | override def hashCode(): Int = index 29 | 30 | override def equals(thing: Any): Boolean = { 31 | thing match { 32 | case inNode:InNode => inNode.index == index 33 | case _ => false 34 | } 35 | } 36 | } 37 | 38 | type InnerEdgeType = InnerEdge 39 | case class InnerEdge(nodePair: NodePair[InNode]) extends UndigraphInnerEdgeTrait { 40 | override def value: NodePair[Node] = NodePair(nodePair._1.value,nodePair._2.value) 41 | } 42 | object InnerEdge{ 43 | def apply(_1:InNode,_2:InNode): InnerEdge = new InnerEdge(NodePair(_1,_2)) 44 | } 45 | 46 | val inNodes:IndexedSet[InNode] = outNodes.zipWithIndex.map(x => InNode(x._1,x._2)) 47 | val nodeToInNode:Map[Node,InNode] = inNodes.map(x => x.value -> x).toMap 48 | 49 | //todo really should be a Set, not an IndexedSet 50 | def neighborSet(indexedSet:IndexedSet[OuterEdgeType]):IndexedSet[InnerEdgeType] = { 51 | indexedSet.map(e => InnerEdge(nodeToInNode(e._1),nodeToInNode(e._2))) 52 | } 53 | 54 | //todo really should be a Set, not an IndexedSet 55 | val inAdjacencyMatrix:Vector[IndexedSet[InnerEdgeType]] = adjacencyMatrix.map(neighborSet) 56 | 57 | def nodes = outNodes 58 | 59 | override def nodeCount: Int = outNodes.size 60 | 61 | /** 62 | * O(ln(n)) 63 | * 64 | * @return Some inner node if it exists in the digraph or None 65 | */ 66 | override def innerNode(value: Node): Option[InNode] = { 67 | nodeToInNode.get(value) 68 | } 69 | 70 | /** 71 | * O(1) 72 | * 73 | * @return InnerNode representation of all of the nodes in the graph. 74 | */ 75 | override def innerNodes: IndexedSet[InNode] = inNodes 76 | 77 | /** 78 | * @return A Traversable of the edges as represented in the graph 79 | */ 80 | override lazy val innerEdges:Vector[InnerEdgeType] = inAdjacencyMatrix.flatten.distinct 81 | 82 | /** 83 | * O(n^2) 84 | * 85 | * @return All of the edges in the graph 86 | */ 87 | override lazy val edges: Seq[OuterEdgeType] = adjacencyMatrix.flatten.distinct 88 | 89 | /** 90 | * O(1) 91 | * 92 | * @return 93 | */ 94 | override def node(i: Int): Node = outNodes.apply(i) 95 | 96 | /** 97 | * O(1) 98 | * 99 | * @return 100 | */ 101 | override def innerNodeForIndex(i: Int): InNode = innerNodes.apply(i) 102 | 103 | override def toString:String = { 104 | s"${this.getClass.getSimpleName}(edges = $edges,nodes = $outNodes)" 105 | } 106 | 107 | override def equals(that: Any): Boolean = 108 | that match { 109 | 110 | case that: Graph[_] => 111 | (edges.toSet.equals(that.edges.toSet)) && (nodes.equals(that.nodes)) 112 | 113 | case _ => false 114 | } 115 | 116 | override lazy val hashCode:Int = edges.toSet.hashCode() + nodes.hashCode() 117 | 118 | } 119 | 120 | object AdjacencyUndigraph{ 121 | 122 | //noinspection ConvertibleToMethodValue 123 | def apply[Node](edges:Iterable[NodePair[Node]] = Seq.empty, 124 | nodes:Seq[Node] = Seq.empty): AdjacencyUndigraph[Node] = { 125 | 126 | val nodeValues = Vector.from((nodes ++ edges.map(_._1) ++ edges.map(_._2)).distinct) 127 | 128 | val successorMap = edges.groupBy(x => x._1) 129 | val predecessorMap = edges.groupBy(x => x._2) 130 | 131 | def getOrEmpty(n:Node,nodeToTrav:Map[Node,Iterable[NodePair[Node]]]) = { 132 | IndexedSet.from(nodeToTrav.getOrElse(n,Vector.empty[NodePair[Node]])) 133 | } 134 | 135 | val edgeAdjacencies: Vector[IndexedSet[NodePair[Node]]] = nodeValues.map(n => getOrEmpty(n,successorMap) ++ getOrEmpty(n,predecessorMap) ) 136 | 137 | new AdjacencyUndigraph(IndexedSet.from(nodeValues),edgeAdjacencies) 138 | } 139 | 140 | def fromPairs[Node](edges:Iterable[(Node,Node)], 141 | nodes:Seq[Node] = Seq.empty): AdjacencyUndigraph[Node] = { 142 | apply(edges.map(x => NodePair(x._1,x._2)),nodes) 143 | } 144 | 145 | } 146 | 147 | /** 148 | * A digraph that exposes the indices of stored nodes. 149 | */ 150 | trait IndexedUndigraph[Node] extends Undigraph[Node] { 151 | 152 | /** 153 | * The type of InnerNodeTrait for this digraph representation 154 | */ 155 | override type InnerNodeType <: InnerIndexedNodeTrait 156 | 157 | override type OuterEdgeType = NodePair[Node] 158 | 159 | /** 160 | * All the nodes in the graph, in an indexed set 161 | */ 162 | def nodes:IndexedSet[Node] 163 | 164 | /** 165 | * @return internal representation of all of the nodes in the graph. 166 | */ 167 | def innerNodes:IndexedSet[InnerNodeType] 168 | 169 | /** 170 | * An internal representation of nodes within the graph 171 | */ 172 | trait InnerIndexedNodeTrait extends UndigraphInnerNodeTrait { 173 | 174 | def index:Int 175 | } 176 | 177 | def node(i:Int):Node 178 | 179 | def innerNodeForIndex(i:Int):InnerNodeType 180 | 181 | //todo if needed, and maybe one for the nodes, too def exists(i:Int,j:Int):Boolean 182 | } -------------------------------------------------------------------------------- /Graph/src/scala/net/walend/disentangle/graph/Digraph.scala: -------------------------------------------------------------------------------- 1 | package net.walend.disentangle.graph 2 | 3 | /** 4 | * A graph with directed zero or one edges from any single node to any other single node. 5 | * 6 | * @author dwalend 7 | * @since v0.1.0 8 | */ 9 | trait Digraph[Node] extends Graph[Node] { 10 | 11 | trait DigraphInnerNodeTrait extends InnerNodeTrait { 12 | 13 | def successors:Set[InnerEdgeType] 14 | 15 | def predecessors:Set[InnerEdgeType] 16 | } 17 | 18 | /** 19 | * The type of InnerNodeTrait for this digraph representation 20 | */ 21 | override type InnerNodeType <: DigraphInnerNodeTrait 22 | 23 | trait DigraphInnerEdgeTrait extends InnerEdgeTrait { 24 | def from:InnerNodeType 25 | def to:InnerNodeType 26 | 27 | override def selfEdge = {from == to} 28 | 29 | override def other(node:InnerNodeType) = { 30 | if(node == from) to 31 | else if (node == to) from 32 | else throw new IllegalArgumentException(s"This edge contains ${from} and ${to}, not $node.") 33 | } 34 | 35 | } 36 | 37 | override type InnerEdgeType <: DigraphInnerEdgeTrait 38 | 39 | def edge(from: InnerNodeType,to: InnerNodeType):Option[InnerEdgeType] 40 | 41 | def edge(from: Node, to: Node): Option[InnerEdgeType] = { 42 | val inFrom = innerNode(from) 43 | val inTo = innerNode(to) 44 | (inFrom, inTo) match { 45 | case (Some(f), Some(t)) => edge(f, t) 46 | case _ => None 47 | } 48 | } 49 | 50 | } 51 | 52 | /** 53 | * A directed graph with edges expressed as Tuple2s so that you can create edges with "a->b" in your code. 54 | * 55 | * @author dwalend 56 | * @since v0.2.1 57 | */ 58 | trait Tuple2Digraph[Node] extends Digraph[Node] { 59 | type OuterEdgeType = (Node,Node) 60 | } -------------------------------------------------------------------------------- /Graph/src/scala/net/walend/disentangle/graph/DigraphFactory.scala: -------------------------------------------------------------------------------- 1 | package net.walend.disentangle.graph 2 | 3 | import scala.util.Random 4 | 5 | /** 6 | * Create various types of graphs. 7 | * 8 | * @author dwalend 9 | * @since v0.1.0 10 | */ 11 | object DigraphFactory { 12 | 13 | /** 14 | * Create a randomly connected graph, where each node has a limited number of connections to other nodes 15 | */ 16 | def createRandomNormalDigraph(nodeCount:Int,maxOutEdgesPerNode:Int):IndexedLabelDigraph[Int,Boolean] = { 17 | 18 | require(maxOutEdgesPerNode < nodeCount) 19 | 20 | // val nodes:Range = 0 until nodeCount 21 | val nodes:Seq[Int] = 0 until nodeCount 22 | 23 | //todo nodes.par 24 | val seqOfListOfEdges = nodes.map{fromNode => 25 | shuffleAndTake(Set.from(nodes),Random.nextInt(maxOutEdgesPerNode),fromNode).map(toNode => (fromNode,toNode,true)) 26 | } 27 | 28 | val edges:Seq[(Int,Int,Boolean)] = Seq.from(seqOfListOfEdges.flatten) 29 | 30 | AdjacencyLabelDigraph(edges,nodes,false) 31 | } 32 | 33 | def shuffleAndTake[T](items:Set[T],toTake:Int,never:T):Seq[T] = { 34 | Random.shuffle(Seq.from(items - never)).take(toTake) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Graph/src/scala/net/walend/disentangle/graph/Graph.scala: -------------------------------------------------------------------------------- 1 | package net.walend.disentangle.graph 2 | 3 | import scala.collection.immutable.{Set, Iterable} 4 | 5 | /** 6 | * Ancestor trait for a variety of Graphs. 7 | * 8 | * A graph has Nodes that can be distinguished from each other. An InnerNodeType provides access to a nodes place in the graph. 9 | * 10 | * I've pulled definitions from Wikipedia: http://en.wikipedia.org/wiki/Graph_(mathematics) where possible 11 | * 12 | * @author dwalend 13 | * @since v0.1.0 14 | */ 15 | trait Graph[Node] { 16 | 17 | /** 18 | * The edge type used to build this graph representation 19 | */ 20 | type OuterEdgeType 21 | 22 | /** 23 | * An internal representation of nodes within the graph 24 | */ 25 | trait InnerNodeTrait { 26 | def value:Node 27 | } 28 | 29 | /** 30 | * The node type returned by graph representation 31 | */ 32 | type InnerNodeType <: InnerNodeTrait 33 | 34 | /** 35 | * An internal representation of edges within the graph 36 | */ 37 | trait InnerEdgeTrait { 38 | def value:OuterEdgeType 39 | 40 | def selfEdge:Boolean 41 | 42 | def other(node:InnerNodeType):InnerNodeType 43 | } 44 | 45 | /** 46 | * The edge type returned by this graph representation 47 | */ 48 | type InnerEdgeType <: InnerEdgeTrait 49 | 50 | /** 51 | * All the nodes in the graph 52 | */ 53 | def nodes:Set[Node] 54 | 55 | /** 56 | * @return number of nodes in the graph 57 | */ 58 | def nodeCount:Int 59 | 60 | /** 61 | * @return InnerNode representation of all of the nodes in the graph. 62 | */ 63 | def innerNodes:Set[InnerNodeType] 64 | 65 | /** 66 | * @param value a node that might be in this digraph 67 | * @return Some inner node if it exists in the digraph or None 68 | */ 69 | def innerNode(value:Node):Option[InnerNodeType] 70 | 71 | /** 72 | * @return A Traversable (usually something more specific) of the edges 73 | */ 74 | def edges:Iterable[OuterEdgeType] 75 | 76 | /** 77 | * @return A Traversable of the edges as represented in the graph 78 | */ 79 | def innerEdges:Iterable[InnerEdgeType] 80 | } 81 | 82 | /** 83 | * A graph that exposes the indices of stored nodes. 84 | * 85 | * Implementers should also include some accessor for edges via indexes. 86 | * 87 | * @author dwalend 88 | * @since v0.2.1 89 | */ 90 | trait IndexedGraph[Node] extends Graph[Node] { 91 | 92 | /** 93 | * An internal representation of nodes within the graph 94 | */ 95 | trait InnerIndexedNodeTrait extends InnerNodeTrait { 96 | def index:Int 97 | } 98 | 99 | /** 100 | * The type of InnerNodeTrait for this digraph representation 101 | */ 102 | override type InnerNodeType <: InnerIndexedNodeTrait 103 | 104 | /** 105 | * All the nodes in the graph, in an indexed set 106 | */ 107 | def nodes:IndexedSet[Node] 108 | 109 | /** 110 | * @return internal representation of all of the nodes in the graph. 111 | */ 112 | def innerNodes:IndexedSet[InnerNodeType] 113 | 114 | def node(i:Int):Node 115 | 116 | def innerNodeForIndex(i:Int):InnerNodeType 117 | 118 | } -------------------------------------------------------------------------------- /Graph/src/scala/net/walend/disentangle/graph/IndexedSet.scala: -------------------------------------------------------------------------------- 1 | package net.walend.disentangle.graph 2 | 3 | import scala.collection.immutable.{Set, SetOps} 4 | import scala.collection.mutable.{Buffer, ReusableBuilder} 5 | import scala.collection.{IterableFactory, IterableFactoryDefaults, mutable} 6 | 7 | /** 8 | * 9 | * 10 | * @author dwalend 11 | * @since v0.1.0 12 | */ 13 | 14 | //todo investigate backing this with a BitSet. 15 | final class IndexedSet[A](outerSeq:IndexedSeq[A]) 16 | extends Set[A] 17 | with SetOps[A,IndexedSet,IndexedSet[A]] 18 | with IterableFactoryDefaults[A, IndexedSet] 19 | with Serializable { 20 | 21 | private val asSet:Set[A] = outerSeq.toSet 22 | 23 | if(outerSeq.size != asSet.size) throw new IllegalArgumentException(s"seq has duplicate members: ${outerSeq.groupBy(x => x).filter{(x: (A, IndexedSeq[A])) => x._2.size > 1}}") 24 | 25 | //Indexed access 26 | def apply(index:Int): A = outerSeq(index) 27 | 28 | def indexOf(a:A): Int = outerSeq.indexOf(a) 29 | 30 | def asSeq: IndexedSeq[A] = outerSeq 31 | 32 | //AbstractSet contract 33 | override def contains(elem: A): Boolean = asSet.contains(elem) 34 | 35 | override def iterator: Iterator[A] = outerSeq.iterator 36 | 37 | override def concat(that: collection.IterableOnce[A]): IndexedSet[A] = { 38 | new IndexedSet((outerSeq ++ that).distinct) 39 | } 40 | 41 | override def incl(elem: A): IndexedSet[A] = { 42 | if(contains(elem)) this 43 | else new IndexedSet(outerSeq :+ elem) 44 | } 45 | 46 | override def excl(elem: A): IndexedSet[A] = { 47 | if(!contains(elem)) this 48 | else new IndexedSet(outerSeq.filterNot(_ == elem)) 49 | } 50 | 51 | override def iterableFactory: IterableFactory[IndexedSet] = IndexedSet 52 | } 53 | 54 | 55 | object IndexedSet extends IterableFactory[IndexedSet] { 56 | // def apply[A](traversable:Traversable[A]) = IndexedSeq(traversable.to[Seq].distinct) 57 | 58 | def from[A](source: IterableOnce[A]): IndexedSet[A] = new IndexedSet[A](IndexedSeq.from(source)) 59 | 60 | def empty[A]: IndexedSet[A] = new IndexedSet[A](IndexedSeq.empty) 61 | 62 | def newBuilder[A]: mutable.Builder[A, IndexedSet[A]] = new IndexedSetBuilderImpl[A] 63 | } 64 | 65 | private final class IndexedSetBuilderImpl[A] extends ReusableBuilder[A, IndexedSet[A]] { 66 | private val elems = mutable.ArrayBuffer.empty[A] 67 | 68 | override def clear(): Unit = elems.clear() 69 | 70 | override def result(): IndexedSet[A] = new IndexedSet(IndexedSeq.from(elems)) 71 | 72 | def addOne(elem: A): IndexedSetBuilderImpl.this.type = { 73 | elems += elem 74 | this 75 | } 76 | 77 | override def addAll(xs: IterableOnce[A]): this.type = { 78 | if (xs.asInstanceOf[AnyRef] eq this) addAll(Buffer.from(xs)) // avoid mutating under our own iterator 79 | else { 80 | val it = xs.iterator 81 | while (it.hasNext) { 82 | addOne(it.next()) 83 | } 84 | } 85 | this 86 | } 87 | } -------------------------------------------------------------------------------- /Graph/src/scala/net/walend/disentangle/graph/LabelDigraph.scala: -------------------------------------------------------------------------------- 1 | package net.walend.disentangle.graph 2 | 3 | /** 4 | * A directed graph with labeled edges. 5 | * 6 | * @author dwalend 7 | * @since v0.1.0 8 | */ 9 | trait LabelDigraph[Node,Label] extends Digraph[Node] { 10 | 11 | type OuterEdgeType = (Node,Node,Label) 12 | 13 | trait LabelDigraphEdgeTrait extends DigraphInnerEdgeTrait { 14 | def label:Label 15 | } 16 | 17 | override type InnerEdgeType <: LabelDigraphEdgeTrait 18 | 19 | /** 20 | * @return the label to return when no edge exists 21 | */ 22 | //todo make this a function () => Label and throw a NoSuchElementException in 0.3 , to match LabelUndigraph 23 | def noEdgeExistsLabel:Label 24 | 25 | /** 26 | * @return the Edge between start and end or noEdgeExistsValue if no edge connects start to end 27 | */ 28 | def label(start:InnerNodeType,end:InnerNodeType):Label 29 | 30 | } 31 | 32 | /** 33 | * A digraph that exposes the indices of stored nodes. 34 | * 35 | * @author dwalend 36 | * @since v0.1.0 37 | */ 38 | trait IndexedLabelDigraph[Node,Label] extends LabelDigraph[Node,Label] with IndexedGraph[Node] { 39 | 40 | type InnerNodeType <: DigraphInnerNodeTrait with InnerIndexedNodeTrait 41 | 42 | /** 43 | * @return the label connecting nodes at index i and j, or noEdgeExistsLabel 44 | * @throws IndexOutOfBoundsException if either i or j does not correspond with a node 45 | */ 46 | def label(i:Int,j:Int):Label 47 | 48 | //todo def edge(i:Int,j:Int):InnerEdgeType when it becomes clear what to do for edges that don't exist, and when the method is needed. (InnerNodeType,InnerNodeType,noEdgeExistsLabel) might be fine. 49 | 50 | } -------------------------------------------------------------------------------- /Graph/src/scala/net/walend/disentangle/graph/LabelUndigraph.scala: -------------------------------------------------------------------------------- 1 | package net.walend.disentangle.graph 2 | 3 | /** 4 | * A directed graph with labeled edges. 5 | * 6 | * @author dwalend 7 | * @since v0.2.1 8 | */ 9 | trait LabelUndigraph[Node,Label] extends Undigraph[Node] { 10 | 11 | type OuterEdgeType = (NodePair[Node],Label) 12 | 13 | /** 14 | * @return the label to return when no edge exists 15 | */ 16 | def noEdgeExistsLabel:Label 17 | 18 | /** 19 | * @return the Label between a pair of nodes, or noEdgeExistsLabel if no edge exists. 20 | */ 21 | def label(between:NodePair[InnerNodeType]):Label 22 | 23 | /** 24 | * @return the Label between a pair of nodes, or noEdgeExistsLable if no edge exists. 25 | * @throws IllegalArgumentException if either node is not in the graph 26 | */ 27 | 28 | def edge(between:NodePair[Node]):InnerEdgeType 29 | } 30 | 31 | /** 32 | * A digraph that exposes the indices of stored nodes. 33 | */ 34 | trait IndexedLabelUndigraph[Node,Label] extends IndexedGraph[Node] with LabelUndigraph[Node,Label] { 35 | 36 | type InnerNodeType <: UndigraphInnerNodeTrait with InnerIndexedNodeTrait 37 | 38 | /** 39 | * @return the label connecting edge i to edge j, or noEdgeExists 40 | */ 41 | def label(i:Int,j:Int):Label 42 | 43 | //todo def edge(i:Int,j:Int):InnerEdgeType when needed 44 | 45 | } -------------------------------------------------------------------------------- /Graph/src/scala/net/walend/disentangle/graph/Undigraph.scala: -------------------------------------------------------------------------------- 1 | package net.walend.disentangle.graph 2 | 3 | /** 4 | * A graph with undirected zero or one edges between any pair of nodes. 5 | * 6 | * @author dwalend 7 | * @since v0.2.1 8 | */ 9 | trait Undigraph[Node] extends Graph[Node] { 10 | 11 | trait UndigraphInnerNodeTrait extends InnerNodeTrait { 12 | 13 | def innerEdges:Set[InnerEdgeType] 14 | 15 | def outerEdges:Set[OuterEdgeType] 16 | } 17 | 18 | /** 19 | * The type of InnerNodeTrait for this digraph representation 20 | */ 21 | type InnerNodeType <: UndigraphInnerNodeTrait 22 | 23 | trait UndigraphInnerEdgeTrait extends InnerEdgeTrait { 24 | def nodePair: NodePair[InnerNodeType] 25 | 26 | override def selfEdge: Boolean = nodePair._1 == nodePair._2 27 | 28 | override def other(node: InnerNodeType): InnerNodeType = nodePair.other(node) 29 | } 30 | 31 | type InnerEdgeType <: UndigraphInnerEdgeTrait 32 | 33 | } 34 | 35 | /** 36 | * A pair of interchangable nodes, often used in Undigraph. Order of the nodes doesn't matter. 37 | * 38 | * @author dwalend 39 | * @since v0.2.1 40 | */ 41 | case class NodePair[+A](_1: A, _2: A) { 42 | 43 | def other[B >: A](node:B):A = { 44 | if(node == _1) _2 45 | else if (node == _2) _1 46 | else throw new IllegalArgumentException(s"This NodePair contains ${_1} and ${_2}, not node.") 47 | } 48 | 49 | def contains[B >: A](elem: B): Boolean = 50 | elem == _1 || elem == _2 51 | 52 | override def equals(that: Any): Boolean = 53 | that match { 54 | 55 | case that: NodePair[_] => 56 | (that canEqual this) && 57 | (((this._1 == that._1) && 58 | (this._2 == that._2)) || 59 | ((this._1 == that._2) && 60 | (this._2 == that._1))) 61 | 62 | case _ => false 63 | } 64 | 65 | override def hashCode:Int = _1.hashCode + _2.hashCode 66 | 67 | } -------------------------------------------------------------------------------- /Graph/src/scala/net/walend/disentangle/graph/mutable/MatrixLabelDigraph.scala: -------------------------------------------------------------------------------- 1 | package net.walend.disentangle.graph.mutable 2 | 3 | import scala.collection.mutable.ArrayBuffer 4 | import scala.collection.{Seq, Iterable} 5 | 6 | import net.walend.disentangle.graph.{IndexedLabelDigraph, IndexedSet} 7 | 8 | /** 9 | * Provides constant-time access and mutator for edges. Stores Nodes in a Vector and Labels in a Vector of ArrayBuffers. 10 | * 11 | * The constructor is O(n) 12 | * 13 | * @author dwalend 14 | * @since v0.1.0 15 | */ 16 | case class MatrixLabelDigraph[Node,Label](outNodes:IndexedSet[Node], //provides the master index values for each node. 17 | edgeMatrix:Vector[ArrayBuffer[Label]], // (row,column) is (start,end), indexed by node. 18 | noEdgeExistsLabel:Label //value for no edge 19 | ) extends IndexedLabelDigraph[Node,Label] with MutableLabelDigraph[Node,Label] { 20 | 21 | override type InnerNodeType = InnerNode 22 | case class InnerNode(override val value:Node, override val index:Int) extends this.DigraphInnerNodeTrait with this.InnerIndexedNodeTrait { 23 | 24 | /** 25 | * O(n^2) 26 | */ 27 | override def successors: IndexedSet[InnerEdgeType] = { 28 | inNodes.zip(edgeMatrix(index)).map(x => InnerEdge(this,x._1,x._2)).filter(_.to != noEdgeExistsLabel) 29 | } 30 | 31 | /** 32 | * O(n^2) 33 | */ 34 | override def predecessors: IndexedSet[InnerEdgeType] = { 35 | val edgeColumn:Seq[Label] = edgeMatrix.map(_(index)) 36 | inNodes.zip(edgeColumn).map(x => InnerEdge(x._1,this,x._2)).filter(_.to != noEdgeExistsLabel) 37 | } 38 | 39 | override def hashCode(): Int = index 40 | 41 | override def equals(obj: Any): Boolean = { 42 | obj match { 43 | case inNode:InnerNode => inNode.index == index 44 | case _ => false 45 | } 46 | } 47 | } 48 | 49 | override type InnerEdgeType = InnerEdge 50 | case class InnerEdge(from:InnerNodeType,to:InnerNodeType,label:Label) extends LabelDigraphEdgeTrait { 51 | override def value: OuterEdgeType = (from.value,to.value,label) 52 | } 53 | 54 | val inNodes:IndexedSet[InnerNode] = outNodes.zipWithIndex.map(x => InnerNode(x._1,x._2)) 55 | val nodeToInNode:Map[Node,InnerNode] = inNodes.map(x => x.value -> x).toMap 56 | 57 | /** 58 | * O(1) 59 | */ 60 | override def nodes = outNodes 61 | 62 | /** 63 | * O(1) 64 | */ 65 | override def nodeCount: Int = outNodes.size 66 | 67 | /** 68 | * O(ln(n)) 69 | */ 70 | override def innerNode(value: Node): Option[InnerNode] = { 71 | nodeToInNode.get(value) 72 | } 73 | 74 | /** 75 | * O(n) 76 | * 77 | * @return InnerNode representation of all of the nodes in the graph. 78 | */ 79 | override def innerNodes: IndexedSet[InnerNode] = { 80 | outNodes.zipWithIndex.map(x => InnerNode(x._1,x._2)) 81 | } 82 | 83 | /** 84 | * @return A Traversable of the edges as represented in the graph 85 | */ 86 | override def innerEdges: Vector[InnerEdgeType] = { 87 | 88 | def edgesInRow(row:(ArrayBuffer[Label],Int)):Seq[InnerEdgeType] = { 89 | val rowIndex = row._2 90 | val cellsWithIndex = row._1.zipWithIndex 91 | val cellsWithEdges = cellsWithIndex.filter(x => (x._1 != noEdgeExistsLabel)) 92 | Seq.from(cellsWithEdges.map(x => InnerEdge(inNodes.apply(rowIndex),inNodes.apply(x._2),x._1))) 93 | } 94 | 95 | edgeMatrix.zipWithIndex.map(row => edgesInRow(row)).flatten 96 | } 97 | 98 | /** 99 | * O(n^2) 100 | * 101 | * @return All of the edges in the graph 102 | */ 103 | override def edges: Vector[(Node, Node, Label)] = { 104 | 105 | def edgesInRow(row:(ArrayBuffer[Label],Int)):Seq[(Node,Node,Label)] = { 106 | val rowIndex = row._2 107 | val cellsWithIndex = row._1.zipWithIndex 108 | val cellsWithEdges = cellsWithIndex.filter(x => (x._1 != noEdgeExistsLabel)) 109 | Seq.from(cellsWithEdges.map(x => (outNodes.apply(rowIndex),outNodes.apply(x._2),x._1))) 110 | } 111 | 112 | edgeMatrix.zipWithIndex.map(row => edgesInRow(row)).flatten 113 | } 114 | 115 | /** 116 | * O(1) 117 | * 118 | * @return the edge between start and end or noEdgeExistsValue 119 | */ 120 | override def label(from: InnerNode, to: InnerNode):Label = edgeMatrix(from.index)(to.index) 121 | 122 | 123 | /** 124 | * O(1) 125 | */ 126 | override def upsertEdge(from: InnerNode, to: InnerNode, label: Label): Unit = edgeMatrix(from.index)(to.index) = label 127 | 128 | 129 | /** 130 | * O(1) 131 | */ 132 | override def node(i: Int): Node = outNodes.apply(i) 133 | 134 | /** 135 | * O(1) 136 | */ 137 | override def innerNodeForIndex(i: Int): InnerNode = innerNodes.apply(i) 138 | 139 | /** 140 | * O(1) 141 | */ 142 | override def label(i: Int, j: Int): Label = edgeMatrix(i)(j) 143 | 144 | override def toString:String = { 145 | s"${this.getClass.getSimpleName}(edges = $edges,nodes = $outNodes,noEdgeExistsValue = $noEdgeExistsLabel)" 146 | } 147 | 148 | override def edge(from: InnerNode, to: InnerNode): Option[InnerEdge] = { 149 | val l = label(from.index,to.index) 150 | if(noEdgeExistsLabel == l) None 151 | else Some(InnerEdge(from,to,l)) 152 | } 153 | } 154 | 155 | object MatrixLabelDigraph{ 156 | 157 | /** 158 | * O(n ln(n) + en) 159 | */ 160 | def apply[Node,Label](edges:Iterable[(Node,Node,Label)] = Seq.empty, 161 | nodes:Seq[Node] = Seq.empty, 162 | noEdgeExistsValue:Label = null) = { 163 | 164 | val nodeValues:IndexedSet[Node] = IndexedSet.from((nodes ++ edges.map(_._1) ++ edges.map(_._2)).distinct) 165 | 166 | val size = nodeValues.size 167 | 168 | val matrix:Vector[ArrayBuffer[Label]] = Vector.from(nodeValues.asSeq.map(x => ArrayBuffer.fill(size)(noEdgeExistsValue))) 169 | 170 | for (edgeTriple <- edges) { 171 | val row = nodeValues.indexOf(edgeTriple._1) 172 | val column = nodeValues.indexOf(edgeTriple._2) 173 | 174 | assert(matrix(row)(column)==noEdgeExistsValue,s"edges includes two edges between ${edgeTriple._1} and ${edgeTriple._2}, ${matrix(row)(column)} and ${edgeTriple._3}") 175 | 176 | matrix(row)(column) = edgeTriple._3 177 | } 178 | 179 | new MatrixLabelDigraph(nodeValues,matrix,noEdgeExistsValue) 180 | } 181 | 182 | } -------------------------------------------------------------------------------- /Graph/src/scala/net/walend/disentangle/graph/mutable/MutableLabelDigraph.scala: -------------------------------------------------------------------------------- 1 | package net.walend.disentangle.graph.mutable 2 | 3 | import net.walend.disentangle.graph.LabelDigraph 4 | 5 | /** 6 | * A graph where edges can be upserted. 7 | * 8 | * @author dwalend 9 | * @since v0.1.0 10 | */ 11 | trait MutableLabelDigraph[Node,Label] extends LabelDigraph[Node,Label] { 12 | 13 | /** 14 | * Set the edge that spans from start to end 15 | * 16 | */ 17 | def upsertEdge(from:InnerNodeType,to:InnerNodeType,label:Label):Unit 18 | 19 | } -------------------------------------------------------------------------------- /Graph/src/scala/net/walend/disentangle/graph/package.scala: -------------------------------------------------------------------------------- 1 | package net.walend.disentangle 2 | 3 | /** 4 | * Representations of various kinds of graphs. The top level, Graph, holds a Set of Nodes and a 5 | * second collection of things that relate the nodes to each other -- the edges. Subtraits add features to these, and 6 | * concrete implementations fill in with code tuned to particular uses. 7 | * 8 | * Where possible, I use definitions from Wikipedia -- http://en.wikipedia.org/wiki/Graph_(mathematics) 9 | * 10 | * I've left room in the hierarchy for a variety of kinds of graphs, but have only created the concrete classes I have 11 | * real reasons to use. Please let me know if you need something specific. 12 | * 13 | * @author dwalend 14 | * @since v0.1.0 15 | */ 16 | package object graph { 17 | 18 | } 19 | -------------------------------------------------------------------------------- /Graph/src/scala/net/walend/disentangle/graph/semiring/FewestNodes.scala: -------------------------------------------------------------------------------- 1 | package net.walend.disentangle.graph.semiring 2 | 3 | import net.walend.disentangle.heap.HeapOrdering 4 | 5 | import scala.annotation.unused 6 | 7 | /** 8 | * Finds the length of a path that traverses the fewest edges. 9 | * 10 | * @author dwalend 11 | * @since v0.1.0 12 | */ 13 | object FewestNodes extends SemiringSupport[Int,Int] { 14 | 15 | def semiring: FewestNodes.Semiring = FewestNodesSemiring 16 | 17 | def heapOrdering: HeapOrdering[Int] = FewestNodesHeapOrdering 18 | 19 | def heapKeyForLabel: FewestNodes.Label => Int = { (label:Label) => label} 20 | 21 | def convertEdgeToLabel(@unused start: Any, @unused end: Any, @unused label: Any): FewestNodes.Label = 1 22 | 23 | val edgeToLabelConverter:(Any,Any,Any) => Int = convertEdgeToLabel 24 | 25 | object FewestNodesSemiring extends Semiring { 26 | 27 | def I = 0 28 | def O: FewestNodes.Label = Int.MaxValue 29 | 30 | def inDomain(label: Label): Boolean = { 31 | I <= label && label < O 32 | } 33 | 34 | def summary(fromThroughToLabel:Label, 35 | currentLabel:Label):Label = { 36 | if(fromThroughToLabel < currentLabel) { 37 | fromThroughToLabel 38 | } 39 | else currentLabel 40 | } 41 | 42 | def extend(fromThroughLabel:Label,throughToLabel:Label):Label = { 43 | if ((fromThroughLabel == O) || (throughToLabel == O)) O 44 | else { 45 | val result = fromThroughLabel + throughToLabel 46 | if(result < 0) O 47 | else result 48 | } 49 | } 50 | } 51 | 52 | /** 53 | * A heap ordering that puts lower numbers on the top of the heap 54 | */ 55 | object FewestNodesHeapOrdering extends HeapOrdering[Int] { 56 | 57 | def lteq(x: Int, y: Int): Boolean = { 58 | x >= y 59 | } 60 | 61 | /** 62 | * @return Some negative integer, zero, or a positive integer as the first argument is less than, equal to, or greater than the second, or None if they can't be compared 63 | */ 64 | def tryCompare(x: Int, y: Int): Option[Int] = { 65 | Option(y-x) 66 | } 67 | 68 | def keyDomainDescription = "between zero and Int.MaxValue (the annihilator)" 69 | 70 | def checkKey(key: Int): Unit = { 71 | require(FewestNodes.FewestNodesSemiring.inDomain(key)||(key == FewestNodes.FewestNodesSemiring.O),s"Key must be $keyDomainDescription, not $key") 72 | } 73 | 74 | /** 75 | * Minimum value for the DoubleHeap 76 | */ 77 | def AlwaysTop:Int = -1 78 | 79 | /** 80 | * A key that will among items on the bottom of the heap. Used primarily to add items that will eventually flow higher. 81 | */ 82 | def AlwaysBottom: Int = Int.MaxValue 83 | } 84 | 85 | } -------------------------------------------------------------------------------- /Graph/src/scala/net/walend/disentangle/graph/semiring/FloydWarshall.scala: -------------------------------------------------------------------------------- 1 | package net.walend.disentangle.graph.semiring 2 | 3 | import net.walend.disentangle.graph.mutable.{MutableLabelDigraph, MatrixLabelDigraph} 4 | import net.walend.disentangle.graph.IndexedLabelDigraph 5 | import scala.collection.{Seq, Iterable} 6 | 7 | /** 8 | * An implementation of the Floyd Warshall algorithm for general graph minimization. 9 | * 10 | * @author dwalend 11 | * @since v0.1.0 12 | */ 13 | object FloydWarshall { 14 | 15 | /** 16 | * O(1) 17 | */ 18 | def relax[Node,Label,Key](labelDigraph:MutableLabelDigraph[Node,Label], 19 | semiring:SemiringSupport[Label,Key]#Semiring) 20 | (from:labelDigraph.InnerNodeType, 21 | through:labelDigraph.InnerNodeType, 22 | to:labelDigraph.InnerNodeType):Label = { 23 | val fromThrough:Label = labelDigraph.label(from,through) 24 | val throughTo:Label = labelDigraph.label(through,to) 25 | 26 | val current:Label = labelDigraph.label(from,to) 27 | 28 | semiring.relax(fromThrough,throughTo,current) 29 | } 30 | 31 | /** 32 | * O(n^3) 33 | */ 34 | def floydWarshall[Node,Label,Key](labelDigraph:MatrixLabelDigraph[Node,Label],support:SemiringSupport[Label,Key]):IndexedLabelDigraph[Node,Label] = { 35 | val innerNodes = labelDigraph.innerNodes 36 | for (k <- innerNodes; i <- innerNodes; j <- innerNodes) { 37 | val summaryLabel = relax(labelDigraph,support.semiring)(i,k,j) 38 | labelDigraph.upsertEdge(i,j,summaryLabel) 39 | } 40 | labelDigraph 41 | } 42 | 43 | /** 44 | * O(n^3) 45 | */ 46 | def allPairsLeastPaths[Node,Label,Key](labelDigraph:MatrixLabelDigraph[Node,Label],support:SemiringSupport[Label,Key]):IndexedLabelDigraph[Node,Label] = { 47 | 48 | floydWarshall(labelDigraph,support) 49 | } 50 | 51 | /** 52 | * Create a digraph of Labels from an arbitrary Digraph. 53 | * 54 | * O(n ln(n) + an) 55 | * 56 | * @return a Digraph with graph's nodes, a self-edge for each node with the semiring's identifier, and an edge for each edge specified by labelForEdge. 57 | */ 58 | def createLabelDigraph[Node,EdgeLabel,Label,Key](edges:Iterable[(Node,Node,EdgeLabel)] = Seq.empty, 59 | extraNodes:Seq[Node] = Seq.empty, 60 | support:SemiringSupport[Label,Key], 61 | labelForEdge:(Node,Node,EdgeLabel)=>Label):MatrixLabelDigraph[Node,Label] = { 62 | val nodes = (extraNodes ++ edges.map(_._1) ++ edges.map(_._2)).distinct 63 | val nonSelfEdges = edges.filter(x => x._1 != x._2) 64 | val labelEdges = nodes.map(x => (x,x,support.semiring.I)) ++ 65 | nonSelfEdges.map(x => (x._1,x._2,labelForEdge(x._1,x._2,x._3))) 66 | 67 | MatrixLabelDigraph(labelEdges,nodes,support.semiring.O) 68 | } 69 | 70 | /** 71 | * O(n^3) 72 | */ 73 | def allPairsLeastPaths[Node,EdgeLabel,Label,Key](edges:Iterable[(Node,Node,EdgeLabel)] = Seq.empty, 74 | extraNodes:Seq[Node] = Seq.empty, 75 | support:SemiringSupport[Label,Key], 76 | labelForEdge:(Node,Node,EdgeLabel)=>Label):IndexedLabelDigraph[Node,Label] = { 77 | val initialDigraph = createLabelDigraph(edges,extraNodes,support,labelForEdge) 78 | floydWarshall(initialDigraph,support) 79 | } 80 | 81 | def defaultSupport[Node] = AllPathsFirstSteps[Node,Int,Int](FewestNodes) 82 | 83 | def allPairsShortestPaths[Node,EdgeLabel](edges:Iterable[(Node,Node,EdgeLabel)], 84 | nodeOrder:Seq[Node] = Seq.empty 85 | ):IndexedLabelDigraph[Node,Option[FirstStepsTrait[Node, Int]]] = { 86 | val support = defaultSupport[Node] 87 | allPairsLeastPaths(edges, nodeOrder, support, support.convertEdgeToLabel(FewestNodes.convertEdgeToLabel)) 88 | } 89 | 90 | } -------------------------------------------------------------------------------- /Graph/src/scala/net/walend/disentangle/graph/semiring/LeastWeights.scala: -------------------------------------------------------------------------------- 1 | package net.walend.disentangle.graph.semiring 2 | 3 | import net.walend.disentangle.heap.HeapOrdering 4 | 5 | import scala.annotation.unused 6 | 7 | /** 8 | * Finds paths that traverse from start to end nodes with the least Double-valued weight. 9 | * 10 | * @author dwalend 11 | * @since v0.1.0 12 | */ 13 | 14 | object LeastWeights extends SemiringSupport[Double,Double] { 15 | 16 | def semiring: LeastWeights.Semiring = LeastWeightsSemiring 17 | 18 | def heapOrdering: HeapOrdering[Double] = LeastWeightsOrdering 19 | 20 | def heapKeyForLabel: LeastWeights.Label => Double = { (label:Label) => label} 21 | 22 | def convertEdgeToLabel[Node, Label](@unused start: Node, @unused end: Node, @unused edge: Label): LeastWeights.Label = 1 23 | 24 | object LeastWeightsSemiring extends Semiring { 25 | 26 | def I = 0 27 | def O: LeastWeights.Label = Double.PositiveInfinity 28 | 29 | def inDomain(label: Label): Boolean = { 30 | I <= label && label < O 31 | } 32 | 33 | def summary(fromThroughToLabel:Label, 34 | currentLabel:Label):Label = { 35 | if(fromThroughToLabel < currentLabel) { 36 | fromThroughToLabel 37 | } 38 | else currentLabel 39 | } 40 | 41 | def extend(fromThroughLabel:Label,throughToLabel:Label):Label = { 42 | if ((fromThroughLabel == O) || (throughToLabel == O)) O 43 | else { 44 | fromThroughLabel + throughToLabel 45 | } 46 | } 47 | } 48 | 49 | /** 50 | * A heap ordering that puts lower numbers on the top of the heap 51 | */ 52 | object LeastWeightsOrdering extends HeapOrdering[Double] { 53 | 54 | def lteq(x: Double, y: Double): Boolean = { 55 | x >= y 56 | } 57 | 58 | /** 59 | * @return Some negative integer, zero, or a positive integer as the first argument is less than, equal to, or greater than the second, or None if they can't be compared 60 | */ 61 | def tryCompare(x: Double, y: Double): Option[Int] = Option(y.compareTo(x)) 62 | 63 | def keyDomainDescription = "between zero and Double.PositiveInfinity (the annihilator)" 64 | 65 | def checkKey(key: Double): Unit = { 66 | require(LeastWeights.LeastWeightsSemiring.inDomain(key)||(key == LeastWeights.LeastWeightsSemiring.O),s"Key must be $keyDomainDescription, not $key") 67 | } 68 | 69 | /** 70 | * Minimum value for the DoubleHeap 71 | */ 72 | def AlwaysTop:Double = -Double.MinPositiveValue 73 | 74 | /** 75 | * A key that will among items on the bottom of the heap. Used primarily to add items that will eventually flow higher. 76 | */ 77 | def AlwaysBottom: Double = Double.PositiveInfinity 78 | } 79 | } 80 | 81 | -------------------------------------------------------------------------------- /Graph/src/scala/net/walend/disentangle/graph/semiring/MostProbable.scala: -------------------------------------------------------------------------------- 1 | package net.walend.disentangle.graph.semiring 2 | 3 | import net.walend.disentangle.heap.HeapOrdering 4 | 5 | import scala.annotation.unused 6 | 7 | /** 8 | * Finds most probable paths that traverse from start to end nodes with the on double-weight edge (weights between zero and one). 9 | * 10 | * @author dwalend 11 | * @since v0.1.0 12 | */ 13 | 14 | object MostProbable extends SemiringSupport[Double,Double] { 15 | 16 | def semiring: MostProbable.Semiring = MostProbableSemiring 17 | 18 | def heapOrdering: HeapOrdering[Double] = MostProbableOrdering 19 | 20 | def heapKeyForLabel: MostProbable.Label => Double = { (label:Label) => label} 21 | 22 | def convertEdgeToLabel[Node, Label](@unused start: Node, @unused end: Node, @unused label: Label): MostProbable.Label = semiring.I 23 | 24 | object MostProbableSemiring extends Semiring { 25 | 26 | def I = 1.0 27 | def O = 0.0 28 | 29 | def inDomain(label: Label): Boolean = { 30 | I >= label && label > O 31 | } 32 | 33 | def summary(fromThroughToLabel:Label, 34 | currentLabel:Label):Label = { 35 | if(fromThroughToLabel > currentLabel) { 36 | fromThroughToLabel 37 | } 38 | else currentLabel 39 | } 40 | 41 | def extend(fromThroughLabel:Label,throughToLabel:Label):Label = { 42 | if ((fromThroughLabel == O) || (throughToLabel == O)) O 43 | else { 44 | fromThroughLabel * throughToLabel 45 | } 46 | } 47 | } 48 | 49 | /** 50 | * A heap ordering that puts lower numbers on the top of the heap 51 | */ 52 | object MostProbableOrdering extends HeapOrdering[Double] { 53 | 54 | def lteq(x: Double, y: Double): Boolean = { 55 | x <= y 56 | } 57 | 58 | /** 59 | * @return Some negative integer, zero, or a positive integer as the first argument is less than, equal to, or greater than the second, or None if they can't be compared 60 | */ 61 | def tryCompare(x: Double, y: Double): Option[Int] = { 62 | Option(x.compareTo(y)) 63 | } 64 | 65 | def keyDomainDescription = "between one and zero (the annihilator)" 66 | 67 | def checkKey(key: Double): Unit = { 68 | require(MostProbable.MostProbableSemiring.inDomain(key)||(key == MostProbable.MostProbableSemiring.O),s"Key must be $keyDomainDescription, not $key") 69 | } 70 | 71 | /** 72 | * Minimum value for the DoubleHeap 73 | */ 74 | def AlwaysTop:Double = semiring.I + 0.01 75 | 76 | /** 77 | * A key that will among items on the bottom of the heap. Used primarily to add items that will eventually flow higher. 78 | */ 79 | def AlwaysBottom: Double = semiring.O 80 | } 81 | } 82 | 83 | -------------------------------------------------------------------------------- /Graph/src/scala/net/walend/disentangle/graph/semiring/OnePathFirstStep.scala: -------------------------------------------------------------------------------- 1 | package net.walend.disentangle.graph.semiring 2 | 3 | import net.walend.disentangle.graph.LabelDigraph 4 | import net.walend.disentangle.heap.HeapOrdering 5 | 6 | /** 7 | * Finds one minimal path that use the core semiring. 8 | * 9 | * @author dwalend 10 | * @since v0.1.0 11 | */ 12 | 13 | class OnePathFirstStep[Node,CoreLabel,Key](coreSupport:SemiringSupport[CoreLabel,Key]) extends SemiringSupport[Option[FirstStepTrait[Node,CoreLabel]],Key]{ 14 | 15 | override type Label = Option[FirstStepTrait[Node, CoreLabel]] 16 | 17 | def semiring: Semiring = OnePathSemiring 18 | 19 | def heapOrdering: HeapOrdering[Key] = coreSupport.heapOrdering 20 | 21 | def heapKeyForLabel:Label=>Key = _.fold(coreSupport.heapOrdering.AlwaysBottom)(x => coreSupport.heapKeyForLabel(x.weight)) 22 | 23 | case class FirstStep(weight:CoreLabel,step:Option[Node]) extends FirstStepTrait[Node,CoreLabel] { 24 | /** 25 | * Overriding equals to speed up. 26 | */ 27 | override def equals(any:Any) = { 28 | if (any.isInstanceOf[FirstStep]) { 29 | val other: FirstStep = any.asInstanceOf[FirstStep] 30 | if (this eq other) true //if they share a memory address, no need to compare 31 | else { 32 | if (weight == other.weight) { 33 | step == other.step 34 | } else false 35 | } 36 | } else false 37 | } 38 | 39 | /** 40 | * Overriding hashCode because I overrode equals. 41 | */ 42 | override def hashCode():Int = { 43 | weight.hashCode() ^ step.hashCode() 44 | } 45 | } 46 | 47 | def convertEdgeToLabel[EdgeLabel](coreLabelForEdge:(Node,Node,EdgeLabel)=>CoreLabel) 48 | (start: Node, end: Node, coreLabel: EdgeLabel):Label = { 49 | Option(FirstStep(coreLabelForEdge(start,end,coreLabel),Option(end))) 50 | } 51 | 52 | def convertEdgeToLabelFunc[EdgeLabel](coreLabelForEdge:(Node,Node,EdgeLabel)=>CoreLabel):((Node,Node,EdgeLabel) => Label) = convertEdgeToLabel(coreLabelForEdge) 53 | 54 | object OnePathSemiring extends Semiring { 55 | 56 | // todo report bug that I can't do this here, but I can if I make coreSemiring a val in the outer OnePathFirstStep 57 | //val coreSemiring = coreSupport.semiring 58 | 59 | def inDomain(label: Label): Boolean = { 60 | label.forall(step => coreSupport.semiring.inDomain(step.weight)) 61 | } 62 | 63 | //identity and annihilator 64 | val I = Option(FirstStep(coreSupport.semiring.I,None)) 65 | val O = None 66 | 67 | def summary(fromThroughToLabel:Label,currentLabel:Label):Label = { 68 | 69 | if(currentLabel != O) { 70 | if(fromThroughToLabel != O){ 71 | val currentStep:FirstStepTrait[Node,CoreLabel] = currentLabel.get 72 | val fromThroughToStep:FirstStepTrait[Node,CoreLabel] = fromThroughToLabel.get 73 | val summ = coreSupport.semiring.summary(fromThroughToStep.weight,currentStep.weight) 74 | if (summ==currentStep.weight) currentLabel 75 | else if (summ==fromThroughToStep.weight) fromThroughToLabel 76 | else throw new IllegalStateException("Core semiring's summary "+summ+" did not return either current "+currentStep.weight+" or proposed "+fromThroughToStep.weight+" weight.") 77 | } 78 | else currentLabel 79 | } 80 | else fromThroughToLabel 81 | } 82 | 83 | def extend(fromThroughLabel:Label,throughToLabel:Label):Label = { 84 | //changing the match/case to if/else made this disappear from the sampling profiler 85 | if((fromThroughLabel != O)&&(throughToLabel != O)) { 86 | val fromThroughStep:FirstStepTrait[Node,CoreLabel] = fromThroughLabel.get 87 | val throughToStep:FirstStepTrait[Node,CoreLabel] = throughToLabel.get 88 | //if fromThroughLabel is identity, use throughToSteps. Otherwise the first step is fine 89 | val step:Option[Node] = if(fromThroughLabel == I) throughToStep.step 90 | else fromThroughStep.step 91 | 92 | Option(FirstStep(coreSupport.semiring.extend(fromThroughStep.weight,throughToStep.weight),step)) 93 | } 94 | else O 95 | } 96 | } 97 | 98 | def leastPath(from:Node,to:Node,edges:Seq[(Node,Node,Label)]):Option[Seq[Node]] = { 99 | import net.walend.disentangle.graph.AdjacencyLabelDigraph 100 | val leastPathDigraph = AdjacencyLabelDigraph(edges = edges,noEdgeExistsValue = semiring.O) 101 | leastPath(from,to)(leastPathDigraph).map(_.map(node => node.value)) 102 | } 103 | 104 | def leastPath(from:Node,to:Node)(leastPathDigraph:LabelDigraph[Node,Label]):Option[Seq[leastPathDigraph.InnerNodeType]] = { 105 | 106 | def leastPathOfInnerNodes(fromInner:Option[leastPathDigraph.InnerNodeType], 107 | toInner:Option[leastPathDigraph.InnerNodeType]):Option[Seq[leastPathDigraph.InnerNodeType]] = { 108 | val fromToOption: Option[(leastPathDigraph.InnerNodeType, leastPathDigraph.InnerNodeType)] = for (f <- fromInner; t <- toInner) yield (f, t) 109 | //If from or to is not in the digraph, return None 110 | fromToOption.flatMap(fromTo => { 111 | val label:Label = leastPathDigraph.label(fromTo._1,fromTo._2) 112 | //If label is None then no Path exists, return None 113 | label.flatMap(firstStep => { 114 | //If there's no step then from == to, return an empty path 115 | //Else follow the path 116 | firstStep.step.fold(Option(Seq.empty[leastPathDigraph.InnerNodeType]))(step => { 117 | val tailOption:Option[Seq[leastPathDigraph.InnerNodeType]] = leastPathOfInnerNodes(leastPathDigraph.innerNode(step),toInner) 118 | tailOption.flatMap(tail => {val iNodeOption = leastPathDigraph.innerNode(step) 119 | iNodeOption.map(iNode => iNode +: tail)}) 120 | }) 121 | }) 122 | }) 123 | } 124 | 125 | val fromInner = leastPathDigraph.innerNode(from) 126 | val toInner = leastPathDigraph.innerNode(to) 127 | leastPathOfInnerNodes(fromInner,toInner) 128 | } 129 | } 130 | 131 | trait FirstStepTrait[Node,CoreLabel] { 132 | 133 | def weight:CoreLabel 134 | 135 | def step:Option[Node] 136 | 137 | } 138 | -------------------------------------------------------------------------------- /Graph/src/scala/net/walend/disentangle/graph/semiring/SemiringSupport.scala: -------------------------------------------------------------------------------- 1 | package net.walend.disentangle.graph.semiring 2 | 3 | import net.walend.disentangle.heap.HeapOrdering 4 | 5 | /** 6 | * Parts for semiring-based graph minimizing algorithms. 7 | * 8 | * @author dwalend 9 | * @since v0.1.0 10 | */ 11 | trait SemiringSupport[L,Key] { 12 | 13 | type Label = L 14 | 15 | def semiring:Semiring 16 | 17 | def heapOrdering:HeapOrdering[Key] 18 | 19 | def heapKeyForLabel:Label => Key 20 | 21 | trait Semiring { 22 | 23 | /** identity */ 24 | def I:Label 25 | /** annihilator */ 26 | def O:Label 27 | 28 | /** 29 | * true if the value is within the Semiring's domain 30 | */ 31 | def inDomain(label:Label):Boolean 32 | 33 | /** 34 | * Implement this method to create the core of a summary operator 35 | */ 36 | def summary(fromThroughTo:Label,current:Label):Label 37 | 38 | /** 39 | * Implement this method to create the core of an extend operator 40 | */ 41 | def extend(fromThrough:Label,throughTo:Label):Label 42 | 43 | /** 44 | * Override relax to add side effects to the relax operator 45 | */ 46 | def relax(fromThrough:Label,throughTo:Label,current:Label):Label = { 47 | summary(extend(fromThrough,throughTo),current) 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Graph/src/scala/net/walend/disentangle/graph/semiring/TransitiveClosure.scala: -------------------------------------------------------------------------------- 1 | package net.walend.disentangle.graph.semiring 2 | 3 | import net.walend.disentangle.heap.HeapOrdering 4 | 5 | import scala.annotation.unused 6 | 7 | /** 8 | * Labels are true if the sink can be reached from the source, false if not. 9 | * 10 | * @author dwalend 11 | * @since v0.1.0 12 | */ 13 | object TransitiveClosure extends SemiringSupport[Boolean,TransitiveClosureHeapKey] { 14 | 15 | def semiring: TransitiveClosure.Semiring = TransitiveClosureSemiring 16 | 17 | def heapOrdering: HeapOrdering[TransitiveClosureHeapKey] = TransitiveClosureHeapOrdering 18 | 19 | def heapKeyForLabel: TransitiveClosure.Label => TransitiveClosureHeapKey = { (label:Label) => TransitiveClosureHeapKey.keyForLabel(label)} 20 | 21 | def convertEdgeToLabel[Node, EdgeLabel](@unused start: Node, @unused end: Node, @unused label: EdgeLabel): TransitiveClosure.Label = true 22 | 23 | object TransitiveClosureSemiring extends Semiring { 24 | 25 | def I = true 26 | def O = false 27 | 28 | def inDomain(label: Label): Boolean = true 29 | 30 | def summary(fromThroughToLabel:Label, currentLabel:Label):Label = { 31 | fromThroughToLabel || currentLabel 32 | } 33 | 34 | def extend(fromThroughLabel:Label,throughToLabel:Label):Label = { 35 | fromThroughLabel && throughToLabel 36 | } 37 | } 38 | 39 | /** 40 | * A heap ordering that puts true above false. 41 | */ 42 | object TransitiveClosureHeapOrdering extends HeapOrdering[TransitiveClosureHeapKey] { 43 | 44 | def lteq(x: TransitiveClosureHeapKey, y: TransitiveClosureHeapKey): Boolean = { 45 | x.state <= y.state 46 | } 47 | 48 | /** 49 | * @return Some negative integer, zero, or a positive integer as the first argument is less than, equal to, or greater than the second, or None if they can't be compared 50 | 51 | */ 52 | def tryCompare(x: TransitiveClosureHeapKey, y: TransitiveClosureHeapKey): Option[Int] = { 53 | Option(x.state - y.state) 54 | } 55 | 56 | def checkKey(key: TransitiveClosureHeapKey): Unit = { 57 | //sealed class. Nothing to check. 58 | } 59 | 60 | /** 61 | * Top value for the Heap if it is ever present 62 | */ 63 | def AlwaysTop:TransitiveClosureHeapKey = TransitiveClosureHeapKey.TopKey 64 | 65 | /** 66 | * A key that will be among items on the bottom of the heap. Used primarily to add items that will eventually flow higher. 67 | */ 68 | def AlwaysBottom: TransitiveClosureHeapKey = TransitiveClosureHeapKey.FalseKey 69 | } 70 | } 71 | 72 | sealed case class TransitiveClosureHeapKey(label:Boolean, state:Int) 73 | 74 | object TransitiveClosureHeapKey { 75 | val TrueKey: TransitiveClosureHeapKey = TransitiveClosureHeapKey(label = true,1) 76 | val FalseKey: TransitiveClosureHeapKey = TransitiveClosureHeapKey(label = false,0) 77 | val TopKey: TransitiveClosureHeapKey = TransitiveClosureHeapKey(label = true,2) 78 | 79 | def keyForLabel(label:Boolean):TransitiveClosureHeapKey = { 80 | if(label) TrueKey 81 | else FalseKey 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Graph/src/scala/net/walend/disentangle/graph/semiring/package.scala: -------------------------------------------------------------------------------- 1 | package net.walend.disentangle.graph 2 | 3 | import net.walend.disentangle.graph.semiring.Brandes.BrandesSteps 4 | 5 | import scala.collection.immutable.Iterable 6 | 7 | /** 8 | * Semirings and semiring-based graph minimizing algorithms. 9 | * 10 | * SemiringSupport is the primary trait. Algorithms in this package -- Floyd-Warshall, Dijkstra's, and Brandes' 11 | * algorithms -- use your choice of SemiringSupport to determine just what they are minimizing. The package also 12 | * includes some common SemiringSupport implementations. 13 | * 14 | * @author dwalend 15 | * @since v0.1.0 16 | */ 17 | package object semiring { 18 | 19 | /** 20 | * @since v0.2.1 21 | * 22 | * Helper methods for LabelDigraphs 23 | */ 24 | implicit class LabelDigraphSemiringAlgorithms[Node,Label](self: LabelDigraph[Node,Label]) { 25 | 26 | def allPairsShortestPaths: Seq[(Node,Node,Option[FirstStepsTrait[Node, Int]])] = self match { 27 | case indexed:IndexedLabelDigraph[Node,Label] => Dijkstra.allPairsShortestPaths(indexed.edges,indexed.nodes.asSeq) 28 | case _ => Dijkstra.allPairsShortestPaths(self.edges) 29 | } 30 | 31 | def allPairsLeastPaths[SemiringLabel,Key](support: SemiringSupport[SemiringLabel, Key], 32 | labelForEdge: (Node, Node, Label) => SemiringLabel):Seq[(Node, Node, SemiringLabel)] = self match { 33 | case indexed:IndexedLabelDigraph[Node,Label] => Dijkstra.allPairsLeastPaths(indexed.edges,support,labelForEdge,indexed.nodes.asSeq) 34 | case _ => Dijkstra.allPairsLeastPaths(self.edges,support,labelForEdge) 35 | } 36 | 37 | def allLeastPathsAndBetweenness[CoreLabel, Key]( coreSupport: SemiringSupport[CoreLabel, Key] = FewestNodes, 38 | labelForEdge: (Node, Node, Label) => CoreLabel = FewestNodes.edgeToLabelConverter): (IndexedSeq[(Node, Node, Option[BrandesSteps[Node, CoreLabel]])], Map[Node, Double]) = self match { 39 | case indexed:IndexedLabelDigraph[Node,Label] => Brandes.allLeastPathsAndBetweenness(indexed.edges,indexed.nodes.asSeq,coreSupport,labelForEdge) 40 | case _ => Brandes.allLeastPathsAndBetweenness(self.edges,coreSupport = coreSupport,labelForEdge = labelForEdge) 41 | } 42 | } 43 | 44 | /** 45 | * @since v0.2.1 46 | * 47 | * Helper methods for LabelUndigraphs 48 | */ 49 | implicit class LabelUndigraphSemiringAlgorithms[Node,Label](self: LabelUndigraph[Node,Label]) { 50 | 51 | def allPairsShortestPaths: Seq[(Node,Node,Option[FirstStepsTrait[Node, Int]])] = self match { 52 | case indexed:IndexedLabelUndigraph[Node,Label] => Dijkstra.allPairsShortestPaths(diEdges,indexed.nodes.asSeq) 53 | case _ => Dijkstra.allPairsShortestPaths(diEdges) 54 | } 55 | 56 | def allPairsLeastPaths[SemiringLabel,Key](support: SemiringSupport[SemiringLabel, Key], 57 | labelForEdge: (Node, Node, Label) => SemiringLabel):Seq[(Node, Node, SemiringLabel)] = self match { 58 | case indexed:IndexedLabelDigraph[Node,Label] => Dijkstra.allPairsLeastPaths(diEdges,support,labelForEdge,indexed.nodes.asSeq) 59 | case _ => Dijkstra.allPairsLeastPaths(diEdges,support,labelForEdge) 60 | } 61 | 62 | def allLeastPathsAndBetweenness[CoreLabel, Key]( coreSupport: SemiringSupport[CoreLabel, Key] = FewestNodes, 63 | labelForEdge: (Node, Node, Label) => CoreLabel = FewestNodes.edgeToLabelConverter): (IndexedSeq[(Node, Node, Option[BrandesSteps[Node, CoreLabel]])], Map[Node, Double]) = { 64 | val digraphResult = self match { 65 | case indexed:IndexedLabelDigraph[Node,Label] => Brandes.allLeastPathsAndBetweenness(indexed.edges,indexed.nodes.asSeq,coreSupport,labelForEdge) 66 | case _ => Brandes.allLeastPathsAndBetweenness(diEdges,coreSupport = coreSupport,labelForEdge = labelForEdge) 67 | } 68 | correctForUndigraph(digraphResult) 69 | } 70 | 71 | def diEdges: Iterable[(Node, Node, Label)] = { 72 | self.edges.map(e => (e._1._1,e._1._2,e._2)) ++ self.edges.map(e => (e._1._2,e._1._1,e._2)) 73 | } 74 | 75 | def correctForUndigraph[CoreLabel]( 76 | digraphResult: (IndexedSeq[(Node, Node, Option[BrandesSteps[Node, CoreLabel]])], Map[Node, Double]) 77 | ): (IndexedSeq[(Node, Node, Option[BrandesSteps[Node, CoreLabel]])], Map[Node, Double]) = { 78 | val halfMap = digraphResult._2.map(x => (x._1,x._2/2)) 79 | (digraphResult._1,halfMap) 80 | } 81 | 82 | } 83 | //todo add for Digraphs and Undirected graphs without labels 84 | } 85 | //todo pathcount as a decorator semiring 86 | //todo core-and-many-decorator semiring 87 | //todo flag in SemiringSupport for where Dijkstra's algorithm is OK -------------------------------------------------------------------------------- /Graph/src/scala/net/walend/disentangle/heap/Heap.scala: -------------------------------------------------------------------------------- 1 | package net.walend.disentangle.heap 2 | 3 | /** 4 | * A structure that makes an extreme key of some key,value pair available 5 | * 6 | * @author dwalend 7 | * @since v0.0.0 8 | */ 9 | trait Heap[K,V] { 10 | 11 | def isEmpty:Boolean 12 | 13 | def insert(key:K,value:V):HeapMember 14 | 15 | def topMember:HeapMember 16 | 17 | def topValue:V 18 | 19 | def topKey:K 20 | 21 | def takeTop():HeapMember 22 | 23 | def takeTopValue():V 24 | 25 | trait HeapMember { 26 | def key:K 27 | def key_(newKey:K):Unit 28 | 29 | /** 30 | * If candidateKey will move the HeapMember higher than the heap, change the key. Otherwise, no change. 31 | * @param candidateKey proposed new key 32 | */ 33 | def raiseKey(candidateKey:K):Unit 34 | def isInHeap: Boolean 35 | def remove():Unit 36 | } 37 | } 38 | 39 | object Heap {} 40 | 41 | /** 42 | * The heap moves the greatest value to the top, according to some HeapOrdering. 43 | * 44 | * @tparam K the key type in this heap map. 45 | */ 46 | trait HeapOrdering[K] extends PartialOrdering[K] { 47 | 48 | /** 49 | * @throws IllegalArgumentException if the key is unusable 50 | */ 51 | def checkKey(key:K):Unit 52 | 53 | /** 54 | * A key that will always be at the top of the heap if present at all. Used to efficiently remove items from the heap. 55 | */ 56 | def AlwaysTop:K 57 | 58 | /** 59 | * A key that will among items on the bottom of the heap. Used primarily to add items that will eventually flow higher. 60 | */ 61 | def AlwaysBottom:K 62 | } 63 | 64 | /** 65 | * A heap ordering that puts the least Double on top. 66 | * 67 | * If you imitate this code, observe that lteq and tryCompare find the inverse of what you'd have for MaxDoubleHeapOrdering. 68 | */ 69 | object MinDoubleHeapOrdering extends HeapOrdering[Double] { 70 | 71 | def lteq(x: Double, y: Double): Boolean = { 72 | y <= x 73 | } 74 | 75 | /** 76 | * @return Some negative integer, zero, or a positive integer as the first argument is less than, equal to, or greater than the second, or None if they can't be compared 77 | 78 | */ 79 | def tryCompare(x: Double, y: Double): Option[Int] = { 80 | if(x>y) Option(-1) 81 | else if(x==y) Option(0) 82 | else if(x AlwaysTop,"key is "+key+" but must be greater than "+ AlwaysTop) 93 | } 94 | 95 | /** 96 | * Minimum value for the DoubleHeap 97 | */ 98 | def AlwaysTop:Double = Double.NegativeInfinity 99 | 100 | /** 101 | * A key that will among items on the bottom of the heap. Used primarily to add items that will eventually flow higher. 102 | */ 103 | def AlwaysBottom: Double = Double.PositiveInfinity 104 | } 105 | 106 | -------------------------------------------------------------------------------- /Graph/test/resources/contiguous-usa.dat.txt: -------------------------------------------------------------------------------- 1 | AL FL 2 | AL GA 3 | AL MS 4 | AL TN 5 | AR LA 6 | AR MO 7 | AR MS 8 | AR OK 9 | AR TN 10 | AR TX 11 | AZ CA 12 | AZ NM 13 | AZ NV 14 | AZ UT 15 | CA NV 16 | CA OR 17 | CO KS 18 | CO NE 19 | CO NM 20 | CO OK 21 | CO UT 22 | CO WY 23 | CT MA 24 | CT NY 25 | CT RI 26 | DC MD 27 | DC VA 28 | DE MD 29 | DE NJ 30 | DE PA 31 | FL GA 32 | GA NC 33 | GA SC 34 | GA TN 35 | IA IL 36 | IA MN 37 | IA MO 38 | IA NE 39 | IA SD 40 | IA WI 41 | ID MT 42 | ID NV 43 | ID OR 44 | ID UT 45 | ID WA 46 | ID WY 47 | IL IN 48 | IL KY 49 | IL MO 50 | IL WI 51 | IN KY 52 | IN MI 53 | IN OH 54 | KS MO 55 | KS NE 56 | KS OK 57 | KY MO 58 | KY OH 59 | KY TN 60 | KY VA 61 | KY WV 62 | LA MS 63 | LA TX 64 | MA NH 65 | MA NY 66 | MA RI 67 | MA VT 68 | MD PA 69 | MD VA 70 | MD WV 71 | ME NH 72 | MI OH 73 | MI WI 74 | MN ND 75 | MN SD 76 | MN WI 77 | MO NE 78 | MO OK 79 | MO TN 80 | MS TN 81 | MT ND 82 | MT SD 83 | MT WY 84 | NC SC 85 | NC TN 86 | NC VA 87 | ND SD 88 | NE SD 89 | NE WY 90 | NH VT 91 | NJ NY 92 | NJ PA 93 | NM OK 94 | NM TX 95 | NV OR 96 | NV UT 97 | NY PA 98 | NY VT 99 | OH PA 100 | OH WV 101 | OK TX 102 | OR WA 103 | PA WV 104 | SD WY 105 | TN VA 106 | UT WY 107 | VA WV 108 | -------------------------------------------------------------------------------- /Graph/test/src/net/walend/disentangle/graph/SomeGraph.scala: -------------------------------------------------------------------------------- 1 | package net.walend.disentangle.graph 2 | 3 | /** 4 | * An example graph for eyeball testing 5 | * 6 | * @author dwalend 7 | * @since v0.1.0 8 | */ 9 | 10 | object SomeGraph { 11 | 12 | //exciting example graph 13 | 14 | val A = "A" 15 | val B = "B" 16 | val C = "C" 17 | val D = "D" 18 | val E = "E" 19 | val F = "F" 20 | val G = "G" 21 | val H = "H" 22 | 23 | val testNodes = Seq(A,B,C,D,E,F,G,H) 24 | 25 | val ab = (A,B,"ab") 26 | val bc = (B,C,"bc") 27 | val cd = (C,D,"cd") 28 | val de = (D,E,"de") 29 | val ef = (E,F,"ef") 30 | val eb = (E,B,"eb") 31 | val eh = (E,H,"eh") 32 | val hc = (H,C,"hc") 33 | 34 | val testEdges = Seq(ab,bc,cd,de,ef,eb,eh,hc) 35 | 36 | val testDigraph:IndexedLabelDigraph[String,String] = AdjacencyLabelDigraph(testEdges,testNodes,"") 37 | 38 | val af = (A,F,"af") 39 | val be = (B,E,"be") 40 | 41 | val brandesTestEdges = Seq(ab,bc,cd,de,ef,af,be) 42 | 43 | val testLabeledUndirectedEdges = testEdges.map(x => (NodePair(x._1,x._2),x._3)) 44 | 45 | val testLabelUndigraph = AdjacencyLabelUndigraph(testLabeledUndirectedEdges,testNodes) 46 | 47 | val testUndirectedEdges = testLabeledUndirectedEdges.map(_._1) 48 | 49 | val testUndigraph = AdjacencyUndigraph(testUndirectedEdges,testNodes) 50 | } 51 | -------------------------------------------------------------------------------- /Graph/test/src/net/walend/disentangle/graph/semiring/FewestNodesTest.scala: -------------------------------------------------------------------------------- 1 | package net.walend.disentangle.graph.semiring 2 | 3 | import munit.FunSuite 4 | import net.walend.disentangle.graph.SomeGraph 5 | 6 | /** 7 | * Tests algorithms with FewestNodes 8 | * 9 | * @author dwalend 10 | * @since v0.1.0 11 | */ 12 | 13 | class FewestNodesTest extends FunSuite { 14 | import SomeGraph._ 15 | 16 | test("Initializing the label graph should produce a label graph with self-arcs and arcs where SomeGraph has them") { 17 | 18 | val labelGraph = FloydWarshall.createLabelDigraph(testDigraph.edges,Seq.from(testDigraph.nodes),FewestNodes,FewestNodes.convertEdgeToLabel) 19 | 20 | val expectedArcs = Set( 21 | (A,B,1), 22 | (A,A,0), 23 | (B,C,1), 24 | (B,B,0), 25 | (C,C,0), 26 | (C,D,1), 27 | (D,D,0), 28 | (D,E,1), 29 | (E,B,1), 30 | (E,F,1), 31 | (E,H,1), 32 | (E,E,0), 33 | (F,F,0), 34 | (G,G,0), 35 | (H,C,1), 36 | (H,H,0) 37 | ) 38 | 39 | assertEquals(Set.from(labelGraph.edges), expectedArcs) 40 | } 41 | 42 | val expectedArcs: Set[(String, String, Int)] = Set( 43 | (A,A,0), 44 | (A,B,1), 45 | (A,C,2), 46 | (A,D,3), 47 | (A,E,4), 48 | (A,F,5), 49 | (A,H,5), 50 | (B,B,0), 51 | (B,C,1), 52 | (B,D,2), 53 | (B,E,3), 54 | (B,F,4), 55 | (B,H,4), 56 | (C,B,3), 57 | (C,C,0), 58 | (C,D,1), 59 | (C,E,2), 60 | (C,F,3), 61 | (C,H,3), 62 | (D,B,2), 63 | (D,C,3), 64 | (D,D,0), 65 | (D,E,1), 66 | (D,F,2), 67 | (D,H,2), 68 | (E,B,1), 69 | (E,C,2), 70 | (E,D,3), 71 | (E,E,0), 72 | (E,F,1), 73 | (E,H,1), 74 | (F,F,0), 75 | (G,G,0), 76 | (H,B,4), 77 | (H,C,1), 78 | (H,D,2), 79 | (H,E,3), 80 | (H,F,4), 81 | (H,H,0) 82 | ) 83 | 84 | test("The Floyd-Warshall algorithm should produce the correct label graph for Somegraph") { 85 | 86 | val labelGraph = FloydWarshall.allPairsLeastPaths(testDigraph.edges,Seq.from(testDigraph.nodes),FewestNodes,FewestNodes.convertEdgeToLabel) 87 | 88 | assertEquals(Set.from(labelGraph.edges), expectedArcs) 89 | } 90 | 91 | test("Dijkstra's algorithm should produce the correct label graph for Somegraph") { 92 | 93 | val labels = Dijkstra.allPairsLeastPaths(testDigraph.edges, FewestNodes, FewestNodes.convertEdgeToLabel, Seq.from(testDigraph.nodes)) 94 | 95 | assertEquals(labels.size, expectedArcs.size) 96 | assertEquals(Set.from(labels), expectedArcs) 97 | } 98 | 99 | test("Dijkstra's algorithm should produce the correct label graph for Somegraph using the implicit method on testDigraph") { 100 | 101 | val labels = testDigraph.allPairsLeastPaths(FewestNodes, FewestNodes.convertEdgeToLabel) 102 | 103 | assertEquals(labels.size, expectedArcs.size) 104 | assertEquals(Set.from(labels), expectedArcs) 105 | } 106 | 107 | val expectedBetweenness:Map[String,Double] = Map( 108 | A -> 0.0, 109 | B -> 6.5, 110 | C -> 13.0, 111 | D -> 13.0, 112 | E -> 13.0, 113 | F -> 0.0, 114 | G -> 0.0, 115 | H -> 1.5 116 | ) 117 | 118 | test("Brandes' algorithm should produce both the correct label graph and betweenness for Somegraph") { 119 | 120 | val labelGraphAndBetweenness = Brandes.allLeastPathsAndBetweenness(testDigraph.edges,Seq.from(testDigraph.nodes),FewestNodes,FewestNodes.convertEdgeToLabel) 121 | 122 | assertEquals(labelGraphAndBetweenness._2, expectedBetweenness) 123 | } 124 | /* 125 | "Parallel Dijkstra for Enron data" should "be calculated" in { 126 | 127 | import scala.io.Source 128 | import scala.pickling._ 129 | import scala.pickling.json._ 130 | 131 | val support = FewestNodes 132 | 133 | val fileContents = Source.fromURL(getClass.getResource("/Enron2000Apr.json")).mkString 134 | val edges = JSONPickle(fileContents).unpickle[Seq[(String,String,Int)]] 135 | 136 | val labels = Dijkstra.parAllPairsLeastPaths(edges, FewestNodes, FewestNodes.convertEdgeToLabel, Seq.empty) 137 | } 138 | */ 139 | } 140 | -------------------------------------------------------------------------------- /Graph/test/src/net/walend/disentangle/graph/semiring/LeastWeightsTest.scala: -------------------------------------------------------------------------------- 1 | package net.walend.disentangle.graph.semiring 2 | 3 | import net.walend.disentangle.graph.SomeGraph 4 | import munit.FunSuite 5 | 6 | /** 7 | * Tests algorithms with LeastWeights 8 | * 9 | * @author dwalend 10 | * @since v0.1.0 11 | */ 12 | 13 | class LeastWeightsTest extends FunSuite { 14 | import SomeGraph.* 15 | 16 | val arcsToWeights:Map[(String,String,String),Double] = Map(ab -> 1.0 17 | ,bc -> 2.0 18 | ,cd -> 3.0 19 | ,de -> 4.0 20 | ,ef -> 5.0 21 | ,eb -> 6.0 22 | ,eh -> 7.0 23 | ,hc -> 8.0 24 | ) 25 | 26 | def convertArcToLabel(start: String, end: String, arc: String): LeastWeights.Label = arcsToWeights((start, end, arc)) 27 | 28 | 29 | val expectedArcs: Set[(String, String, Double)] = Set( 30 | (A,A,0.0), 31 | (A,B,1.0), 32 | (A,C,3.0), 33 | (A,D,6.0), 34 | (A,E,10.0), 35 | (A,F,15.0), 36 | (A,H,17.0), 37 | (B,B,0.0), 38 | (B,C,2.0), 39 | (B,D,5.0), 40 | (B,E,9.0), 41 | (B,F,14.0), 42 | (B,H,16.0), 43 | (C,B,13.0), 44 | (C,C,0.0), 45 | (C,D,3.0), 46 | (C,E,7.0), 47 | (C,F,12.0), 48 | (C,H,14.0), 49 | (D,B,10.0), 50 | (D,C,12.0), 51 | (D,D,0.0), 52 | (D,E,4.0), 53 | (D,F,9.0), 54 | (D,H,11.0), 55 | (E,B,6.0), 56 | (E,C,8.0), 57 | (E,D,11.0), 58 | (E,E,0.0), 59 | (E,F,5.0), 60 | (E,H,7.0), 61 | (F,F,0.0), 62 | (G,G,0.0), 63 | (H,B,21.0), 64 | (H,C,8.0), 65 | (H,D,11.0), 66 | (H,E,15.0), 67 | (H,F,20.0), 68 | (H,H,0.0) 69 | ) 70 | 71 | 72 | test("The Floyd-Warshall algorithm should produce the correct label graph for Somegraph") { 73 | 74 | val labelGraph = FloydWarshall.allPairsLeastPaths(testDigraph.edges,Seq.from(testDigraph.nodes),LeastWeights,convertArcToLabel) 75 | 76 | assertEquals(Set.from(labelGraph.edges), expectedArcs) 77 | } 78 | 79 | test("Dijkstra's algorithm should produce the correct label graph for Somegraph") { 80 | 81 | val edges = Dijkstra.allPairsLeastPaths(testDigraph.edges,LeastWeights,convertArcToLabel,Seq.from(testDigraph.nodes)) 82 | 83 | assertEquals(Set.from(edges) -- expectedArcs, Set.empty) 84 | assertEquals(expectedArcs -- Set.from(edges), Set.empty) 85 | assertEquals(Set.from(edges), expectedArcs) 86 | } 87 | 88 | val expectedBetweenness:Map[String,Double] = Map( 89 | A -> 0.0, 90 | B -> 8.0, 91 | C -> 13.0, 92 | D -> 13.0, 93 | E -> 13.0, 94 | F -> 0.0, 95 | G -> 0.0, 96 | H -> 0.0 97 | ) 98 | 99 | test("Brandes' algorithm should produce both the correct label graph and betweenness for Somegraph") { 100 | 101 | val labelGraphAndBetweenness = Brandes.allLeastPathsAndBetweenness(testDigraph.edges,Seq.from(testDigraph.nodes),LeastWeights,convertArcToLabel) 102 | 103 | assertEquals(labelGraphAndBetweenness._2, expectedBetweenness) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /Graph/test/src/net/walend/disentangle/graph/semiring/MostProbableTest.scala: -------------------------------------------------------------------------------- 1 | package net.walend.disentangle.graph.semiring 2 | 3 | import munit.FunSuite 4 | import net.walend.disentangle.graph.SomeGraph 5 | 6 | /** 7 | * Tests algorithms with MostProbable 8 | * 9 | * @author dwalend 10 | * @since v0.1.0 11 | */ 12 | 13 | class MostProbableTest extends FunSuite { 14 | import SomeGraph._ 15 | 16 | val arcsToWeights:Map[(String,String,String),Double] = Map(ab -> 1.0 17 | ,bc -> 0.9 18 | ,cd -> 0.8 19 | ,de -> 0.7 20 | ,ef -> 0.6 21 | ,eb -> 0.5 22 | ,eh -> 0.4 23 | ,hc -> 0.3 24 | ) 25 | 26 | def convertEdgeToLabel(start: String, end: String, edge: String): LeastWeights.Label = arcsToWeights((start, end, edge)) 27 | 28 | 29 | val expectedArcs: Set[(String, String, Double)] = Set( 30 | (A,A,1.0), 31 | (A,B,1.0), 32 | (A,C,0.9), 33 | (A,D,0.7200000000000001), 34 | (A,E,0.504), 35 | (A,F,0.3024), 36 | (A,H,0.2016), 37 | (B,B,1.0), 38 | (B,C,0.9), 39 | (B,D,0.7200000000000001), 40 | (B,E,0.504), 41 | (B,F,0.3024), 42 | (B,H,0.2016), 43 | (C,B,0.27999999999999997), 44 | (C,C,1.0), 45 | (C,D,0.8), 46 | (C,E,0.5599999999999999), 47 | (C,F,0.33599999999999997), 48 | (C,H,0.22399999999999998), 49 | (D,B,0.35), 50 | (D,C,0.315), 51 | (D,D,1.0), 52 | (D,E,0.7), 53 | (D,F,0.42), 54 | (D,H,0.27999999999999997), 55 | (E,B,0.5), 56 | (E,C,0.45), 57 | (E,D,0.36000000000000004), 58 | (E,E,1.0), 59 | (E,F,0.6), 60 | (E,H,0.4), 61 | (F,F,1.0), 62 | (G,G,1.0), 63 | (H,B,0.08399999999999999), 64 | (H,C,0.3), 65 | (H,D,0.24), 66 | (H,E,0.16799999999999998), 67 | (H,F,0.10079999999999999), 68 | (H,H,1.0) 69 | ) 70 | 71 | 72 | test("The Floyd-Warshall algorithm should produce the correct label graph for Somegraph"){ 73 | 74 | val labelGraph = FloydWarshall.allPairsLeastPaths(testDigraph.edges,Seq.from(testDigraph.nodes),MostProbable,convertEdgeToLabel) 75 | 76 | assertEquals(Set.from(labelGraph.edges), expectedArcs) 77 | } 78 | 79 | test("Dijkstra's algorithm should produce the correct label graph for Somegraph"){ 80 | 81 | val edges = Dijkstra.allPairsLeastPaths(testDigraph.edges, MostProbable, convertEdgeToLabel, Seq.from(testDigraph.nodes)) 82 | 83 | assertEquals(Set.from(edges) -- expectedArcs, Set.empty) 84 | assertEquals(expectedArcs -- Set.from(edges), Set.empty) 85 | assertEquals(Set.from(edges), expectedArcs) 86 | } 87 | 88 | val expectedBetweenness:Map[String,Double] = Map( 89 | A -> 0.0, 90 | B -> 8.0, 91 | C -> 13.0, 92 | D -> 13.0, 93 | E -> 13.0, 94 | F -> 0.0, 95 | G -> 0.0, 96 | H -> 0.0 97 | ) 98 | 99 | test("Brandes' algorithm should produce both the correct label graph and betweenness for Somegraph") { 100 | 101 | val labelGraphAndBetweenness = Brandes.allLeastPathsAndBetweenness(testDigraph.edges,Seq.from(testDigraph.nodes),MostProbable,convertEdgeToLabel) 102 | 103 | assertEquals(labelGraphAndBetweenness._2, expectedBetweenness) 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /Graph/test/src/net/walend/disentangle/graph/semiring/OnePathFirstStepTest.scala: -------------------------------------------------------------------------------- 1 | package net.walend.disentangle.graph.semiring 2 | 3 | import net.walend.disentangle.graph.SomeGraph 4 | import munit.FunSuite 5 | 6 | /** 7 | * 8 | * 9 | * @author dwalend 10 | * @since v0.1.0 11 | */ 12 | class OnePathFirstStepTest extends FunSuite { 13 | import SomeGraph._ 14 | 15 | val support: OnePathFirstStep[String, Int, Int] = new OnePathFirstStep[String,Int,Int](FewestNodes) 16 | 17 | val expectedArcs: Set[(String, String, Option[FirstStepTrait[String, Int]])] = Set[(String, String, Option[FirstStepTrait[String,Int]])]( 18 | (A,A,Some(support.FirstStep(0,None))), 19 | (A,B,Some(support.FirstStep(1,Some(B)))), 20 | (A,C,Some(support.FirstStep(2,Some(B)))), 21 | (A,D,Some(support.FirstStep(3,Some(B)))), 22 | (A,E,Some(support.FirstStep(4,Some(B)))), 23 | (A,F,Some(support.FirstStep(5,Some(B)))), 24 | (A,H,Some(support.FirstStep(5,Some(B)))), 25 | (B,B,Some(support.FirstStep(0,None))), 26 | (B,C,Some(support.FirstStep(1,Some(C)))), 27 | (B,D,Some(support.FirstStep(2,Some(C)))), 28 | (B,E,Some(support.FirstStep(3,Some(C)))), 29 | (B,F,Some(support.FirstStep(4,Some(C)))), 30 | (B,H,Some(support.FirstStep(4,Some(C)))), 31 | (C,B,Some(support.FirstStep(3,Some(D)))), 32 | (C,C,Some(support.FirstStep(0,None))), 33 | (C,D,Some(support.FirstStep(1,Some(D)))), 34 | (C,E,Some(support.FirstStep(2,Some(D)))), 35 | (C,F,Some(support.FirstStep(3,Some(D)))), 36 | (C,H,Some(support.FirstStep(3,Some(D)))), 37 | (D,B,Some(support.FirstStep(2,Some(E)))), 38 | (D,C,Some(support.FirstStep(3,Some(E)))), 39 | (D,D,Some(support.FirstStep(0,None))), 40 | (D,E,Some(support.FirstStep(1,Some(E)))), 41 | (D,F,Some(support.FirstStep(2,Some(E)))), 42 | (D,H,Some(support.FirstStep(2,Some(E)))), 43 | (E,B,Some(support.FirstStep(1,Some(B)))), 44 | (E,C,Some(support.FirstStep(2,Some(B)))), // 45 | (E,D,Some(support.FirstStep(3,Some(B)))), // 46 | (E,E,Some(support.FirstStep(0,None))), 47 | (E,F,Some(support.FirstStep(1,Some(F)))), 48 | (E,H,Some(support.FirstStep(1,Some(H)))), 49 | (F,F,Some(support.FirstStep(0,None))), 50 | (G,G,Some(support.FirstStep(0,None))), 51 | (H,B,Some(support.FirstStep(4,Some(C)))), 52 | (H,C,Some(support.FirstStep(1,Some(C)))), 53 | (H,D,Some(support.FirstStep(2,Some(C)))), 54 | (H,E,Some(support.FirstStep(3,Some(C)))), 55 | (H,F,Some(support.FirstStep(4,Some(C)))), 56 | (H,H,Some(support.FirstStep(0,None))) 57 | ) 58 | 59 | test("The Floyd-Warshall algorithm should produce the correct label graph for Somegraph"){ 60 | 61 | val labelGraph = FloydWarshall.allPairsLeastPaths(testDigraph.edges,Seq.from(testDigraph.nodes),support,support.convertEdgeToLabelFunc[String](FewestNodes.convertEdgeToLabel)) 62 | 63 | assertEquals(Set.from(labelGraph.edges) -- expectedArcs, Set.empty) 64 | assertEquals(Set.from(labelGraph.edges), expectedArcs) 65 | } 66 | 67 | test("Dijkstra's algorithm should produce the correct label graph for Somegraph"){ 68 | 69 | val labelTuples:Seq[(String,String,Option[FirstStepTrait[String,Int]])] = Dijkstra.allPairsLeastPaths(edges = testDigraph.edges, support = support, labelForEdge = support.convertEdgeToLabelFunc[String](FewestNodes.convertEdgeToLabel), nodeOrder = Seq.from(testDigraph.nodes)) 70 | 71 | assertEquals(labelTuples.size, expectedArcs.size) 72 | assertEquals(Set.from(labelTuples), expectedArcs) 73 | } 74 | 75 | test("OnePathFirstStep and FloydWarshall's algorithm should produce labels that can create the correct shortest paths for Somegraph"){ 76 | 77 | val expectedPaths = Map( 78 | (A,A) -> Some(List()), 79 | (A,B) -> Some(List(B)), 80 | (A,C) -> Some(List(B, C)), 81 | (A,D) -> Some(List(B, C, D)), 82 | (A,E) -> Some(List(B, C, D, E)), 83 | (A,F) -> Some(List(B, C, D, E, F)), 84 | (A,H) -> Some(List(B, C, D, E, H)), 85 | (B,B) -> Some(List()), 86 | (B,C) -> Some(List(C)), 87 | (B,D) -> Some(List(C, D)), 88 | (B,E) -> Some(List(C, D, E)), 89 | (B,F) -> Some(List(C, D, E, F)), 90 | (B,H) -> Some(List(C, D, E, H)), 91 | (C,B) -> Some(List(D, E, B)), 92 | (C,C) -> Some(List()), 93 | (C,D) -> Some(List(D)), 94 | (C,E) -> Some(List(D, E)), 95 | (C,F) -> Some(List(D, E, F)), 96 | (C,H) -> Some(List(D, E, H)), 97 | (D,B) -> Some(List(E, B)), 98 | (D,C) -> Some(List(E, B, C)), 99 | (D,D) -> Some(List()), 100 | (D,E) -> Some(List(E)), 101 | (D,F) -> Some(List(E, F)), 102 | (D,H) -> Some(List(E, H)), 103 | (E,B) -> Some(List(B)), 104 | (E,C) -> Some(List(B, C)), 105 | (E,D) -> Some(List(B, C, D)), 106 | (E,E) -> Some(List()), 107 | (E,F) -> Some(List(F)), 108 | (E,H) -> Some(List(H)), 109 | (F,F) -> Some(List()), 110 | (G,G) -> Some(List()), 111 | (H,B) -> Some(List(C, D, E, B)), 112 | (H,C) -> Some(List(C)), 113 | (H,D) -> Some(List(C, D)), 114 | (H,E) -> Some(List(C, D, E)), 115 | (H,F) -> Some(List(C, D, E, F)), 116 | (H,H) -> Some(List()) 117 | ) 118 | 119 | val labelGraph = FloydWarshall.allPairsLeastPaths(testDigraph.edges,Seq.from(testDigraph.nodes),support,support.convertEdgeToLabelFunc[String](FewestNodes.convertEdgeToLabel)) 120 | 121 | val pairsToPathsOfInnerNodes = labelGraph.edges.map(edge => ((edge._1,edge._2),support.leastPath(edge._1,edge._2)(labelGraph))) 122 | 123 | val pairsToPaths = pairsToPathsOfInnerNodes.map(x => (x._1,Some(x._2.get.map(node => node.value)))).toMap 124 | 125 | assertEquals(pairsToPaths, expectedPaths) 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /Graph/test/src/net/walend/disentangle/graph/semiring/TransitiveClosureTest.scala: -------------------------------------------------------------------------------- 1 | package net.walend.disentangle.graph.semiring 2 | 3 | import net.walend.disentangle.graph.SomeGraph 4 | import munit.FunSuite 5 | 6 | /** 7 | * Tests algorithms with FewestNodes 8 | * 9 | * @author dwalend 10 | * @since v0.1.0 11 | */ 12 | 13 | class TransitiveClosureTest extends FunSuite { 14 | import SomeGraph.* 15 | 16 | val expectedArcs: Set[(String, String, Boolean)] = Set( 17 | (A,A,true), 18 | (A,B,true), 19 | (A,C,true), 20 | (A,D,true), 21 | (A,E,true), 22 | (A,F,true), 23 | (A,H,true), 24 | (B,B,true), 25 | (B,C,true), 26 | (B,D,true), 27 | (B,E,true), 28 | (B,F,true), 29 | (B,H,true), 30 | (C,B,true), 31 | (C,C,true), 32 | (C,D,true), 33 | (C,E,true), 34 | (C,F,true), 35 | (C,H,true), 36 | (D,B,true), 37 | (D,C,true), 38 | (D,D,true), 39 | (D,E,true), 40 | (D,F,true), 41 | (D,H,true), 42 | (E,B,true), 43 | (E,C,true), 44 | (E,D,true), 45 | (E,E,true), 46 | (E,F,true), 47 | (E,H,true), 48 | (F,F,true), 49 | (G,G,true), 50 | (H,B,true), 51 | (H,C,true), 52 | (H,D,true), 53 | (H,E,true), 54 | (H,F,true), 55 | (H,H,true) 56 | ) 57 | 58 | test("The Floyd-Warshall algorithm should produce the correct label graph for Somegraph") { 59 | 60 | val labelGraph = FloydWarshall.allPairsLeastPaths(testDigraph.edges,Seq.from(testDigraph.nodes),TransitiveClosure,TransitiveClosure.convertEdgeToLabel) 61 | 62 | assertEquals(Set.from(labelGraph.edges), expectedArcs) 63 | } 64 | 65 | test("Dijkstra's algorithm should produce the correct label graph for Somegraph") { 66 | 67 | val edges = Dijkstra.allPairsLeastPaths(testDigraph.edges, TransitiveClosure, TransitiveClosure.convertEdgeToLabel, Seq.from(testDigraph.nodes)) 68 | 69 | assertEquals(edges.size, expectedArcs.size) 70 | assertEquals(Set.from(edges), expectedArcs) 71 | } 72 | 73 | val expectedBetweenness:Map[String,Double] = Map( 74 | A -> 0.0, 75 | B -> 6.5, 76 | C -> 13.0, 77 | D -> 13.0, 78 | E -> 13.0, 79 | F -> 0.0, 80 | G -> 0.0, 81 | H -> 1.5 82 | ) 83 | 84 | test("Brandes' algorithm should produce both the correct label graph and betweenness for Somegraph") { 85 | 86 | val labelGraphAndBetweenness = Brandes.allLeastPathsAndBetweenness(testDigraph.edges,Seq.from(testDigraph.nodes),TransitiveClosure,TransitiveClosure.convertEdgeToLabel) 87 | 88 | assertEquals(labelGraphAndBetweenness._2, expectedBetweenness) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Graph/test/src/net/walend/disentangle/graph/semiring/UndirectedGraphTest.scala: -------------------------------------------------------------------------------- 1 | package net.walend.disentangle.graph.semiring 2 | 3 | import munit.FunSuite 4 | import net.walend.disentangle.graph.SomeGraph 5 | import net.walend.disentangle.graph.semiring.Brandes.BrandesSteps 6 | 7 | /** 8 | * 9 | * 10 | * @author dwalend 11 | * @since v0.2.1 12 | */ 13 | class UndirectedGraphTest extends FunSuite { 14 | 15 | import SomeGraph._ 16 | 17 | val support: AllPathsFirstSteps[String, Int, Int] = new AllPathsFirstSteps[String,Int,Int](FewestNodes) 18 | 19 | val expectedShortestPaths = Vector( 20 | (A,A,Some(support.FirstSteps(0,Set()))), 21 | (A,B,Some(support.FirstSteps(1,Set(B)))), 22 | (A,C,Some(support.FirstSteps(2,Set(B)))), 23 | (A,D,Some(support.FirstSteps(3,Set(B)))), 24 | (A,E,Some(support.FirstSteps(2,Set(B)))), 25 | (A,F,Some(support.FirstSteps(3,Set(B)))), 26 | (A,H,Some(support.FirstSteps(3,Set(B)))), 27 | (B,A,Some(support.FirstSteps(1,Set(A)))), 28 | (B,B,Some(support.FirstSteps(0,Set()))), 29 | (B,C,Some(support.FirstSteps(1,Set(C)))), 30 | (B,D,Some(support.FirstSteps(2,Set(C, E)))), 31 | (B,E,Some(support.FirstSteps(1,Set(E)))), 32 | (B,F,Some(support.FirstSteps(2,Set(E)))), 33 | (B,H,Some(support.FirstSteps(2,Set(C, E)))), 34 | (C,A,Some(support.FirstSteps(2,Set(B)))), 35 | (C,B,Some(support.FirstSteps(1,Set(B)))), 36 | (C,C,Some(support.FirstSteps(0,Set()))), 37 | (C,D,Some(support.FirstSteps(1,Set(D)))), 38 | (C,E,Some(support.FirstSteps(2,Set(D, B, H)))), 39 | (C,F,Some(support.FirstSteps(3,Set(D, B, H)))), 40 | (C,H,Some(support.FirstSteps(1,Set(H)))), 41 | (D,A,Some(support.FirstSteps(3,Set(E, C)))), 42 | (D,B,Some(support.FirstSteps(2,Set(E, C)))), 43 | (D,C,Some(support.FirstSteps(1,Set(C)))), 44 | (D,D,Some(support.FirstSteps(0,Set()))), 45 | (D,E,Some(support.FirstSteps(1,Set(E)))), 46 | (D,F,Some(support.FirstSteps(2,Set(E)))), 47 | (D,H,Some(support.FirstSteps(2,Set(E, C)))), 48 | (E,A,Some(support.FirstSteps(2,Set(B)))), 49 | (E,B,Some(support.FirstSteps(1,Set(B)))), 50 | (E,C,Some(support.FirstSteps(2,Set(B, H, D)))), 51 | (E,D,Some(support.FirstSteps(1,Set(D)))), 52 | (E,E,Some(support.FirstSteps(0,Set()))), 53 | (E,F,Some(support.FirstSteps(1,Set(F)))), 54 | (E,H,Some(support.FirstSteps(1,Set(H)))), 55 | (F,A,Some(support.FirstSteps(3,Set(E)))), 56 | (F,B,Some(support.FirstSteps(2,Set(E)))), 57 | (F,C,Some(support.FirstSteps(3,Set(E)))), 58 | (F,D,Some(support.FirstSteps(2,Set(E)))), 59 | (F,E,Some(support.FirstSteps(1,Set(E)))), 60 | (F,F,Some(support.FirstSteps(0,Set()))), 61 | (F,H,Some(support.FirstSteps(2,Set(E)))), 62 | (G,G,Some(support.FirstSteps(0,Set()))), 63 | (H,A,Some(support.FirstSteps(3,Set(C, E)))), 64 | (H,B,Some(support.FirstSteps(2,Set(C, E)))), 65 | (H,C,Some(support.FirstSteps(1,Set(C)))), 66 | (H,D,Some(support.FirstSteps(2,Set(C, E)))), 67 | (H,E,Some(support.FirstSteps(1,Set(E)))), 68 | (H,F,Some(support.FirstSteps(2,Set(E)))), 69 | (H,H,Some(support.FirstSteps(0,Set()))) 70 | ) 71 | 72 | test("Dijkstra's algorithm should produce the correct label graph for Somegraph") { 73 | 74 | val allShortestPaths: Seq[(String, String, Option[FirstStepsTrait[String, Int]])] = SomeGraph.testLabelUndigraph.allPairsShortestPaths 75 | 76 | assertEquals(allShortestPaths, expectedShortestPaths) 77 | } 78 | 79 | test("Brandes algorithm should produce the correct label graph and betweeness values for Somegraph") { 80 | 81 | val expectedFirstSteps: Seq[(String, String, Option[BrandesSteps[String, Int]])] = Vector((A,A,Some(BrandesSteps(0,1,List()))), 82 | (B,A,Some(BrandesSteps(1,1,List(0)))), 83 | (E,A,Some(BrandesSteps(2,1,List(1)))), 84 | (C,A,Some(BrandesSteps(2,1,List(1)))), 85 | (H,A,Some(BrandesSteps(3,2,List(2, 3)))), 86 | (D,A,Some(BrandesSteps(3,2,List(2, 3)))), 87 | (F,A,Some(BrandesSteps(3,1,List(2)))), 88 | (A,B,Some(BrandesSteps(1,1,List(1)))), 89 | (B,B,Some(BrandesSteps(0,1,List()))), 90 | (E,B,Some(BrandesSteps(1,1,List(1)))), 91 | (C,B,Some(BrandesSteps(1,1,List(1)))), 92 | (H,B,Some(BrandesSteps(2,2,List(2, 3)))), 93 | (D,B,Some(BrandesSteps(2,2,List(2, 3)))), 94 | (F,B,Some(BrandesSteps(2,1,List(2)))), 95 | (A,E,Some(BrandesSteps(2,1,List(1)))), 96 | (B,E,Some(BrandesSteps(1,1,List(2)))), 97 | (E,E,Some(BrandesSteps(0,1,List()))), 98 | (C,E,Some(BrandesSteps(2,3,List(5, 1, 4)))), 99 | (H,E,Some(BrandesSteps(1,1,List(2)))), 100 | (D,E,Some(BrandesSteps(1,1,List(2)))), 101 | (F,E,Some(BrandesSteps(1,1,List(2)))), 102 | (A,C,Some(BrandesSteps(2,1,List(1)))), 103 | (B,C,Some(BrandesSteps(1,1,List(3)))), 104 | (E,C,Some(BrandesSteps(2,3,List(1, 4, 5)))), 105 | (C,C,Some(BrandesSteps(0,1,List()))), 106 | (H,C,Some(BrandesSteps(1,1,List(3)))), 107 | (D,C,Some(BrandesSteps(1,1,List(3)))), 108 | (F,C,Some(BrandesSteps(3,3,List(2)))), 109 | (A,H,Some(BrandesSteps(3,2,List(1)))), 110 | (B,H,Some(BrandesSteps(2,2,List(2, 3)))), 111 | (E,H,Some(BrandesSteps(1,1,List(4)))), 112 | (C,H,Some(BrandesSteps(1,1,List(4)))), 113 | (H,H,Some(BrandesSteps(0,1,List()))), 114 | (D,H,Some(BrandesSteps(2,2,List(2, 3)))), 115 | (F,H,Some(BrandesSteps(2,1,List(2)))), 116 | (A,D,Some(BrandesSteps(3,2,List(1)))), 117 | (B,D,Some(BrandesSteps(2,2,List(3, 2)))), 118 | (E,D,Some(BrandesSteps(1,1,List(5)))), 119 | (C,D,Some(BrandesSteps(1,1,List(5)))), 120 | (H,D,Some(BrandesSteps(2,2,List(3, 2)))), 121 | (D,D,Some(BrandesSteps(0,1,List()))), 122 | (F,D,Some(BrandesSteps(2,1,List(2)))), 123 | (A,F,Some(BrandesSteps(3,1,List(1)))), 124 | (B,F,Some(BrandesSteps(2,1,List(2)))), 125 | (E,F,Some(BrandesSteps(1,1,List(6)))), 126 | (C,F,Some(BrandesSteps(3,3,List(5, 1, 4)))), 127 | (H,F,Some(BrandesSteps(2,1,List(2)))), 128 | (D,F,Some(BrandesSteps(2,1,List(2)))), 129 | (F,F,Some(BrandesSteps(0,1,List())))) 130 | 131 | val expectedBetweennesses = Map( 132 | A -> 0.0, 133 | B -> 5.666666666666667, 134 | C -> 2.5, 135 | D -> 0.6666666666666666, 136 | E -> 7.5, 137 | F -> 0.0, 138 | H -> 0.6666666666666666 139 | ) 140 | 141 | val allShortestPathsAndBetweenesses: (Seq[(String, String, Option[BrandesSteps[String, Int]])], Map[String, Double]) = 142 | SomeGraph.testLabelUndigraph.allLeastPathsAndBetweenness() 143 | 144 | assertEquals(allShortestPathsAndBetweenesses._1, expectedFirstSteps) 145 | assertEquals(allShortestPathsAndBetweenesses._2, expectedBetweennesses) 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /Graph/test/src/net/walend/disentangle/heap/FibonacciHeapTest.scala: -------------------------------------------------------------------------------- 1 | package net.walend.disentangle.heap 2 | 3 | import munit.FunSuite 4 | 5 | 6 | /** 7 | * Tests of a Fibonacci Heap 8 | * 9 | * @author dwalend 10 | * @since v0.0.0 11 | */ 12 | class FibonacciHeapTest extends FunSuite { 13 | 14 | def emptyHeap = new FibonacciHeap[Double,String](MinDoubleHeapOrdering) 15 | def oneMemberHeap:FibonacciHeap[Double,String] = { 16 | val heap = emptyHeap 17 | heap.insert(1,"A") 18 | heap 19 | } 20 | 21 | def twoMemberHeap:FibonacciHeap[Double,String] = { 22 | val heap = emptyHeap 23 | heap.insert(1,"A") 24 | heap.insert(2,"B") 25 | heap 26 | } 27 | 28 | test("A newly created heap should be empty") { 29 | assertEquals(emptyHeap.isEmpty, true) 30 | } 31 | 32 | test("throw an IllegalStateException when asked for its min value") { 33 | intercept[IllegalStateException]{emptyHeap.topMember} 34 | } 35 | 36 | test("throw an IllegalStateException when asked for its min key value") { 37 | intercept[IllegalStateException]{emptyHeap.topKey} 38 | } 39 | 40 | test("not be empty after an item is added") { 41 | assert(!oneMemberHeap.isEmpty) 42 | } 43 | 44 | test( "be empty after a heap with one item has that item removed") { 45 | val heap = oneMemberHeap 46 | heap.takeTop() 47 | assertEquals(heap.isEmpty, true) 48 | } 49 | 50 | test("not be empty after a heap with two item has that item removed") { 51 | val heap = twoMemberHeap 52 | 53 | heap.takeTop() 54 | assertEquals(heap.isEmpty, false) 55 | } 56 | 57 | test("not change what is at the top of the heap if a key in the heap changes to be higher") { 58 | 59 | val heap = emptyHeap 60 | val a = heap.insert(1,"A") 61 | val b = heap.insert(2,"B") 62 | 63 | b.key_(3) 64 | 65 | assertEquals(heap.takeTop(), a) 66 | } 67 | 68 | test("change what is at the top of the heap if a key in the heap changes to be lower than the min") { 69 | 70 | val heap = emptyHeap 71 | val a = heap.insert(1,"A") 72 | val b = heap.insert(2,"B") 73 | 74 | b.key_(0) 75 | 76 | assertEquals(heap.takeTop(), b) 77 | } 78 | 79 | test("change what is at the top of the heap if the current min in the heap changes to be lower than another key") { 80 | 81 | val heap = emptyHeap 82 | val a = heap.insert(1,"A") 83 | val b = heap.insert(2,"B") 84 | 85 | a.key_(3) 86 | 87 | assertEquals(heap.takeTop(), b) 88 | } 89 | 90 | test("be able to handle a lot of operations on a larger heap") { 91 | val heap = emptyHeap 92 | 93 | val a:heap.FibonacciHeapMember = heap.insert(8,"A") 94 | val b = heap.insert(7,"B") 95 | val c = heap.insert(6,"C") 96 | val d = heap.insert(5,"D") 97 | val e = heap.insert(4,"E") 98 | val f = heap.insert(3,"F") 99 | val g = heap.insert(2,"G") 100 | val h = heap.insert(1,"H") 101 | 102 | assertEquals(heap.takeTop().value, "H") 103 | g.key_(0) 104 | f.key_(9) 105 | 106 | b.remove() 107 | 108 | c.remove() 109 | 110 | assertEquals(heap.topMember.value, "G") 111 | 112 | g.key_(10) 113 | 114 | assertEquals(heap.takeTop().value, "E") 115 | a.key_(-1) 116 | assertEquals(heap.takeTop().value, "A") 117 | assertEquals(heap.takeTop().value, "D") 118 | 119 | assertEquals(heap.takeTop().value, "F") 120 | assertEquals(heap.takeTop().value, "G") 121 | assert(heap.isEmpty) 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /GraphJvm/src/scala/net/walend/disentangle/graph/par/ParIndexedSet.scala: -------------------------------------------------------------------------------- 1 | package scala.net.walend.disentangle.graph.par 2 | 3 | import net.walend.disentangle.graph.IndexedSet 4 | 5 | import scala.collection.generic.{CanCombineFrom, GenericParCompanion, GenericParTemplate, ParSetFactory} 6 | import scala.collection.immutable.{Set, SetOps} 7 | import scala.collection.mutable.{Buffer, ReusableBuilder} 8 | import scala.collection.parallel.immutable.{ParSeq, ParSet} 9 | import scala.collection.parallel.{Combiner, IterableSplitter, ParIterable, ParSetLike, SeqSplitter} 10 | import scala.collection.{IterableFactory, IterableFactoryDefaults, mutable} 11 | 12 | /** 13 | * A parallel indexed set 14 | * 15 | * @author dwalend 16 | * @since v0.3.0 17 | */ 18 | 19 | final class ParIndexedSet[A](outerSeq:ParSeq[A]) 20 | extends GenericParTemplate[A, ParIndexedSet] 21 | with scala.collection.parallel.ParSet[A] 22 | with ParIterable[A] 23 | with ParSetLike[A, ParIndexedSet, ParIndexedSet[A], scala.collection.immutable.Set[A]] { 24 | 25 | private val asSet:ParSet[A] = outerSeq.toSet 26 | 27 | override def contains(elem: A): Boolean = asSet.contains(elem) 28 | 29 | override def +(elem: A): ParIndexedSet[A] = { 30 | if (contains(elem)) this 31 | else new ParIndexedSet(outerSeq :+ elem) 32 | } 33 | 34 | override def -(elem: A): ParIndexedSet[A] = { 35 | if (!contains(elem)) this 36 | else new ParIndexedSet(outerSeq.filterNot(_ == elem)) 37 | } 38 | 39 | override def size: Int = asSet.size 40 | 41 | override def seq: IndexedSet[A] = new IndexedSet[A](IndexedSeq.from(outerSeq)) 42 | 43 | override def splitter: IterableSplitter[A] = ???//SeqSplitter[A] 44 | 45 | override def empty: ParIndexedSet[A] = new ParIndexedSet(ParSeq.empty[A]) 46 | 47 | override def companion: GenericParCompanion[ParIndexedSet] = ParIndexedSet 48 | 49 | override def stringPrefix = "ParIndexedSet" 50 | 51 | } 52 | 53 | object ParIndexedSet extends ParSetFactory[ParIndexedSet] { 54 | def newCombiner[T]: Combiner[T, ParIndexedSet[T]] = ??? 55 | 56 | implicit def canBuildFrom[S, T]: CanCombineFrom[ParIndexedSet[S], T, ParIndexedSet[T]] = new GenericCanCombineFrom[S, T] 57 | } -------------------------------------------------------------------------------- /GraphJvm/src/scala/net/walend/disentangle/graph/semiring/par/ParBrandes.scala: -------------------------------------------------------------------------------- 1 | package net.walend.disentangle.graph.semiring.par 2 | 3 | import net.walend.disentangle.graph.IndexedLabelDigraph 4 | import net.walend.disentangle.graph.semiring.Brandes.{BrandesSteps, BrandesSupport} 5 | import net.walend.disentangle.graph.semiring.{Brandes, FewestNodes, SemiringSupport} 6 | import net.walend.disentangle.heap.HeapOrdering 7 | 8 | import scala.collection.immutable.Iterable 9 | 10 | import scala.collection.parallel.immutable.{ParMap, ParSeq} 11 | 12 | 13 | /** 14 | * Brandes' algorithm for betweenness and minimal paths. 15 | * 16 | * @author dwalend 17 | * @since v0.1.0 18 | */ 19 | //noinspection ReferenceMustBePrefixed 20 | 21 | object ParBrandes { 22 | 23 | /** 24 | * This method runs Dijkstra's algorithm and finds betweenness for all nodes in the label graph. 25 | */ 26 | def parAllLeastPathsAndBetweenness[Node, EdgeLabel, CoreLabel, Key]( 27 | edges: Iterable[(Node, Node, EdgeLabel)], 28 | nodeOrder: Seq[Node] = Seq.empty, 29 | coreSupport: SemiringSupport[CoreLabel, Key] = FewestNodes, 30 | labelForEdge: (Node, Node, EdgeLabel) => CoreLabel = FewestNodes.edgeToLabelConverter 31 | ): 32 | (ParSeq[(Node, Node, Option[BrandesSteps[Node, CoreLabel]])], ParMap[Node, Double]) = { 33 | val support = new BrandesSupport[Node,CoreLabel,Key](coreSupport) 34 | type Label = support.Label 35 | 36 | //todo make parallel 37 | val initialGraph: IndexedLabelDigraph[Node,Label] = Brandes.createLabelDigraph(edges, nodeOrder, support, labelForEdge) 38 | 39 | val innerNodes = ParSeq.fromSpecific(initialGraph.innerNodes) 40 | 41 | val edgesAndBetweenParts = for (sink <- innerNodes) yield { 42 | val edgeAndNodeStack = Brandes.brandesDijkstra(initialGraph, support)(sink) 43 | val partialB = Brandes.partialBetweenness(support, initialGraph)(sink, edgeAndNodeStack._2, edgeAndNodeStack._1) 44 | val filteredEdges = edgeAndNodeStack._1.filter(_._3 != support.semiring.O) 45 | (filteredEdges, partialB) 46 | } 47 | 48 | def betweennessForNode(innerNode: initialGraph.InnerNodeType): Double = edgesAndBetweenParts.map(x => x._2(innerNode.index)).sum 49 | 50 | //noinspection ScalaUnnecessaryParentheses 51 | val betweennessMap = innerNodes.map(innerNode => (innerNode.value -> betweennessForNode(innerNode))).toMap 52 | 53 | val shortPaths = edgesAndBetweenParts.flatMap(x => x._1) 54 | 55 | (shortPaths, betweennessMap) 56 | } 57 | } -------------------------------------------------------------------------------- /GraphJvm/src/scala/net/walend/disentangle/graph/semiring/par/ParDijkstra.scala: -------------------------------------------------------------------------------- 1 | package net.walend.disentangle.graph.semiring.par 2 | 3 | import net.walend.disentangle.graph.semiring.{Dijkstra, FewestNodes, FirstStepsTrait, SemiringSupport} 4 | 5 | import scala.collection.parallel.immutable.ParSeq 6 | 7 | /** 8 | * An implementation of Dijkstra's algorithm for general graph minimization for both single-source and single-sink. 9 | * 10 | * @author dwalend 11 | * @since v0.1.0 12 | */ 13 | 14 | object ParDijkstra { 15 | 16 | /** 17 | * O(n^2 ln(n) + na) / cores 18 | */ 19 | def parAllPairsLeastPaths[Node,EdgeLabel,Label,Key](edges: Iterable[(Node, Node, EdgeLabel)], 20 | support: SemiringSupport[Label, Key], 21 | labelForEdge: (Node, Node, EdgeLabel) => Label, 22 | nodeOrder: Seq[Node] = Seq.empty):ParSeq[(Node, Node, Label)] = { 23 | val labelDigraph = Dijkstra.createLabelDigraph(edges, support, labelForEdge, nodeOrder) 24 | 25 | //profiler blamed both flatten and fold of IndexedSets as trouble 26 | ParSeq.fromSpecific(labelDigraph.innerNodes).flatMap(source => Dijkstra.dijkstraSingleSource(labelDigraph, support)(source)) 27 | } 28 | 29 | def parAllPairsShortestPaths[Node,EdgeLabel]( 30 | edges:Iterable[(Node,Node,EdgeLabel)], 31 | nodeOrder:Seq[Node] = Seq.empty 32 | ):ParSeq[(Node,Node,Option[FirstStepsTrait[Node, Int]])] = { 33 | val support = Dijkstra.defaultSupport[Node] 34 | parAllPairsLeastPaths(edges, support, support.convertEdgeToLabel(FewestNodes.convertEdgeToLabel), nodeOrder) 35 | } 36 | 37 | } 38 | 39 | -------------------------------------------------------------------------------- /GraphJvm/src/scala/net/walend/disentangle/graph/semiring/par/package.scala: -------------------------------------------------------------------------------- 1 | package net.walend.disentangle.graph.semiring 2 | 3 | import net.walend.disentangle.graph.semiring.Brandes.BrandesSteps 4 | import net.walend.disentangle.graph.{IndexedLabelDigraph, IndexedLabelUndigraph, LabelDigraph, LabelUndigraph} 5 | import net.walend.disentangle.graph.semiring.{Dijkstra, FewestNodes, FirstStepsTrait, SemiringSupport} 6 | 7 | import scala.collection.immutable.Iterable 8 | 9 | import scala.collection.parallel.immutable.{ParMap, ParSeq} 10 | 11 | package object par { 12 | /** 13 | * @since v0.2.1 14 | * 15 | * Helper methods for LabelDigraphs 16 | */ 17 | implicit class LabelDigraphSemiringAlgorithms[Node, Label](self: LabelDigraph[Node, Label]) { 18 | 19 | def parAllPairsShortestPaths: ParSeq[(Node, Node, Option[FirstStepsTrait[Node, Int]])] = self match { 20 | case indexed: IndexedLabelDigraph[Node, Label] => ParDijkstra.parAllPairsShortestPaths(indexed.edges, indexed.nodes.asSeq) 21 | case _ => ParDijkstra.parAllPairsShortestPaths(self.edges) 22 | } 23 | 24 | def parAllPairsLeastPaths[SemiringLabel, Key](support: SemiringSupport[SemiringLabel, Key], 25 | labelForEdge: (Node, Node, Label) => SemiringLabel): ParSeq[(Node, Node, SemiringLabel)] = self match { 26 | case indexed: IndexedLabelDigraph[Node, Label] => ParDijkstra.parAllPairsLeastPaths(indexed.edges, support, labelForEdge, indexed.nodes.asSeq) 27 | case _ => ParDijkstra.parAllPairsLeastPaths(self.edges, support, labelForEdge) 28 | } 29 | 30 | def parAllLeastPathsAndBetweenness[CoreLabel, Key](coreSupport: SemiringSupport[CoreLabel, Key] = FewestNodes, 31 | labelForEdge: (Node, Node, Label) => CoreLabel = FewestNodes.edgeToLabelConverter): (ParSeq[(Node, Node, Option[BrandesSteps[Node, CoreLabel]])], ParMap[Node, Double]) = self match { 32 | case indexed: IndexedLabelDigraph[Node, Label] => ParBrandes.parAllLeastPathsAndBetweenness(indexed.edges, indexed.nodes.asSeq, coreSupport, labelForEdge) 33 | case _ => ParBrandes.parAllLeastPathsAndBetweenness(self.edges, coreSupport = coreSupport, labelForEdge = labelForEdge) 34 | } 35 | } 36 | 37 | /** 38 | * @since v0.2.1 39 | * 40 | * Helper methods for LabelUndigraphs 41 | */ 42 | implicit class LabelUndigraphSemiringAlgorithms[Node, Label](self: LabelUndigraph[Node, Label]) { 43 | 44 | def parAllPairsShortestPaths: ParSeq[(Node, Node, Option[FirstStepsTrait[Node, Int]])] = self match { 45 | case indexed: IndexedLabelUndigraph[Node, Label] => ParDijkstra.parAllPairsShortestPaths(diEdges, indexed.nodes.asSeq) 46 | case _ => ParDijkstra.parAllPairsShortestPaths(diEdges) 47 | } 48 | 49 | def parAllPairsLeastPaths[SemiringLabel, Key](support: SemiringSupport[SemiringLabel, Key], 50 | labelForEdge: (Node, Node, Label) => SemiringLabel): ParSeq[(Node, Node, SemiringLabel)] = self match { 51 | case indexed: IndexedLabelDigraph[Node, Label] => ParDijkstra.parAllPairsLeastPaths(diEdges, support, labelForEdge, indexed.nodes.asSeq) 52 | case _ => ParDijkstra.parAllPairsLeastPaths(diEdges, support, labelForEdge) 53 | } 54 | 55 | def parAllLeastPathsAndBetweenness[CoreLabel, Key]( 56 | coreSupport: SemiringSupport[CoreLabel, Key] = FewestNodes, 57 | labelForEdge: (Node, Node, Label) => CoreLabel = FewestNodes.edgeToLabelConverter 58 | ): (ParSeq[(Node, Node, Option[BrandesSteps[Node, CoreLabel]])], ParMap[Node, Double]) = { 59 | val digraphResult = self match { 60 | case indexed: IndexedLabelDigraph[Node, Label] => ParBrandes.parAllLeastPathsAndBetweenness(indexed.edges, indexed.nodes.asSeq, coreSupport, labelForEdge) 61 | case _ => ParBrandes.parAllLeastPathsAndBetweenness(diEdges, coreSupport = coreSupport, labelForEdge = labelForEdge) 62 | } 63 | correctForUndigraph(digraphResult) 64 | } 65 | 66 | def diEdges: Iterable[(Node, Node, Label)] = { 67 | self.edges.map(e => (e._1._1, e._1._2, e._2)) ++ self.edges.map(e => (e._1._2, e._1._1, e._2)) 68 | } 69 | 70 | def correctForUndigraph[CoreLabel]( 71 | digraphResult: (ParSeq[(Node, Node, Option[BrandesSteps[Node, CoreLabel]])], ParMap[Node, Double]) 72 | ) = { 73 | val halfMap = digraphResult._2.map(x => (x._1, x._2 / 2)) 74 | (digraphResult._1, halfMap) 75 | } 76 | 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /GraphJvm/test/resources/contiguous-usa.dat.txt: -------------------------------------------------------------------------------- 1 | AL FL 2 | AL GA 3 | AL MS 4 | AL TN 5 | AR LA 6 | AR MO 7 | AR MS 8 | AR OK 9 | AR TN 10 | AR TX 11 | AZ CA 12 | AZ NM 13 | AZ NV 14 | AZ UT 15 | CA NV 16 | CA OR 17 | CO KS 18 | CO NE 19 | CO NM 20 | CO OK 21 | CO UT 22 | CO WY 23 | CT MA 24 | CT NY 25 | CT RI 26 | DC MD 27 | DC VA 28 | DE MD 29 | DE NJ 30 | DE PA 31 | FL GA 32 | GA NC 33 | GA SC 34 | GA TN 35 | IA IL 36 | IA MN 37 | IA MO 38 | IA NE 39 | IA SD 40 | IA WI 41 | ID MT 42 | ID NV 43 | ID OR 44 | ID UT 45 | ID WA 46 | ID WY 47 | IL IN 48 | IL KY 49 | IL MO 50 | IL WI 51 | IN KY 52 | IN MI 53 | IN OH 54 | KS MO 55 | KS NE 56 | KS OK 57 | KY MO 58 | KY OH 59 | KY TN 60 | KY VA 61 | KY WV 62 | LA MS 63 | LA TX 64 | MA NH 65 | MA NY 66 | MA RI 67 | MA VT 68 | MD PA 69 | MD VA 70 | MD WV 71 | ME NH 72 | MI OH 73 | MI WI 74 | MN ND 75 | MN SD 76 | MN WI 77 | MO NE 78 | MO OK 79 | MO TN 80 | MS TN 81 | MT ND 82 | MT SD 83 | MT WY 84 | NC SC 85 | NC TN 86 | NC VA 87 | ND SD 88 | NE SD 89 | NE WY 90 | NH VT 91 | NJ NY 92 | NJ PA 93 | NM OK 94 | NM TX 95 | NV OR 96 | NV UT 97 | NY PA 98 | NY VT 99 | OH PA 100 | OH WV 101 | OK TX 102 | OR WA 103 | PA WV 104 | SD WY 105 | TN VA 106 | UT WY 107 | VA WV 108 | -------------------------------------------------------------------------------- /GraphJvm/test/src/net/walend/disentangle/graph/semiring/par/BrandesJungTest.scala: -------------------------------------------------------------------------------- 1 | package net.walend.disentangle.graph.semiring.par 2 | 3 | import munit.FunSuite 4 | import net.walend.disentangle.graph.semiring.{Brandes, FewestNodes} 5 | import net.walend.disentangle.graph.{AdjacencyLabelDigraph, LabelDigraph} 6 | 7 | import scala.collection.Map 8 | 9 | /** 10 | * 11 | * 12 | * @author dwalend 13 | * @since v0.1.0 14 | */ 15 | class BrandesJungTest extends FunSuite { 16 | 17 | val support: FewestNodes.type = FewestNodes 18 | val brandesSupport: Brandes.BrandesSupport[String, Int, Int] = Brandes.BrandesSupport[String]() 19 | 20 | def jungBetweenness[Node,Label](arcs:Seq[(Node,Node,Label)]):Seq[(Node,Double)] = { 21 | import edu.uci.ics.jung.algorithms.scoring.BetweennessCentrality 22 | import edu.uci.ics.jung.graph.UndirectedSparseGraph 23 | 24 | val jungGraph = new UndirectedSparseGraph[Node,Any]() 25 | 26 | val nodes = (arcs.map(_._1) ++ arcs.map(_._2)).distinct 27 | for(node <- nodes) { 28 | jungGraph.addVertex(node) 29 | } 30 | 31 | var edgeCounter = 0 32 | for(arc <- arcs) { 33 | jungGraph.addEdge(edgeCounter,arc._1,arc._2) 34 | edgeCounter = edgeCounter + 1 35 | } 36 | 37 | val jungBetweenCalc = new BetweennessCentrality(jungGraph) 38 | 39 | import scala.jdk.CollectionConverters.CollectionHasAsScala 40 | val jb = Seq.from(jungGraph.getVertices.asScala).map(node => (node,jungBetweenCalc.getVertexScore(node).toDouble)) 41 | 42 | jb 43 | } 44 | 45 | def usStateEdges: Seq[(String, String, Unit)] = { 46 | 47 | import scala.io.Source 48 | 49 | /* 50 | AL FL 51 | AL GA 52 | AL MS 53 | AL TN 54 | */ 55 | 56 | val lines = Source.fromURL(getClass.getResource("/contiguous-usa.dat.txt")).getLines() 57 | //turn them into arcs 58 | def arcFromLine(line:String):Option[(String,String,Unit)] = { 59 | val splitLine: Array[String] = line.split(" ") 60 | Some((splitLine(0),splitLine(1),())) 61 | } 62 | 63 | val arcs = Seq.from(lines.flatMap(arcFromLine)) 64 | 65 | arcs 66 | } 67 | 68 | test("Brandes' algorithm should produce the same betweenness as Jung for the US state dataSeq, even in parallel") { 69 | 70 | val arcs = usStateEdges 71 | 72 | //now make an arc going the other way 73 | val allArcs = arcs ++ arcs.map(arc => (arc._2,arc._1,arc._3)) 74 | 75 | //find betweenness 76 | val labelGraphAndBetweenness = Brandes.allLeastPathsAndBetweenness(allArcs,Seq.empty,support,FewestNodes.convertEdgeToLabel) 77 | val betweennesses:Map[String,Double] = Seq.from(labelGraphAndBetweenness._2).map(bet => (bet._1, bet._2/2)).toMap 78 | 79 | //find betweenness in parallel 80 | val parLabelGraphAndBetweenness = Brandes.allLeastPathsAndBetweenness(allArcs,Seq.empty,support,FewestNodes.convertEdgeToLabel) 81 | val parBetweennesses:Map[String,Double] = Seq.from(parLabelGraphAndBetweenness._2).map(bet => (bet._1, bet._2/2)).toMap 82 | 83 | //find betweenness with Jung 84 | val jungB:Map[String,Double] = jungBetweenness(arcs).toMap 85 | 86 | //if only betweennesses should be(jungBetwennesses) 87 | for(node <- betweennesses.keys) { 88 | import scala.math.abs 89 | val epsilon = 0.000000000000001 * betweennesses(node) 90 | assert(abs(betweennesses(node) - jungB(node)) <= epsilon,s"$node's betweenness ${betweennesses(node)} does not match jung's ${jungB(node)}") 91 | assert(abs(betweennesses(node) - parBetweennesses(node)) <= epsilon,s"$node's betweenness ${betweennesses(node)} does not match parBetweennesses's ${parBetweennesses(node)}") 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /GraphJvm/test/src/net/walend/disentangle/graph/semiring/par/ParTests.scala: -------------------------------------------------------------------------------- 1 | package net.walend.disentangle.graph.semiring.par 2 | 3 | import munit.FunSuite 4 | import net.walend.disentangle.graph.SomeGraph 5 | import net.walend.disentangle.graph.semiring.{AllPathsFirstStepsTest, BrandesTest, FewestNodes, FewestNodesTest, UndirectedGraphTest} 6 | 7 | 8 | class ParTests extends FunSuite{ 9 | 10 | test("Dijkstra's algorithm should produce the correct label graph for Somegraph using default values for the parallel algorithm and AllPathsFirstSteps") { 11 | val answers: AllPathsFirstStepsTest = new AllPathsFirstStepsTest() 12 | 13 | val arcs = ParDijkstra.parAllPairsShortestPaths(SomeGraph.testDigraph.edges, Seq.from(SomeGraph.testDigraph.nodes)) 14 | 15 | assertEquals(arcs.size, answers.expectedArcs.size) 16 | assertEquals(Set.from(arcs), answers.expectedArcs) 17 | } 18 | 19 | test("Dijkstra's algorithm should produce the correct label graph for Somegraph using default values for the parallel algorithm with the implicit extension method and AllPathsFirstSteps") { 20 | val answers = new AllPathsFirstStepsTest() 21 | val arcs = SomeGraph.testDigraph.parAllPairsShortestPaths 22 | 23 | assertEquals(arcs.size, answers.expectedArcs.size) 24 | assertEquals(Set.from(arcs), answers.expectedArcs) 25 | } 26 | 27 | test("Parallel Dijkstra's algorithm should produce the correct label graph for Somegraph and FewestNodes") { 28 | val answers = new FewestNodesTest() 29 | val labels = ParDijkstra.parAllPairsLeastPaths(SomeGraph.testDigraph.edges, FewestNodes, FewestNodes.convertEdgeToLabel, Seq.from(SomeGraph.testDigraph.nodes)) 30 | 31 | assertEquals(labels.size, answers.expectedArcs.size) 32 | assertEquals(Set.from(labels), answers.expectedArcs) 33 | } 34 | 35 | test("Parallel Dijkstra's algorithm should produce the correct label graph for Somegraph using the implicit method on testDigraph and FewestNodes") { 36 | val answers = new FewestNodesTest() 37 | 38 | val labels = SomeGraph.testDigraph.parAllPairsLeastPaths(FewestNodes, FewestNodes.convertEdgeToLabel) 39 | 40 | assertEquals(labels.size, answers.expectedArcs.size) 41 | assertEquals(Set.from(labels), answers.expectedArcs) 42 | } 43 | 44 | test("Dijkstra's algorithm should produce the correct label graph for Somegraph in parallel on an UndirectedGraph") { 45 | val answers: UndirectedGraphTest = new UndirectedGraphTest() 46 | val allShortestPaths = SomeGraph.testLabelUndigraph.parAllPairsShortestPaths 47 | 48 | assertEquals(Vector.from(allShortestPaths), answers.expectedShortestPaths) 49 | } 50 | 51 | test("Brandes' algorithm should produce the correct label graph and betweenness using the implicit method on a Digraph in parallel") { 52 | val answers = new BrandesTest() 53 | 54 | val labelGraphAndBetweenness = ParBrandes.parAllLeastPathsAndBetweenness(SomeGraph.testDigraph.edges, Seq.from(SomeGraph.testDigraph.nodes), answers.support, FewestNodes.convertEdgeToLabel) 55 | answers.checkBrandesResults((Seq.from(labelGraphAndBetweenness._1), Map.from(labelGraphAndBetweenness._2))) 56 | } 57 | 58 | 59 | } 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013-2018 dwalend 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /benchmark/build.sbt: -------------------------------------------------------------------------------- 1 | libraryDependencies += "net.sf.jung" % "jung-graph-impl" % "2.0.1" //for timing comparisons 2 | 3 | libraryDependencies += "net.sf.jung" % "jung-algorithms" % "2.0.1" //for timing comparisons 4 | 5 | libraryDependencies += "com.github.scopt" %% "scopt" % "3.5.0" //command line parser 6 | 7 | libraryDependencies += "com.lihaoyi" %% "upickle" % "0.4.4" //json output 8 | 9 | scalacOptions ++= Seq("-unchecked", "-deprecation","-feature") 10 | 11 | fork in run := true 12 | 13 | javaOptions in run += "-Xmx3G" //prevents big GC 14 | 15 | javaOptions in run += "-Xms3G" //prevents big GC 16 | 17 | javaOptions in run += "-server" //does hotspot optimizations earlier 18 | 19 | //javaOptions in run += "-Dscala.concurrent.context.numThreads=4" 20 | 21 | packagedArtifacts := Map.empty 22 | -------------------------------------------------------------------------------- /benchmark/results/v0.2.0/c4.8xlarge/dijkstra.csv: -------------------------------------------------------------------------------- 1 | nodes,measured,expected 2 | 32,3334405,3334405 3 | 38,4004204,4935184 4 | 45,4743421,7242563 5 | 53,5755285,10478451 6 | 64,8568289,16005144 7 | 76,12727910,23502364 8 | 90,15216870,34245387 9 | 107,22171548,50265540 10 | 128,31746619,74690672 11 | 152,48805043,109055959 12 | 181,63117533,160013961 13 | 215,93959029,233252189 14 | 256,137891914,341443072 15 | 304,194275809,496409849 16 | 362,285549738,725398377 17 | 430,423434251,1053425063 18 | 512,629677040,1536493824 19 | 608,905394907,2226383440 20 | 724,1391420896,3242963643 21 | 861,1799226453,4707100707 22 | 1024,2625585659,6828861440 23 | 1217,3947135247,9885891612 24 | 1448,6001632828,14337335101 25 | 1722,9672814377,20759546928 26 | 2048,16051358779,30046990336 27 | 2435,25645964276,43439767643 28 | 2896,38258982608,62811262515 29 | 3444,61124226037,90762764118 30 | 4096,99167544327,131114139648 31 | 4870,154486720596,189204724861 32 | 5792,248565796217,273092738501 33 | 6888,419332643723,393949362087 34 | 8192,676717772585,568161271807 35 | -------------------------------------------------------------------------------- /benchmark/results/v0.2.0/c4.8xlarge/parDijkstra.csv: -------------------------------------------------------------------------------- 1 | nodes,measured,expected 2 | 32,14000049,14000049 3 | 38,11115724,20721186 4 | 45,13717293,30409097 5 | 53,16904239,43995505 6 | 64,20098429,67200235 7 | 76,19867353,98678550 8 | 90,18891197,143784905 9 | 107,20548353,211048156 10 | 128,27912258,313601097 11 | 152,35687892,457889423 12 | 181,41798039,671844991 13 | 215,50155542,979347764 14 | 256,46930540,1433605017 15 | 304,72230704,2084258577 16 | 362,92347988,3045704656 17 | 430,127589332,4422978763 18 | 512,184516616,6451222579 19 | 608,250291516,9347837847 20 | 724,397779097,13616117392 21 | 861,477747206,19763538186 22 | 1024,619289587,28672100352 23 | 1217,1119472756,41507545417 24 | 1448,1445746650,60197664634 25 | 1722,2211097205,87162379561 26 | 2048,3791533930,126157241548 27 | 2435,4927281684,182389024597 28 | 2896,7925984635,263723438803 29 | 3444,11913083255,381082425512 30 | 4096,18302779448,550504326758 31 | 4870,29470940961,794407223805 32 | 5792,43266635675,1146624876271 33 | 6888,69797325878,1654061331107 34 | 8192,103165886853,2385518749286 35 | -------------------------------------------------------------------------------- /benchmark/results/v0.2.0/laptop/brandes.csv: -------------------------------------------------------------------------------- 1 | nodes,measured,expected 2 | 32,3286000,3286000 3 | 38,3246000,4863541 4 | 45,8736000,7137424 5 | 53,7264000,10326337 6 | 64,7790000,15772800 7 | 76,10670000,23161184 8 | 90,15529000,33748253 9 | 107,27922000,49535843 10 | 128,31503000,73606400 11 | 152,46778000,107472812 12 | 181,59850000,157691065 13 | 215,89952000,229866106 14 | 256,128455000,336486400 15 | 304,172524000,489203551 16 | 362,257105000,714867890 17 | 430,361275000,1038132667 18 | 512,568815000,1514188800 19 | 608,780143000,2194063404 20 | 724,1278047000,3195886082 21 | 861,1796435000,4638768512 22 | 1024,2670340000,6729728000 23 | 1217,4129691000,9742379776 24 | 1448,6483718000,14129202404 25 | 1722,10396385000,20458184056 26 | 2048,15573673000,29610803200 27 | 2435,24552800000,42809159798 28 | 2896,43901943000,61899441916 29 | 3444,64154131000,89445176244 30 | 4096,97098457000,129210777600 31 | -------------------------------------------------------------------------------- /benchmark/results/v0.2.0/laptop/dijkstra.csv: -------------------------------------------------------------------------------- 1 | nodes,measured,expected 2 | 32,2977000,2977000 3 | 38,3209000,4406196 4 | 45,4337000,6466254 5 | 53,6407000,9355297 6 | 64,12706000,14289600 7 | 76,12255000,20983215 8 | 90,21509000,30574726 9 | 107,29184000,44877725 10 | 128,34039000,66684799 11 | 152,45720000,97366574 12 | 181,57475000,142862538 13 | 215,83186000,208250577 14 | 256,119261000,304844800 15 | 304,171712000,443201147 16 | 362,242188000,647645073 17 | 430,398228000,940511549 18 | 512,508011000,1371801600 19 | 608,763804000,1987743990 20 | 724,1332881000,2895359971 21 | 861,1656373000,4202560518 22 | 1024,2569609000,6096896000 23 | 1217,3741746000,8826252158 24 | 1448,6220672000,12800558599 25 | 1722,8956721000,18534392555 26 | 2048,13473090000,26826342400 27 | 2435,21003244000,38783587559 28 | 2896,32046220000,56078709247 29 | 3444,57140741000,81034172148 30 | 4096,85375113000,117060403200 31 | 4870,132167887000,168924430569 32 | 5792,587816884000,243820736388 33 | -------------------------------------------------------------------------------- /benchmark/results/v0.2.0/laptop/jung.csv: -------------------------------------------------------------------------------- 1 | nodes,measured,expected 2 | 32,2078000,2078000 3 | 38,3039000,3075605 4 | 45,4270000,4513563 5 | 53,6357000,6530167 6 | 64,7939000,9974400 7 | 76,15982000,14646665 8 | 90,15596000,21341713 9 | 107,29710000,31325466 10 | 128,40364000,46547200 11 | 152,47232000,67963635 12 | 181,74758000,99720643 13 | 215,120069000,145362680 14 | 256,138027000,212787199 15 | 304,161214000,309362440 16 | 362,233249000,452068008 17 | 430,339245000,656494121 18 | 512,490886000,957542400 19 | 608,644197000,1387481361 20 | 724,1065074000,2021013779 21 | 861,1428937000,2933463472 22 | 1024,2262164000,4255744000 23 | 1217,3117068000,6160884106 24 | 1448,5417814000,8935022092 25 | 1722,6603388000,12937342199 26 | 2048,10786054000,18725273600 27 | 2435,17129257000,27071647614 28 | 2896,31811640000,39143956269 29 | 3444,40218618000,56563322043 30 | 4096,62337256000,81710284800 31 | 4870,120288796000,117912316668 32 | -------------------------------------------------------------------------------- /benchmark/results/v0.2.0/laptop/parBrandes.csv: -------------------------------------------------------------------------------- 1 | nodes,measured,expected 2 | 32,6784000,6784000 3 | 38,8432000,10040859 4 | 45,9634000,14735328 5 | 53,10480000,21318890 6 | 64,10710000,32563200 7 | 76,13563000,47816638 8 | 90,15106000,69673813 9 | 107,23040000,102267548 10 | 128,26426000,151961600 11 | 152,38157000,221879355 12 | 181,47481000,325555747 13 | 215,65726000,474562284 14 | 256,81058000,694681600 15 | 304,144610000,1009968621 16 | 362,158389000,1475856290 17 | 430,195709000,2143241636 18 | 512,267392000,3126067200 19 | 608,426021000,4529679286 20 | 724,564412000,6597958363 21 | 861,792412000,9576812413 22 | 1024,1185044000,13893632000 23 | 1217,2265511000,20113300182 24 | 1448,2856064000,29169966253 25 | 1722,4371148000,42236250955 26 | 2048,6952483000,61131980800 27 | 2435,10785737000,88380200874 28 | 2896,23550551000,127792396215 29 | 3444,31243780000,184661009020 30 | 4096,49691278000,266757734400 31 | -------------------------------------------------------------------------------- /benchmark/results/v0.2.0/laptop/parBrandesProfile.csv: -------------------------------------------------------------------------------- 1 | nodes,measured,expected 2 | 32,6185000,6185000 3 | 38,8506000,9154292 4 | 45,9167000,13434257 5 | 53,12295000,19436517 6 | 64,11737000,29688000 7 | 76,15791000,43594621 8 | 90,17845000,63521894 9 | 107,24130000,93237734 10 | 128,24112000,138544000 11 | 152,56538000,202288297 12 | 181,51551000,296810480 13 | 215,58198000,432660337 14 | 256,94272000,633344000 15 | 304,198797000,920792441 16 | 362,134691000,1345544097 17 | 430,201351000,1954001993 18 | 512,274326000,2850048000 19 | 608,413039000,4129726766 20 | 724,549622000,6015385094 21 | 861,873133000,8731218275 22 | 1024,1174147000,12666880000 23 | -------------------------------------------------------------------------------- /benchmark/results/v0.2.0/laptop/parDijkstra.csv: -------------------------------------------------------------------------------- 1 | nodes,measured,expected 2 | 32,5087000,5087000 3 | 38,5350000,7529164 4 | 45,5610000,11049324 5 | 53,6304000,15986025 6 | 64,8061000,24417600 7 | 76,10912000,35855430 8 | 90,12225000,52245089 9 | 107,15298000,76685586 10 | 128,17540000,113948800 11 | 152,23196000,166376810 12 | 181,33021000,244118822 13 | 215,48381000,355851759 14 | 256,55307000,520908799 15 | 304,76909000,757327591 16 | 362,110464000,1106674668 17 | 430,160269000,1607115301 18 | 512,206001000,2344089600 19 | 608,294512000,3396591764 20 | 724,547817000,4947496196 21 | 861,646644000,7181197633 22 | 1024,1050027000,10418176000 23 | 1217,1620440000,15082010322 24 | 1448,2559968000,21873174872 25 | 1722,3667884000,31670962353 26 | 2048,6436990000,45839974400 27 | 2435,9204851000,66272122913 28 | 2896,17047481000,95825459838 29 | 3444,27214695000,138468536687 30 | 4096,40785923000,200028979200 31 | 4870,62209951000,288652528823 32 | 5792,447457735000,416632880755 33 | -------------------------------------------------------------------------------- /benchmark/results/v0.2.0/r3.8xlarge/brandes.csv: -------------------------------------------------------------------------------- 1 | nodes,measured,expected 2 | 32,3868315,3868315 3 | 38,5170966,5725413 4 | 45,5919128,8402254 5 | 53,8291791,12156277 6 | 64,11789908,18567912 7 | 76,14679815,27265598 8 | 90,22341414,39728811 9 | 107,26321067,58314135 10 | 128,44774966,86650256 11 | 152,55673883,126518166 12 | 181,89837151,185635640 13 | 215,119951717,270600884 14 | 256,168332323,396115455 15 | 304,247579831,575895750 16 | 362,347107295,841550269 17 | 430,519329953,1222101086 18 | 512,756236431,1782519552 19 | 608,1179661897,2582875343 20 | 724,1779758480,3762231914 21 | 861,2605047772,5460808831 22 | 1024,3917270322,7922309120 23 | 1217,6065170667,11468835612 24 | 1448,9419328342,16633051003 25 | 1722,17069060916,24083597157 26 | 2048,21409355313,34858160128 27 | 2435,33710162496,50395409307 28 | 2896,54079479467,72868697400 29 | 3444,85574758266,105295835953 30 | 4096,138888209368,152108335104 31 | 4870,206905893018,219500473173 32 | 5792,340841009246,316820763145 33 | 6888,498959858980,457029133115 34 | 8192,862352757988,659136118783 35 | 9741,1338768945303,949882870299 36 | 11585,2147520558673,1368915888141 37 | 13777,3496650859029,1971800939898 38 | 16384,5478707298013,2839355588608 -------------------------------------------------------------------------------- /benchmark/results/v0.2.0/r3.8xlarge/brandesLinear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwalend/Disentangle/4710aa0eed5e244c6a51904847ab8400a155183a/benchmark/results/v0.2.0/r3.8xlarge/brandesLinear.png -------------------------------------------------------------------------------- /benchmark/results/v0.2.0/r3.8xlarge/brandesLog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwalend/Disentangle/4710aa0eed5e244c6a51904847ab8400a155183a/benchmark/results/v0.2.0/r3.8xlarge/brandesLog.png -------------------------------------------------------------------------------- /benchmark/results/v0.2.0/r3.8xlarge/dijkstra.csv: -------------------------------------------------------------------------------- 1 | nodes,measured,expected 2 | 32,3459677,3459677 3 | 38,4266255,5120597 4 | 45,6026794,7514663 5 | 53,8600930,10872121 6 | 64,11444843,16606449 7 | 76,15097381,24385336 8 | 90,19774618,35531970 9 | 107,27361911,52153992 10 | 128,41921748,77496764 11 | 152,57368311,113153140 12 | 181,75938810,166025609 13 | 215,107613720,242015362 14 | 256,162060342,354270924 15 | 304,230663998,515059730 16 | 362,341599949,752651247 17 | 430,491096892,1093001738 18 | 512,729193227,1594219161 19 | 608,1088773263,2310027600 20 | 724,1613934797,3364800235 21 | 861,2446759062,4883944227 22 | 1024,3811335941,7085418496 23 | 1217,5707260042,10257299828 24 | 1448,8780516909,14875981919 25 | 1722,14592348076,21539473171 26 | 2048,20783022752,31175841382 27 | 2435,33869515420,45071778923 28 | 2896,49498462637,65171051586 29 | 3444,76809726568,94172677727 30 | 4096,113258077580,136040035123 31 | 4870,175736253995,196313055820 32 | 5792,290282846334,283352701984 33 | 6888,473080107170,408749851077 34 | 8192,754946494004,589506818867 35 | 9741,1206937724850,849539895036 36 | 11585,1984076106263,1224307434409 37 | 13777,3236717503113,1763505391971 38 | 16384,5431462104533,2539413988966 39 | -------------------------------------------------------------------------------- /benchmark/results/v0.2.0/r3.8xlarge/dijkstraLinear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwalend/Disentangle/4710aa0eed5e244c6a51904847ab8400a155183a/benchmark/results/v0.2.0/r3.8xlarge/dijkstraLinear.png -------------------------------------------------------------------------------- /benchmark/results/v0.2.0/r3.8xlarge/dijkstraLog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwalend/Disentangle/4710aa0eed5e244c6a51904847ab8400a155183a/benchmark/results/v0.2.0/r3.8xlarge/dijkstraLog.png -------------------------------------------------------------------------------- /benchmark/results/v0.2.0/r3.8xlarge/floydWarshall.csv: -------------------------------------------------------------------------------- 1 | nodes,measured,expected 2 | 32,5220229,5220229 3 | 38,5910973,8741589 4 | 45,9281679,14517009 5 | 53,15127009,23717408 6 | 64,24203369,41761832 7 | 76,38458510,69932716 8 | 90,59041786,116136076 9 | 107,103740346,195160064 10 | 128,184556692,334094656 11 | 152,285886697,559461729 12 | 181,490408091,944659604 13 | 215,802715973,1583270061 14 | 256,1317326397,2672757248 15 | 304,2792280443,4475693838 16 | 362,5131016571,7557276838 17 | 430,10999840848,12666160495 18 | 512,18408545062,21382057984 19 | 608,32604894025,35805550711 20 | 724,59111210470,60458214708 21 | 861,83092209637,101683169382 22 | 1024,163112294188,171056463872 23 | 1217,294267904637,287151675506 24 | 1448,433733148138,483665717667 25 | -------------------------------------------------------------------------------- /benchmark/results/v0.2.0/r3.8xlarge/parBrandes.csv: -------------------------------------------------------------------------------- 1 | nodes,measured,expected 2 | 32,12508882,12508882 3 | 38,14869934,18514140 4 | 45,14153566,27170177 5 | 53,15942680,39309475 6 | 64,18605527,60042633 7 | 76,20891559,88168144 8 | 90,23065562,128470151 9 | 107,27112269,188569088 10 | 128,35422189,280198956 11 | 152,63229248,409118908 12 | 181,55073265,600285735 13 | 215,67097113,875035910 14 | 256,74119773,1280909516 15 | 304,108335741,1862260953 16 | 362,138483089,2721301915 17 | 430,181828695,3951880414 18 | 512,236502899,5764092825 19 | 608,339485684,8352185095 20 | 724,531408056,12165843545 21 | 861,694000659,17658492986 22 | 1024,1013481240,25618190336 23 | 1217,1793746267,37086512178 24 | 1448,1769972169,53785917720 25 | 1722,2878356717,77878578909 26 | 2048,4176114805,112720037478 27 | 2435,5842668102,162962485830 28 | 2896,9068130497,235633845040 29 | 3444,15292448263,340492743489 30 | 4096,21392489342,491869254451 31 | 4870,34296086640,709793674474 32 | 5792,53442390554,1024496076802 33 | 6888,82039588253,1477884685373 34 | 8192,130677587161,2131433435955 35 | 9741,243826437208,3071614575957 36 | 11585,355697760845,4426632089859 37 | 13777,563253240096,6376167733153 38 | 16384,885186816848,9181559416422 39 | -------------------------------------------------------------------------------- /benchmark/results/v0.2.0/r3.8xlarge/parDijkstra.csv: -------------------------------------------------------------------------------- 1 | nodes,measured,expected 2 | 32,8982913,8982913 3 | 38,10075696,13295425 4 | 45,11967743,19511523 5 | 53,11193242,28229029 6 | 64,12342985,43117982 7 | 76,14107535,63315552 8 | 90,18625309,92257341 9 | 107,19006177,135415756 10 | 128,24745946,201217251 11 | 152,30695367,293797604 12 | 181,39561029,431078855 13 | 215,47833283,628383212 14 | 256,60842365,919850291 15 | 304,82510486,1337331995 16 | 362,111497381,1954228871 17 | 430,151989559,2837935312 18 | 512,226207205,4139326310 19 | 608,332660264,5997894302 20 | 724,431705785,8736569274 21 | 861,666734213,12680965909 22 | 1024,900122681,18397005824 23 | 1217,1526887142,26632668880 24 | 1448,1801867083,38624892256 25 | 1722,2498589203,55926380863 26 | 2048,3640759639,80946825625 27 | 2435,6148049719,117027071841 28 | 2896,8862920484,169214029667 29 | 3444,14730626555,244515592352 30 | 4096,21992314724,353222511820 31 | 4870,33566728012,509719000127 32 | 5792,54202305332,735713961228 33 | 6888,81879325157,1061302645012 34 | 8192,128351672331,1530630884556 35 | 9741,194533324453,2205796369759 36 | 11585,372975029677,3178865301168 37 | 13777,603309091540,4578871238878 38 | 16384,906714521231,6593486887321 39 | -------------------------------------------------------------------------------- /benchmark/results/v0.2.0/t2.micro/dijkstra.csv: -------------------------------------------------------------------------------- 1 | nodes,measured,expected 2 | 32,5510710,5510710 3 | 38,7502892,8156289 4 | 45,5559976,11969652 5 | 53,7004889,17317544 6 | 64,9715940,26451408 7 | 76,15206572,38841926 8 | 90,32468479,56596724 9 | 107,28258261,83072936 10 | 128,35095683,123439903 11 | 152,83675889,180234785 12 | 181,83374338,264452139 13 | 215,105084053,385491616 14 | 256,181458254,564296704 15 | 304,218845933,820407456 16 | 362,317950893,1198852597 17 | 430,412465393,1740976285 18 | 512,651851744,2539335168 19 | 608,1021345949,3679503086 20 | 724,1428040325,5359586546 21 | 861,2243627398,7779339023 22 | 1024,3487240417,11285934080 23 | 1217,5340947955,16338231788 24 | 1448,12136474465,23695050815 25 | -------------------------------------------------------------------------------- /benchmark/results/v0.2.0/t2.micro/parDijkstra.csv: -------------------------------------------------------------------------------- 1 | nodes,measured,expected 2 | 32,9376509,9376509 3 | 38,8226361,13877979 4 | 45,12909313,20366441 5 | 53,12564453,29465914 6 | 64,20161835,45007243 7 | 76,23832935,66089791 8 | 90,31380831,96299696 9 | 107,31564639,141349143 10 | 128,51296751,210033801 11 | 152,89620629,306670662 12 | 181,82516378,449967039 13 | 215,143842738,655916498 14 | 256,202653801,960154521 15 | 304,262573913,1395928636 16 | 362,359838424,2039855512 17 | 430,501844128,2962282502 18 | 512,745961378,4320695347 19 | 608,1149785776,6260698495 20 | 724,1504755642,9119371458 21 | 861,2296875484,13236596077 22 | 1024,3592626880,19203090432 23 | 1217,5132826908,27799607928 24 | 1448,13077198639,40317283477 25 | -------------------------------------------------------------------------------- /benchmark/src/html/plot.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Performance Graphs 6 | 7 | 8 | 9 | 13 | 32 | 33 | 34 |
35 |
36 | 40 |
41 |
42 | 46 | 47 | -------------------------------------------------------------------------------- /benchmark/src/js/queue.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var slice = [].slice; 3 | 4 | function queue(parallelism) { 5 | var q, 6 | tasks = [], 7 | started = 0, // number of tasks that have been started (and perhaps finished) 8 | active = 0, // number of tasks currently being executed (started but not finished) 9 | remaining = 0, // number of tasks not yet finished 10 | popping, // inside a synchronous task callback? 11 | error = null, 12 | await = noop, 13 | all; 14 | 15 | if (!parallelism) parallelism = Infinity; 16 | 17 | function pop() { 18 | while (popping = started < tasks.length && active < parallelism) { 19 | var i = started++, 20 | t = tasks[i], 21 | a = slice.call(t, 1); 22 | a.push(callback(i)); 23 | ++active; 24 | t[0].apply(null, a); 25 | } 26 | } 27 | 28 | function callback(i) { 29 | return function(e, r) { 30 | --active; 31 | if (error != null) return; 32 | if (e != null) { 33 | error = e; // ignore new tasks and squelch active callbacks 34 | started = remaining = NaN; // stop queued tasks from starting 35 | notify(); 36 | } else { 37 | tasks[i] = r; 38 | if (--remaining) popping || pop(); 39 | else notify(); 40 | } 41 | }; 42 | } 43 | 44 | function notify() { 45 | if (error != null) await(error); 46 | else if (all) await(error, tasks); 47 | else await.apply(null, [error].concat(tasks)); 48 | } 49 | 50 | return q = { 51 | defer: function() { 52 | if (!error) { 53 | tasks.push(arguments); 54 | ++remaining; 55 | pop(); 56 | } 57 | return q; 58 | }, 59 | await: function(f) { 60 | await = f; 61 | all = false; 62 | if (!remaining) notify(); 63 | return q; 64 | }, 65 | awaitAll: function(f) { 66 | await = f; 67 | all = true; 68 | if (!remaining) notify(); 69 | return q; 70 | } 71 | }; 72 | } 73 | 74 | function noop() {} 75 | 76 | queue.version = "1.0.7"; 77 | if (typeof define === "function" && define.amd) define(function() { return queue; }); 78 | else if (typeof module === "object" && module.exports) module.exports = queue; 79 | else this.queue = queue; 80 | })(); 81 | -------------------------------------------------------------------------------- /benchmark/src/scala/net/walend/disentangle/graph/semiring/benchmark/BrandesTiming.scala: -------------------------------------------------------------------------------- 1 | package net.walend.disentangle.graph.semiring.benchmark 2 | 3 | import net.walend.disentangle.graph.semiring.{Brandes, FewestNodes} 4 | 5 | /** 6 | * @author dwalend 7 | * @since v0.0.1 8 | */ 9 | object BrandesTiming extends Timeable { 10 | 11 | def measureTime(nodeCount:Int):Long = { 12 | import net.walend.disentangle.graph.DigraphFactory 13 | 14 | val support = FewestNodes 15 | 16 | val graph = DigraphFactory.createRandomNormalDigraph(nodeCount,16) 17 | 18 | val result = TimingStudy.timeFunction{Brandes.allLeastPathsAndBetweenness(graph.edges,Seq.from(graph.nodes),support,FewestNodes.convertEdgeToLabel)} 19 | 20 | result._2 21 | } 22 | 23 | def expectedTime(calibration:(Int,Long),nodeCount:Int):Long = { 24 | DijkstraTiming.expectedTime(calibration,nodeCount) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /benchmark/src/scala/net/walend/disentangle/graph/semiring/benchmark/DijkstraTiming.scala: -------------------------------------------------------------------------------- 1 | package net.walend.disentangle.graph.semiring.benchmark 2 | 3 | import net.walend.disentangle.graph.semiring.{Dijkstra, FewestNodes, AllPathsFirstSteps} 4 | 5 | /** 6 | * @author dwalend 7 | * @since v0.0.1 8 | */ 9 | object DijkstraTiming extends Timeable { 10 | 11 | def measureTime(nodeCount:Int):Long = { 12 | import net.walend.disentangle.graph.DigraphFactory 13 | 14 | val support = new AllPathsFirstSteps[Int,Int,Int](FewestNodes) 15 | // val support = FFewestNodes 16 | // val support = new OnePathFirstStep[Int,Int,Int](FFewestNodes) 17 | val graph = DigraphFactory.createRandomNormalDigraph(nodeCount,16) 18 | 19 | val result = TimingStudy.timeFunction{Dijkstra.allPairsLeastPaths(graph.edges, support, support.convertEdgeToLabelFunc[Boolean](FewestNodes.convertEdgeToLabel), Seq.from(graph.nodes))} 20 | 21 | result._2 22 | } 23 | 24 | def expectedTime(calibration:(Int,Long),nodeCount:Int):Long = { 25 | 26 | //O(|V|^2 ln|V|) 27 | def bigO(nodeCount:Int):Double = { 28 | Math.pow(nodeCount,2) * Math.log(nodeCount) 29 | } 30 | 31 | ((bigO(nodeCount)/bigO(calibration._1))*calibration._2).toLong 32 | } 33 | /* 34 | def expectedTimeSingleDijkstra(calibration:(Int,Long),nodeCount:Int):Long = { 35 | 36 | //O(|V| ln|V|) 37 | def bigO(nodeCount:Int):Double = { 38 | nodeCount * Math.log(nodeCount) 39 | } 40 | 41 | ((bigO(nodeCount)/bigO(calibration._1))*calibration._2).toLong 42 | } 43 | */ 44 | /* 45 | def timeScalaGraphConvertDijkstra(nodeCount:Int):Long = { 46 | 47 | import scalax.collection.Graph 48 | import scalax.collection.GraphEdge.DiEdge 49 | 50 | import net.walend.graph.semiring.{AllPathsFirstSteps, Dijkstra => DDijkstra, FewestNodes => FFewestNodes} 51 | import ConvertToLabelDigraph 52 | 53 | val support:AllPathsFirstSteps[Int,Int,Int] = new AllPathsFirstSteps(FFewestNodes) 54 | 55 | val graph:Graph[Int,DiEdge] = GraphFactory.createRandomNormalGraph(nodeCount,16) 56 | 57 | import scala.language.higherKinds 58 | def convertToLabel[E[X] <: EdgeLikeIn[X]](edge:E[Int]):(Int,Int,Option[FirstStepsTrait[Int,Int]]) = { 59 | (edge._1,edge._2,Some(support.FirstSteps(1,Set.empty[Int]))) 60 | } 61 | 62 | val result = timeFunction{ 63 | val labelGraphParts = ConvertToLabelDigraph.convert(graph,support)(convertToLabel) 64 | 65 | def labelForLabel[N,E,L](from:N,to:N,edge:E):L = edge.asInstanceOf[L] 66 | DDijkstra.allPairsShortestPaths(labelGraphParts._1,labelGraphParts._2,support,labelForLabel) 67 | } 68 | 69 | result._2 70 | } 71 | */ 72 | 73 | } 74 | -------------------------------------------------------------------------------- /benchmark/src/scala/net/walend/disentangle/graph/semiring/benchmark/FloydWarshallTiming.scala: -------------------------------------------------------------------------------- 1 | package net.walend.disentangle.graph.semiring.benchmark 2 | 3 | import net.walend.disentangle.graph.DigraphFactory 4 | import net.walend.disentangle.graph.semiring.{FewestNodes, FloydWarshall, AllPathsFirstSteps} 5 | 6 | /** 7 | * @author dwalend 8 | * @since v0.0.1 9 | */ 10 | object FloydWarshallTiming extends Timeable { 11 | 12 | def measureTime(nodeCount:Int):Long = { 13 | 14 | val support = new AllPathsFirstSteps[Int,Int,Int](FewestNodes) 15 | 16 | val graph = DigraphFactory.createRandomNormalDigraph(nodeCount,16) 17 | 18 | val result = TimingStudy.timeFunction{FloydWarshall.allPairsLeastPaths(graph.edges,Seq.from(graph.nodes),support,support.convertEdgeToLabelFunc[Boolean](FewestNodes.convertEdgeToLabel))} 19 | 20 | result._2 21 | } 22 | 23 | def expectedTime(calibration:(Int,Long),nodeCount:Int):Long = { 24 | (Math.pow(nodeCount.toDouble/calibration._1,3) * calibration._2).toLong 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /benchmark/src/scala/net/walend/disentangle/graph/semiring/benchmark/JungDijkstraTiming.scala: -------------------------------------------------------------------------------- 1 | package net.walend.disentangle.graph.semiring.benchmark 2 | 3 | import edu.uci.ics.jung.algorithms.shortestpath.DijkstraShortestPath 4 | import edu.uci.ics.jung.graph.DirectedSparseGraph 5 | 6 | import net.walend.disentangle.graph.DigraphFactory 7 | 8 | /** 9 | * @author dwalend 10 | * @since v0.0.1 11 | */ 12 | object JungDijkstraTiming extends Timeable { 13 | 14 | def measureTime(nodeCount: Int): Long = { 15 | 16 | val graph = DigraphFactory.createRandomNormalDigraph(nodeCount, 16) 17 | 18 | val jungGraph = new DirectedSparseGraph[Int, Any]() 19 | for (node <- graph.nodes) { 20 | jungGraph.addVertex(node) 21 | } 22 | 23 | var i = 0 24 | for (edge <- graph.edges) { 25 | jungGraph.addEdge(i, edge._1, edge._2) 26 | i = i + 1 27 | } 28 | 29 | val dijkstraShortestPath = new DijkstraShortestPath(jungGraph) 30 | val result = TimingStudy.timeFunction { 31 | 32 | import scala.jdk.CollectionConverters.CollectionHasAsScala 33 | for (node <- jungGraph.getVertices.asScala) { 34 | dijkstraShortestPath.getIncomingEdgeMap(node) 35 | } 36 | } 37 | 38 | result._2 39 | } 40 | 41 | def expectedTime(calibration:(Int,Long),nodeCount:Int):Long = { 42 | DijkstraTiming.expectedTime(calibration,nodeCount) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /benchmark/src/scala/net/walend/disentangle/graph/semiring/benchmark/ParBrandesTiming.scala: -------------------------------------------------------------------------------- 1 | package net.walend.disentangle.graph.semiring.benchmark 2 | 3 | import net.walend.disentangle.graph.semiring.Brandes 4 | import net.walend.disentangle.graph.semiring.FewestNodes 5 | 6 | import net.walend.disentangle.graph.semiring.par.ParBrandes 7 | 8 | /** 9 | * 10 | * 11 | * @author dwalend 12 | * @since v0.1.2 13 | */ 14 | 15 | object ParBrandesTiming extends Timeable { 16 | 17 | def measureTime(nodeCount:Int):Long = { 18 | import net.walend.disentangle.graph.DigraphFactory 19 | 20 | val support = FewestNodes 21 | 22 | val graph = DigraphFactory.createRandomNormalDigraph(nodeCount,16) 23 | 24 | val result = TimingStudy.timeFunction{ParBrandes.parAllLeastPathsAndBetweenness(Seq.from(graph.edges),Seq.from(graph.nodes),support,FewestNodes.convertEdgeToLabel)} 25 | 26 | result._2 27 | } 28 | 29 | def expectedTime(calibration:(Int,Long),nodeCount:Int):Long = { 30 | DijkstraTiming.expectedTime(calibration,nodeCount) 31 | } 32 | } 33 | 34 | 35 | -------------------------------------------------------------------------------- /benchmark/src/scala/net/walend/disentangle/graph/semiring/benchmark/ParDijkstraTiming.scala: -------------------------------------------------------------------------------- 1 | package net.walend.disentangle.graph.semiring.benchmark 2 | 3 | import net.walend.disentangle.graph.semiring.{AllPathsFirstSteps, Dijkstra, FewestNodes} 4 | 5 | import net.walend.disentangle.graph.semiring.par.ParDijkstra 6 | 7 | /** 8 | * @author dwalend 9 | * @since v0.1.2 10 | */ 11 | 12 | object ParDijkstraTiming extends Timeable { 13 | 14 | def measureTime(nodeCount:Int):Long = { 15 | import net.walend.disentangle.graph.DigraphFactory 16 | 17 | val support = new AllPathsFirstSteps[Int,Int,Int](FewestNodes) 18 | 19 | val graph = DigraphFactory.createRandomNormalDigraph(nodeCount,16) 20 | 21 | val result = TimingStudy.timeFunction{ParDijkstra.parAllPairsLeastPaths(graph.edges, support, support.convertEdgeToLabelFunc[Boolean](FewestNodes.convertEdgeToLabel), Seq.from(graph.nodes))} 22 | 23 | result._2 24 | } 25 | 26 | override def expectedTime(calibration: (Int, Long), nodeCount: Int): Long = DijkstraTiming.expectedTime(calibration,nodeCount) 27 | } 28 | -------------------------------------------------------------------------------- /benchmark/src/scala/net/walend/disentangle/graph/semiring/benchmark/TimingStudies.scala: -------------------------------------------------------------------------------- 1 | package net.walend.disentangle.graph.semiring.benchmark 2 | 3 | import java.io.File 4 | 5 | import scopt.OptionParser 6 | 7 | /** 8 | * @author dwalend 9 | * @since v0.1.2 10 | */ 11 | object TimingStudies { 12 | 13 | val studies: Map[String, Timeable ] = Map( 14 | "dijkstra"->DijkstraTiming, 15 | "jungDijkstra" -> JungDijkstraTiming, 16 | "floydWarshall" -> FloydWarshallTiming, 17 | "brandes" -> BrandesTiming, 18 | "parDijkstra" -> ParDijkstraTiming, 19 | // "parBrandes" -> ParBrandesTiming 20 | ) 21 | 22 | case class ArgsConfig(algorithm:Timeable = DijkstraTiming,lowExponent:Int = 5,highExponent:Int = 7, out:Option[File] = None) { 23 | def validate() = { 24 | require(lowExponent <= highExponent, s"--highExponent $highExponent must be greater than or equal to --lowExponent $lowExponent") 25 | } 26 | } 27 | 28 | def main(args:Array[String]): Unit = { 29 | 30 | val argsParser = new OptionParser[ArgsConfig]("Disentangler Timing Studies"){ 31 | head("sbt \"benchmark/run ...\"") 32 | 33 | opt[String]('a',"algorithm") action {(x,c) => 34 | c.copy(algorithm = studies(x))} validate { x => 35 | if(studies.contains(x)) success else failure(s"--algorithm must be one of ${studies.keySet.mkString(", ")}") 36 | } text (s"algorithm determines what algorithm to measure, one of ${studies.keySet.mkString(", ")}") 37 | 38 | opt[Int]('l', "lowExponent") action { (x, c) => 39 | c.copy(lowExponent = x) } validate { x => 40 | if (x >= 5) success else failure("--lowExponent must be >= 5 for 32 nodes in the test.") 41 | } text ("lowExponent defines the lower number of nodes in the study via 2^lowExponent.") 42 | 43 | opt[Int]('h', "highExponent") action { (x, c) => 44 | c.copy(highExponent = x) 45 | } text ("highExponent defines the maximum number of nodes in the study via 2^highExponent.") 46 | 47 | opt[File]('o', "out") valueName("") action { (x, c) => 48 | c.copy(out = Some(x)) } text("out is the path to the output file") 49 | 50 | 51 | } 52 | 53 | val argsConfig: Option[ArgsConfig] = argsParser.parse(args,ArgsConfig()) 54 | 55 | argsConfig.fold(()){ argsConfig => 56 | // val fileType = argsConfig.out.fold("csv")(file => file.getName.split('.').lastOption.fold("csv")(end => end)) 57 | argsConfig.validate() 58 | 59 | val timingStudy = TimingStudy(argsConfig.algorithm.measureTime, 60 | argsConfig.algorithm.expectedTime, 61 | argsConfig.lowExponent, 62 | argsConfig.highExponent, 63 | argsConfig.out 64 | ) 65 | 66 | timingStudy.study() 67 | 68 | // val output = argsConfig.out.fold(System.out)(file => new PrintStream(new FileOutputStream(file))) 69 | /* 70 | val formatOutput:(Seq[(Int, Long, Long, Double)] => String) = fileType match { 71 | case s if s == "csv" => formatOutputCsv 72 | case s if s == "json" => formatOutputJson 73 | case _ => formatOutputCsv 74 | } 75 | */ 76 | // output.println(formatOutput(results)) 77 | 78 | // output.flush() 79 | } 80 | } 81 | 82 | /* 83 | def formatOutputCsv(results:Seq[(Int, Long, Long, Double)]):String = { 84 | 85 | //values are in nanoseconds 86 | val header:String = "nodes,measured,expected" 87 | val columns:Seq[String] = results.map(x => s"${x._1},${x._2},${x._3}") 88 | 89 | val lines:Seq[String] = header +: columns 90 | lines.mkString("\n") 91 | } 92 | */ 93 | /* 94 | def formatOutputJson(results:Seq[(Int, Long, Long, Double)]):String = { 95 | 96 | val nodes = ("nodes",results.map(x => x._1)) 97 | //values are in nanoseconds 98 | val measured = ("mesaured",results.map(x => x._2)) 99 | val expected = ("expected",results.map(x => x._3)) 100 | 101 | import upickle.default._ 102 | 103 | write((nodes,measured,expected)) 104 | } 105 | */ 106 | /* 107 | def expectedTimeSingleDijkstra(calibration:(Int,Long),nodeCount:Int):Long = { 108 | 109 | //O(|V| ln|V|) 110 | def bigO(nodeCount:Int):Double = { 111 | nodeCount * Math.log(nodeCount) 112 | } 113 | 114 | ((bigO(nodeCount)/bigO(calibration._1))*calibration._2).toLong 115 | } 116 | 117 | 118 | */ 119 | } 120 | -------------------------------------------------------------------------------- /benchmark/src/scala/net/walend/disentangle/graph/semiring/benchmark/TimingStudy.scala: -------------------------------------------------------------------------------- 1 | package net.walend.disentangle.graph.semiring.benchmark 2 | 3 | import java.io.{File, FileOutputStream, PrintStream} 4 | 5 | /** 6 | * 7 | * 8 | * @author dwalend 9 | * @since v0.1.2 10 | */ 11 | object TimingStudy { 12 | 13 | def timeFunction[T](body: => T):(T,Long) = { 14 | val startTime:Long = System.nanoTime() 15 | val result = body 16 | val endTime:Long = System.nanoTime() 17 | (result,endTime-startTime) 18 | } 19 | 20 | def timeToStdOut[T](name:String)(body: => T) = { 21 | val (result,time) = timeFunction(body) 22 | 23 | println(s"$name,${time/1000000000}") 24 | 25 | result 26 | } 27 | } 28 | 29 | /** 30 | * @param timeF Runs the timing study. 31 | * @param expectedF Predicted time based on early, calibration results 32 | * @param minExponent Use 2^^minExponent nodes as the smallest graph in the study 33 | * @param maxExponent Use 2^^maxExponent nodes as the largest graph in the study 34 | * @param outFile File to write output, will use std out for None 35 | */ 36 | case class TimingStudy(timeF:Int => Long,expectedF:((Int,Long),Int) => Long,minExponent:Int,maxExponent:Int,outFile:Option[File]) { 37 | 38 | def study():Seq[(Int,Long,Long)] = { 39 | 40 | warmUp(64,{timeF(32)}) 41 | warmUp(64,{timeF(64)}) 42 | warmUp(64,{timeF(128)}) 43 | 44 | writeHeader() 45 | 46 | val nodeCounts: Seq[Int] = nodeCountSeq(minExponent,maxExponent) 47 | 48 | val headNodeCount = nodeCounts.head 49 | val calibration: (Int, Long) = (headNodeCount,timeF(headNodeCount)) 50 | val headExpected = expectedF(calibration,headNodeCount) 51 | writeResult(calibration._1,calibration._2,headExpected) 52 | 53 | nodeCounts.tail.map(x => writeResult(x,timeF(x),expectedF(calibration,x))) 54 | } 55 | 56 | def nodeCountSeq(minExponent:Int,maxExponent:Int):Seq[Int] = { 57 | val exponents: Seq[Double] = (0 to (maxExponent - minExponent) * 4).map(_.toDouble).map(_ / 4 + minExponent) 58 | 59 | exponents.map(x => Math.pow(2, x).toInt) 60 | } 61 | 62 | def warmUp[T](number:Int,body: => T) = { 63 | for(i <- 0 until number) body 64 | } 65 | 66 | def writeHeader():Unit = { 67 | val header:String = "nodes,measured,expected" 68 | withOut(false) { out => 69 | out.println(header) 70 | } 71 | } 72 | 73 | def writeResult(nodeCount:Int,result:Long,expected:Long):(Int,Long,Long) = { 74 | withOut(true) { out => 75 | out.println(s"${nodeCount},${result},${expected}") 76 | } 77 | (nodeCount,result,expected) 78 | } 79 | 80 | def withOut[T](append:Boolean)(block:(PrintStream => T)): T = { 81 | val out = outFile.fold(System.out)(file => new PrintStream(new FileOutputStream(file,append))) 82 | val result = block(out) 83 | if(out != System.out) out.close() 84 | result 85 | } 86 | } 87 | 88 | trait Timeable { 89 | 90 | def measureTime(nodeCount:Int):Long 91 | 92 | def expectedTime(calibration:(Int,Long),nodeCount:Int):Long 93 | } -------------------------------------------------------------------------------- /benchmark/test/src/net/walend/disentangle/graph/semiring/benchmark/TimingStudiesTest.scala: -------------------------------------------------------------------------------- 1 | package net.walend.disentangle.graph.semiring.benchmark 2 | 3 | import net.walend.disentangle.graph.semiring.{Dijkstra, FloydWarshall, Brandes, FewestNodes, AllPathsFirstSteps} 4 | 5 | /** 6 | * @author dwalend 7 | * @since v0.0.1 8 | */ 9 | object TimingStudiesTest { 10 | 11 | def main (args:Array[String]):Unit = { 12 | 13 | //Time Brandes' algorithm with AllShortestPaths 14 | val brandesResults = study(11,timeBrandes,expectedTimeDijkstra) 15 | brandesResults.foreach(x => println(x)) 16 | } 17 | 18 | def timeFloyd(nodeCount:Int):Long = { 19 | import net.walend.disentangle.graph.DigraphFactory 20 | 21 | val support = new AllPathsFirstSteps[Int,Int,Int](FewestNodes) 22 | 23 | val graph = DigraphFactory.createRandomNormalDigraph(nodeCount,16) 24 | 25 | val result = timeFunction{FloydWarshall.allPairsLeastPaths(graph.edges,Seq.from(graph.nodes),support,support.convertEdgeToLabelFunc[Boolean](FewestNodes.convertEdgeToLabel))} 26 | 27 | result._2 28 | } 29 | 30 | def timeDijkstra(nodeCount:Int):Long = { 31 | import net.walend.disentangle.graph.DigraphFactory 32 | 33 | val support = new AllPathsFirstSteps[Int,Int,Int](FewestNodes) 34 | // val support = FFewestNodes 35 | 36 | val graph = DigraphFactory.createRandomNormalDigraph(nodeCount,16) 37 | 38 | val result = timeFunction{Dijkstra.allPairsLeastPaths(graph.edges, support, support.convertEdgeToLabelFunc[Boolean](FewestNodes.convertEdgeToLabel), Seq.from(graph.nodes))} 39 | /* 40 | val result = timeFunction{ 41 | val initNode = initialGraph.innerNodes.head 42 | DDijkstra.dijkstraSingleSource(initialGraph, support)(initNode) 43 | } 44 | */ 45 | 46 | // println(s"$nodeCount ${result._2}") 47 | 48 | result._2 49 | } 50 | 51 | def timeBrandes(nodeCount:Int):Long = { 52 | import net.walend.disentangle.graph.DigraphFactory 53 | 54 | val support = FewestNodes 55 | 56 | val graph = DigraphFactory.createRandomNormalDigraph(nodeCount,16) 57 | 58 | val result = timeFunction{Brandes.allLeastPathsAndBetweenness(graph.edges,Seq.from(graph.nodes),support,FewestNodes.convertEdgeToLabel)} 59 | 60 | /* 61 | val result = timeFunction{ 62 | val initNode = initialGraph.innerNodes.head 63 | DDijkstra.dijkstraSingleSource(initialGraph, support)(initNode) 64 | } 65 | */ 66 | 67 | // println(s"$nodeCount ${result._2}") 68 | 69 | result._2 70 | } 71 | /* 72 | def timeJungDijkstra(nodeCount:Int):Long = { 73 | 74 | val graph = GraphFactory.createRandomNormalGraph(nodeCount,16) 75 | 76 | import scala.collection.JavaConversions._ 77 | 78 | import edu.uci.ics.jung.algorithms.shortestpath.DijkstraShortestPath 79 | import edu.uci.ics.jung.graph.DirectedSparseGraph 80 | 81 | val jungGraph = new DirectedSparseGraph[Int,Any]() 82 | for(node <- graph.nodes) { 83 | jungGraph.addVertex(node) 84 | } 85 | 86 | var i=0 87 | for(edge <- graph.edges) { 88 | jungGraph.addEdge(i,edge._1,edge._2) 89 | i = i + 1 90 | } 91 | 92 | val dijkstraShortestPath = new DijkstraShortestPath(jungGraph) 93 | val result = timeFunction{for(node <- jungGraph.getVertices){ 94 | dijkstraShortestPath.getIncomingEdgeMap(node) 95 | }} 96 | 97 | result._2 98 | } 99 | */ 100 | def expectedTimeDijkstra(calibration:(Int,Long),nodeCount:Int):Long = { 101 | 102 | //O(|V|^2 ln|V|) 103 | def bigO(nodeCount:Int):Double = { 104 | Math.pow(nodeCount,2) * Math.log(nodeCount) 105 | } 106 | 107 | ((bigO(nodeCount)/bigO(calibration._1))*calibration._2).toLong 108 | } 109 | 110 | def expectedTimeSingleDijkstra(calibration:(Int,Long),nodeCount:Int):Long = { 111 | 112 | //O(|V| ln|V|) 113 | def bigO(nodeCount:Int):Double = { 114 | nodeCount * Math.log(nodeCount) 115 | } 116 | 117 | ((bigO(nodeCount)/bigO(calibration._1))*calibration._2).toLong 118 | } 119 | /* 120 | def timeScalaGraphConvertDijkstra(nodeCount:Int):Long = { 121 | 122 | import scalax.collection.Graph 123 | import scalax.collection.GraphEdge.DiEdge 124 | 125 | val support:AllPathsFirstSteps[Int,Int,Int] = new AllPathsFirstSteps(FewestNodes) 126 | 127 | val graph:Graph[Int,DiEdge] = GraphFactory.createRandomNormalGraph(nodeCount,16) 128 | 129 | import scala.language.higherKinds 130 | def convertToLabel[E[X] <: EdgeLikeIn[X]](edge:E[Int]):(Int,Int,Option[FirstStepsTrait[Int,Int]]) = { 131 | (edge._1,edge._2,Some(support.FirstSteps(1,Set.empty[Int]))) 132 | } 133 | 134 | val result = timeFunction{ 135 | val labelGraphParts = ConvertToLabelDigraph.convert(graph,support)(convertToLabel) 136 | 137 | def labelForLabel[N,E,L](from:N,to:N,edge:E):L = edge.asInstanceOf[L] 138 | Dijkstra.allPairsLeastPaths(labelGraphParts._1, support, labelForLabel, labelGraphParts._2) 139 | } 140 | 141 | result._2 142 | } 143 | */ 144 | def expectedTimeFloyd(calibration:(Int,Long),nodeCount:Int):Long = { 145 | (Math.pow(nodeCount.toDouble/calibration._1,3) * calibration._2).toLong 146 | } 147 | 148 | def study(maxExponent:Int,timeF:Int => Long,expectedF:((Int,Long),Int) => Long):Seq[(Int,Long,Long,Double)] = { 149 | 150 | warmUp(16,{timeF(32)}) 151 | warmUp(16,{timeF(64)}) 152 | warmUp(16,{timeF(128)}) 153 | val nodeCountAndTime:Seq[(Int,Long)] = nodeCountsFrom32(maxExponent).map(x=>(x,timeF(x))) 154 | 155 | val calibration = nodeCountAndTime.head 156 | val expected = nodeCountAndTime.map(x => x._1 -> expectedF(calibration,x._1)).toMap 157 | val ratio = nodeCountAndTime.map(x => x._1 -> x._2.toDouble/expected(x._1)).toMap 158 | 159 | nodeCountAndTime.map(x => (x._1,x._2,expected(x._1),ratio(x._1))) 160 | } 161 | 162 | def nodeCountsFrom32(exponent:Int):Seq[Int] = { 163 | val exponents: Seq[Double] = (0 to (exponent-5) * 4).map(_.toDouble).map(_/4 + 5.0) 164 | 165 | exponents.map(x => Math.pow(2,x).toInt) 166 | } 167 | 168 | def warmUp[T](number:Int,body: => T): Unit = { 169 | for(_ <- 0 until number) body 170 | } 171 | 172 | def timeFunction[T](body: => T):(T,Long) = { 173 | val startTime:Long = System.nanoTime() 174 | val result = body 175 | val endTime:Long = System.nanoTime() 176 | (result,endTime-startTime) 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | import sbt.Keys._ 2 | 3 | name := "Disentangle" 4 | 5 | //organization in ThisBuild := "net.walend.disentangle" 6 | organization in ThisBuild := "net.walend.disentangle" 7 | 8 | // Project version. Only release version (w/o SNAPSHOT suffix) can be promoted. 9 | version := "0.2.3-SNAPSHOT" 10 | 11 | isSnapshot := true 12 | 13 | scalaVersion in ThisBuild := "2.12.6" 14 | 15 | scalacOptions in ThisBuild ++= Seq("-unchecked", "-deprecation","-feature") 16 | 17 | sbtVersion := "1.1.6" 18 | 19 | lazy val root: Project = Project( 20 | id = "root", 21 | base = file(".") 22 | ).aggregate(graphCross,toScalaGraph,benchmark,examples) 23 | .settings( 24 | packagedArtifacts := Map.empty // prevent publishing superproject artifacts 25 | ) 26 | 27 | //.settings(unidocSettings: _*) 28 | 29 | lazy val graphCross = project.in(file("graph")). 30 | aggregate(graphJS, graphJVM). 31 | settings( 32 | publish := {}, 33 | publishLocal := {} 34 | ) 35 | 36 | lazy val graph = crossProject.in(file("graph")). 37 | settings( 38 | name := "graph" 39 | ). 40 | jvmSettings( 41 | libraryDependencies ++= Seq( 42 | "org.scalatest" %% "scalatest" % "3.0.5" % "test", 43 | "net.sf.jung" % "jung-graph-impl" % "2.0.1" % "test", //for timing comparisons 44 | "net.sf.jung" % "jung-algorithms" % "2.0.1" % "test" //for timing comparisons 45 | ) 46 | ). 47 | jsSettings( 48 | // Add JS-specific settings here 49 | ) 50 | 51 | lazy val graphJVM = graph.jvm 52 | lazy val graphJS = graph.js 53 | 54 | lazy val toScalaGraph = project.dependsOn(graphJVM) 55 | lazy val benchmark = project.dependsOn(graphJVM,toScalaGraph % "test->test;compile->compile") //todo remove dependency on toScalaGraph 56 | 57 | lazy val examples = project.dependsOn(graphJVM % "test->test;compile->compile").enablePlugins(TutPlugin) 58 | 59 | //git.remoteRepo := "git@github.com:dwalend/disentangle.git" 60 | 61 | //tod why didn't this work? 62 | //credentials += Credentials(Path.userHome / ".sbt" / "1.0" / "sonatype.sbt") 63 | 64 | credentials += Credentials("Sonatype Nexus Repository Manager", 65 | "oss.sonatype.org", 66 | "dwalend", 67 | "changeme") 68 | 69 | publishMavenStyle := true 70 | 71 | publishTo in ThisBuild := { 72 | val nexus = "https://oss.sonatype.org/" 73 | if (isSnapshot.value) 74 | Some("snapshots" at nexus + "content/repositories/snapshots") 75 | else 76 | Some("releases" at nexus + "service/local/staging/deploy/maven2") 77 | } 78 | 79 | publishArtifact in Test := false 80 | 81 | //pomIncludeRepository := { _ => false } //only needed if there are dependencies outside Sonatype Nexus 82 | 83 | // Your profile name of the sonatype account. The default is the same with the organization value 84 | sonatypeProfileName in ThisBuild := "net.walend" 85 | 86 | pomIncludeRepository := { _ => false } 87 | 88 | homepage := Some(url("https://github.com/dwalend/disentangler")) 89 | 90 | // To sync with Maven central, you need to supply the following information: 91 | pomExtra in Global := { 92 | https://github.com/dwalend/Disentangle 93 | 94 | 95 | MIT License 96 | http://www.opensource.org/licenses/mit-license.php 97 | 98 | 99 | 100 | scm:git:github.com:dwalend/Disentangle.git 101 | github.com/dwalend/Disentangle.git 102 | scm:git:git@github.com:dwalend/Disentangle.git 103 | 104 | 105 | 106 | dwalend 107 | David Walend 108 | https://github.com/dwalend 109 | 110 | 111 | } -------------------------------------------------------------------------------- /build.sc: -------------------------------------------------------------------------------- 1 | import mill._ 2 | import scalalib._ 3 | import mill.define.{Command, Target} 4 | import mill.scalajslib.ScalaJSModule 5 | import publish._ 6 | 7 | object Shared { 8 | val version = "0.3.0" 9 | val scalacOptions = Seq("-deprecation") 10 | val scalaJSVersion = "1.13.0" //todo see javascript work 11 | val scalaVersion = "3.2.2" 12 | val javaVersion = "17.0.6" 13 | } 14 | 15 | object Graph extends ScalaJSModule with PublishModule { 16 | override def artifactName: T[String] = "Disentangle-Graph" 17 | 18 | override def publishVersion: T[String] = Shared.version 19 | 20 | override def scalaJSVersion: T[String] = Shared.scalaJSVersion 21 | 22 | override def scalaVersion: T[String] = Shared.scalaVersion 23 | 24 | def javaVersion = Shared.javaVersion 25 | 26 | override def scalacOptions: Target[Seq[String]] = Shared.scalacOptions 27 | 28 | object test extends Tests with TestModule.Munit { 29 | override def ivyDeps = Agg( 30 | ivy"org.scalameta::munit::0.7.29" 31 | ) 32 | } 33 | 34 | def millw(): Command[PathRef] = T.command { 35 | val target = mill.modules.Util.download("https://raw.githubusercontent.com/lefou/millw/main/millw") 36 | val millw = millSourcePath / "millw" 37 | os.copy.over(target.path, millw) 38 | os.perms.set(millw, os.perms(millw) + java.nio.file.attribute.PosixFilePermission.OWNER_EXECUTE) 39 | target 40 | } 41 | 42 | override def pomSettings: T[PomSettings] = PomSettings( 43 | description = "Disentangle Graph", 44 | organization = "net.walend.disentangle", 45 | url = "https://github.com/dwalend/disentangle", 46 | licenses = Seq(License.MIT), 47 | versionControl = VersionControl.github("dwalend", "disentangle"), 48 | developers = Seq( 49 | Developer("dwalend", "David Walend", "https://github.com/dwalend") 50 | ) 51 | ) 52 | /* 53 | to publish - follow https://dev.to/awwsmm/publish-your-scala-project-to-maven-in-5-minutes-with-sonatype-326l 54 | 55 | Then 56 | 57 | ./millw mill.scalalib.PublishModule/publishAll Graph.publishArtifacts dwalend:MyPassword --gpgArgs --passphrase=MyPassphrase,--batch,--yes,-a,-b --release true 58 | */ 59 | } 60 | 61 | //todo move the rest of the tests to Munit 62 | 63 | object GraphJvm extends ScalaModule { 64 | override def artifactName: T[String] = "Disentangle-Graph-JVM" 65 | 66 | override def scalaVersion: T[String] = Shared.scalaVersion 67 | 68 | def javaVersion = Shared.javaVersion 69 | 70 | override def scalacOptions = Shared.scalacOptions 71 | 72 | override def ivyDeps = Agg( 73 | ivy"org.scala-lang.modules::scala-parallel-collections:1.0.4" 74 | ) 75 | 76 | override def moduleDeps: Seq[JavaModule] = super.moduleDeps ++ Seq(Graph) 77 | 78 | object test extends Tests with TestModule.Munit { 79 | override def moduleDeps: Seq[JavaModule] = super.moduleDeps ++ Seq(Graph.test) 80 | 81 | override def ivyDeps = Agg( 82 | ivy"org.scalameta::munit::0.7.29", 83 | ivy"net.sf.jung:jung-graph-impl:2.1.1", 84 | ivy"net.sf.jung:jung-algorithms:2.1.1" 85 | ) 86 | } 87 | } 88 | 89 | object Examples extends ScalaModule { 90 | override def artifactName: T[String] = "Disentangle-Examples" 91 | 92 | def scalaJSVersion = Shared.scalaJSVersion 93 | override def scalaVersion: T[String] = Shared.scalaVersion 94 | def javaVersion = Shared.javaVersion 95 | 96 | override def scalacOptions: Target[Seq[String]] = Shared.scalacOptions 97 | 98 | override def moduleDeps: Seq[JavaModule] = super.moduleDeps ++ Seq(Graph,GraphJvm) 99 | 100 | object test extends Tests with TestModule.Munit { 101 | override def moduleDeps: Seq[JavaModule] = super.moduleDeps ++ Seq(Graph.test) 102 | override def ivyDeps = Agg( 103 | ivy"org.scalameta::munit::0.7.29" 104 | ) 105 | } 106 | } 107 | 108 | object Benchmark extends ScalaModule { 109 | override def artifactName: T[String] = "Disentangle-Benchmark" 110 | 111 | def scalaJSVersion = Shared.scalaJSVersion 112 | override def scalaVersion: T[String] = Shared.scalaVersion 113 | def javaVersion = Shared.javaVersion 114 | 115 | override def scalacOptions = Shared.scalacOptions 116 | 117 | override def moduleDeps: Seq[JavaModule] = super.moduleDeps ++ Seq(Graph,GraphJvm) 118 | 119 | override def ivyDeps = Agg( 120 | ivy"org.scalatest::scalatest:3.2.15", 121 | ivy"net.sf.jung:jung-graph-impl:2.1.1", 122 | ivy"net.sf.jung:jung-algorithms:2.1.1", 123 | ivy"com.github.scopt::scopt:4.1.0" 124 | ) 125 | 126 | object test extends Tests with TestModule.ScalaTest { 127 | 128 | override def moduleDeps: Seq[JavaModule] = super.moduleDeps ++ Seq(Graph.test) 129 | 130 | override def ivyDeps = Agg( 131 | ivy"org.scalatest::scalatest:3.2.15" 132 | ) 133 | } 134 | } 135 | 136 | object Experiments extends ScalaModule { 137 | override def artifactName: T[String] = "Disentangle-Experiments" 138 | 139 | def scalaJSVersion = Shared.scalaJSVersion 140 | 141 | override def scalaVersion: T[String] = Shared.scalaVersion 142 | 143 | def javaVersion = Shared.javaVersion 144 | 145 | override def scalacOptions: Target[Seq[String]] = Shared.scalacOptions 146 | 147 | override def moduleDeps: Seq[JavaModule] = super.moduleDeps ++ Seq(Graph) 148 | 149 | object test extends Tests with TestModule.Munit { 150 | override def moduleDeps: Seq[JavaModule] = super.moduleDeps ++ Seq(Graph.test) 151 | 152 | override def ivyDeps = Agg( 153 | ivy"org.scalameta::munit::0.7.29" 154 | ) 155 | } 156 | } -------------------------------------------------------------------------------- /millw: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # This is a wrapper script, that automatically download mill from GitHub release pages 4 | # You can give the required mill version with --mill-version parameter 5 | # If no version is given, it falls back to the value of DEFAULT_MILL_VERSION 6 | # 7 | # Project page: https://github.com/lefou/millw 8 | # Script Version: 0.3.8 9 | # 10 | # If you want to improve this script, please also contribute your changes back! 11 | # 12 | # Licensed under the Apache License, Version 2.0 13 | 14 | 15 | DEFAULT_MILL_VERSION=0.10.2 16 | 17 | set -e 18 | 19 | MILL_REPO_URL="https://github.com/com-lihaoyi/mill" 20 | 21 | # Explicit commandline argument takes precedence over all other methods 22 | if [ "x$1" = "x--mill-version" ] ; then 23 | shift 24 | if [ "x$1" != "x" ] ; then 25 | MILL_VERSION="$1" 26 | shift 27 | else 28 | echo "You specified --mill-version without a version." 1>&2 29 | echo "Please provide a version that matches one provided on" 1>&2 30 | echo "${MILL_REPO_URL}/releases" 1>&2 31 | false 32 | fi 33 | fi 34 | 35 | # Please note, that if a MILL_VERSION is already set in the environment, 36 | # We reuse it's value and skip searching for a value. 37 | 38 | # If not already set, read .mill-version file 39 | if [ "x${MILL_VERSION}" = "x" ] ; then 40 | if [ -f ".mill-version" ] ; then 41 | MILL_VERSION="$(head -n 1 .mill-version 2> /dev/null)" 42 | fi 43 | fi 44 | 45 | if [ "x${XDG_CACHE_HOME}" != "x" ] ; then 46 | MILL_DOWNLOAD_PATH="${XDG_CACHE_HOME}/mill/download" 47 | else 48 | MILL_DOWNLOAD_PATH="${HOME}/.cache/mill/download" 49 | fi 50 | 51 | # If not already set, try to fetch newest from Github 52 | if [ "x${MILL_VERSION}" = "x" ] ; then 53 | # TODO: try to load latest version from release page 54 | echo "No mill version specified." 1>&2 55 | echo "You should provide a version via '.mill-version' file or --mill-version option." 1>&2 56 | 57 | mkdir -p "${MILL_DOWNLOAD_PATH}" 58 | LANG=C touch -d '1 hour ago' "${MILL_DOWNLOAD_PATH}/.expire_latest" 2>/dev/null || ( 59 | # we might be on OSX or BSD which don't have -d option for touch 60 | # but probably a -A [-][[hh]mm]SS 61 | touch "${MILL_DOWNLOAD_PATH}/.expire_latest"; touch -A -010000 "${MILL_DOWNLOAD_PATH}/.expire_latest" 62 | ) || ( 63 | # in case we still failed, we retry the first touch command with the intention 64 | # to show the (previously suppressed) error message 65 | LANG=C touch -d '1 hour ago' "${MILL_DOWNLOAD_PATH}/.expire_latest" 66 | ) 67 | 68 | if [ "${MILL_DOWNLOAD_PATH}/.latest" -nt "${MILL_DOWNLOAD_PATH}/.expire_latest" ] ; then 69 | # we know a current latest version 70 | MILL_VERSION="$(head -n 1 ${MILL_DOWNLOAD_PATH}/.latest 2> /dev/null)" 71 | fi 72 | 73 | if [ "x${MILL_VERSION}" = "x" ] ; then 74 | # we don't know a current latest version 75 | echo "Retrieving latest mill version ..." 1>&2 76 | LANG=C curl -s -i -f -I ${MILL_REPO_URL}/releases/latest 2> /dev/null | grep --ignore-case Location: | sed s'/^.*tag\///' | tr -d '\r\n' > "${MILL_DOWNLOAD_PATH}/.latest" 77 | MILL_VERSION="$(head -n 1 ${MILL_DOWNLOAD_PATH}/.latest 2> /dev/null)" 78 | fi 79 | 80 | if [ "x${MILL_VERSION}" = "x" ] ; then 81 | # Last resort 82 | MILL_VERSION="${DEFAULT_MILL_VERSION}" 83 | echo "Falling back to hardcoded mill version ${MILL_VERSION}" 1>&2 84 | else 85 | echo "Using mill version ${MILL_VERSION}" 1>&2 86 | fi 87 | fi 88 | 89 | MILL="${MILL_DOWNLOAD_PATH}/${MILL_VERSION}" 90 | 91 | # If not already downloaded, download it 92 | if [ ! -s "${MILL}" ] ; then 93 | 94 | # support old non-XDG download dir 95 | MILL_OLD_DOWNLOAD_PATH="${HOME}/.mill/download" 96 | OLD_MILL="${MILL_OLD_DOWNLOAD_PATH}/${MILL_VERSION}" 97 | if [ -x "${OLD_MILL}" ] ; then 98 | MILL="${OLD_MILL}" 99 | else 100 | VERSION_PREFIX="$(echo -n $MILL_VERSION | cut -b -4)" 101 | case $VERSION_PREFIX in 102 | 0.0. | 0.1. | 0.2. | 0.3. | 0.4. ) 103 | DOWNLOAD_SUFFIX="" 104 | ;; 105 | *) 106 | DOWNLOAD_SUFFIX="-assembly" 107 | ;; 108 | esac 109 | unset VERSION_PREFIX 110 | 111 | DOWNLOAD_FILE=$(mktemp mill.XXXX) 112 | # TODO: handle command not found 113 | echo "Downloading mill ${MILL_VERSION} from ${MILL_REPO_URL}/releases ..." 1>&2 114 | MILL_VERSION_TAG=$(echo $MILL_VERSION | sed 's/\([^-]+\)\(-M[0-9]+\)?\(-.*\)?/\1\2/') 115 | curl -L -o "${DOWNLOAD_FILE}" "${MILL_REPO_URL}/releases/download/${MILL_VERSION_TAG}/${MILL_VERSION}${DOWNLOAD_SUFFIX}" 116 | chmod +x "${DOWNLOAD_FILE}" 117 | mkdir -p "${MILL_DOWNLOAD_PATH}" 118 | mv "${DOWNLOAD_FILE}" "${MILL}" 119 | 120 | unset DOWNLOAD_FILE 121 | unset DOWNLOAD_SUFFIX 122 | fi 123 | fi 124 | 125 | unset MILL_DOWNLOAD_PATH 126 | unset MILL_OLD_DOWNLOAD_PATH 127 | unset OLD_MILL 128 | unset MILL_VERSION 129 | unset MILL_VERSION_TAG 130 | unset MILL_REPO_URL 131 | 132 | exec $MILL "$@" 133 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.0.3 -------------------------------------------------------------------------------- /project/build.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.1") 2 | 3 | addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "2.0") 4 | 5 | //this looked nice, but didnt quite work with sonatype. I wound up just using sbt-sonatype and tagging by hand todo remove or revisit 6 | addSbtPlugin("com.github.gseitz" % "sbt-release" % "1.0.7") 7 | 8 | addSbtPlugin("com.eed3si9n" % "sbt-unidoc" % "0.4.1") -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.24") 2 | 3 | addSbtPlugin("org.tpolecat" % "tut-plugin" % "0.6.5") -------------------------------------------------------------------------------- /todo.txt: -------------------------------------------------------------------------------- 1 | Done 2 | 3 | * Fixed bug in edge(,) methods in Adjacency Digraphs to return the correct edge. 4 | * Added DigraphInnerEdgeTrait and LabelDigraphInnerEdgeTrait to get inner nodes and labels from edges. 5 | 6 | TODO This release 7 | 8 | * Clustering algorithm 9 | * Congressional record 10 | * A* 11 | 12 | * Dijkstra's and Brandes - fill heap as nodes become reachable to reduce the heap's average complexity 13 | * Semiring algorithm - create label graph methods for label-less directed graphs, undirected graphs. 14 | * IndexedSubSet backed by BitSet 15 | 16 | Next release 17 | 18 | 19 | Future 20 | 21 | * Subgraph representation 22 | 23 | Far Future 24 | 25 | * Graph layout based on clusters 26 | 27 | Blog 28 | 29 | * Blog about controlling complexity 30 | * Blog about examples 31 | * Blog about computational stability 32 | 33 | 34 | Later 35 | 36 | * More Enron 37 | * path representation for net.walend.graph 38 | * Prim's MST algorithm 39 | * Faster transitive closure 40 | * Dijkstra's algorithm special case speed-ups 41 | * Dijkstra's algorithms for infinite graphs 42 | * A* and A* for infinite graphs. 43 | 44 | 45 | Publicize 46 | 47 | * Blog a bit 48 | * Simplicity for scala talk 49 | 50 | Overall 51 | * Add more to the package doc. 52 | * Images in package doc 53 | 54 | Structures 55 | 56 | * MultiPath representation (Probably a subgraph via having) 57 | * Concurrent Graph (Or at least Graph-With-Concurrently-Modifiable-Edges) 58 | 59 | Algorithms 60 | 61 | * MST with Fibonacci heap 62 | * Variations on Dijkstra's algorithm from wikipedia 63 | * Lazy Dijkstra. (Single-Source graph with Dijkstra's algorithm) 64 | * A* algorithm 65 | * Parallel queued graph minimization (will need a concurrent mutable edge graph) 66 | 67 | Graph storage 68 | 69 | * DynamoDB clusters 70 | 71 | 72 | Semirings 73 | 74 | * More Semirings 75 | 76 | Figure out 77 | 78 | * path representation. 79 | 80 | 81 | --- 82 | 83 | Prim's MST algorithm 84 | 85 | Put all the verticies in a priority queue with variable key values 86 | Initialize all the keys to O. 87 | Pick the root and set its key to I. 88 | While the queue is not empty 89 | extract the minimum edge and node 90 | if the node isn't in the MST 91 | for each node reachable from it not currently in the MST 92 | if some node is closer by using this node 93 | replace the key with the closer value 94 | 95 | --- 96 | 97 | 98 | -------------------------------------------------------------------------------- /version.sbt: -------------------------------------------------------------------------------- 1 | version in ThisBuild := "0.2.3-SNAPSHOT" --------------------------------------------------------------------------------