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.
- data/ChangeLog +19 -10
- data/Gemfile +3 -0
- data/{README → README.rdoc} +70 -98
- data/Rakefile +44 -150
- data/examples/canvas.rb +63 -64
- data/examples/examples.rb +42 -42
- data/examples/graph.dot +46 -0
- data/examples/images/example.jpg +0 -0
- data/examples/images/module_graph.jpg +0 -0
- data/examples/images/rgl_modules.png +0 -0
- data/examples/{insel-der-tausend-gefahren.rb → insel_der_tausend_gefahren.rb} +18 -19
- data/examples/north.rb +2 -2
- data/examples/north2.rb +11 -11
- data/examples/rdep-rgl.rb +218 -222
- data/lib/rgl/adjacency.rb +78 -74
- data/lib/rgl/base.rb +160 -78
- data/lib/rgl/bellman_ford.rb +115 -0
- data/lib/rgl/bidirectional.rb +17 -10
- data/lib/rgl/bipartite.rb +87 -0
- data/lib/rgl/condensation.rb +13 -4
- data/lib/rgl/connected_components.rb +38 -30
- data/lib/rgl/dijkstra.rb +158 -0
- data/lib/rgl/dijkstra_visitor.rb +42 -0
- data/lib/rgl/dot.rb +40 -32
- data/lib/rgl/edge_properties_map.rb +55 -0
- data/lib/rgl/edmonds_karp.rb +136 -0
- data/lib/rgl/enumerable_ext.rb +4 -1
- data/lib/rgl/graph_iterator.rb +15 -0
- data/lib/rgl/graph_visitor.rb +138 -0
- data/lib/rgl/graph_wrapper.rb +15 -0
- data/lib/rgl/graphxml.rb +20 -10
- data/lib/rgl/implicit.rb +68 -66
- data/lib/rgl/mutable.rb +37 -31
- data/lib/rgl/path_builder.rb +40 -0
- data/lib/rgl/prim.rb +52 -0
- data/lib/rgl/rdot.rb +411 -374
- data/lib/rgl/topsort.rb +23 -16
- data/lib/rgl/transitivity.rb +29 -27
- data/lib/rgl/traversal.rb +67 -205
- data/rakelib/dep_graph.rake +4 -3
- data/test/bellman_ford_test.rb +187 -0
- data/test/bipartite_test.rb +47 -0
- data/test/components_test.rb +80 -0
- data/test/cycles_test.rb +60 -0
- data/test/dijkstra_test.rb +148 -0
- data/test/directed_graph_test.rb +118 -0
- data/test/dot_test.rb +26 -0
- data/test/edge_properties_map_test.rb +63 -0
- data/test/edge_test.rb +35 -0
- data/test/edmonds_karp_test.rb +105 -0
- data/{tests/TestGraph.rb → test/graph_test.rb} +6 -6
- data/test/graph_xml_test.rb +57 -0
- data/test/implicit_test.rb +53 -0
- data/test/prim_test.rb +98 -0
- data/{tests/TestRdot.rb → test/rdot_test.rb} +309 -308
- data/{tests → test}/test_helper.rb +4 -1
- data/{tests/TestTransitivity.rb → test/transitivity_test.rb} +43 -43
- data/test/traversal_test.rb +221 -0
- data/test/undirected_graph_test.rb +103 -0
- metadata +226 -145
- data/examples/example.jpg +0 -0
- data/examples/module_graph.jpg +0 -0
- data/install.rb +0 -49
- data/tests/TestComponents.rb +0 -65
- data/tests/TestCycles.rb +0 -61
- data/tests/TestDirectedGraph.rb +0 -125
- data/tests/TestDot.rb +0 -18
- data/tests/TestEdge.rb +0 -34
- data/tests/TestGraphXML.rb +0 -57
- data/tests/TestImplicit.rb +0 -52
- data/tests/TestTraversal.rb +0 -220
- data/tests/TestUnDirectedGraph.rb +0 -102
@@ -0,0 +1,115 @@
|
|
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
|
+
# Bellman-Ford shortest paths algorithm has the following event points:
|
11
|
+
#
|
12
|
+
# * examine_edge
|
13
|
+
# * edge_relaxed
|
14
|
+
# * edge_not_relaxed
|
15
|
+
# * edge_minimized
|
16
|
+
# * edge_not_minimized
|
17
|
+
#
|
18
|
+
class BellmanFordVisitor < DijkstraVisitor
|
19
|
+
|
20
|
+
def_event_handlers :edge_minimized, :edge_not_minimized
|
21
|
+
|
22
|
+
def initialize(graph)
|
23
|
+
super(graph)
|
24
|
+
|
25
|
+
# by default, through an exception if a negative-weight cycle is detected
|
26
|
+
@edge_not_minimized_event_handler = lambda do |u, v|
|
27
|
+
raise ArgumentError.new("there is a negative-weight cycle including edge (#{u}, #{v})")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
class BellmanFordAlgorithm
|
34
|
+
|
35
|
+
# Initializes Bellman-Ford algorithm for a _graph_ with provided edges weights map.
|
36
|
+
#
|
37
|
+
def initialize(graph, edge_weights_map, visitor)
|
38
|
+
@graph = graph
|
39
|
+
@edge_weights_map = EdgePropertiesMap.new(edge_weights_map, @graph.directed?)
|
40
|
+
@visitor = visitor
|
41
|
+
end
|
42
|
+
|
43
|
+
# Finds the shortest path form the _source_ to every other vertex of the graph.
|
44
|
+
#
|
45
|
+
# Returns the shortest paths map that contains the shortest path (if it exists) from the source to any vertex of the
|
46
|
+
# graph.
|
47
|
+
#
|
48
|
+
def shortest_paths(source)
|
49
|
+
init(source)
|
50
|
+
relax_edges
|
51
|
+
PathBuilder.new(source, @visitor.parents_map).paths(@graph.vertices)
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def init(source)
|
57
|
+
@visitor.set_source(source)
|
58
|
+
end
|
59
|
+
|
60
|
+
def relax_edges
|
61
|
+
(@graph.size - 1).times do
|
62
|
+
@graph.each_edge do |u, v|
|
63
|
+
relax_edge(u, v)
|
64
|
+
relax_edge(v, u) unless @graph.directed?
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
@graph.each_edge do |u, v|
|
69
|
+
if @visitor.distance_map[u] + @edge_weights_map.edge_property(u, v) < @visitor.distance_map[v]
|
70
|
+
@visitor.handle_edge_not_minimized(u, v)
|
71
|
+
else
|
72
|
+
@visitor.handle_edge_minimized(u, v)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def relax_edge(u, v)
|
78
|
+
@visitor.handle_examine_edge(u, v)
|
79
|
+
|
80
|
+
new_v_distance = @visitor.distance_map[u] + @edge_weights_map.edge_property(u, v)
|
81
|
+
|
82
|
+
if new_v_distance < @visitor.distance_map[v]
|
83
|
+
@visitor.distance_map[v] = new_v_distance
|
84
|
+
@visitor.parents_map[v] = u
|
85
|
+
|
86
|
+
@visitor.handle_edge_relaxed(u, v)
|
87
|
+
else
|
88
|
+
@visitor.handle_edge_not_relaxed(u, v)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
end # class BellmanFordAlgorithm
|
93
|
+
|
94
|
+
module Graph
|
95
|
+
|
96
|
+
# Finds the shortest paths from the _source_ to each vertex of the graph.
|
97
|
+
#
|
98
|
+
# Returns a Hash that maps each vertex of the graph to an Array of vertices that represents the shortest path
|
99
|
+
# from the _source_ to the vertex. If the path doesn't exist, the corresponding hash value is nil. For the _source_
|
100
|
+
# vertex returned hash contains a trivial one-vertex path - [source].
|
101
|
+
#
|
102
|
+
# Unlike Dijkstra algorithm, Bellman-Ford shortest paths algorithm works with negative edge weights.
|
103
|
+
#
|
104
|
+
# Raises ArgumentError if an edge weight is undefined.
|
105
|
+
#
|
106
|
+
# Raises ArgumentError or the graph has negative-weight cycles. This behavior can be overridden my a custom handler
|
107
|
+
# for visitor's _edge_not_minimized_ event.
|
108
|
+
#
|
109
|
+
def bellman_ford_shortest_paths(edge_weights_map, source, visitor = BellmanFordVisitor.new(self))
|
110
|
+
BellmanFordAlgorithm.new(self, edge_weights_map, visitor).shortest_paths(source)
|
111
|
+
end
|
112
|
+
|
113
|
+
end # module Graph
|
114
|
+
|
115
|
+
end # module RGL
|
data/lib/rgl/bidirectional.rb
CHANGED
@@ -3,38 +3,45 @@ require 'rgl/base'
|
|
3
3
|
module RGL
|
4
4
|
|
5
5
|
# BGL defines the concept BidirectionalGraph as follows:
|
6
|
-
#
|
6
|
+
#
|
7
7
|
# The BidirectionalGraph concept refines IncidenceGraph and adds the
|
8
|
-
# requirement for efficient access to the in-edges of each vertex.
|
8
|
+
# requirement for efficient access to the in-edges of each vertex. This
|
9
9
|
# concept is separated from IncidenceGraph because, for directed graphs,
|
10
10
|
# efficient access to in-edges typically requires more storage space,
|
11
|
-
# and many algorithms do not require access to in-edges.
|
11
|
+
# and many algorithms do not require access to in-edges. For undirected
|
12
12
|
# graphs, this is not an issue; because the in_edges() and out_edges()
|
13
13
|
# functions are the same, they both return the edges incident to the vertex.
|
14
|
+
#
|
14
15
|
module BidirectionalGraph
|
16
|
+
|
15
17
|
include Graph
|
16
18
|
|
17
19
|
# Iterator providing access to the in-edges (for directed graphs) or incident
|
18
20
|
# edges (for undirected graphs) of vertex _v_. For both directed and
|
19
21
|
# undirected graphs, the target of an out-edge is required to be vertex _v_
|
20
22
|
# and the source is required to be a vertex that is adjacent to _v_.
|
21
|
-
|
23
|
+
#
|
24
|
+
def each_in_neighbor(v)
|
22
25
|
raise NotImplementedError
|
23
26
|
yield u
|
24
27
|
end
|
25
|
-
|
28
|
+
|
26
29
|
# Returns the number of in-edges (for directed graphs) or the number of
|
27
30
|
# incident edges (for undirected graphs) of vertex _v_.
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
+
#
|
32
|
+
def in_degree(v)
|
33
|
+
r = 0
|
34
|
+
each_in_neighbor(v) { |u| r += 1 }
|
31
35
|
r
|
32
36
|
end
|
33
37
|
|
34
|
-
# Returns the number of in-edges plus out-edges (for directed graphs) or the
|
38
|
+
# Returns the number of in-edges plus out-edges (for directed graphs) or the
|
35
39
|
# number of incident edges (for undirected graphs) of vertex _v_.
|
36
|
-
|
40
|
+
#
|
41
|
+
def degree(v)
|
37
42
|
in_degree(v) + out_degree(v)
|
38
43
|
end
|
44
|
+
|
39
45
|
end
|
46
|
+
|
40
47
|
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'rgl/base'
|
2
|
+
require 'rgl/traversal'
|
3
|
+
|
4
|
+
module RGL
|
5
|
+
|
6
|
+
module Graph
|
7
|
+
|
8
|
+
# Separates graph's vertices into two disjoint sets so that every edge of the graph connects vertices from different
|
9
|
+
# sets. If it's possible, the graph is bipartite.
|
10
|
+
#
|
11
|
+
# Returns an array of two disjoint vertices sets (represented as arrays) if the graph is bipartite. Otherwise,
|
12
|
+
# returns nil.
|
13
|
+
#
|
14
|
+
def bipartite_sets
|
15
|
+
raise NotUndirectedError.new('bipartite sets can only be found for an undirected graph') if directed?
|
16
|
+
|
17
|
+
bfs = BipartiteBFSIterator.new(self)
|
18
|
+
|
19
|
+
# if necessary, we start BFS from each vertex to make sure
|
20
|
+
# that all connected components of the graph are processed
|
21
|
+
each_vertex do |u|
|
22
|
+
next if bfs.finished_vertex?(u)
|
23
|
+
|
24
|
+
bfs.reset_start(u)
|
25
|
+
bfs.move_forward_until { @found_odd_cycle }
|
26
|
+
|
27
|
+
return if bfs.found_odd_cycle
|
28
|
+
end
|
29
|
+
|
30
|
+
bfs.bipartite_sets_map.inject([[], []]) do |sets, (vertex, set)|
|
31
|
+
sets[set] << vertex
|
32
|
+
sets
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Returns true if the graph is bipartite. Otherwise returns false.
|
37
|
+
#
|
38
|
+
def bipartite?
|
39
|
+
!bipartite_sets.nil?
|
40
|
+
end
|
41
|
+
|
42
|
+
end # module Graph
|
43
|
+
|
44
|
+
class BipartiteBFSIterator < BFSIterator
|
45
|
+
|
46
|
+
attr_reader :bipartite_sets_map, :found_odd_cycle
|
47
|
+
|
48
|
+
def reset
|
49
|
+
super
|
50
|
+
|
51
|
+
@bipartite_sets_map = {}
|
52
|
+
@found_odd_cycle = false
|
53
|
+
end
|
54
|
+
|
55
|
+
def set_to_begin
|
56
|
+
super
|
57
|
+
|
58
|
+
@bipartite_sets_map[@start_vertex] = 0
|
59
|
+
end
|
60
|
+
|
61
|
+
def reset_start(new_start)
|
62
|
+
@start_vertex = new_start
|
63
|
+
set_to_begin
|
64
|
+
end
|
65
|
+
|
66
|
+
def handle_tree_edge(u, v)
|
67
|
+
@bipartite_sets_map[v] = (@bipartite_sets_map[u] + 1) % 2 unless u.nil? # put v into the other set
|
68
|
+
end
|
69
|
+
|
70
|
+
def handle_back_edge(u, v)
|
71
|
+
verify_odd_cycle(u, v)
|
72
|
+
end
|
73
|
+
|
74
|
+
def handle_forward_edge(u, v)
|
75
|
+
verify_odd_cycle(u, v)
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def verify_odd_cycle(u, v)
|
81
|
+
u_set = @bipartite_sets_map[u]
|
82
|
+
@found_odd_cycle = true if u_set && u_set == @bipartite_sets_map[v]
|
83
|
+
end
|
84
|
+
|
85
|
+
end # class BipartiteBFSIterator
|
86
|
+
|
87
|
+
end # module RGL
|
data/lib/rgl/condensation.rb
CHANGED
@@ -1,23 +1,28 @@
|
|
1
1
|
require 'rgl/base'
|
2
|
+
require 'rgl/connected_components'
|
2
3
|
require 'rgl/implicit'
|
3
4
|
|
4
5
|
module RGL
|
6
|
+
|
5
7
|
module Graph
|
8
|
+
|
6
9
|
# Returns an RGL::ImplicitGraph where the strongly connected components of
|
7
10
|
# this graph are condensed into single nodes represented by Set instances
|
8
|
-
# containing the members of each strongly connected component.
|
11
|
+
# containing the members of each strongly connected component. Edges
|
9
12
|
# between the different strongly connected components are preserved while
|
10
13
|
# edges within strongly connected components are omitted.
|
11
14
|
#
|
12
15
|
# Raises RGL::NotDirectedError if run on an undirected graph.
|
16
|
+
#
|
13
17
|
def condensation_graph
|
14
18
|
raise NotDirectedError,
|
15
|
-
|
19
|
+
"condensation_graph only supported for directed graphs" unless directed?
|
16
20
|
|
17
21
|
# Get the component map for the strongly connected components.
|
18
22
|
comp_map = strongly_connected_components.comp_map
|
23
|
+
|
19
24
|
# Invert the map such that for any number, n, in the component map a Set
|
20
|
-
# instance is created containing all of the nodes which map to n.
|
25
|
+
# instance is created containing all of the nodes which map to n. The Set
|
21
26
|
# instances will be used to map to the number, n, with which the elements
|
22
27
|
# of the set are associated.
|
23
28
|
inv_comp_map = {}
|
@@ -30,18 +35,22 @@ module RGL
|
|
30
35
|
g.vertex_iterator do |b|
|
31
36
|
inv_comp_map.each_value(&b)
|
32
37
|
end
|
38
|
+
|
33
39
|
g.adjacent_iterator do |scc, b|
|
34
40
|
scc.each do |v|
|
35
41
|
each_adjacent(v) do |w|
|
36
42
|
# Do not make the cluster reference itself in the graph.
|
37
|
-
if comp_map[v] != comp_map[w]
|
43
|
+
if comp_map[v] != comp_map[w]
|
38
44
|
b.call(inv_comp_map[comp_map[w]])
|
39
45
|
end
|
40
46
|
end
|
41
47
|
end
|
42
48
|
end
|
49
|
+
|
43
50
|
g.directed = true
|
44
51
|
end
|
45
52
|
end
|
53
|
+
|
46
54
|
end
|
55
|
+
|
47
56
|
end
|
@@ -11,7 +11,7 @@ module RGL
|
|
11
11
|
module Graph
|
12
12
|
|
13
13
|
# Compute the connected components of an undirected graph, using a
|
14
|
-
# DFS (Depth-first search)-based approach.
|
14
|
+
# DFS (Depth-first search)-based approach. A _connected component_ of
|
15
15
|
# an undirected graph is a set of vertices that are all reachable
|
16
16
|
# from each other.
|
17
17
|
#
|
@@ -19,32 +19,35 @@ module RGL
|
|
19
19
|
# with an array of vertices for each component.
|
20
20
|
#
|
21
21
|
# It raises an exception if the graph is directed.
|
22
|
-
|
22
|
+
#
|
23
23
|
def each_connected_component
|
24
24
|
raise NotUndirectedError,
|
25
|
-
|
26
|
-
|
25
|
+
"each_connected_component only works " +
|
26
|
+
"for undirected graphs." if directed?
|
27
|
+
|
27
28
|
comp = []
|
28
29
|
vis = DFSVisitor.new(self)
|
29
30
|
vis.set_finish_vertex_event_handler { |v| comp << v }
|
30
|
-
|
31
|
+
|
32
|
+
vis.set_start_vertex_event_handler do |v|
|
31
33
|
yield comp unless comp.empty?
|
32
34
|
comp = []
|
33
|
-
|
35
|
+
end
|
36
|
+
|
34
37
|
depth_first_search(vis) { |v| }
|
35
38
|
yield comp unless comp.empty?
|
36
39
|
end
|
37
40
|
|
38
41
|
# This GraphVisitor is used by strongly_connected_components to compute
|
39
42
|
# the strongly connected components of a directed graph.
|
40
|
-
|
43
|
+
#
|
41
44
|
class TarjanSccVisitor < DFSVisitor
|
42
45
|
|
43
46
|
attr_reader :comp_map
|
44
47
|
|
45
48
|
# Creates a new TarjanSccVisitor for graph _g_, which should be directed.
|
46
|
-
|
47
|
-
def initialize
|
49
|
+
#
|
50
|
+
def initialize(g)
|
48
51
|
super g
|
49
52
|
@root_map = {}
|
50
53
|
@comp_map = {}
|
@@ -54,25 +57,28 @@ module RGL
|
|
54
57
|
@stack = []
|
55
58
|
end
|
56
59
|
|
57
|
-
def handle_examine_vertex
|
58
|
-
@root_map[v] =
|
59
|
-
@comp_map[v] =
|
60
|
-
@dfs_time
|
60
|
+
def handle_examine_vertex(v)
|
61
|
+
@root_map[v] = v
|
62
|
+
@comp_map[v] = -1
|
63
|
+
@dfs_time += 1
|
61
64
|
@discover_time_map[v] = @dfs_time
|
62
65
|
@stack.push(v)
|
63
66
|
end
|
64
67
|
|
65
|
-
def handle_finish_vertex
|
68
|
+
def handle_finish_vertex(v)
|
66
69
|
# Search adjacent vertex w with earliest discover time
|
67
70
|
root_v = @root_map[v]
|
71
|
+
|
68
72
|
graph.each_adjacent(v) do |w|
|
69
73
|
if @comp_map[w] == -1
|
70
74
|
root_v = min_discover_time(root_v, @root_map[w])
|
71
75
|
end
|
72
76
|
end
|
77
|
+
|
73
78
|
@root_map[v] = root_v
|
74
|
-
|
75
|
-
|
79
|
+
|
80
|
+
if root_v == v # v is topmost vertex of a SCC
|
81
|
+
begin # pop off all vertices until v
|
76
82
|
w = @stack.pop
|
77
83
|
@comp_map[w] = @c_index
|
78
84
|
end until w == v
|
@@ -81,32 +87,32 @@ module RGL
|
|
81
87
|
end
|
82
88
|
|
83
89
|
# Return the number of components found so far.
|
84
|
-
|
90
|
+
#
|
85
91
|
def num_comp
|
86
92
|
@c_index
|
87
93
|
end
|
88
94
|
|
89
95
|
private
|
90
96
|
|
91
|
-
def min_discover_time
|
97
|
+
def min_discover_time(u, v)
|
92
98
|
@discover_time_map[u] < @discover_time_map[v] ? u : v
|
93
99
|
end
|
94
100
|
|
95
|
-
end
|
101
|
+
end # class TarjanSccVisitor
|
96
102
|
|
97
103
|
# This is Tarjan's algorithm for strongly connected components, from his
|
98
|
-
# paper "Depth first search and linear graph algorithms".
|
99
|
-
# the components in a single application of DFS.
|
104
|
+
# paper "Depth first search and linear graph algorithms". It calculates
|
105
|
+
# the components in a single application of DFS. We implement the
|
100
106
|
# algorithm with the help of the DFSVisitor TarjanSccVisitor.
|
101
107
|
#
|
102
108
|
# === Definition
|
103
|
-
#
|
109
|
+
#
|
104
110
|
# A _strongly connected component_ of a directed graph G=(V,E) is a
|
105
111
|
# maximal set of vertices U which is in V, such that for every pair of
|
106
|
-
# vertices u and
|
112
|
+
# vertices u and v in U, we have both a path from u to v and a path
|
107
113
|
# from v to u. That is to say, u and v are reachable from each other.
|
108
|
-
#
|
109
|
-
# @Article{Tarjan:1972:DFS,
|
114
|
+
#
|
115
|
+
# @Article!{Tarjan:1972:DFS,
|
110
116
|
# author = "R. E. Tarjan",
|
111
117
|
# key = "Tarjan",
|
112
118
|
# title = "Depth First Search and Linear Graph Algorithms",
|
@@ -121,18 +127,20 @@ module RGL
|
|
121
127
|
# bibdate = "Thu Jan 23 09:56:44 1997",
|
122
128
|
# bibsource = "Parallel/Multi.bib, Misc/Reverse.eng.bib",
|
123
129
|
# }
|
124
|
-
#
|
130
|
+
#
|
125
131
|
# The output of the algorithm is recorded in a TarjanSccVisitor _vis_.
|
126
132
|
# vis.comp_map will contain numbers giving the component ID assigned to
|
127
133
|
# each vertex. The number of components is vis.num_comp.
|
128
|
-
|
134
|
+
#
|
129
135
|
def strongly_connected_components
|
130
136
|
raise NotDirectedError,
|
131
|
-
|
137
|
+
"strong_components only works for directed graphs." unless directed?
|
138
|
+
|
132
139
|
vis = TarjanSccVisitor.new(self)
|
133
140
|
depth_first_search(vis) { |v| }
|
134
141
|
vis
|
135
142
|
end
|
136
143
|
|
137
|
-
end
|
138
|
-
|
144
|
+
end # module Graph
|
145
|
+
|
146
|
+
end # module RGL
|