gratr 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. data/Grater.xcf +0 -0
  2. data/README +328 -0
  3. data/Rakefile +220 -0
  4. data/examples/graph_self.rb +54 -0
  5. data/examples/module_graph.jpg +0 -0
  6. data/examples/module_graph.rb +12 -0
  7. data/examples/self_graph.jpg +0 -0
  8. data/examples/visualize.jpg +0 -0
  9. data/examples/visualize.rb +8 -0
  10. data/install.rb +49 -0
  11. data/lib/gratr.rb +33 -0
  12. data/lib/gratr/adjacency_graph.rb +230 -0
  13. data/lib/gratr/base.rb +34 -0
  14. data/lib/gratr/biconnected.rb +116 -0
  15. data/lib/gratr/chinese_postman.rb +123 -0
  16. data/lib/gratr/common.rb +73 -0
  17. data/lib/gratr/comparability.rb +92 -0
  18. data/lib/gratr/digraph.rb +113 -0
  19. data/lib/gratr/digraph_distance.rb +185 -0
  20. data/lib/gratr/dot.rb +90 -0
  21. data/lib/gratr/edge.rb +145 -0
  22. data/lib/gratr/graph.rb +315 -0
  23. data/lib/gratr/graph_api.rb +82 -0
  24. data/lib/gratr/import.rb +44 -0
  25. data/lib/gratr/labels.rb +103 -0
  26. data/lib/gratr/maximum_flow.rb +107 -0
  27. data/lib/gratr/rdot.rb +326 -0
  28. data/lib/gratr/search.rb +409 -0
  29. data/lib/gratr/strong_components.rb +127 -0
  30. data/lib/gratr/undirected_graph.rb +153 -0
  31. data/tests/TestBiconnected.rb +53 -0
  32. data/tests/TestChinesePostman.rb +53 -0
  33. data/tests/TestComplement.rb +54 -0
  34. data/tests/TestDigraph.rb +333 -0
  35. data/tests/TestDigraphDistance.rb +138 -0
  36. data/tests/TestEdge.rb +171 -0
  37. data/tests/TestInspection.rb +57 -0
  38. data/tests/TestMultiEdge.rb +57 -0
  39. data/tests/TestNeighborhood.rb +64 -0
  40. data/tests/TestProperties.rb +160 -0
  41. data/tests/TestSearch.rb +257 -0
  42. data/tests/TestStrongComponents.rb +85 -0
  43. data/tests/TestTriagulated.rb +137 -0
  44. data/tests/TestUndirectedGraph.rb +219 -0
  45. metadata +92 -0
@@ -0,0 +1,409 @@
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
+ module GRATR
31
+ module Graph
32
+ module Search
33
+
34
+ # Options are mostly callbacks passed in as a hash.
35
+ # The following are valid, anything else is ignored
36
+ # :enter_vertex => Proc Called upon entry of a vertex
37
+ # :exit_vertex => Proc Called upon exit of a vertex
38
+ # :root_vertex => Proc Called when a vertex the a root of a tree
39
+ # :start_vertex => Proc Called for the first vertex of the search
40
+ # :examine_edge => Proc Called when an edge is examined
41
+ # :tree_edge => Proc Called when the edge is a member of the tree
42
+ # :back_edge => Proc Called when the edge is a back edge
43
+ # :forward_edge => Proc Called when the edge is a forward edge
44
+ # :adjacent => Proc that given a vertex returns adjacent nodes, defaults to adjacent call of graph useful for changing the definition of adjacent in some algorithms
45
+ #
46
+ # :start => Vertex Specifies the vertex to start search from
47
+ #
48
+ # If a &block is specified it defaults to :enter_vertex
49
+ #
50
+ # Returns the list of vertexes as reached by enter_vertex
51
+ # This allows for calls like, g.bfs.each {|v| ...}
52
+ #
53
+ # Can also be called like bfs_examine_edge {|e| ... } or
54
+ # dfs_back_edge {|e| ... } for any of the callbacks
55
+ #
56
+ # A full example usage is as follows:
57
+ #
58
+ # ev = Proc.new {|x| puts "Enter Vertex #{x}"}
59
+ # xv = Proc.new {|x| puts "Exit Vertex #{x}"}
60
+ # sv = Proc.new {|x| puts "Start Vertex #{x}"}
61
+ # ee = Proc.new {|x| puts "Examine Arc #{x}"}
62
+ # te = Proc.new {|x| puts "Tree Arc #{x}"}
63
+ # be = Proc.new {|x| puts "Back Arc #{x}"}
64
+ # fe = Proc.new {|x| puts "Forward Arc #{x}"}
65
+ # Digraph[1,2,2,3,3,4].dfs({
66
+ # :enter_vertex => ev,
67
+ # :exit_vertex => xv,
68
+ # :start_vertex => sv,
69
+ # :examine_edge => ee,
70
+ # :tree_edge => te,
71
+ # :back_edge => be,
72
+ # :forward_edge => fe })
73
+ #
74
+ # Which outputs:
75
+ #
76
+ # Start Vertex 1
77
+ # Enter Vertex 1
78
+ # Examine Arc (1=2)
79
+ # Tree Arc (1=2)
80
+ # Enter Vertex 2
81
+ # Examine Arc (2=3)
82
+ # Tree Arc (2=3)
83
+ # Enter Vertex 3
84
+ # Examine Arc (3=4)
85
+ # Tree Arc (3=4)
86
+ # Enter Vertex 4
87
+ # Examine Arc (1=4)
88
+ # Back Arc (1=4)
89
+ # Exit Vertex 4
90
+ # Exit Vertex 3
91
+ # Exit Vertex 2
92
+ # Exit Vertex 1
93
+ def bfs(options={}, &block) gratr_search_helper(:shift, options, &block); end
94
+
95
+ # See options for bfs method
96
+ def dfs(options={}, &block) gratr_search_helper(:pop, options, &block); end
97
+
98
+ # Routine to compute a spanning forest for the given search method
99
+ # Returns two values, first is a hash of predecessors and second an array of root nodes
100
+ def spanning_forest(start, routine)
101
+ predecessor = {}
102
+ roots = []
103
+ te = Proc.new {|e| predecessor[e.target] = e.source}
104
+ rv = Proc.new {|v| roots << v}
105
+ method(routine).call :start => start, :tree_edge => te, :root_vertex => rv
106
+ [predecessor, roots]
107
+ end
108
+
109
+ # Return the dfs spanning forest for the given start node, see spanning_forest
110
+ def dfs_spanning_forest(start) spanning_forest(start, :dfs); end
111
+
112
+ # Return the bfs spanning forest for the given start node, see spanning_forest
113
+ def bfs_spanning_forest(start) spanning_forest(start, :bfs); end
114
+
115
+ # Returns a hash of predecessors in a tree rooted at the start node. If this is a connected graph
116
+ # then it will be a spanning tree and contain all vertices. An easier way to tell if it's a spanning tree is to
117
+ # use a spanning_forest call and check if there is a single root node.
118
+ def tree_from_vertex(start, routine)
119
+ predecessor={}
120
+ correct_tree = false
121
+ te = Proc.new {|e| predecessor[e.target] = e.source if correct_tree}
122
+ rv = Proc.new {|v| correct_tree = (v == start)}
123
+ method(routine).call :start => start, :tree_edge => te, :root_vertex => rv
124
+ predecessor
125
+ end
126
+
127
+ # Returns a hash of predecessors for the depth first search tree rooted at the given node
128
+ def dfs_tree_from_vertex(start) tree_from_vertex(start, :dfs); end
129
+
130
+ # Returns a hash of predecessors for the depth first search tree rooted at the given node
131
+ def bfs_tree_from_vertex(start) tree_from_vertex(start, :bfs); end
132
+
133
+ # An inner class used for greater efficiency in lexicograph_bfs
134
+ #
135
+ # Original desgn taken from Golumbic's, "Algorithmic Graph Theory and
136
+ # Perfect Graphs" pg, 87-89
137
+ class LexicographicQueue
138
+
139
+ # Called with the initial values (array)
140
+ def initialize(values)
141
+ @node = Struct.new(:back, :forward, :data)
142
+ @node.class_eval { def hash() @hash; end; @@cnt=0 }
143
+ @set = {}
144
+ @tail = @node.new(nil, nil, Array.new(values))
145
+ @tail.instance_eval { @hash = (@@cnt+=1) }
146
+ values.each {|a| @set[a] = @tail}
147
+ end
148
+
149
+ # Pop an entry with maximum lexical value from queue
150
+ def pop()
151
+ return nil unless @tail
152
+ value = @tail[:data].pop
153
+ @tail = @tail[:forward] while @tail and @tail[:data].size == 0
154
+ @set.delete(value); value
155
+ end
156
+
157
+ # Increase lexical value of given values (array)
158
+ def add_lexeme(values)
159
+ fix = {}
160
+ values.select {|v| @set[v]}.each do |w|
161
+ sw = @set[w]
162
+ if fix[sw]
163
+ s_prime = sw[:back]
164
+ else
165
+ s_prime = @node.new(sw[:back], sw, [])
166
+ s_prime.instance_eval { @hash = (@@cnt+=1) }
167
+ @tail = s_prime if @tail == sw
168
+ sw[:back][:forward] = s_prime if sw[:back]
169
+ sw[:back] = s_prime
170
+ fix[sw] = true
171
+ end
172
+ s_prime[:data] << w
173
+ sw[:data].delete(w)
174
+ @set[w] = s_prime
175
+ end
176
+ fix.keys.select {|n| n[:data].size == 0}.each do |e|
177
+ e[:forward][:back] = e[:back] if e[:forward]
178
+ e[:back][:forward] = e[:forward] if e[:back]
179
+ end
180
+ end
181
+
182
+ end
183
+
184
+ # Lexicographic breadth-first search, the usual queue of vertices
185
+ # is replaced by a queue of unordered subsets of the vertices,
186
+ # which is sometimes refined but never reordered.
187
+ #
188
+ # Originally developed by Rose, Tarjan, and Leuker, "Algorithmic
189
+ # aspects of vertex elimination on graphs", SIAM J. Comput. 5, 266-283
190
+ # MR53 #12077
191
+ #
192
+ # Implementation taken from Golumbic's, "Algorithmic Graph Theory and
193
+ # Perfect Graphs" pg, 84-90
194
+ def lexicograph_bfs(&block)
195
+ lex_q = GRATR::Graph::Search::LexicographicQueue.new(vertices)
196
+ result = []
197
+ num_vertices.times do
198
+ v = lex_q.pop
199
+ result.unshift(v)
200
+ lex_q.add_lexeme(adjacent(v))
201
+ end
202
+ result.each {|r| block.call(r)} if block
203
+ result
204
+ end
205
+
206
+
207
+ # A* Heuristic best first search
208
+ #
209
+ # start is the starting vertex for the search
210
+ #
211
+ # func is a Proc that when passed a vertex returns the heuristic
212
+ # weight of sending the path through that node. It must always
213
+ # be equal to or less than the true cost
214
+ #
215
+ # options are mostly callbacks passed in as a hash, the default block is
216
+ # :discover_vertex and weight is assumed to be the label for the Arc.
217
+ # The following options are valid, anything else is ignored.
218
+ #
219
+ # * :weight => can be a Proc, or anything else is accessed using the [] for the
220
+ # the label or it defaults to using
221
+ # the value stored in the label for the Arc. If it is a Proc it will
222
+ # pass the edge to the proc and use the resulting value.
223
+ # * :discover_vertex => Proc invoked when a vertex is first discovered
224
+ # and is added to the open list.
225
+ # * :examine_vertex => Proc invoked when a vertex is popped from the
226
+ # queue (i.e., it has the lowest cost on the open list).
227
+ # * :examine_edge => Proc invoked on each out-edge of a vertex
228
+ # immediately after it is examined.
229
+ # * :edge_relaxed => Proc invoked on edge (u,v) if d[u] + w(u,v) < d[v].
230
+ # * :edge_not_relaxed=> Proc invoked if the edge is not relaxed (see above).
231
+ # * :black_target => Proc invoked when a vertex that is on the closed
232
+ # list is "rediscovered" via a more efficient path, and is re-added
233
+ # to the OPEN list.
234
+ # * :finish_vertex => Proc invoked on a vertex when it is added to the
235
+ # closed list, which happens after all of its out edges have been
236
+ # examined.
237
+ #
238
+ # Returns array of nodes in path, or calls block on all nodes,
239
+ # upon failure returns nil
240
+ #
241
+ # Can also be called like astar_examine_edge {|e| ... } or
242
+ # astar_edge_relaxed {|e| ... } for any of the callbacks
243
+ #
244
+ # The criteria for expanding a vertex on the open list is that it has the
245
+ # lowest f(v) = g(v) + h(v) value of all vertices on open.
246
+ #
247
+ # The time complexity of A* depends on the heuristic. It is exponential
248
+ # in the worst case, but is polynomial when the heuristic function h
249
+ # meets the following condition: |h(x) - h*(x)| < O(log h*(x)) where h*
250
+ # is the optimal heuristic, i.e. the exact cost to get from x to the goal.
251
+ #
252
+ # Also see: http://en.wikipedia.org/wiki/A-star_search_algorithm
253
+ #
254
+ 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
+
258
+ d = { start => 0 }
259
+ f = { start => func.call(start) }
260
+ color = {start => :gray}
261
+ p = Hash.new {|k| p[k] = k}
262
+ queue = [start]
263
+ block.call(start) if block
264
+ 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)
270
+ w = cost(e, options[:weight])
271
+ raise ArgumentError unless w
272
+ if d[v].nil? or (w + d[u]) < d[v]
273
+ options.handle_edge(:edge_relaxed, u, v)
274
+ d[v] = w + d[u]
275
+ f[v] = d[v] + func.call(u)
276
+ p[v] = u
277
+ unless color[v] == :gray
278
+ options.handle_vertex(:black_target, v) if color[v] == :black
279
+ color[v] = :gray
280
+ options.handle_vertex(:discover_vertex, v)
281
+ queue << v
282
+ block.call(v) if block
283
+ return [start]+queue if v == goal
284
+ end
285
+ else
286
+ options.handle_edge(:edge_not_relaxed, u, v)
287
+ end
288
+ end # adjacent(u)
289
+ color[u] = :black
290
+ options.handle_vertex(:finish_vertex,u)
291
+ end # queue.empty?
292
+ nil # failure, on fall through
293
+ end # astar
294
+
295
+ # Best first has all the same options as astar with func set to h(v) = 0.
296
+ # There is an additional option zero which should be defined to zero
297
+ # for the operation '+' on the objects used in the computation of cost.
298
+ # The parameter zero defaults to 0.
299
+ def best_first(start, goal, options, zero=0, &block)
300
+ func = Proc.new {|v| zero}
301
+ astar(start, goal, func, options, &block)
302
+ end
303
+
304
+ alias_method :pre_search_method_missing, :method_missing # :nodoc:
305
+ def method_missing(sym,*args, &block) # :nodoc:
306
+ m1=/^dfs_(\w+)$/.match(sym.to_s)
307
+ dfs((args[0] || {}).merge({m1.captures[0].to_sym => block})) if m1
308
+ m2=/^bfs_(\w+)$/.match(sym.to_s)
309
+ bfs((args[0] || {}).merge({m2.captures[0].to_sym => block})) if m2
310
+ pre_search_method_missing(sym, *args, &block) unless m1 or m2
311
+ end
312
+
313
+ private
314
+
315
+ def gratr_search_helper(op, options={}, &block) # :nodoc:
316
+ return nil if size == 0
317
+ result = []
318
+ # Create options hash that handles callbacks
319
+ options = {:enter_vertex => block, :start => to_a[0]}.merge(options)
320
+ options.instance_eval "def handle_vertex(sym,u) self[sym].call(u) if self[sym]; end"
321
+ options.instance_eval "def handle_edge(sym,e) self[sym].call(e) if self[sym]; end"
322
+ # Create waiting list that is a queue or stack depending on op specified.
323
+ # First entry is the start vertex.
324
+ waiting = [options[:start]]
325
+ waiting.instance_eval "def next() #{op.to_s}; end"
326
+ # Create color map with all set to unvisited except for start vertex
327
+ # will be set to waiting
328
+ color_map = vertices.inject({}) {|a,v| a[v] = :unvisited; a}
329
+ color_map.merge!(waiting[0] => :waiting)
330
+ options.handle_vertex(:start_vertex, waiting[0])
331
+ options.handle_vertex(:root_vertex, waiting[0])
332
+ # Perform the actual search until nothing is waiting
333
+ until waiting.empty?
334
+ # Loop till the search iterator exhausts the waiting list
335
+ visited_edges={} # This prevents retraversing edges in undirected graphs
336
+ until waiting.empty?
337
+ gratr_search_iteration(options, waiting, color_map, visited_edges, result, op == :pop)
338
+ end
339
+ # Waiting list is exhausted, see if a new root vertex is available
340
+ u=color_map.detect {|key,value| value == :unvisited}
341
+ waiting.push(u[0]) if u
342
+ options.handle_vertex(:root_vertex, u[0]) if u
343
+ end; result
344
+ end
345
+
346
+ def gratr_search_iteration(options, waiting, color_map, visited_edges, result, recursive=false) # :nodoc:
347
+ # Get the next waiting vertex in the list
348
+ u = waiting.next
349
+ options.handle_vertex(:enter_vertex,u)
350
+ result << u
351
+ # Examine all adjacent outgoing edges, not previously traversed
352
+ adj_proc = options[:adjacent] || self.method(:adjacent).to_proc
353
+ adj_proc.call(u,:type => :edges, :direction => :out).reject {|w| visited_edges[w]}.each do |e|
354
+ e = e.reverse unless directed? or e.source == u # Preserves directionality where required
355
+ v = e.target
356
+ options.handle_edge(:examine_edge, e)
357
+ visited_edges[e]=true
358
+ case color_map[v]
359
+ # If it's unvisited it goes into the waiting list
360
+ when :unvisited
361
+ options.handle_edge(:tree_edge, e)
362
+ color_map[v] = :waiting
363
+ waiting.push(v)
364
+ # If it's recursive (i.e. dfs) then call self
365
+ gratr_search_iteration(options, waiting, color_map, visited_edges, result, true) if recursive
366
+ when :waiting
367
+ options.handle_edge(:back_edge, e)
368
+ else
369
+ options.handle_edge(:forward_edge, e)
370
+ end
371
+ end
372
+ # Finished with this vertex
373
+ options.handle_vertex(:exit_vertex, u)
374
+ color_map[u] = :visited
375
+ end
376
+
377
+ public
378
+ # Topological Sort Iterator
379
+ #
380
+ # The topological sort algorithm creates a linear ordering of the vertices
381
+ # such that if edge (u,v) appears in the graph, then u comes before v in
382
+ # the ordering. The graph must be a directed acyclic graph (DAG).
383
+ #
384
+ # The iterator can also be applied to undirected graph or to a DG graph
385
+ # which contains a cycle. In this case, the Iterator does not reach all
386
+ # vertices. The implementation of acyclic? and cyclic? uses this fact.
387
+ #
388
+ # Can be called with a block as a standard Ruby iterator, or it can
389
+ # be used directly as it will return the result as an Array
390
+ def topsort(start = nil, &block)
391
+ result = []
392
+ go = true
393
+ back = Proc.new {|e| go = false }
394
+ push = Proc.new {|v| result.unshift(v) if go}
395
+ start ||= vertices[0]
396
+ dfs({:exit_vertex => push, :back_edge => back, :start => start})
397
+ result.each {|v| block.call(v)} if block; result
398
+ end
399
+
400
+ # Returns true if a graph contains no cycles, false otherwise
401
+ def acyclic?() topsort.size == size; end
402
+
403
+ # Returns false if a graph contains no cycles, true otherwise
404
+ def cyclic?() not acyclic?; end
405
+
406
+
407
+ end # Search
408
+ end # Graph
409
+ end # GRATR
@@ -0,0 +1,127 @@
1
+ #--
2
+ # Copyright (c) 2006 Shawn Patrick Garbett
3
+ #
4
+ # Redistribution and use in source and binary forms, with or without modification,
5
+ # are permitted provided that the following conditions are met:
6
+ #
7
+ # * Redistributions of source code must retain the above copyright notice(s),
8
+ # this list of conditions and the following disclaimer.
9
+ # * Redistributions in binary form must reproduce the above copyright notice,
10
+ # this list of conditions and the following disclaimer in the documentation
11
+ # and/or other materials provided with the distribution.
12
+ # * Neither the name of the Shawn Garbett nor the names of its contributors
13
+ # may be used to endorse or promote products derived from this software
14
+ # without specific prior written permission.
15
+ #
16
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17
+ # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
20
+ # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21
+ # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22
+ # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23
+ # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24
+ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25
+ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
+ #++
27
+
28
+
29
+ require 'set'
30
+
31
+ module GRATR
32
+ module Graph
33
+ module StrongComponents
34
+ # strong_components computes the strongly connected components
35
+ # of a graph using Tarjan's algorithm based on DFS. See: Robert E. Tarjan
36
+ # _Depth_First_Search_and_Linear_Graph_Algorithms_. SIAM Journal on
37
+ # Computing, 1(2):146-160, 1972
38
+ #
39
+ # The output of the algorithm is an array of components where is
40
+ # component is an array of vertices
41
+ #
42
+ # A strongly connected component of a directed graph G=(V,E) is a maximal
43
+ # set of vertices U which is in V such that for every pair of
44
+ # vertices u and v in U, we have both a path from u to v
45
+ # and path from v to u. That is to say that u and v are reachable
46
+ # from each other.
47
+ #
48
+ def strong_components
49
+
50
+ dfs_num = 0
51
+ stack = []; result = []; root = {}; comp = {}; number = {}
52
+
53
+ # Enter vertex callback
54
+ enter = Proc.new do |v|
55
+ root[v] = v
56
+ comp[v] = :new
57
+ number[v] = (dfs_num += 1)
58
+ stack.push(v)
59
+ end
60
+
61
+ # Exit vertex callback
62
+ exit = Proc.new do |v|
63
+ adjacent(v).each do |w|
64
+ if comp[w] == :new
65
+ root[v] = (number[root[v]] < number[root[w]] ? root[v] : root[w])
66
+ end
67
+ end
68
+ if root[v] == v
69
+ component = []
70
+ begin
71
+ w = stack.pop
72
+ comp[w] = :assigned
73
+ component << w
74
+ end until w == v
75
+ result << component
76
+ end
77
+ end
78
+
79
+ # Execute depth first search
80
+ dfs({:enter_vertex => enter, :exit_vertex => exit}); result
81
+
82
+ end # strong_components
83
+
84
+ # Returns a condensation graph of the strongly connected components
85
+ # Each node is an array of nodes from the original graph
86
+ def condensation
87
+ sc = strong_components
88
+ cg = self.class.new
89
+ map = sc.inject({}) do |a,c|
90
+ c.each {|v| a[v] = c }; a
91
+ end
92
+ sc.each do |c|
93
+ c.each do |v|
94
+ adjacent(v).each {|v| cg.add_edge!(c, map[v]) unless c == map[v]}
95
+ end
96
+ end; cg
97
+ end
98
+
99
+ # Compute transitive closure of a graph. That is any node that is reachable
100
+ # along a path is added as a directed edge.
101
+ def transitive_closure!
102
+ cgtc = condensation.gratr_inner_transitive_closure!
103
+ cgtc.each do |cgv|
104
+ cgtc.adjacent(cgv).each do |adj|
105
+ cgv.each do |u|
106
+ adj.each {|v| add_edge!(u,v)}
107
+ end
108
+ end
109
+ end; self
110
+ end
111
+
112
+ # This returns the transitive closure of a graph. The original graph
113
+ # is not changed.
114
+ def transitive_closure() self.class.new(self).transitive_closure!; end
115
+
116
+ private
117
+ def gratr_inner_transitive_closure! # :nodoc:
118
+ topsort.reverse.each do |u|
119
+ adjacent(u).each do |v|
120
+ adjacent(v).each {|w| add_edge!(u,w) unless edge?(u,w)}
121
+ end
122
+ end; self
123
+ end
124
+ end # StrongComponens
125
+
126
+ end # Graph
127
+ end # GRATR