rgl 0.4.0 → 0.5.0

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 (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