gratr 0.4.2 → 0.4.3

Sign up to get free protection for your applications and to get access to all the features.
data/README CHANGED
@@ -269,6 +269,13 @@ Rick Bradley who reworked the library and added many graph theoretic constructs.
269
269
  The core interface is about to be updated to allow for cleaner dependency injects. Luke Kanies has also contributed
270
270
  several optimizations for speed and logged some bugs. This will result in a 0.5 release coming soon.
271
271
 
272
+ === 0.4.3
273
+
274
+ * Fixed bug in dot output dependency.
275
+ * Changed method.call to send in a couple places.
276
+ * Fixed bug in unconnected vertices for reversal. Ticket #7237
277
+ * Fixed bugs in A* Search. Ticket #7250.
278
+
272
279
  === 0.4.2
273
280
 
274
281
  * Fixed bug in parallel edge adjacency detection.
@@ -31,3 +31,12 @@ require 'gratr/base'
31
31
  require 'gratr/digraph'
32
32
  require 'gratr/undirected_graph'
33
33
  require 'gratr/common'
34
+
35
+ # Load priority queue classes
36
+ begin
37
+ # Use installed Gem
38
+ require 'priority_queue'
39
+ rescue LoadError # Use local copy
40
+ require 'priority-queue/lib/priority_queue/ruby_priority_queue'
41
+ PriorityQueue = RubyPriorityQueue
42
+ end
@@ -26,7 +26,7 @@
26
26
  # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
27
  #++
28
28
 
29
- GRATR_VERSION = "0.4.2"
29
+ GRATR_VERSION = "0.4.3"
30
30
 
31
31
  module GRATR
32
32
  class NoVertexError < IndexError; end
@@ -64,11 +64,13 @@ module GRATR
64
64
 
65
65
  # A digraph uses the Arc class for edges
66
66
  def edge_class() @parallel_edges ? GRATR::MultiArc : GRATR::Arc; end
67
-
67
+
68
68
  # Reverse all edges in a graph
69
69
  def reversal
70
- return new(self) unless directed?
71
- edges.inject(self.class.new) {|a,e| a << e.reverse}
70
+ result = self.class.new
71
+ edges.inject(result) {|a,e| a << e.reverse}
72
+ vertices.each { |v| result.add_vertex!(v) unless result.vertex?(v) }
73
+ result
72
74
  end
73
75
 
74
76
  # Return true if the Graph is oriented.
@@ -7,6 +7,12 @@
7
7
  #
8
8
  # It also supports undirected edges.
9
9
 
10
+ class Hash
11
+ def stringify_keys
12
+ inject({}) {|options, (key, value)| options[key.to_s] = value; options}
13
+ end
14
+ end unless Hash.respond_to? :stringify_keys
15
+
10
16
  module DOT
11
17
 
12
18
  # These glogal vars are used to make nice graph source.
@@ -26,7 +26,6 @@
26
26
  # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
27
  #++
28
28
 
29
-
30
29
  module GRATR
31
30
  module Graph
32
31
  module Search
@@ -102,7 +101,7 @@ module GRATR
102
101
  roots = []
103
102
  te = Proc.new {|e| predecessor[e.target] = e.source}
104
103
  rv = Proc.new {|v| roots << v}
105
- method(routine).call :start => start, :tree_edge => te, :root_vertex => rv
104
+ send routine, :start => start, :tree_edge => te, :root_vertex => rv
106
105
  [predecessor, roots]
107
106
  end
108
107
 
@@ -120,7 +119,7 @@ module GRATR
120
119
  correct_tree = false
121
120
  te = Proc.new {|e| predecessor[e.target] = e.source if correct_tree}
122
121
  rv = Proc.new {|v| correct_tree = (v == start)}
123
- method(routine).call :start => start, :tree_edge => te, :root_vertex => rv
122
+ send routine, :start => start, :tree_edge => te, :root_vertex => rv
124
123
  predecessor
125
124
  end
126
125
 
@@ -252,44 +251,58 @@ module GRATR
252
251
  # Also see: http://en.wikipedia.org/wiki/A-star_search_algorithm
253
252
  #
254
253
  def astar(start, goal, func, options, &block)
255
- options.instance_eval "def handle_vertex(sym,u) self[sym].call(u) if self[sym]; end"
256
- options.instance_eval "def handle_edge(sym,u,v) self[sym].call(#{edge_class}[u,v]) if self[sym]; end"
257
-
254
+ options.instance_eval "def handle_callback(sym,u) self[sym].call(u) if self[sym]; end"
255
+
256
+ # Initialize
258
257
  d = { start => 0 }
259
- f = { start => func.call(start) }
260
- color = {start => :gray}
261
- p = Hash.new {|k| p[k] = k}
262
- queue = [start]
258
+
259
+ color = {start => :gray} # Open is :gray, Closed is :black
260
+ parent = Hash.new {|k| parent[k] = k}
261
+ f = {start => func.call(start)}
262
+ queue = PriorityQueue.new.push(start,f[start])
263
263
  block.call(start) if block
264
+
265
+ # Process queue
264
266
  until queue.empty?
265
- u = queue.pop
266
- options.handle_vertex(:examine_vertex, u)
267
- adjacent(u).each do |v|
268
- e = edge_class[u,v]
269
- options.handle_edge(:examine_edge, u, v)
267
+ u,dummy = queue.delete_min
268
+ options.handle_callback(:examine_vertex, u)
269
+
270
+ # Unravel solution if goal is reached.
271
+ if u == goal
272
+ solution = [goal]
273
+ while u != start
274
+ solution << parent[u]; u = parent[u]
275
+ end
276
+ return solution.reverse
277
+ end
278
+
279
+ adjacent(u, :type => :edges).each do |e|
280
+ v = e.source == u ? e.target : e.source
281
+ options.handle_callback(:examine_edge, e)
270
282
  w = cost(e, options[:weight])
271
283
  raise ArgumentError unless w
272
284
  if d[v].nil? or (w + d[u]) < d[v]
273
- options.handle_edge(:edge_relaxed, u, v)
285
+ options.handle_callback(:edge_relaxed, e)
274
286
  d[v] = w + d[u]
275
- f[v] = d[v] + func.call(u)
276
- p[v] = u
287
+ f[v] = d[v] + func.call(v)
288
+ parent[v] = u
277
289
  unless color[v] == :gray
278
- options.handle_vertex(:black_target, v) if color[v] == :black
290
+ options.handle_callback(:black_target, v) if color[v] == :black
279
291
  color[v] = :gray
280
- options.handle_vertex(:discover_vertex, v)
281
- queue << v
292
+ options.handle_callback(:discover_vertex, v)
293
+ queue.push v, f[v]
282
294
  block.call(v) if block
283
- return [start]+queue if v == goal
284
295
  end
285
296
  else
286
- options.handle_edge(:edge_not_relaxed, u, v)
297
+ options.handle_callback(:edge_not_relaxed, e)
287
298
  end
288
299
  end # adjacent(u)
289
300
  color[u] = :black
290
- options.handle_vertex(:finish_vertex,u)
301
+ options.handle_callback(:finish_vertex,u)
291
302
  end # queue.empty?
303
+
292
304
  nil # failure, on fall through
305
+
293
306
  end # astar
294
307
 
295
308
  # Best first has all the same options as astar with func set to h(v) = 0.
@@ -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