rgl 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. data/ChangeLog +19 -10
  2. data/Gemfile +3 -0
  3. data/{README → README.rdoc} +70 -98
  4. data/Rakefile +44 -150
  5. data/examples/canvas.rb +63 -64
  6. data/examples/examples.rb +42 -42
  7. data/examples/graph.dot +46 -0
  8. data/examples/images/example.jpg +0 -0
  9. data/examples/images/module_graph.jpg +0 -0
  10. data/examples/images/rgl_modules.png +0 -0
  11. data/examples/{insel-der-tausend-gefahren.rb → insel_der_tausend_gefahren.rb} +18 -19
  12. data/examples/north.rb +2 -2
  13. data/examples/north2.rb +11 -11
  14. data/examples/rdep-rgl.rb +218 -222
  15. data/lib/rgl/adjacency.rb +78 -74
  16. data/lib/rgl/base.rb +160 -78
  17. data/lib/rgl/bellman_ford.rb +115 -0
  18. data/lib/rgl/bidirectional.rb +17 -10
  19. data/lib/rgl/bipartite.rb +87 -0
  20. data/lib/rgl/condensation.rb +13 -4
  21. data/lib/rgl/connected_components.rb +38 -30
  22. data/lib/rgl/dijkstra.rb +158 -0
  23. data/lib/rgl/dijkstra_visitor.rb +42 -0
  24. data/lib/rgl/dot.rb +40 -32
  25. data/lib/rgl/edge_properties_map.rb +55 -0
  26. data/lib/rgl/edmonds_karp.rb +136 -0
  27. data/lib/rgl/enumerable_ext.rb +4 -1
  28. data/lib/rgl/graph_iterator.rb +15 -0
  29. data/lib/rgl/graph_visitor.rb +138 -0
  30. data/lib/rgl/graph_wrapper.rb +15 -0
  31. data/lib/rgl/graphxml.rb +20 -10
  32. data/lib/rgl/implicit.rb +68 -66
  33. data/lib/rgl/mutable.rb +37 -31
  34. data/lib/rgl/path_builder.rb +40 -0
  35. data/lib/rgl/prim.rb +52 -0
  36. data/lib/rgl/rdot.rb +411 -374
  37. data/lib/rgl/topsort.rb +23 -16
  38. data/lib/rgl/transitivity.rb +29 -27
  39. data/lib/rgl/traversal.rb +67 -205
  40. data/rakelib/dep_graph.rake +4 -3
  41. data/test/bellman_ford_test.rb +187 -0
  42. data/test/bipartite_test.rb +47 -0
  43. data/test/components_test.rb +80 -0
  44. data/test/cycles_test.rb +60 -0
  45. data/test/dijkstra_test.rb +148 -0
  46. data/test/directed_graph_test.rb +118 -0
  47. data/test/dot_test.rb +26 -0
  48. data/test/edge_properties_map_test.rb +63 -0
  49. data/test/edge_test.rb +35 -0
  50. data/test/edmonds_karp_test.rb +105 -0
  51. data/{tests/TestGraph.rb → test/graph_test.rb} +6 -6
  52. data/test/graph_xml_test.rb +57 -0
  53. data/test/implicit_test.rb +53 -0
  54. data/test/prim_test.rb +98 -0
  55. data/{tests/TestRdot.rb → test/rdot_test.rb} +309 -308
  56. data/{tests → test}/test_helper.rb +4 -1
  57. data/{tests/TestTransitivity.rb → test/transitivity_test.rb} +43 -43
  58. data/test/traversal_test.rb +221 -0
  59. data/test/undirected_graph_test.rb +103 -0
  60. metadata +226 -145
  61. data/examples/example.jpg +0 -0
  62. data/examples/module_graph.jpg +0 -0
  63. data/install.rb +0 -49
  64. data/tests/TestComponents.rb +0 -65
  65. data/tests/TestCycles.rb +0 -61
  66. data/tests/TestDirectedGraph.rb +0 -125
  67. data/tests/TestDot.rb +0 -18
  68. data/tests/TestEdge.rb +0 -34
  69. data/tests/TestGraphXML.rb +0 -57
  70. data/tests/TestImplicit.rb +0 -52
  71. data/tests/TestTraversal.rb +0 -220
  72. data/tests/TestUnDirectedGraph.rb +0 -102
@@ -0,0 +1,158 @@
1
+ require 'rgl/dijkstra_visitor'
2
+ require 'rgl/edge_properties_map'
3
+ require 'rgl/path_builder'
4
+
5
+ require 'delegate'
6
+ require 'algorithms'
7
+
8
+ module RGL
9
+
10
+ class DijkstraAlgorithm
11
+
12
+ # Distance combinator is a lambda that accepts the distance (usually from the source) to vertex _u_ and the weight
13
+ # of the edge connecting vertex _u_ to another vertex _v_ and returns the distance to vertex _v_ if it's reached
14
+ # through the vertex _u_. By default, the distance to vertex _u_ and the edge's weight are summed.
15
+ DEFAULT_DISTANCE_COMBINATOR = lambda { |distance, edge_weight| distance + edge_weight }
16
+
17
+ # Initializes Dijkstra's algorithm for a _graph_ with provided edges weights map.
18
+ #
19
+ def initialize(graph, edge_weights_map, visitor, distance_combinator = nil)
20
+ @graph = graph
21
+ @edge_weights_map = build_edge_weights_map(edge_weights_map)
22
+ @visitor = visitor
23
+ @distance_combinator = distance_combinator || DEFAULT_DISTANCE_COMBINATOR
24
+ end
25
+
26
+ # Finds the shortest path from the _source_ to the _target_ in the graph.
27
+ #
28
+ # Returns the shortest path, if it exists, as an Array of vertices. Otherwise, returns nil.
29
+ #
30
+ def shortest_path(source, target)
31
+ init(source)
32
+ relax_edges(target, true)
33
+ PathBuilder.new(source, @visitor.parents_map).path(target)
34
+ end
35
+
36
+ # Finds the shortest path form the _source_ to every other vertex of the graph and builds shortest paths map.
37
+ #
38
+ # Returns the shortest paths map that contains the shortest path (if it exists) from the source to any vertex of the
39
+ # graph.
40
+ #
41
+ def shortest_paths(source)
42
+ find_shortest_paths(source)
43
+ PathBuilder.new(source, @visitor.parents_map).paths(@graph.vertices)
44
+ end
45
+
46
+ # Finds the shortest path from the _source_ to every other vertex.
47
+ #
48
+ def find_shortest_paths(source)
49
+ init(source)
50
+ relax_edges
51
+ end
52
+
53
+ private
54
+
55
+ def init(source)
56
+ @visitor.set_source(source)
57
+
58
+ @queue = Queue.new
59
+ @queue.push(source, 0)
60
+ end
61
+
62
+ def relax_edges(target = nil, break_on_target = false)
63
+ until @queue.empty?
64
+ u = @queue.pop
65
+
66
+ break if break_on_target && u == target
67
+
68
+ @visitor.handle_examine_vertex(u)
69
+
70
+ @graph.each_adjacent(u) do |v|
71
+ relax_edge(u, v) unless @visitor.finished_vertex?(v)
72
+ end
73
+
74
+ @visitor.color_map[u] = :BLACK
75
+ @visitor.handle_finish_vertex(u)
76
+ end
77
+ end
78
+
79
+ def relax_edge(u, v)
80
+ @visitor.handle_examine_edge(u, v)
81
+
82
+ new_v_distance = @distance_combinator.call(@visitor.distance_map[u], @edge_weights_map.edge_property(u, v))
83
+
84
+ if new_v_distance < @visitor.distance_map[v]
85
+ old_v_distance = @visitor.distance_map[v]
86
+
87
+ @visitor.distance_map[v] = new_v_distance
88
+ @visitor.parents_map[v] = u
89
+
90
+ if @visitor.color_map[v] == :WHITE
91
+ @visitor.color_map[v] = :GRAY
92
+ @queue.push(v, new_v_distance)
93
+ elsif @visitor.color_map[v] == :GRAY
94
+ @queue.decrease_key(v, old_v_distance, new_v_distance)
95
+ end
96
+
97
+ @visitor.handle_edge_relaxed(u, v)
98
+ else
99
+ @visitor.handle_edge_not_relaxed(u, v)
100
+ end
101
+ end
102
+
103
+ def build_edge_weights_map(edge_weights_map)
104
+ edge_weights_map.is_a?(EdgePropertiesMap) ? edge_weights_map : NonNegativeEdgePropertiesMap.new(edge_weights_map, @graph.directed?)
105
+ end
106
+
107
+ class Queue < SimpleDelegator # :nodoc:
108
+
109
+ def initialize
110
+ @heap = Containers::Heap.new { |a, b| a.distance < b.distance }
111
+ super(@heap)
112
+ end
113
+
114
+ def push(vertex, distance)
115
+ @heap.push(vertex_key(vertex, distance), vertex)
116
+ end
117
+
118
+ def decrease_key(vertex, old_distance, new_distance)
119
+ @heap.change_key(vertex_key(vertex, old_distance), vertex_key(vertex, new_distance))
120
+ end
121
+
122
+ def vertex_key(vertex, distance)
123
+ VertexKey.new(vertex, distance)
124
+ end
125
+
126
+ VertexKey = Struct.new(:vertex, :distance)
127
+
128
+ end
129
+
130
+ end # class DijkstraAlgorithm
131
+
132
+ module Graph
133
+
134
+ # Finds the shortest path from the _source_ to the _target_ in the graph.
135
+ #
136
+ # If the path exists, returns it as an Array of vertices. Otherwise, returns nil.
137
+ #
138
+ # Raises ArgumentError if edge weight is negative or undefined.
139
+ #
140
+ def dijkstra_shortest_path(edge_weights_map, source, target, visitor = DijkstraVisitor.new(self))
141
+ DijkstraAlgorithm.new(self, edge_weights_map, visitor).shortest_path(source, target)
142
+ end
143
+
144
+ # Finds the shortest paths from the _source_ to each vertex of the graph.
145
+ #
146
+ # Returns a Hash that maps each vertex of the graph to an Array of vertices that represents the shortest path
147
+ # from the _source_ to the vertex. If the path doesn't exist, the corresponding hash value is nil. For the _source_
148
+ # vertex returned hash contains a trivial one-vertex path - [source].
149
+ #
150
+ # Raises ArgumentError if edge weight is negative or undefined.
151
+ #
152
+ def dijkstra_shortest_paths(edge_weights_map, source, visitor = DijkstraVisitor.new(self))
153
+ DijkstraAlgorithm.new(self, edge_weights_map, visitor).shortest_paths(source)
154
+ end
155
+
156
+ end # module Graph
157
+
158
+ end # module RGL
@@ -0,0 +1,42 @@
1
+ require 'rgl/base'
2
+ require 'rgl/graph_visitor'
3
+
4
+ module RGL
5
+
6
+ # Dijkstra shortest path algorithm has the following event points:
7
+ #
8
+ # * examine_vertex
9
+ # * examine_edge
10
+ # * edge_relaxed
11
+ # * edge_not_relaxed
12
+ # * finish_vertex
13
+ #
14
+ class DijkstraVisitor
15
+
16
+ include GraphVisitor
17
+
18
+ attr_accessor :distance_map, :parents_map
19
+
20
+ def_event_handlers :edge_relaxed, :edge_not_relaxed
21
+
22
+ # Returns visitor into initial state.
23
+ #
24
+ def reset
25
+ super
26
+
27
+ @distance_map = Hash.new(INFINITY)
28
+ @parents_map = {}
29
+ end
30
+
31
+ # Initializes visitor with a new source.
32
+ #
33
+ def set_source(source)
34
+ reset
35
+
36
+ color_map[source] = :GRAY
37
+ distance_map[source] = 0
38
+ end
39
+
40
+ end # DijkstraVisitor
41
+
42
+ end # RGL
@@ -1,6 +1,6 @@
1
1
  # dot.rb
2
2
  #
3
- # $Id: dot.rb,v 1.8 2008/08/26 20:07:09 javanthropus Exp $
3
+ # $Id$
4
4
  #
5
5
  # Minimal Dot support, based on Dave Thomas's dot module (included in rdoc).
6
6
  # rdot.rb is a modified version which also contains support for undirected
@@ -12,50 +12,57 @@ module RGL
12
12
 
13
13
  module Graph
14
14
 
15
- # Return a RGL::DOT::Digraph for directed graphs or a DOT::Subgraph for an
16
- # undirected Graph. _params_ can contain any graph property specified in
17
- # rdot.rb.
15
+ # Return a RGL::DOT::Digraph for directed graphs or a DOT::Graph for an
16
+ # undirected Graph. _params_ can contain any graph property specified in
17
+ # rdot.rb.
18
+ #
19
+ def to_dot_graph(params = {})
20
+ params['name'] ||= self.class.name.gsub(/:/, '_')
21
+ fontsize = params['fontsize'] ? params['fontsize'] : '8'
22
+ graph = (directed? ? DOT::Digraph : DOT::Graph).new(params)
23
+ edge_class = directed? ? DOT::DirectedEdge : DOT::Edge
18
24
 
19
- def to_dot_graph (params = {})
20
- params['name'] ||= self.class.name.gsub(/:/,'_')
21
- fontsize = params['fontsize'] ? params['fontsize'] : '8'
22
- graph = (directed? ? DOT::Digraph : DOT::Subgraph).new(params)
23
- edge_class = directed? ? DOT::DirectedEdge : DOT::Edge
24
- each_vertex do |v|
25
- name = v.to_s
26
- graph << DOT::Node.new('name' => name,
27
- 'fontsize' => fontsize,
28
- 'label' => name)
29
- end
30
- each_edge do |u,v|
31
- graph << edge_class.new('from' => u.to_s,
32
- 'to' => v.to_s,
33
- 'fontsize' => fontsize)
25
+ each_vertex do |v|
26
+ name = v.to_s
27
+ graph << DOT::Node.new(
28
+ 'name' => name,
29
+ 'fontsize' => fontsize,
30
+ 'label' => name
31
+ )
32
+ end
33
+
34
+ each_edge do |u, v|
35
+ graph << edge_class.new(
36
+ 'from' => u.to_s,
37
+ 'to' => v.to_s,
38
+ 'fontsize' => fontsize
39
+ )
34
40
  end
41
+
35
42
  graph
36
43
  end
37
44
 
38
45
  # Output the DOT-graph to stream _s_.
39
-
40
- def print_dotted_on (params = {}, s = $stdout)
46
+ #
47
+ def print_dotted_on(params = {}, s = $stdout)
41
48
  s << to_dot_graph(params).to_s << "\n"
42
49
  end
43
50
 
44
51
  # Call dotty[http://www.graphviz.org] for the graph which is written to the
45
- # file 'graph.dot' in the # current directory.
46
-
47
- def dotty (params = {})
52
+ # file 'graph.dot' in the current directory.
53
+ #
54
+ def dotty(params = {})
48
55
  dotfile = "graph.dot"
49
- File.open(dotfile, "w") {|f|
56
+ File.open(dotfile, "w") do |f|
50
57
  print_dotted_on(params, f)
51
- }
58
+ end
52
59
  system("dotty", dotfile)
53
60
  end
54
61
 
55
62
  # Use dot[http://www.graphviz.org] to create a graphical representation of
56
- # the graph. Returns the filename of the graphics file.
57
-
58
- def write_to_graphic_file (fmt='png', dotfile="graph")
63
+ # the graph. Returns the filename of the graphics file.
64
+ #
65
+ def write_to_graphic_file(fmt='png', dotfile="graph")
59
66
  src = dotfile + ".dot"
60
67
  dot = dotfile + "." + fmt
61
68
 
@@ -63,9 +70,10 @@ module RGL
63
70
  f << self.to_dot_graph.to_s << "\n"
64
71
  end
65
72
 
66
- system( "dot -T#{fmt} #{src} -o #{dot}" )
73
+ system("dot -T#{fmt} #{src} -o #{dot}")
67
74
  dot
68
75
  end
69
76
 
70
- end # module Graph
71
- end # module RGL
77
+ end # module Graph
78
+
79
+ end # module RGL
@@ -0,0 +1,55 @@
1
+ module RGL
2
+
3
+ class EdgePropertiesMap
4
+
5
+ def initialize(edge_properties_map, directed)
6
+ @edge_properties_map = edge_properties_map
7
+ @directed = directed
8
+
9
+ check_properties
10
+ end
11
+
12
+ def edge_property(u, v)
13
+ if @directed
14
+ property = @edge_properties_map[[u, v]]
15
+ else
16
+ property = @edge_properties_map[[u, v]] || @edge_properties_map[[v, u]]
17
+ end
18
+
19
+ validate_property(property, u, v)
20
+
21
+ property
22
+ end
23
+
24
+ private
25
+
26
+ def check_properties
27
+ @edge_properties_map.each { |(u, v), property| validate_property(property, u, v) } if @edge_properties_map.respond_to?(:each)
28
+ end
29
+
30
+ def validate_property(property, u, v)
31
+ report_missing_property(property, u, v)
32
+ end
33
+
34
+ def report_missing_property(property, u, v)
35
+ raise ArgumentError.new("property of edge (#{u}, #{v}) is not defined") unless property
36
+ end
37
+
38
+ end # EdgePropertiesMap
39
+
40
+ class NonNegativeEdgePropertiesMap < EdgePropertiesMap
41
+
42
+ private
43
+
44
+ def validate_property(property, u, v)
45
+ super
46
+ report_negative_property(property, u, v)
47
+ end
48
+
49
+ def report_negative_property(property, u, v)
50
+ raise ArgumentError.new("property of edge (#{u}, #{v}) is negative") if property < 0
51
+ end
52
+
53
+ end
54
+
55
+ end
@@ -0,0 +1,136 @@
1
+ require 'rgl/edge_properties_map'
2
+ require 'rgl/traversal'
3
+
4
+ module RGL
5
+
6
+ class EdmondsKarpAlgorithm
7
+
8
+ # Initializes Edmonds-Karp algorithm for a _graph_ with provided edges capacities map.
9
+ #
10
+ def initialize(graph, edge_capacities_map)
11
+ raise NotDirectedError.new('Edmonds-Karp algorithm can only be applied to a directed graph') unless graph.directed?
12
+
13
+ @graph = graph
14
+ validate_edge_capacities(edge_capacities_map)
15
+ @edge_capacities_map = NonNegativeEdgePropertiesMap.new(edge_capacities_map, true)
16
+ end
17
+
18
+ # Finds the maximum flow from the _source_ to the _sink_ in the graph.
19
+ #
20
+ # Returns flows map as a hash that maps each edge of the graph to a flow through that edge that is required to reach
21
+ # the maximum total flow.
22
+ #
23
+ def maximum_flow(source, sink)
24
+ raise ArgumentError.new("source and sink can't be equal") if source == sink
25
+
26
+ @flow_map = Hash.new(0)
27
+ @residual_capacity_map = lambda { |u, v| @edge_capacities_map.edge_property(u, v) - @flow_map[[u, v]] }
28
+
29
+ loop do
30
+ bfs = EdmondsKarpBFSIterator.new(@graph, source, sink, @residual_capacity_map)
31
+
32
+ bfs.move_forward_until { bfs.color_map[sink] == :GRAY }
33
+
34
+ if bfs.color_map[sink] == :WHITE
35
+ break # no more augmenting paths
36
+ else
37
+ min_residual_capacity = INFINITY
38
+
39
+ augmenting_path = [sink]
40
+
41
+ while augmenting_path.first != source
42
+ v = augmenting_path.first
43
+ u = bfs.parents_map[v]
44
+
45
+ augmenting_path.unshift(u)
46
+ min_residual_capacity = [min_residual_capacity, @residual_capacity_map[u, v]].min
47
+ end
48
+
49
+ augmenting_path.each_cons(2) do |(u, v)|
50
+ @flow_map[[u, v]] += min_residual_capacity
51
+ @flow_map[[v, u]] -= min_residual_capacity
52
+ end
53
+ end
54
+ end
55
+
56
+ @flow_map
57
+ end
58
+
59
+ private
60
+
61
+ def validate_edge_capacities(edge_capacities_map)
62
+ @graph.each_edge do |u, v|
63
+ raise ArgumentError.new("reverse edge for (#{u}, #{v}) is missing") unless @graph.has_edge?(v, u)
64
+ validate_capacity(u, v, edge_capacities_map)
65
+ end
66
+ end
67
+
68
+ def validate_capacity(u, v, edge_capacities_map)
69
+ capacity = get_capacity(u, v, edge_capacities_map)
70
+ reverse_capacity = get_capacity(v, u, edge_capacities_map)
71
+
72
+ validate_negative_capacity(u, v, capacity)
73
+ validate_negative_capacity(v, u, reverse_capacity)
74
+
75
+ raise ArgumentError.new("either (#{u}, #{v}) or (#{v}, #{u}) should have 0 capacity") unless [capacity, reverse_capacity].include?(0)
76
+ end
77
+
78
+ def get_capacity(u, v, edge_capacities_map)
79
+ edge_capacities_map.fetch([u, v]) { raise ArgumentError.new("capacity for edge (#{u}, #{v}) is missing") }
80
+ end
81
+
82
+ def validate_negative_capacity(u, v, capacity)
83
+ raise ArgumentError.new("capacity of edge (#{u}, #{v}) is negative") unless capacity >= 0
84
+ end
85
+
86
+ class EdmondsKarpBFSIterator < BFSIterator
87
+
88
+ attr_accessor :parents_map
89
+
90
+ def initialize(graph, start, stop, residual_capacities)
91
+ super(graph, start)
92
+ @residual_capacities = residual_capacities
93
+ @stop_vertex = stop
94
+ end
95
+
96
+ def reset
97
+ super
98
+ @parents_map = {}
99
+ end
100
+
101
+ def follow_edge?(u, v)
102
+ # follow only edges with positive residual capacity
103
+ super && @residual_capacities[u, v] > 0
104
+ end
105
+
106
+ def handle_tree_edge(u, v)
107
+ super
108
+ @parents_map[v] = u
109
+ end
110
+
111
+ end # class EdmondsKarpBFSIterator
112
+
113
+ end # class EdmondsKarpAlgorithm
114
+
115
+ module Graph
116
+
117
+ # Finds the maximum flow from the _source_ to the _sink_ in the graph.
118
+ #
119
+ # Returns flows map as a hash that maps each edge of the graph to a flow through that edge that is required to reach
120
+ # the maximum total flow.
121
+ #
122
+ # For the method to work, the graph should be first altered so that for each directed edge (u, v) it contains reverse
123
+ # edge (u, v). Capacities of the primary edges should be non-negative, while reverse edges should have zero capacity.
124
+ #
125
+ # Raises ArgumentError if the graph is not directed.
126
+ #
127
+ # Raises ArgumentError if a reverse edge is missing, edge capacity is missing, an edge has negative capacity, or a
128
+ # reverse edge has positive capacity.
129
+ #
130
+ def maximum_flow(edge_capacities_map, source, sink)
131
+ EdmondsKarpAlgorithm.new(self, edge_capacities_map).maximum_flow(source, sink)
132
+ end
133
+
134
+ end # module Graph
135
+
136
+ end