gratr19 0.4.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. data/README +335 -0
  2. data/examples/graph_self.rb +54 -0
  3. data/examples/module_graph.jpg +0 -0
  4. data/examples/module_graph.rb +12 -0
  5. data/examples/self_graph.jpg +0 -0
  6. data/examples/visualize.jpg +0 -0
  7. data/examples/visualize.rb +8 -0
  8. data/install.rb +49 -0
  9. data/lib/gratr.rb +42 -0
  10. data/lib/gratr/adjacency_graph.rb +230 -0
  11. data/lib/gratr/base.rb +34 -0
  12. data/lib/gratr/biconnected.rb +116 -0
  13. data/lib/gratr/chinese_postman.rb +123 -0
  14. data/lib/gratr/common.rb +74 -0
  15. data/lib/gratr/comparability.rb +92 -0
  16. data/lib/gratr/digraph.rb +115 -0
  17. data/lib/gratr/digraph_distance.rb +185 -0
  18. data/lib/gratr/dot.rb +90 -0
  19. data/lib/gratr/edge.rb +145 -0
  20. data/lib/gratr/graph.rb +314 -0
  21. data/lib/gratr/graph_api.rb +82 -0
  22. data/lib/gratr/import.rb +44 -0
  23. data/lib/gratr/labels.rb +103 -0
  24. data/lib/gratr/maximum_flow.rb +107 -0
  25. data/lib/gratr/rdot.rb +332 -0
  26. data/lib/gratr/search.rb +422 -0
  27. data/lib/gratr/strong_components.rb +127 -0
  28. data/lib/gratr/undirected_graph.rb +153 -0
  29. data/lib/gratr/version.rb +6 -0
  30. data/lib/priority-queue/benchmark/dijkstra.rb +171 -0
  31. data/lib/priority-queue/compare_comments.rb +49 -0
  32. data/lib/priority-queue/ext/priority_queue/CPriorityQueue/extconf.rb +2 -0
  33. data/lib/priority-queue/lib/priority_queue.rb +14 -0
  34. data/lib/priority-queue/lib/priority_queue/c_priority_queue.rb +1 -0
  35. data/lib/priority-queue/lib/priority_queue/poor_priority_queue.rb +46 -0
  36. data/lib/priority-queue/lib/priority_queue/ruby_priority_queue.rb +525 -0
  37. data/lib/priority-queue/setup.rb +1551 -0
  38. data/lib/priority-queue/test/priority_queue_test.rb +371 -0
  39. data/tests/TestBiconnected.rb +53 -0
  40. data/tests/TestChinesePostman.rb +53 -0
  41. data/tests/TestComplement.rb +54 -0
  42. data/tests/TestDigraph.rb +333 -0
  43. data/tests/TestDigraphDistance.rb +138 -0
  44. data/tests/TestDot.rb +75 -0
  45. data/tests/TestEdge.rb +171 -0
  46. data/tests/TestInspection.rb +57 -0
  47. data/tests/TestMultiEdge.rb +57 -0
  48. data/tests/TestNeighborhood.rb +64 -0
  49. data/tests/TestProperties.rb +160 -0
  50. data/tests/TestSearch.rb +277 -0
  51. data/tests/TestStrongComponents.rb +85 -0
  52. data/tests/TestTriagulated.rb +137 -0
  53. data/tests/TestUndirectedGraph.rb +219 -0
  54. metadata +152 -0
@@ -0,0 +1,153 @@
1
+ #--
2
+ # Copyright (c) 2006 Shawn Patrick Garbett
3
+ # Copyright (c) 2002,2004,2005 by Horst Duchene
4
+ #
5
+ # Redistribution and use in source and binary forms, with or without modification,
6
+ # are permitted provided that the following conditions are met:
7
+ #
8
+ # * Redistributions of source code must retain the above copyright notice(s),
9
+ # this list of conditions and the following disclaimer.
10
+ # * Redistributions in binary form must reproduce the above copyright notice,
11
+ # this list of conditions and the following disclaimer in the documentation
12
+ # and/or other materials provided with the distribution.
13
+ # * Neither the name of the Shawn Garbett nor the names of its contributors
14
+ # may be used to endorse or promote products derived from this software
15
+ # without specific prior written permission.
16
+ #
17
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
18
+ # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
21
+ # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22
+ # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23
+ # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
24
+ # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
25
+ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
+ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
+ #++
28
+
29
+
30
+ require 'gratr/adjacency_graph'
31
+ require 'gratr/search'
32
+ require 'gratr/biconnected'
33
+ require 'gratr/comparability'
34
+ require 'set'
35
+
36
+ module GRATR
37
+ class UndirectedGraph
38
+ include AdjacencyGraph
39
+ include Graph::Search
40
+ include Graph::Biconnected
41
+ include Graph::Comparability
42
+
43
+ def initialize(*params)
44
+ raise ArgumentError if params.any? do |p|
45
+ !(p.kind_of? GRATR::Graph or p.kind_of? Array)
46
+ end if self.class == GRATR::UndirectedGraph
47
+ super(*params)
48
+ end
49
+
50
+ # UndirectedGraph is by definition undirected, always returns false
51
+ def directed?() false; end
52
+
53
+ # Redefine degree (default was sum)
54
+ def degree(v) in_degree(v); end
55
+
56
+ # A vertex of an undirected graph is balanced by definition
57
+ def balanced?(v) true; end
58
+
59
+ # UndirectedGraph uses Edge for the edge class.
60
+ def edge_class() @parallel_edges ? GRATR::MultiEdge : GRATR::Edge; end
61
+
62
+ def remove_edge!(u, v=nil)
63
+ unless u.kind_of? GRATR::Arc
64
+ raise ArgumentError if @parallel_edges
65
+ u = edge_class[u,v]
66
+ end
67
+ super(u.reverse) unless u.source == u.target
68
+ super(u)
69
+ end
70
+
71
+ # A triangulated graph is an undirected perfect graph that every cycle of length greater than
72
+ # three possesses a chord. They have also been called chordal, rigid circuit, monotone transitive,
73
+ # and perfect elimination graphs.
74
+ #
75
+ # Implementation taken from Golumbic's, "Algorithmic Graph Theory and
76
+ # Perfect Graphs" pg. 90
77
+ def triangulated?
78
+ a = Hash.new {|h,k| h[k]=Set.new}; sigma=lexicograph_bfs
79
+ inv_sigma = sigma.inject({}) {|acc,val| acc[val] = sigma.index(val); acc}
80
+ sigma[0..-2].each do |v|
81
+ x = adjacent(v).select {|w| inv_sigma[v] < inv_sigma[w] }
82
+ unless x.empty?
83
+ u = sigma[x.map {|y| inv_sigma[y]}.min]
84
+ a[u].merge(x - [u])
85
+ end
86
+ return false unless a[v].all? {|z| adjacent?(v,z)}
87
+ end
88
+ true
89
+ end
90
+
91
+ def chromatic_number
92
+ return triangulated_chromatic_number if triangulated?
93
+ raise NotImplementedError
94
+ end
95
+
96
+ # An interval graph can have its vertices into one-to-one
97
+ # correspondence with a set of intervals F of a linearly ordered
98
+ # set (like the real line) such that two vertices are connected
99
+ # by an edge of G if and only if their corresponding intervals
100
+ # have nonempty intersection.
101
+ def interval?() triangulated? and complement.comparability?; end
102
+
103
+ # A permutation diagram consists of n points on each of two parallel
104
+ # lines and n straight line segments matchin the points. The intersection
105
+ # graph of the line segments is called a permutation graph.
106
+ def permutation?() comparability? and complement.comparability?; end
107
+
108
+ # An undirected graph is defined to be split if there is a partition
109
+ # V = S + K of its vertex set into a stable set S and a complete set K.
110
+ def split?() triangulated? and complement.triangulated?; end
111
+
112
+ private
113
+ # Implementation taken from Golumbic's, "Algorithmic Graph Theory and
114
+ # Perfect Graphs" pg. 99
115
+ def triangulated_chromatic_number
116
+ chi = 1; s= Hash.new {|h,k| h[k]=0}
117
+ sigma=lexicograph_bfs
118
+ inv_sigma = sigma.inject({}) {|acc,val| acc[val] = sigma.index(val); acc}
119
+ sigma.each do |v|
120
+ x = adjacent(v).select {|w| inv_sigma[v] < inv_sigma[w] }
121
+ unless x.empty?
122
+ u = sigma[x.map {|y| inv_sigma[y]}.min]
123
+ s[u] = [s[u], x.size-1].max
124
+ chi = [chi, x.size+1].max if s[v] < x.size
125
+ end
126
+ end; chi
127
+ end
128
+
129
+ end # UndirectedGraph
130
+
131
+ # This is a UndirectedGraph that allows for parallel edges, but does not
132
+ # allow loops
133
+ class UndirectedPseudoGraph < UndirectedGraph
134
+ def initialize(*params)
135
+ raise ArgumentError if params.any? do |p|
136
+ !(p.kind_of? Graph or p.kind_of? Array)
137
+ end
138
+ super(:parallel_edges, *params)
139
+ end
140
+ end
141
+
142
+ # This is a UndirectedGraph that allows for parallel edges and loops
143
+ class UndirectedMultiGraph < UndirectedGraph
144
+ def initialize(*params)
145
+ raise ArgumentError if params.any? do |p|
146
+ !(p.kind_of? Graph or p.kind_of? Array)
147
+ end
148
+ super(:parallel_edges, :loops, *params)
149
+ end
150
+ end
151
+
152
+
153
+ end # GRATR
@@ -0,0 +1,6 @@
1
+ module GRATR
2
+ VERSION = "0.4.4"
3
+
4
+ # Deprecated.
5
+ GRATR_VERSION = VERSION
6
+ end
@@ -0,0 +1,171 @@
1
+ $:.unshift "~/lib/ruby"
2
+ require 'priority_queue/ruby_priority_queue'
3
+ require 'priority_queue/poor_priority_queue'
4
+ require 'priority_queue/c_priority_queue'
5
+ require 'benchmark'
6
+
7
+ class Node
8
+ attr_reader :neighbours, :id
9
+
10
+ def initialize(id)
11
+ @neighbours = []
12
+ @id = id
13
+ end
14
+
15
+ def inspect
16
+ to_s
17
+ end
18
+
19
+ def to_s
20
+ "(#{@id})"
21
+ end
22
+ end
23
+
24
+ # Build a graph by adding nodes with random connections
25
+
26
+ # Return a random graph with an average degree of degree
27
+ def make_graph(nodes, degree)
28
+ nodes = Array.new(nodes) { | i | Node.new(i.to_s) }
29
+ nodes.each do | n |
30
+ (degree / 2).times do
31
+ true while (n1 = nodes[rand(nodes.length)]) == n
32
+ n.neighbours << nodes[rand(nodes.length)]
33
+ n1.neighbours << n
34
+ n.neighbours << n1
35
+ end
36
+ end
37
+ end
38
+
39
+ def draw_graph(nodes, out)
40
+ dot = [] << "graph g {"
41
+ nodes.each do | n1 |
42
+ dot << "N#{n1.id} [label='#{n1.id}'];"
43
+ n1.neighbours.each do | n2 |
44
+ dot << "N#{n1.id} -- N#{n2.id};" if n1.id <= n2.id
45
+ end
46
+ end
47
+ dot << "}"
48
+
49
+ # system "echo '#{dot}' | neato -Gepsilon=0.001 -Goverlap=scale -Gsplines=true -Gsep=.4 -Tps -o #{out}"
50
+ system "echo '#{dot}' | neato -Gepsilon=0.05 -Goverlap=scale -Gsep=.4 -Tps -o #{out}"
51
+ end
52
+
53
+ def dijkstra(start_node, queue_klass)
54
+ # Priority Queue with unfinished nodes
55
+ active = queue_klass.new
56
+ # Distances for all nodes
57
+ distances = Hash.new { 1.0 / 0.0 }
58
+ # Parent pointers describing shortest paths for all nodes
59
+ parents = Hash.new
60
+
61
+ # Initialize with start node
62
+ active[start_node] = 0
63
+ until active.empty?
64
+ u, distance = active.delete_min
65
+ distances[u] = distance
66
+ d = distance + 1
67
+ u.neighbours.each do | v |
68
+ next unless d < distances[v] # we can't relax this one
69
+ active[v] = distances[v] = d
70
+ parents[v] = u
71
+ end
72
+ end
73
+ end
74
+
75
+ srand
76
+
77
+ sizes = Array.new(4) { | base | Array.new(9) { | mult | (mult+1) * 10**(base+2) } }.flatten
78
+ degrees = [2, 4, 16]
79
+ degrees = [4, 16]
80
+ degrees = [16]
81
+ queues = [ CPriorityQueue, PoorPriorityQueue, RubyPriorityQueue ]
82
+ queues = [ CPriorityQueue, RubyPriorityQueue ]
83
+
84
+ max_time = 400
85
+ ignore = Hash.new
86
+
87
+ repeats = 5
88
+
89
+
90
+ STDOUT.sync = true
91
+
92
+ results = Hash.new { | h, k | h[k] =
93
+ Hash.new { | h1, k1 | h1[k1] = Hash.new { 0 }
94
+ }
95
+ }
96
+
97
+ Benchmark.bm(30) do | b |
98
+ sizes.each do | size |
99
+ break if !ignore.empty? and ignore.values.inject(true) { | r, v | r and v }
100
+ puts
101
+ puts "Testing with graphs of size #{size}"
102
+ degrees.each do | degree |
103
+ repeats.times do | r |
104
+ nodes = make_graph(size, degree)
105
+ queues.each do | queue |
106
+ next if ignore[queue]
107
+ GC.start
108
+ results[queue][degree][size] += (b.report("#{queue}: #{size} (#{degree})") do dijkstra(nodes[1], queue) end).real
109
+ end
110
+ end
111
+ queues.each do | queue |
112
+ ignore[queue] ||= ((results[queue][degree][size] / repeats) > max_time)
113
+ end
114
+ end
115
+
116
+ indices = queues.map { | q | degrees.map { | d | %&"#{q} (Graph of Degree: #{d})"& } }.flatten
117
+ File.open("results.csv", "wb") do | f |
118
+ f.puts "size\t" + indices.join("\t")
119
+ sizes.each do | size |
120
+ f.puts "#{size}\t" + queues.map { | q | degrees.map { | d |
121
+ (results[q][d].has_key?(size) and results[q][d][size] > 0.0) ? results[q][d][size] / repeats : "''"
122
+ } }.join("\t")
123
+ end
124
+ end
125
+
126
+ File.open("results.gp", 'wb') do | f |
127
+ lines = []
128
+ indices.each_with_index do | t, i |
129
+ lines << " 'results.csv' using 1:#{i+2} with lines title #{t}"
130
+ end
131
+ f.puts "set term png"
132
+ f.puts "set out 'results.png'"
133
+ f.puts "set xlabel 'Number of nodes'"
134
+ f.puts "set ylabel 'Time in seconds (real)'"
135
+ f.puts "set logscale xy"
136
+ f.puts "set title 'Dijkstras Shortest Path Algorithm using different PQ Implementations'"
137
+ f.puts "plot \\"
138
+ f.puts lines.join(",\\\n")
139
+ end
140
+ system "gnuplot results.gp"
141
+
142
+ queues.each do | q |
143
+ File.open("result-#{q}.gp", 'wb') do | f |
144
+ lines = []
145
+ degrees.map { | d | %&"#{q} (Graph of Degree: #{d})"& }.flatten.each do | t |
146
+ lines << " 'results.csv' using 1:#{indices.index(t)+2} with lines title #{t}"
147
+ end
148
+ f.puts "set term png"
149
+ f.puts "set out 'result-#{q}.png'"
150
+ f.puts "set xlabel 'Number of nodes'"
151
+ f.puts "set ylabel 'Time in seconds (real)'"
152
+ f.puts "set logscale xy"
153
+ f.puts "set title 'Dijkstras Shortest Path Algorithm on Networks of different degrees'"
154
+ f.puts "plot \\"
155
+ f.puts lines.join(",\\\n")
156
+ end
157
+ system "gnuplot result-#{q}.gp"
158
+ end
159
+ end
160
+ end
161
+
162
+ __END__
163
+
164
+ nodes = make_graph(100, 4)
165
+ draw_graph(nodes, "100-4.ps")
166
+ nodes = make_graph(100, 10)
167
+ draw_graph(nodes, "100-10.ps")
168
+ nodes = make_graph(10, 10)
169
+ draw_graph(nodes, "10-10.ps")
170
+ nodes = make_graph(1000, 2)
171
+ draw_graph(nodes, "1000-2.ps")
@@ -0,0 +1,49 @@
1
+ c_file = File.read("ext/priority_queue/CPriorityQueue/priority_queue.c")
2
+ rb_file = File.read("lib/priority_queue/ruby_priority_queue.rb")
3
+
4
+ c_comments = Hash.new { "" }
5
+
6
+ c_file.scan(%r(/\*(.*?)\*/\s*static\s+\w+\s*pq_(\w+)\(.*?\))m).each do | match |
7
+ c_comments[match[1]] = match[0].gsub(%r(\n\s*\* {0,1})m, "\n").strip
8
+ end
9
+
10
+ rb_comments = Hash.new { "" }
11
+
12
+ rb_file.scan(%r(((?:\n\s*#[^\n]*)*)\s*def\s+(\w+))m).each do | match |
13
+ rb_comments[match[1]] = match[0].gsub(%r(\n\s*# {0,1})m, "\n").strip
14
+ end
15
+
16
+ add_comments = Hash.new
17
+
18
+ (rb_comments.keys + c_comments.keys).uniq.each do | key |
19
+ #next if rb_comments[key].gsub(/\s+/m, " ") == c_comments[key].gsub(/\s+/m, " ")
20
+ if c_comments[key].empty?
21
+ add_comments[key] = rb_comments[key]
22
+ elsif rb_comments[key].empty?
23
+ add_comments[key] = c_comments[key]
24
+ elsif rb_comments[key] != c_comments[key]
25
+
26
+ puts key
27
+ puts "Ruby"
28
+ puts rb_comments[key]
29
+ puts "C"
30
+ puts c_comments[key]
31
+ puts
32
+ puts "Choose [c,r]"
33
+ 1 until /^([cr])/ =~ gets
34
+ add_comments[key] = ($1 == "c" ? c_comments : rb_comments)[key]
35
+ puts "-" * 80
36
+ puts
37
+ else
38
+ add_comments[key] = rb_comments[key]
39
+ end
40
+
41
+ end
42
+
43
+ File.open("lib/priority_queue/ruby_priority_queue.new.rb", "wb") do | o |
44
+ o <<
45
+ rb_file.gsub(%r(((?:\n\s*#[^\n]*)*)(\s*def\s+(\w+)))m) do | match |
46
+ name, all = $3, $2
47
+ "\n" + (add_comments[name].gsub(/^/, "#")) + all
48
+ end
49
+ end
@@ -0,0 +1,2 @@
1
+ require 'mkmf'
2
+ create_makefile("CPriorityQueue")
@@ -0,0 +1,14 @@
1
+ # A priority queue implementation.
2
+ # This extension contains two implementations, a c extension and a pure ruby
3
+ # implementation. When the compiled extension can not be found, it falls back
4
+ # to the pure ruby extension.
5
+ #
6
+ # See CPriorityQueue and RubyPriorityQueue for more information.
7
+
8
+ begin
9
+ require 'priority_queue/CPriorityQueue'
10
+ PriorityQueue = CPriorityQueue
11
+ rescue LoadError # C Version could not be found, try ruby version
12
+ require 'priority_queue/ruby_priority_queue'
13
+ PriorityQueue = RubyPriorityQueue
14
+ end
@@ -0,0 +1 @@
1
+ require "priority_queue/CPriorityQueue"
@@ -0,0 +1,46 @@
1
+ # A Poor mans Priority Queue. (Very inefficent but minimal implemention).
2
+ class PoorPriorityQueue < Hash
3
+ def push(object, priority)
4
+ self[object] = priority
5
+ end
6
+
7
+ def min
8
+ return nil if self.empty?
9
+ min_k = self.keys.first
10
+ min_p = self[min_k]
11
+ self.each do | k, p |
12
+ min_k, min_p = k, p if p < min_p
13
+ end
14
+ [min_k, min_p]
15
+ end
16
+
17
+ def min_key
18
+ min[0] rescue nil
19
+ end
20
+
21
+ def min_priority
22
+ min[1] rescue nil
23
+ end
24
+
25
+ def delete_min
26
+ return nil if self.empty?
27
+ min_k, min_p = *min
28
+ self.delete(min_k)
29
+ [min_k, min_p]
30
+ end
31
+
32
+ def delete_min_return_key
33
+ delete_min[0] rescue nil
34
+ end
35
+
36
+ def delete_min_return_priority
37
+ delete_min[1] rescue nil
38
+ end
39
+
40
+ def delete(object)
41
+ return nil unless self.has_key?(object)
42
+ result = [object, self[object]]
43
+ super
44
+ result
45
+ end
46
+ end