plexus 0.5.4 → 0.5.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. data/Gemfile +3 -0
  2. data/LICENSE +37 -0
  3. data/README.md +208 -0
  4. data/Rakefile +25 -0
  5. data/lib/plexus.rb +90 -0
  6. data/lib/plexus/adjacency_graph.rb +225 -0
  7. data/lib/plexus/arc.rb +60 -0
  8. data/lib/plexus/arc_number.rb +50 -0
  9. data/lib/plexus/biconnected.rb +84 -0
  10. data/lib/plexus/chinese_postman.rb +91 -0
  11. data/lib/plexus/classes/graph_classes.rb +28 -0
  12. data/lib/plexus/common.rb +63 -0
  13. data/lib/plexus/comparability.rb +63 -0
  14. data/lib/plexus/directed_graph.rb +78 -0
  15. data/lib/plexus/directed_graph/algorithms.rb +95 -0
  16. data/lib/plexus/directed_graph/distance.rb +167 -0
  17. data/lib/plexus/dot.rb +94 -0
  18. data/lib/plexus/edge.rb +38 -0
  19. data/lib/plexus/ext.rb +79 -0
  20. data/lib/plexus/graph.rb +628 -0
  21. data/lib/plexus/graph_api.rb +35 -0
  22. data/lib/plexus/labels.rb +112 -0
  23. data/lib/plexus/maximum_flow.rb +77 -0
  24. data/lib/plexus/ruby_compatibility.rb +17 -0
  25. data/lib/plexus/search.rb +510 -0
  26. data/lib/plexus/strong_components.rb +93 -0
  27. data/lib/plexus/support/support.rb +9 -0
  28. data/lib/plexus/undirected_graph.rb +56 -0
  29. data/lib/plexus/undirected_graph/algorithms.rb +90 -0
  30. data/lib/plexus/version.rb +6 -0
  31. data/spec/biconnected_spec.rb +27 -0
  32. data/spec/chinese_postman_spec.rb +27 -0
  33. data/spec/community_spec.rb +44 -0
  34. data/spec/complement_spec.rb +27 -0
  35. data/spec/digraph_distance_spec.rb +121 -0
  36. data/spec/digraph_spec.rb +339 -0
  37. data/spec/dot_spec.rb +48 -0
  38. data/spec/edge_spec.rb +158 -0
  39. data/spec/inspection_spec.rb +38 -0
  40. data/spec/multi_edge_spec.rb +32 -0
  41. data/spec/neighborhood_spec.rb +36 -0
  42. data/spec/properties_spec.rb +146 -0
  43. data/spec/search_spec.rb +227 -0
  44. data/spec/spec.opts +4 -0
  45. data/spec/spec_helper.rb +59 -0
  46. data/spec/strong_components_spec.rb +61 -0
  47. data/spec/triangulated_spec.rb +125 -0
  48. data/spec/undirected_graph_spec.rb +220 -0
  49. data/vendor/priority-queue/CHANGELOG +33 -0
  50. data/vendor/priority-queue/Makefile +140 -0
  51. data/vendor/priority-queue/README +133 -0
  52. data/vendor/priority-queue/benchmark/dijkstra.rb +171 -0
  53. data/vendor/priority-queue/compare_comments.rb +49 -0
  54. data/vendor/priority-queue/doc/c-vs-rb.png +0 -0
  55. data/vendor/priority-queue/doc/compare_big.gp +14 -0
  56. data/vendor/priority-queue/doc/compare_big.png +0 -0
  57. data/vendor/priority-queue/doc/compare_small.gp +15 -0
  58. data/vendor/priority-queue/doc/compare_small.png +0 -0
  59. data/vendor/priority-queue/doc/results.csv +37 -0
  60. data/vendor/priority-queue/ext/priority_queue/CPriorityQueue/extconf.rb +2 -0
  61. data/vendor/priority-queue/ext/priority_queue/CPriorityQueue/priority_queue.c +947 -0
  62. data/vendor/priority-queue/lib/priority_queue.rb +14 -0
  63. data/vendor/priority-queue/lib/priority_queue/c_priority_queue.rb +1 -0
  64. data/vendor/priority-queue/lib/priority_queue/poor_priority_queue.rb +46 -0
  65. data/vendor/priority-queue/lib/priority_queue/ruby_priority_queue.rb +526 -0
  66. data/vendor/priority-queue/priority_queue.so +0 -0
  67. data/vendor/priority-queue/setup.rb +1551 -0
  68. data/vendor/priority-queue/test/priority_queue_test.rb +371 -0
  69. data/vendor/rdot.rb +360 -0
  70. metadata +100 -10
@@ -0,0 +1,93 @@
1
+ module Plexus
2
+ module StrongComponents
3
+ # strong_components computes the strongly connected components
4
+ # of a graph using Tarjan's algorithm based on DFS. See: Robert E. Tarjan
5
+ # _Depth_First_Search_and_Linear_Graph_Algorithms_. SIAM Journal on
6
+ # Computing, 1(2):146-160, 1972
7
+ #
8
+ # The output of the algorithm is an array of components where is
9
+ # component is an array of vertices
10
+ #
11
+ # A strongly connected component of a directed graph G=(V,E) is a maximal
12
+ # set of vertices U which is in V such that for every pair of
13
+ # vertices u and v in U, we have both a path from u to v
14
+ # and path from v to u. That is to say that u and v are reachable
15
+ # from each other.
16
+ #
17
+ def strong_components
18
+ dfs_num = 0
19
+ stack = []; result = []; root = {}; comp = {}; number = {}
20
+
21
+ # Enter vertex callback
22
+ enter = Proc.new do |v|
23
+ root[v] = v
24
+ comp[v] = :new
25
+ number[v] = (dfs_num += 1)
26
+ stack.push(v)
27
+ end
28
+
29
+ # Exit vertex callback
30
+ exit = Proc.new do |v|
31
+ adjacent(v).each do |w|
32
+ if comp[w] == :new
33
+ root[v] = (number[root[v]] < number[root[w]] ? root[v] : root[w])
34
+ end
35
+ end
36
+ if root[v] == v
37
+ component = []
38
+ begin
39
+ w = stack.pop
40
+ comp[w] = :assigned
41
+ component << w
42
+ end until w == v
43
+ result << component
44
+ end
45
+ end
46
+
47
+ # Execute depth first search
48
+ dfs({:enter_vertex => enter, :exit_vertex => exit}); result
49
+
50
+ end # strong_components
51
+
52
+ # Returns a condensation graph of the strongly connected components
53
+ # Each node is an array of nodes from the original graph
54
+ def condensation
55
+ sc = strong_components
56
+ cg = DirectedMultiGraph.new
57
+ map = sc.inject({}) do |a,c|
58
+ c.each {|v| a[v] = c }; a
59
+ end
60
+ sc.each do |c|
61
+ c.each do |v|
62
+ adjacent(v).each {|v1| cg.add_edge!(c, map[v1]) unless cg.edge?(c, map[v1]) }
63
+ end
64
+ end;
65
+ cg
66
+ end
67
+
68
+ # Compute transitive closure of a graph. That is any node that is reachable
69
+ # along a path is added as a directed edge.
70
+ def transitive_closure!
71
+ cgtc = condensation.plexus_inner_transitive_closure!
72
+ cgtc.each do |cgv|
73
+ cgtc.adjacent(cgv).each do |adj|
74
+ cgv.each do |u|
75
+ adj.each {|v| add_edge!(u,v)}
76
+ end
77
+ end
78
+ end; self
79
+ end
80
+
81
+ # This returns the transitive closure of a graph. The original graph
82
+ # is not changed.
83
+ def transitive_closure() self.class.new(self).transitive_closure!; end
84
+
85
+ def plexus_inner_transitive_closure! # :nodoc:
86
+ sort.reverse.each do |u|
87
+ adjacent(u).each do |v|
88
+ adjacent(v).each {|w| add_edge!(u,w) unless edge?(u,w)}
89
+ end
90
+ end; self
91
+ end
92
+ end # StrongComponents
93
+ end # Plexus
@@ -0,0 +1,9 @@
1
+ module Plexus
2
+ # Errors
3
+ # TODO FIXME: must review all raise lines and streamline things
4
+
5
+ # Base error class for the library.
6
+ class PlexusError < StandardError; end
7
+
8
+ class NoArcError < PlexusError; end
9
+ end
@@ -0,0 +1,56 @@
1
+ module Plexus
2
+ module UndirectedGraphBuilder
3
+ autoload :Algorithms, "plexus/undirected_graph/algorithms"
4
+
5
+ include Plexus::GraphBuilder
6
+ extends_host
7
+
8
+ module ClassMethods
9
+ def [](*a)
10
+ self.new.from_array(*a)
11
+ end
12
+ end
13
+
14
+ def initialize(*params)
15
+ args = (params.pop if params.last.kind_of? Hash) || {}
16
+ args[:algorithmic_category] = Plexus::UndirectedGraphBuilder::Algorithms
17
+ super *(params << args)
18
+ end
19
+ end
20
+
21
+ # This is a Digraph that allows for parallel edges, but does not allow loops.
22
+ module UndirectedPseudoGraphBuilder
23
+ include UndirectedGraphBuilder
24
+ extends_host
25
+
26
+ module ClassMethods
27
+ def [](*a)
28
+ self.new.from_array(*a)
29
+ end
30
+ end
31
+
32
+ def initialize(*params)
33
+ args = (params.pop if params.last.kind_of? Hash) || {}
34
+ args[:parallel_edges] = true
35
+ super *(params << args)
36
+ end
37
+ end
38
+
39
+ # This is a Digraph that allows for parallel edges and loops.
40
+ module UndirectedMultiGraphBuilder
41
+ include UndirectedPseudoGraphBuilder
42
+ extends_host
43
+
44
+ module ClassMethods
45
+ def [](*a)
46
+ self.new.from_array(*a)
47
+ end
48
+ end
49
+
50
+ def initialize(*params)
51
+ args = (params.pop if params.last.kind_of? Hash) || {}
52
+ args[:loops] = true
53
+ super *(params << args)
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,90 @@
1
+ module Plexus
2
+ module UndirectedGraphBuilder
3
+ module Algorithms
4
+
5
+ include Search
6
+ include Biconnected
7
+ include Comparability
8
+
9
+ # UndirectedGraph is by definition undirected, always returns false
10
+ def directed?() false; end
11
+
12
+ # Redefine degree (default was sum)
13
+ def degree(v) in_degree(v); end
14
+
15
+ # A vertex of an undirected graph is balanced by definition
16
+ def balanced?(v) true; end
17
+
18
+ # UndirectedGraph uses Edge for the edge class.
19
+ def edge_class() @parallel_edges ? Plexus::MultiEdge : Plexus::Edge; end
20
+
21
+ def remove_edge!(u, v=nil)
22
+ unless u.kind_of? Plexus::Arc
23
+ raise ArgumentError if @parallel_edges
24
+ u = edge_class[u,v]
25
+ end
26
+ super(u.reverse) unless u.source == u.target
27
+ super(u)
28
+ end
29
+
30
+ # A triangulated graph is an undirected perfect graph that every cycle of length greater than
31
+ # three possesses a chord. They have also been called chordal, rigid circuit, monotone transitive,
32
+ # and perfect elimination graphs.
33
+ #
34
+ # Implementation taken from Golumbic's, "Algorithmic Graph Theory and
35
+ # Perfect Graphs" pg. 90
36
+ def triangulated?
37
+ a = Hash.new {|h,k| h[k]=Set.new}; sigma=lexicograph_bfs
38
+ inv_sigma = sigma.inject({}) {|acc,val| acc[val] = sigma.index(val); acc}
39
+ sigma[0..-2].each do |v|
40
+ x = adjacent(v).select {|w| inv_sigma[v] < inv_sigma[w] }
41
+ unless x.empty?
42
+ u = sigma[x.map {|y| inv_sigma[y]}.min]
43
+ a[u].merge(x - [u])
44
+ end
45
+ return false unless a[v].all? {|z| adjacent?(v,z)}
46
+ end
47
+ true
48
+ end
49
+
50
+ def chromatic_number
51
+ return triangulated_chromatic_number if triangulated?
52
+ raise NotImplementedError
53
+ end
54
+
55
+ # An interval graph can have its vertices into one-to-one
56
+ # correspondence with a set of intervals F of a linearly ordered
57
+ # set (like the real line) such that two vertices are connected
58
+ # by an edge of G if and only if their corresponding intervals
59
+ # have nonempty intersection.
60
+ def interval?() triangulated? and complement.comparability?; end
61
+
62
+ # A permutation diagram consists of n points on each of two parallel
63
+ # lines and n straight line segments matchin the points. The intersection
64
+ # graph of the line segments is called a permutation graph.
65
+ def permutation?() comparability? and complement.comparability?; end
66
+
67
+ # An undirected graph is defined to be split if there is a partition
68
+ # V = S + K of its vertex set into a stable set S and a complete set K.
69
+ def split?() triangulated? and complement.triangulated?; end
70
+
71
+ private
72
+ # Implementation taken from Golumbic's, "Algorithmic Graph Theory and
73
+ # Perfect Graphs" pg. 99
74
+ def triangulated_chromatic_number
75
+ chi = 1; s= Hash.new {|h,k| h[k]=0}
76
+ sigma=lexicograph_bfs
77
+ inv_sigma = sigma.inject({}) {|acc,val| acc[val] = sigma.index(val); acc}
78
+ sigma.each do |v|
79
+ x = adjacent(v).select {|w| inv_sigma[v] < inv_sigma[w] }
80
+ unless x.empty?
81
+ u = sigma[x.map {|y| inv_sigma[y]}.min]
82
+ s[u] = [s[u], x.size-1].max
83
+ chi = [chi, x.size+1].max if s[v] < x.size
84
+ end
85
+ end; chi
86
+ end
87
+
88
+ end # UndirectedGraphAlgorithms
89
+ end # UndirectedGraphBuilder
90
+ end # Plexus
@@ -0,0 +1,6 @@
1
+ module Plexus
2
+ MAJOR = 0
3
+ MINOR = 5
4
+ PATCH = 5
5
+ VERSION = [MAJOR, MINOR, PATCH].join('.')
6
+ end
@@ -0,0 +1,27 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe "Biconnected" do # :nodoc:
4
+ describe "tarjan" do
5
+ it do
6
+ tarjan = UndirectedGraph[ 1, 2,
7
+ 1, 5,
8
+ 1, 6,
9
+ 1, 7,
10
+ 2, 3,
11
+ 2, 4,
12
+ 3, 4,
13
+ 2, 5,
14
+ 5, 6,
15
+ 7, 8,
16
+ 7, 9,
17
+ 8, 9 ]
18
+ graphs, articulations = tarjan.biconnected
19
+ articulations.sort.should == [1,2,7]
20
+ graphs.size.should == 4
21
+ graphs.find {|g| g.size == 2}.vertices.sort.should == [1,7]
22
+ graphs.find {|g| g.size == 4}.vertices.sort.should == [1,2,5,6]
23
+ graphs.find {|g| g.size == 3 && g.vertex?(2)}.vertices.sort.should == [2,3,4]
24
+ graphs.find {|g| g.size == 3 && g.vertex?(7)}.vertices.sort.should == [7,8,9]
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,27 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe "ChinesePostman" do # :nodoc:
4
+
5
+ before do
6
+ @simple=Digraph[ 0,1, 0,2, 1,2, 1,3, 2,3, 3,0 ]
7
+ @weight = Proc.new {|e| 1}
8
+ end
9
+
10
+ describe "closed_simple_tour" do
11
+ it do
12
+ tour = @simple.closed_chinese_postman_tour(0, @weight)
13
+ tour.size.should == 11
14
+ tour[0].should == 0
15
+ tour[10].should == 0
16
+ edges = Set.new
17
+ 0.upto(9) do |n|
18
+ edges << Arc[tour[n],tour[n+1]]
19
+ @simple.edge?(tour[n],tour[n+1]).should be_true
20
+ end
21
+ edges.size.should == @simple.edges.size
22
+ end
23
+
24
+ end
25
+
26
+
27
+ end
@@ -0,0 +1,44 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe "Community" do # :nodoc:
4
+ before do
5
+ @graph = Digraph[2,1, 3,1, 5,4, 6,5, 7,6, 7,2].add_vertex!(8)
6
+ end
7
+
8
+ describe "ancestors_must_return_ancestors" do
9
+ it do
10
+ @graph.ancestors(1).sort.should == [2,3,7]
11
+ @graph.ancestors(2).sort.should == [7]
12
+ @graph.ancestors(3).sort.should == []
13
+ @graph.ancestors(4).sort.should == [5,6,7]
14
+ @graph.ancestors(5).sort.should == [6,7]
15
+ @graph.ancestors(6).sort.should == [7]
16
+ @graph.ancestors(7).sort.should == []
17
+ end
18
+ end
19
+
20
+ describe "descendants_must_return_descendants" do
21
+ it do
22
+ @graph.descendants(1).sort.should == []
23
+ @graph.descendants(2).sort.should == [1]
24
+ @graph.descendants(3).sort.should == [1]
25
+ @graph.descendants(4).sort.should == []
26
+ @graph.descendants(5).sort.should == [4]
27
+ @graph.descendants(6).sort.should == [4,5]
28
+ @graph.descendants(7).sort.should == [1,2,4,5,6]
29
+ end
30
+ end
31
+
32
+ describe "family_must_return_family" do
33
+ it do
34
+ @graph.family(1).sort.should == [2,3,4,5,6,7]
35
+ @graph.family(2).sort.should == [1,3,4,5,6,7]
36
+ @graph.family(3).sort.should == [1,2,4,5,6,7]
37
+ @graph.family(4).sort.should == [1,2,3,5,6,7]
38
+ @graph.family(5).sort.should == [1,2,3,4,6,7]
39
+ @graph.family(6).sort.should == [1,2,3,4,5,7]
40
+ @graph.family(7).sort.should == [1,2,3,4,5,6]
41
+ end
42
+ end
43
+
44
+ end
@@ -0,0 +1,27 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe "Complement" do # :nodoc:
4
+
5
+ describe "square" do
6
+ it do
7
+ x = UndirectedGraph[:a,:b, :b,:c, :c,:d, :d,:a].complement
8
+ x.edges.size.should == 2
9
+ x.edges.include?(Edge[:a,:c]).should be_true
10
+ x.edges.include?(Edge[:b,:d]).should be_true
11
+ end
12
+ end
13
+
14
+ describe "g1" do
15
+ it do
16
+ g1 = UndirectedGraph[ :a,:b, :a,:d, :a,:e, :a,:i, :a,:g, :a,:h,
17
+ :b,:c, :b,:f,
18
+ :c,:d, :c,:h,
19
+ :d,:h, :d,:e,
20
+ :e,:f,
21
+ :f,:g, :f,:h, :f,:i,
22
+ :h,:i ].complement
23
+ g1.edges.size.should == 19
24
+ end
25
+ end
26
+
27
+ end
@@ -0,0 +1,121 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe "DigraphDistance" do # :nodoc:
4
+
5
+ before do
6
+ @d = Digraph[ :a,:b, :a,:e,
7
+ :b,:c, :b,:e,
8
+ :c,:d,
9
+ :d,:c,
10
+ :e,:b, :e,:f,
11
+ :f,:c, :f,:d, :f,:e ]
12
+
13
+ @w = { Arc[:a,:b] => 9,
14
+ Arc[:a,:e] => 3,
15
+ Arc[:b,:c] => 2,
16
+ Arc[:b,:e] => 6,
17
+ Arc[:c,:d] => 1,
18
+ Arc[:d,:c] => 2,
19
+ Arc[:e,:b] => 2,
20
+ Arc[:e,:f] => 1,
21
+ Arc[:f,:c] => 2,
22
+ Arc[:f,:d] => 7,
23
+ Arc[:f,:e] => 2 }
24
+ @a = { :a => 0,
25
+ :b => 5,
26
+ :c => 6,
27
+ :d => 7,
28
+ :e => 3,
29
+ :f => 4 }
30
+ @simple_weight = Proc.new {|e| 1}
31
+ end
32
+
33
+ describe "shortest_path" do
34
+ it do
35
+ x = Digraph[ :s,:u, :s,:w,
36
+ :j,:v,
37
+ :u,:j,
38
+ :v,:y,
39
+ :w,:u, :w,:v, :w,:y, :w,:x,
40
+ :x,:z ]
41
+ x.should be_acyclic
42
+ cost, path = x.shortest_path(:s,@simple_weight)
43
+ cost.should == {:x=>2, :v=>2, :y=>2, :w=>1, :s=>0, :z=>3, :u=>1, :j=> 2}
44
+ path.should == {:x=>:w, :v=>:w, :y=>:w, :w=>:s, :z=>:x, :u=>:s, :j=>:u}
45
+ end
46
+ end
47
+
48
+ describe "dijkstra_with_proc" do
49
+ it do
50
+ p = Proc.new {|e| @w[e]}
51
+ distance, path = @d.dijkstras_algorithm(:a,p)
52
+ distance.should == @a
53
+ path.should == { :d => :c, :c => :f, :f => :e, :b => :e, :e => :a}
54
+ end
55
+ end
56
+
57
+ describe "dijkstra_with_label" do
58
+ it do
59
+ @w.keys.each {|e| @d[e] = @w[e]}
60
+ @d.dijkstras_algorithm(:a)[0].should == @a
61
+ end
62
+ end
63
+
64
+ describe "dijkstra_with_bracket_label" do
65
+ it do
66
+ @w.keys.each do |e|
67
+ @d[e] = {:xyz => (@w[e])}
68
+ end
69
+ @d.dijkstras_algorithm(:a, :xyz)[0].should == @a
70
+ @w.keys.each do |e|
71
+ @d[e] = [@w[e]]
72
+ end
73
+ @d.dijkstras_algorithm(:a, 0)[0].should == @a
74
+ end
75
+ end
76
+
77
+ describe "floyd_warshall" do
78
+ it do
79
+ simple = Digraph[ 0,1, 0,2, 1,2, 1,3, 2,3, 3,0 ]
80
+
81
+ cost, path, delta = simple.floyd_warshall(@simple_weight)
82
+ # Costs
83
+ cost[0].should == {0=>3, 1=>1, 2=>1, 3=>2}
84
+ cost[1].should == {0=>2, 1=>3, 2=>1, 3=>1}
85
+ cost[2].should == {0=>2, 1=>3, 2=>3, 3=>1}
86
+ cost[3].should == {0=>1, 1=>2, 2=>2, 3=>3}
87
+
88
+ # Paths
89
+ path[0].should == {0=>1, 1=>1, 2=>2, 3=>1}
90
+ path[1].should == {0=>3, 1=>3, 2=>2, 3=>3}
91
+ path[2].should == {0=>3, 1=>3, 2=>3, 3=>3}
92
+ path[3].should == {0=>0, 1=>0, 2=>0, 3=>0}
93
+
94
+ # Deltas
95
+ delta[0].should == 1
96
+ delta[1].should == 1
97
+ delta[2].should == -1
98
+ delta[3].should == -1
99
+ end
100
+ end
101
+
102
+ describe "bellman_ford_moore" do
103
+ it do
104
+ fig24 = Digraph[ [:s,:e] => 8,
105
+ [:s,:d] => 4,
106
+ [:e,:c] => 2,
107
+ [:e,:d] => -5,
108
+ [:c,:b] => -2,
109
+ [:d,:c] => -2,
110
+ [:d,:a] => 4,
111
+ [:a,:c] => 10,
112
+ [:a,:b] => 9,
113
+ [:b,:c] => 5,
114
+ [:b,:a] => -3]
115
+ cost, path = fig24.bellman_ford_moore(:s)
116
+ cost.should == {:e=>8, :d=>3, :c=>1, :b=>-1, :a=>-4, :s=>0}
117
+ path.should == {:e=>:s, :d=>:e, :c=>:d, :b=>:c, :a=>:b}
118
+ end
119
+ end
120
+
121
+ end