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,35 @@
1
+ module Plexus
2
+ # This module defines the minimum set of functions required to make a graph that can
3
+ # use the algorithms defined by this library.
4
+ #
5
+ # Each implementation module must implement the following routines:
6
+ #
7
+ # * directed? # Is the graph directed?
8
+ # * add_vertex!(v,l=nil) # Add a vertex to the graph and return the graph. `l` is an optional label.
9
+ # * add_edge!(u,v=nil,l=nil) # Add an edge to the graph and return the graph. `u` can be an {Arc} or {Edge}, or `u,v` a {Edge} pair. The last parameter `l` is an optional label.
10
+ # * remove_vertex!(v) # Remove a vertex to the graph and return the graph.
11
+ # * remove_edge!(u,v=nil) # Remove an edge from the graph and return the graph.
12
+ # * vertices # Returns an array of of all vertices.
13
+ # * edges # Returns an array of all edges.
14
+ # * edge_class # Returns the class used to store edges.
15
+ module GraphAPI
16
+ # @raise if the API is not completely implemented
17
+ def self.included(klass)
18
+ @api_methods ||= [:directed?, :add_vertex!, :add_edge!, :remove_vertex!, :remove_edge!, :vertices, :edges, :edge_class]
19
+ ruby_18 { @api_methods.each { |m| m.to_s } }
20
+
21
+ @api_methods.each do |meth|
22
+ raise "Must implement #{meth}" unless klass.instance_methods.include?(meth)
23
+ end
24
+
25
+ klass.class_eval do
26
+ # Is this right?
27
+ alias remove_arc! remove_edge!
28
+ alias add_arc! add_edge!
29
+ alias arcs edges
30
+ alias arc_class edge_class
31
+ end
32
+ end
33
+
34
+ end # GraphAPI
35
+ end # Plexus
@@ -0,0 +1,112 @@
1
+ module Plexus
2
+ # This module add support for labels.
3
+ #
4
+ # The graph labeling process consist in assigning labels, traditionally represented
5
+ # by integers, to the edges or vertices, or both, of a graph. Plexus recommands you
6
+ # abide by this rule and do use integers as labels.
7
+ #
8
+ # Some algorithms can make use of labeling (sea {Plexus::Search} for instance).
9
+ module Labels
10
+
11
+ # Return a label for an edge or vertex.
12
+ def [](u)
13
+ (u.is_a? Plexus::Arc) ? edge_label(u) : vertex_label(u)
14
+ end
15
+
16
+ # Set a label for an edge or vertex.
17
+ def []=(u, value)
18
+ (u.is_a? Plexus::Arc) ? edge_label_set(u, value) : vertex_label_set(u, value)
19
+ end
20
+
21
+ # Delete a label entirely.
22
+ def delete_label(u)
23
+ (u.is_a? Plexus::Arc) ? edge_label_delete(u) : vertex_label_delete(u)
24
+ end
25
+
26
+ # Get the label for an edge.
27
+ def vertex_label(v)
28
+ vertex_label_dict[v]
29
+ end
30
+
31
+ # Set the label for an edge.
32
+ def vertex_label_set(v, l)
33
+ vertex_label_dict[v] = l
34
+ self
35
+ end
36
+
37
+ # Get the label for an edge.
38
+ def edge_label(u, v = nil, n = nil)
39
+ u = edge_convert(u,v,n)
40
+ edge_label_dict[u]
41
+ end
42
+
43
+ # Set the label for an edge.
44
+ def edge_label_set(u, v = nil, l = nil, n = nil)
45
+ u.is_a?(Plexus::Arc) ? l = v : u = edge_convert(u, v, n)
46
+ edge_label_dict[u] = l
47
+ self
48
+ end
49
+
50
+ # Delete all graph labels.
51
+ def clear_all_labels
52
+ @vertex_labels = {}
53
+ @edge_labels = {}
54
+ end
55
+
56
+ # Delete an edge label.
57
+ def edge_label_delete(u, v = nil, n = nil)
58
+ u = edge_convert(u, v, n)
59
+ edge_label_dict.delete(u)
60
+ end
61
+
62
+ # Delete a vertex label.
63
+ def vertex_label_delete(v)
64
+ vertex_label_dict.delete(v)
65
+ end
66
+
67
+ protected
68
+
69
+ def vertex_label_dict
70
+ @vertex_labels ||= {}
71
+ end
72
+
73
+ def edge_label_dict
74
+ @edge_labels ||= {}
75
+ end
76
+
77
+ # A generic cost function.
78
+ #
79
+ # It either calls the `weight` function with an edge constructed from the
80
+ # two specified nodes, or calls the `[]` operator of the label when given
81
+ # a single value.
82
+ #
83
+ # If no weight value is specified, the label itself is treated as the cost value.
84
+ #
85
+ # Note: This function will not work for Pseudo or Multi graphs at present.
86
+ # FIXME: Remove u,v interface to fix Pseudo Multi graph problems.
87
+ def cost(u, v = nil, weight = nil)
88
+ u.is_a?(Arc) ? weight = v : u = edge_class[u,v]
89
+ case weight
90
+ when Proc
91
+ weight.call(u)
92
+ when nil
93
+ self[u]
94
+ else
95
+ self[u][weight]
96
+ end
97
+ end
98
+ alias property cost # makes sense for property retrieval in general
99
+
100
+ # A function to set properties specified by the user.
101
+ def property_set(u, name, value)
102
+ case name
103
+ when Proc
104
+ name.call(value)
105
+ when nil
106
+ self[u] = value
107
+ else
108
+ self[u][name] = value
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,77 @@
1
+ module Plexus
2
+ class Network
3
+ include DigraphBuilder
4
+
5
+ attr_accessor :lower, :upper, :cost, :flow
6
+
7
+ def residual(residual_capacity, cost_property, zero = 0)
8
+ r = Digraph.new
9
+ edges.each do |e1|
10
+ [e1,e1.reverse].each do |e|
11
+ rij = property(e,self.upper) - property(e,self.flow) if edge? e
12
+ rij += property(e.reverse,self.flow) - property(e.reverse,self.lower) if edge? e.reverse
13
+ r.add_edge!(e) if rij > zero
14
+ r.property_set(e,residual_capacity, rij)
15
+ r.property_set(e,cost, cost(e,cost_property))
16
+ end
17
+ end
18
+ r
19
+ end
20
+
21
+ def maximum_flow() eliminate_lower_bounds.maximum_flow_prime.restore_lower_bounds(self); end
22
+
23
+ private
24
+
25
+ def eliminate_lower_bounds
26
+ no_lower_bounds = Digraph.new(self)
27
+ if self.upper.kind_of? Proc then
28
+ no_lower_bounds.upper = Proc.new {|e| self.upper.call(e) - property(e,self.lower) }
29
+ else
30
+ no_lower_bounds.edges.each {|e| no_lower_bounds[e][self.upper] -= property(e,self.lower)}
31
+ end
32
+ no_lower_bounds
33
+ end
34
+
35
+ def restore_lower_bounds(src)
36
+ src.edges.each do |e|
37
+ (src.flow ? src[e][src.flow] : src[e]) = property(e, self.flow) + src.property(e, self.lower)
38
+ end
39
+ src
40
+ end
41
+
42
+ def maximum_flow_prime
43
+ end
44
+ end # Network
45
+
46
+ module GraphBuilder
47
+ module MaximumFlow
48
+ # Maximum flow, it returns an array with the maximum flow and a hash of flow per edge
49
+ # Currently a highly inefficient implementation, FIXME, This should use Goldberg and Tarjan's method.
50
+ def maximum_flow(s, t, capacity = nil, zero = 0)
51
+ flow = Hash.new(zero)
52
+ available = Hash.new(zero)
53
+ total = zero
54
+ edges.each {|e| available[e] = cost(e,capacity)}
55
+ adj_positive = Proc.new do |u|
56
+ adjacent(u).select {|r| available[edge_class[u,r]] > zero}
57
+ end
58
+ while (tree = bfs_tree_from_vertex(start))[t]
59
+ route = [t]
60
+ while route[-1] != s
61
+ route << tree[route[route[-1]]]
62
+ raise ArgumentError, "No route from #{s} to #{t} possible"
63
+ end; route.reverse
64
+ amt = route.map {|e| available[e]}.min
65
+ route.each do |e|
66
+ flow[e] += amt
67
+ available[e] -= amt
68
+ end
69
+ total += amt
70
+ end
71
+
72
+ [total, flow]
73
+ end
74
+
75
+ end # MaximumFlow
76
+ end # GraphBuilder
77
+ end # Plexus
@@ -0,0 +1,17 @@
1
+ if RUBY_VERSION < "1.9"
2
+ def ruby_18
3
+ yield
4
+ end
5
+
6
+ def ruby_19
7
+ false
8
+ end
9
+ else
10
+ def ruby_18
11
+ false
12
+ end
13
+
14
+ def ruby_19
15
+ yield
16
+ end
17
+ end
@@ -0,0 +1,510 @@
1
+ module Plexus
2
+ # **Search/traversal algorithms.**
3
+ #
4
+ # This module defines a collection of search/traversal algorithms, in a unified API.
5
+ # Read through the doc to get familiar with the calling pattern.
6
+ #
7
+ # Options are mostly callbacks passed in as a hash. The following are valid,
8
+ # anything else is ignored:
9
+ #
10
+ # * `:enter_vertex` => `Proc` Called upon entry of a vertex.
11
+ # * `:exit_vertex` => `Proc` Called upon exit of a vertex.
12
+ # * `:root_vertex` => `Proc` Called when a vertex is the root of a tree.
13
+ # * `:start_vertex` => `Proc` Called for the first vertex of the search.
14
+ # * `:examine_edge` => `Proc` Called when an edge is examined.
15
+ # * `:tree_edge` => `Proc` Called when the edge is a member of the tree.
16
+ # * `:back_edge` => `Proc` Called when the edge is a back edge.
17
+ # * `:forward_edge` => `Proc` Called when the edge is a forward edge.
18
+ # * `:adjacent` => `Proc` which, given a vertex, returns adjacent nodes, defaults to adjacent call of graph useful for changing the definition of adjacent in some algorithms.
19
+ # * `:start` => vertex Specifies the vertex to start search from.
20
+ #
21
+ # If a `&block` instead of an option hash is specified, it defines `:enter_vertex`.
22
+ #
23
+ # Each search algorithm returns the list of vertexes as reached by `enter_vertex`.
24
+ # This allows for calls like, `g.bfs.each { |v| ... }`
25
+ #
26
+ # Can also be called like `bfs_examine_edge { |e| ... }` or
27
+ # `dfs_back_edge { |e| ... }` for any of the callbacks.
28
+ #
29
+ # A full example usage is as follows:
30
+ #
31
+ # ev = Proc.new { |x| puts "Enter vertex #{x}" }
32
+ # xv = Proc.new { |x| puts "Exit vertex #{x}" }
33
+ # sv = Proc.new { |x| puts "Start vertex #{x}" }
34
+ # ee = Proc.new { |x| puts "Examine Arc #{x}" }
35
+ # te = Proc.new { |x| puts "Tree Arc #{x}" }
36
+ # be = Proc.new { |x| puts "Back Arc #{x}" }
37
+ # fe = Proc.new { |x| puts "Forward Arc #{x}" }
38
+ # Digraph[1,2, 2,3, 3,4].dfs({
39
+ # :enter_vertex => ev,
40
+ # :exit_vertex => xv,
41
+ # :start_vertex => sv,
42
+ # :examine_edge => ee,
43
+ # :tree_edge => te,
44
+ # :back_edge => be,
45
+ # :forward_edge => fe })
46
+ #
47
+ # Which outputs:
48
+ #
49
+ # Start vertex 1
50
+ # Enter vertex 1
51
+ # Examine Arc (1=2)
52
+ # Tree Arc (1=2)
53
+ # Enter vertex 2
54
+ # Examine Arc (2=3)
55
+ # Tree Arc (2=3)
56
+ # Enter vertex 3
57
+ # Examine Arc (3=4)
58
+ # Tree Arc (3=4)
59
+ # Enter vertex 4
60
+ # Examine Arc (1=4)
61
+ # Back Arc (1=4)
62
+ # Exit vertex 4
63
+ # Exit vertex 3
64
+ # Exit vertex 2
65
+ # Exit vertex 1
66
+ # => [1, 2, 3, 4]
67
+ module Search
68
+
69
+ # Performs a breadth-first search.
70
+ #
71
+ # @param [Hash] options
72
+ def bfs(options = {}, &block)
73
+ plexus_search_helper(:shift, options, &block)
74
+ end
75
+ alias :bread_first_search :bfs
76
+
77
+ # Performs a depth-first search.
78
+ #
79
+ # @param [Hash] options
80
+ def dfs(options = {}, &block)
81
+ plexus_search_helper(:pop, options, &block)
82
+ end
83
+ alias :depth_first_search :dfs
84
+
85
+ # Routine which computes a spanning forest for the given search method.
86
+ # Returns two values: a hash of predecessors and an array of root nodes.
87
+ #
88
+ # @param [vertex] start
89
+ # @param [Symbol] routine the search method (`:dfs`, `:bfs`)
90
+ # @return [Array] predecessors and root nodes
91
+ def spanning_forest(start, routine)
92
+ predecessor = {}
93
+ roots = []
94
+ te = Proc.new { |e| predecessor[e.target] = e.source }
95
+ rv = Proc.new { |v| roots << v }
96
+ send routine, :start => start, :tree_edge => te, :root_vertex => rv
97
+ [predecessor, roots]
98
+ end
99
+
100
+ # Returns the dfs spanning forest for the given start node, see {Search#spanning_forest spanning_forest}.
101
+ #
102
+ # @param [vertex] start
103
+ # @return [Array] predecessors and root nodes
104
+ def dfs_spanning_forest(start)
105
+ spanning_forest(start, :dfs)
106
+ end
107
+
108
+ # Returns the bfs spanning forest for the given start node, see {Search#spanning_forest spanning_forest}.
109
+ #
110
+ # @param [vertex] start
111
+ # @return [Array] predecessors and root nodes
112
+ def bfs_spanning_forest(start)
113
+ spanning_forest(start, :bfs)
114
+ end
115
+
116
+ # Returns a hash of predecessors in a tree rooted at the start node. If this is a connected graph,
117
+ # then it will be a spanning tree containing all vertices. An easier way to tell if it's a
118
+ # spanning tree is to use a {Search#spanning_forest spanning_forest} call and check if there is a
119
+ # single root node.
120
+ #
121
+ # @param [vertex] start
122
+ # @param [Symbol] routine the search method (`:dfs`, `:bfs`)
123
+ # @return [Hash] predecessors vertices
124
+ def tree_from_vertex(start, routine)
125
+ predecessor = {}
126
+ correct_tree = false
127
+ te = Proc.new { |e| predecessor[e.target] = e.source if correct_tree }
128
+ rv = Proc.new { |v| correct_tree = (v == start) }
129
+ send routine, :start => start, :tree_edge => te, :root_vertex => rv
130
+ predecessor
131
+ end
132
+
133
+ # Returns a hash of predecessors for the depth-first search tree rooted at the given node.
134
+ #
135
+ # @param [vertex] start
136
+ # @return [Hash] predecessors vertices
137
+ def dfs_tree_from_vertex(start)
138
+ tree_from_vertex(start, :dfs)
139
+ end
140
+
141
+ # Returns a hash of predecessors for the breadth-first search tree rooted at the given node.
142
+ #
143
+ # @param [Proc] start
144
+ # @return [Hash] predecessors vertices
145
+ def bfs_tree_from_vertex(start)
146
+ tree_from_vertex(start, :bfs)
147
+ end
148
+
149
+ # An inner class used for greater efficiency in {Search#lexicograph_bfs}.
150
+ #
151
+ # Original design taken from Golumbic's, *Algorithmic Graph Theory and Perfect Graphs* pg. 87-89.
152
+ class LexicographicQueue
153
+ # Called with the initial values.
154
+ #
155
+ # @param [Array] initial vertices values
156
+ def initialize(values)
157
+ @node = Struct.new(:back, :forward, :data)
158
+ @node.class_eval do
159
+ def hash
160
+ @hash
161
+ end
162
+ @@cnt = 0
163
+ end
164
+ @set = {}
165
+ @tail = @node.new(nil, nil, Array.new(values))
166
+ @tail.instance_eval { @hash = (@@cnt += 1) }
167
+ values.each { |a| @set[a] = @tail }
168
+ end
169
+
170
+ # Pops an entry with the maximum lexical value from the queue.
171
+ #
172
+ # @return [vertex]
173
+ def pop
174
+ return nil unless @tail
175
+ value = @tail[:data].pop
176
+ @tail = @tail[:forward] while @tail and @tail[:data].size == 0
177
+ @set.delete(value)
178
+ value
179
+ end
180
+
181
+ # Increase the lexical value of the given values.
182
+ #
183
+ # @param [Array] vertices values
184
+ def add_lexeme(values)
185
+ fix = {}
186
+
187
+ values.select { |v| @set[v] }.each do |w|
188
+ sw = @set[w]
189
+ if fix[sw]
190
+ s_prime = sw[:back]
191
+ else
192
+ s_prime = @node.new(sw[:back], sw, [])
193
+ s_prime.instance_eval { @hash = (@@cnt += 1) }
194
+ @tail = s_prime if @tail == sw
195
+ sw[:back][:forward] = s_prime if sw[:back]
196
+ sw[:back] = s_prime
197
+ fix[sw] = true
198
+ end
199
+
200
+ s_prime[:data] << w
201
+ sw[:data].delete(w)
202
+ @set[w] = s_prime
203
+ end
204
+
205
+ fix.keys.select { |n| n[:data].size == 0 }.each do |e|
206
+ e[:forward][:back] = e[:back] if e[:forward]
207
+ e[:back][:forward] = e[:forward] if e[:back]
208
+ end
209
+ end
210
+
211
+ end
212
+
213
+ # Lexicographic breadth-first search.
214
+ #
215
+ # The usual queue of vertices is replaced by a queue of *unordered subsets*
216
+ # of the vertices, which is sometimes refined but never reordered.
217
+ #
218
+ # Originally developed by Rose, Tarjan, and Leuker, *Algorithmic
219
+ # aspects of vertex elimination on graphs*, SIAM J. Comput. 5, 266-283
220
+ # MR53 #12077
221
+ #
222
+ # Implementation taken from Golumbic's, *Algorithmic Graph Theory and
223
+ # Perfect Graphs*, pg. 84-90.
224
+ #
225
+ # @return [vertex]
226
+ def lexicograph_bfs(&block)
227
+ lex_q = Plexus::Search::LexicographicQueue.new(vertices)
228
+ result = []
229
+ num_vertices.times do
230
+ v = lex_q.pop
231
+ result.unshift(v)
232
+ lex_q.add_lexeme(adjacent(v))
233
+ end
234
+ result.each { |r| block.call(r) } if block
235
+ result
236
+ end
237
+
238
+ # A* Heuristic best first search.
239
+ #
240
+ # `start` is the starting vertex for the search.
241
+ #
242
+ # `func` is a `Proc` that when passed a vertex returns the heuristic
243
+ # weight of sending the path through that node. It must always
244
+ # be equal to or less than the true cost.
245
+ #
246
+ # `options` are mostly callbacks passed in as a hash, the default block is
247
+ # `:discover_vertex` and the weight is assumed to be the label for the {Arc}.
248
+ # The following options are valid, anything else is ignored:
249
+ #
250
+ # * `:weight` => can be a `Proc`, or anything else is accessed using the `[]` for the
251
+ # the label or it defaults to using
252
+ # the value stored in the label for the {Arc}. If it is a `Proc` it will
253
+ # pass the edge to the proc and use the resulting value.
254
+ # * `:discover_vertex` => `Proc` invoked when a vertex is first discovered
255
+ # and is added to the open list.
256
+ # * `:examine_vertex` => `Proc` invoked when a vertex is popped from the
257
+ # queue (i.e., it has the lowest cost on the open list).
258
+ # * `:examine_edge` => `Proc` invoked on each out-edge of a vertex
259
+ # immediately after it is examined.
260
+ # * `:edge_relaxed` => `Proc` invoked on edge `(u,v) if d[u] + w(u,v) < d[v]`.
261
+ # * `:edge_not_relaxed`=> `Proc` invoked if the edge is not relaxed (see above).
262
+ # * `:black_target` => `Proc` invoked when a vertex that is on the closed
263
+ # list is "rediscovered" via a more efficient path, and is re-added
264
+ # to the open list.
265
+ # * `:finish_vertex` => Proc invoked on a vertex when it is added to the
266
+ # closed list, which happens after all of its out edges have been
267
+ # examined.
268
+ #
269
+ # Can also be called like `astar_examine_edge {|e| ... }` or
270
+ # `astar_edge_relaxed {|e| ... }` for any of the callbacks.
271
+ #
272
+ # The criteria for expanding a vertex on the open list is that it has the
273
+ # lowest `f(v) = g(v) + h(v)` value of all vertices on open.
274
+ #
275
+ # The time complexity of A* depends on the heuristic. It is exponential
276
+ # in the worst case, but is polynomial when the heuristic function h
277
+ # meets the following condition: `|h(x) - h*(x)| < O(log h*(x))` where `h*`
278
+ # is the optimal heuristic, i.e. the exact cost to get from `x` to the `goal`.
279
+ #
280
+ # See also: [A* search algorithm](http://en.wikipedia.org/wiki/A*_search_algorithm) on Wikipedia.
281
+ #
282
+ # @param [vertex] start the starting vertex for the search
283
+ # @param [vertex] goal the vertex to reach
284
+ # @param [Proc] func heuristic weight computing process
285
+ # @param [Hash] options
286
+ # @return [Array(vertices), call, nil] an array of nodes in path, or calls block on all nodes,
287
+ # upon failure returns `nil`
288
+ def astar(start, goal, func, options, &block)
289
+ options.instance_eval "def handle_callback(sym,u) self[sym].call(u) if self[sym]; end"
290
+
291
+ # Initialize.
292
+ d = { start => 0 }
293
+ color = { start => :gray } # Open is :gray, closed is :black.
294
+ parent = Hash.new { |k| parent[k] = k }
295
+ f = { start => func.call(start) }
296
+ queue = PriorityQueue.new.push(start, f[start])
297
+ block.call(start) if block
298
+
299
+ # Process queue.
300
+ until queue.empty?
301
+ u, dummy = queue.delete_min
302
+ options.handle_callback(:examine_vertex, u)
303
+
304
+ # Unravel solution if the goal is reached.
305
+ if u == goal
306
+ solution = [goal]
307
+ while u != start
308
+ solution << parent[u]
309
+ u = parent[u]
310
+ end
311
+ return solution.reverse
312
+ end
313
+
314
+ adjacent(u, :type => :edges).each do |e|
315
+ v = e.source == u ? e.target : e.source
316
+ options.handle_callback(:examine_edge, e)
317
+ w = cost(e, options[:weight])
318
+ raise ArgumentError unless w
319
+
320
+ if d[v].nil? or (w + d[u]) < d[v]
321
+ options.handle_callback(:edge_relaxed, e)
322
+ d[v] = w + d[u]
323
+ f[v] = d[v] + func.call(v)
324
+ parent[v] = u
325
+
326
+ unless color[v] == :gray
327
+ options.handle_callback(:black_target, v) if color[v] == :black
328
+ color[v] = :gray
329
+ options.handle_callback(:discover_vertex, v)
330
+ queue.push v, f[v]
331
+ block.call(v) if block
332
+ end
333
+ else
334
+ options.handle_callback(:edge_not_relaxed, e)
335
+ end
336
+ end # adjacent(u)
337
+
338
+ color[u] = :black
339
+ options.handle_callback(:finish_vertex, u)
340
+ end # queue.empty?
341
+
342
+ nil # failure, on fall through
343
+ end # astar
344
+
345
+ # `best_first` has all the same options as {Search#astar astar}, with `func` set to `h(v) = 0`.
346
+ # There is an additional option, `zero`, which should be defined as the zero element
347
+ # for the `+` operation performed on the objects used in the computation of cost.
348
+ #
349
+ # @param [vertex] start the starting vertex for the search
350
+ # @param [vertex] goal the vertex to reach
351
+ # @param [Proc] func heuristic weight computing process
352
+ # @param [Hash] options
353
+ # @param [Integer] zero (0)
354
+ # @return [Array(vertices), call, nil] an array of nodes in path, or calls block on all nodes,
355
+ # upon failure returns `nil`
356
+ def best_first(start, goal, options, zero = 0, &block)
357
+ func = Proc.new { |v| zero }
358
+ astar(start, goal, func, options, &block)
359
+ end
360
+
361
+ # @private
362
+ alias_method :pre_search_method_missing, :method_missing
363
+ def method_missing(sym, *args, &block)
364
+ m1 = /^dfs_(\w+)$/.match(sym.to_s)
365
+ dfs((args[0] || {}).merge({ m1.captures[0].to_sym => block })) if m1
366
+ m2 = /^bfs_(\w+)$/.match(sym.to_s)
367
+ bfs((args[0] || {}).merge({ m2.captures[0].to_sym => block })) if m2
368
+ pre_search_method_missing(sym, *args, &block) unless m1 or m2
369
+ end
370
+
371
+ private
372
+
373
+ # Performs the search using a specific algorithm and a set of options.
374
+ #
375
+ # @param [Symbol] op the algorithm to be used te perform the search
376
+ # @param [Hash] options
377
+ # @return [Object] result
378
+ def plexus_search_helper(op, options = {}, &block)
379
+ return nil if size == 0
380
+ result = []
381
+
382
+ # Create the options hash handling callbacks.
383
+ options = {:enter_vertex => block, :start => to_a[0]}.merge(options)
384
+ options.instance_eval "def handle_vertex(sym,u) self[sym].call(u) if self[sym]; end"
385
+ options.instance_eval "def handle_edge(sym,e) self[sym].call(e) if self[sym]; end"
386
+
387
+ # Create a waiting list, which is a queue or a stack, depending on the op specified.
388
+ # The first entry is the start vertex.
389
+ waiting = [options[:start]]
390
+ waiting.instance_eval "def next; #{op.to_s}; end"
391
+
392
+ # Create a color map, all elements set to "unvisited" except for start vertex,
393
+ # which will be set to waiting.
394
+ color_map = vertices.inject({}) { |a,v| a[v] = :unvisited; a }
395
+ color_map.merge!(waiting[0] => :waiting)
396
+ options.handle_vertex(:start_vertex, waiting[0])
397
+ options.handle_vertex(:root_vertex, waiting[0])
398
+
399
+ # Perform the actual search until nothing is "waiting".
400
+ until waiting.empty?
401
+ # Loop till the search iterator exhausts the waiting list.
402
+ visited_edges = {} # This prevents retraversing edges in undirected graphs.
403
+ until waiting.empty?
404
+ plexus_search_iteration(options, waiting, color_map, visited_edges, result, op == :pop)
405
+ end
406
+ # Waiting for the list to be exhausted, check if a new root vertex is available.
407
+ u = color_map.detect { |key,value| value == :unvisited }
408
+ waiting.push(u[0]) if u
409
+ options.handle_vertex(:root_vertex, u[0]) if u
410
+ end
411
+
412
+ result
413
+ end
414
+
415
+ # Performs a search iteration (step).
416
+ #
417
+ # @private
418
+ def plexus_search_iteration(options, waiting, color_map, visited_edges, result, recursive = false)
419
+ # Fetch the next waiting vertex in the list.
420
+ #sleep
421
+ u = waiting.next
422
+ options.handle_vertex(:enter_vertex, u)
423
+ result << u
424
+
425
+ # Examine all adjacent outgoing edges, but only those not previously traversed.
426
+ adj_proc = options[:adjacent] || self.method(:adjacent).to_proc
427
+ adj_proc.call(u, :type => :edges, :direction => :out).reject { |w| visited_edges[w] }.each do |e|
428
+ e = e.reverse unless directed? or e.source == u # Preserves directionality where required.
429
+ v = e.target
430
+ options.handle_edge(:examine_edge, e)
431
+ visited_edges[e] = true
432
+
433
+ case color_map[v]
434
+ # If it's unvisited, it goes into the waiting list.
435
+ when :unvisited
436
+ options.handle_edge(:tree_edge, e)
437
+ color_map[v] = :waiting
438
+ waiting.push(v)
439
+ # If it's recursive (i.e. dfs), then call self.
440
+ plexus_search_iteration(options, waiting, color_map, visited_edges, result, true) if recursive
441
+ when :waiting
442
+ options.handle_edge(:back_edge, e)
443
+ else
444
+ options.handle_edge(:forward_edge, e)
445
+ end
446
+ end
447
+
448
+ # Done with this vertex!
449
+ options.handle_vertex(:exit_vertex, u)
450
+ color_map[u] = :visited
451
+ end
452
+
453
+ public
454
+
455
+ # Topological Sort Iterator.
456
+ #
457
+ # The topological sort algorithm creates a linear ordering of the vertices
458
+ # such that if edge (u,v) appears in the graph, then u comes before v in
459
+ # the ordering. The graph must be a directed acyclic graph (DAG).
460
+ #
461
+ # The iterator can also be applied to undirected graph or to a DG graph
462
+ # which contains a cycle. In this case, the Iterator does not reach all
463
+ # vertices. The implementation of acyclic? and cyclic? uses this fact.
464
+ #
465
+ # Can be called with a block as a standard ruby iterator, or can
466
+ # be used directly as it will return the result as an Array.
467
+ #
468
+ # @param [vertex] start (nil) the start vertex (nil will fallback on the first
469
+ # vertex inserted within the graph)
470
+ # @return [Array] a linear representation of the sorted graph
471
+ def topsort(start = nil, &block)
472
+ result = []
473
+ go = true
474
+ back = Proc.new { |e| go = false }
475
+ push = Proc.new { |v| result.unshift(v) if go }
476
+ start ||= vertices[0]
477
+ dfs({ :exit_vertex => push, :back_edge => back, :start => start })
478
+ result.each { |v| block.call(v) } if block
479
+ result
480
+ end
481
+
482
+ # Does a top sort, but trudges forward if a cycle occurs. Use with caution.
483
+ #
484
+ # @param [vertex] start (nil) the start vertex (nil will fallback on the first
485
+ # vertex inserted within the graph)
486
+ # @return [Array] a linear representation of the sorted graph
487
+ def sort(start = nil, &block)
488
+ result = []
489
+ push = Proc.new { |v| result.unshift(v) }
490
+ start ||= vertices[0]
491
+ dfs({ :exit_vertex => push, :start => start })
492
+ result.each { |v| block.call(v) } if block
493
+ result
494
+ end
495
+
496
+ # Returns true if a graph contains no cycles, false otherwise.
497
+ #
498
+ # @return [Boolean]
499
+ def acyclic?
500
+ topsort.size == size
501
+ end
502
+
503
+ # Returns false if a graph contains no cycles, true otherwise.
504
+ #
505
+ # @return [Boolean]
506
+ def cyclic?
507
+ not acyclic?
508
+ end
509
+ end
510
+ end