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 +7 -0
- data/lib/gratr.rb +9 -0
- data/lib/gratr/base.rb +1 -1
- data/lib/gratr/digraph.rb +5 -3
- data/lib/gratr/rdot.rb +6 -0
- data/lib/gratr/search.rb +37 -24
- data/lib/priority-queue/benchmark/dijkstra.rb +171 -0
- data/lib/priority-queue/compare_comments.rb +49 -0
- data/lib/priority-queue/ext/priority_queue/CPriorityQueue/extconf.rb +2 -0
- data/lib/priority-queue/lib/priority_queue.rb +14 -0
- data/lib/priority-queue/lib/priority_queue/c_priority_queue.rb +1 -0
- data/lib/priority-queue/lib/priority_queue/poor_priority_queue.rb +46 -0
- data/lib/priority-queue/lib/priority_queue/ruby_priority_queue.rb +525 -0
- data/lib/priority-queue/setup.rb +1551 -0
- data/lib/priority-queue/test/priority_queue_test.rb +371 -0
- data/tests/TestDigraph.rb +3 -3
- data/tests/TestDot.rb +75 -0
- data/tests/TestSearch.rb +36 -16
- metadata +12 -2
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.
|
data/lib/gratr.rb
CHANGED
@@ -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
|
data/lib/gratr/base.rb
CHANGED
data/lib/gratr/digraph.rb
CHANGED
@@ -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
|
-
|
71
|
-
edges.inject(
|
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.
|
data/lib/gratr/rdot.rb
CHANGED
@@ -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.
|
data/lib/gratr/search.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
256
|
-
|
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
|
-
|
260
|
-
color = {start => :gray}
|
261
|
-
|
262
|
-
|
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.
|
266
|
-
options.
|
267
|
-
|
268
|
-
|
269
|
-
|
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.
|
285
|
+
options.handle_callback(:edge_relaxed, e)
|
274
286
|
d[v] = w + d[u]
|
275
|
-
f[v] = d[v] + func.call(
|
276
|
-
|
287
|
+
f[v] = d[v] + func.call(v)
|
288
|
+
parent[v] = u
|
277
289
|
unless color[v] == :gray
|
278
|
-
options.
|
290
|
+
options.handle_callback(:black_target, v) if color[v] == :black
|
279
291
|
color[v] = :gray
|
280
|
-
options.
|
281
|
-
queue
|
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.
|
297
|
+
options.handle_callback(:edge_not_relaxed, e)
|
287
298
|
end
|
288
299
|
end # adjacent(u)
|
289
300
|
color[u] = :black
|
290
|
-
options.
|
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,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
|