networkx 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +18 -11
  3. data/CONTRIBUTING.md +2 -2
  4. data/Guardfile +1 -1
  5. data/ISSUE_TEMPLATE.md +15 -0
  6. data/PULL_REQUEST_TEMPLATE.md +12 -0
  7. data/README.md +4 -4
  8. data/RELEASE_POLICY.md +20 -0
  9. data/lib/networkx.rb +37 -1
  10. data/lib/networkx/auxillary_functions/cliques.rb +65 -0
  11. data/lib/networkx/auxillary_functions/cycles.rb +104 -0
  12. data/lib/networkx/auxillary_functions/dag.rb +54 -0
  13. data/lib/networkx/auxillary_functions/eccentricity.rb +36 -0
  14. data/lib/networkx/auxillary_functions/mis.rb +23 -0
  15. data/lib/networkx/auxillary_functions/mst.rb +35 -0
  16. data/lib/networkx/auxillary_functions/union_find.rb +24 -0
  17. data/lib/networkx/auxillary_functions/vitality.rb +13 -0
  18. data/lib/networkx/auxillary_functions/wiener.rb +13 -0
  19. data/lib/networkx/converters/to_csv.rb +47 -0
  20. data/lib/networkx/converters/to_json.rb +39 -0
  21. data/lib/networkx/digraph.rb +228 -0
  22. data/lib/networkx/flow/capacityscaling.rb +255 -0
  23. data/lib/networkx/flow/edmondskarp.rb +113 -0
  24. data/lib/networkx/flow/preflowpush.rb +252 -0
  25. data/lib/networkx/flow/shortestaugmentingpath.rb +157 -0
  26. data/lib/networkx/flow/utils.rb +160 -0
  27. data/lib/networkx/graph.rb +341 -0
  28. data/lib/networkx/link_analysis/hits.rb +59 -0
  29. data/lib/networkx/link_analysis/pagerank.rb +47 -0
  30. data/lib/networkx/multidigraph.rb +240 -0
  31. data/lib/networkx/multigraph.rb +171 -0
  32. data/lib/networkx/operators/all.rb +61 -0
  33. data/lib/networkx/operators/binary.rb +244 -0
  34. data/lib/networkx/operators/product.rb +204 -0
  35. data/lib/networkx/operators/unary.rb +17 -0
  36. data/lib/networkx/shortest_path/astar.rb +71 -0
  37. data/lib/networkx/shortest_path/dense.rb +31 -0
  38. data/lib/networkx/shortest_path/unweighted.rb +139 -0
  39. data/lib/networkx/shortest_path/weighted.rb +408 -0
  40. data/lib/networkx/to_matrix.rb +52 -0
  41. data/lib/networkx/traversals/bfs.rb +58 -0
  42. data/lib/networkx/traversals/dfs.rb +79 -0
  43. data/lib/networkx/traversals/edge_dfs.rb +90 -0
  44. data/lib/networkx/version.rb +1 -1
  45. data/networkx.gemspec +4 -1
  46. metadata +70 -4
@@ -0,0 +1,59 @@
1
+ module NetworkX
2
+ # TODO: Reduce method length and method complexity
3
+
4
+ # Computes hits and authority scores for all the graphs
5
+ #
6
+ # @param graph [Graph, DiGraph] a graph
7
+ # @param max_iter [Integer] max iterations to run the hits algorithm
8
+ # @param tol [Numeric] tolerences to cut off the loop
9
+ # @param nstart [Array<Numeric>] starting hub values for the nodes
10
+ #
11
+ # @return [Array<Numeric, Numeric>] hits and authority scores
12
+ def self.hits(graph, max_iter=100, tol=1e-8, nstart)
13
+ return [{}, {}] if graph.nodes.empty?
14
+ h = nstart
15
+ sum = h.values.inject(:+)
16
+ h.each_key { |k| h[k] /= (sum * 1.0) }
17
+ i = 0
18
+ a = {}
19
+
20
+ loop do
21
+ hlast = Marshal.load(Marshal.dump(h))
22
+ h, a = {}, {}
23
+ hlast.each do |k, _v|
24
+ h[k] = 0
25
+ a[k] = 0
26
+ end
27
+ h.each_key { |k| graph.adj[k].each { |nbr, attrs| a[k] += hlast[nbr] * (attrs[:weight] || 1) } }
28
+ h.each_key { |k| graph.adj[k].each { |nbr, attrs| h[k] += a[nbr] * (attrs[:weight] || 1) } }
29
+ smax = h.values.max
30
+ h.each_key { |k| h[k] /= smax }
31
+ smax = a.values.max
32
+ a.each_key { |k| a[k] /= smax }
33
+ break if h.keys.map { |k| (h[k] - hlast[k]).abs }.inject(:+) < tol
34
+ raise ArgumentError, 'Power Iteration failed to converge!' if i > max_iter
35
+ i += 1
36
+ end
37
+ [h, a]
38
+ end
39
+
40
+ # Computes authority matrix for the graph
41
+ #
42
+ # @param graph [Graph, DiGraph] a graph
43
+ #
44
+ # @return [NMatrix] authority matrix for the graph
45
+ def self.authority_matrix(graph)
46
+ matrix, = to_matrix(graph, 0)
47
+ matrix.transpose.dot matrix
48
+ end
49
+
50
+ # Computes hub matrix for the graph
51
+ #
52
+ # @param graph [Graph, DiGraph] a graph
53
+ #
54
+ # @return [NMatrix] hub matrix for the graph
55
+ def self.hub_matrix(graph)
56
+ matrix, = to_matrix(graph, 0)
57
+ matrix.dot matrix.transpose
58
+ end
59
+ end
@@ -0,0 +1,47 @@
1
+ module NetworkX
2
+ # TODO: Reduce method length and method complexity
3
+
4
+ # Computes pagerank values for the graph
5
+ #
6
+ # @param graph [Graph] a graph
7
+ # @param init [Array<Numeric>] initial pagerank values for the nodes
8
+ # @param alpha [Numeric] the alpha value to compute the pagerank
9
+ # @param eps [Numeric] tolerence to check for convergence
10
+ # @param max_iter [Integer] max iterations for the pagerank algorithm to run
11
+ #
12
+ # @return [Array<Numeric>] pagerank values of the graph
13
+ def self.pagerank(graph, init, alpha=0.85, eps=1e-4, max_iter=100)
14
+ dim = graph.nodes.length
15
+ raise ArgumentError, 'Init array needs to have same length as number of graph nodes!'\
16
+ unless dim == init.length
17
+ matrix = []
18
+ elem_ind = {}
19
+ p = []
20
+ curr = init.values
21
+ init.keys.each_with_index { |n, i| elem_ind[n] = i }
22
+ graph.adj.each do |_u, u_edges|
23
+ adj_arr = Array.new(dim, 0)
24
+ u_edges.each do |v, _|
25
+ adj_arr[elem_ind[v]] = 1
26
+ end
27
+ matrix << adj_arr
28
+ end
29
+ (0..(dim - 1)).each do |i|
30
+ p[i] = []
31
+ (0..(dim - 1)).each { |j| p[i][j] = matrix[i][j] / (matrix[i].inject(:+) * 1.0) }
32
+ end
33
+
34
+ max_iter.times do |_|
35
+ prev = curr.clone
36
+ dim.times do |i|
37
+ ip = 0
38
+ dim.times { |j| ip += p.transpose[i][j] * prev[j] }
39
+ curr[i] = (alpha * ip) + (1 - alpha) / (dim * 1.0)
40
+ end
41
+ err = 0
42
+ dim.times { |i| err += (prev[i] - curr[i]).abs }
43
+ return curr if err < eps
44
+ end
45
+ raise ArgumentError, 'PageRank failed to converge!'
46
+ end
47
+ end
@@ -0,0 +1,240 @@
1
+ module NetworkX
2
+ # Describes the class for making MultiDiGraphs
3
+ #
4
+ # @attr_reader adj [Hash{ Object => Hash{ Object => Hash{ Integer => Hash{ Object => Object } } } }]
5
+ # Stores the edges and their attributes in an adjencency list form
6
+ # @attr_reader pred [Hash{ Object => Hash{ Object => Hash{ Integer => Hash{ Object => Object } } } }]
7
+ # Stores the reverse edges and their attributes in an adjencency list form
8
+ # @attr_reader nodes [Hash{ Object => Hash{ Object => Object } }] Stores the nodes and their attributes
9
+ # @attr_reader graph [Hash{ Object => Object }] Stores the attributes of the graph
10
+ class MultiDiGraph < DiGraph
11
+ # Returns a new key
12
+ #
13
+ # @param node_1 [Object] the first node of a given edge
14
+ # @param node_2 [Object] the second node of a given edge
15
+ def new_edge_key(node_1, node_2)
16
+ return 0 if @adj[node_1][node_2].nil?
17
+ key = @adj[node_1][node_2].length
18
+ key += 1 while @adj[node_1][node_2].key?(key)
19
+ key
20
+ end
21
+
22
+ # Adds the respective edge
23
+ #
24
+ # @example Add an edge with attribute name
25
+ # graph.add_edge(node1, node2, name: "Edge1")
26
+ #
27
+ # @example Add an edge with no attribute
28
+ # graph.add_edge("Bangalore", "Chennai")
29
+ #
30
+ # @param node_1 [Object] the first node of the edge
31
+ # @param node_2 [Object] the second node of the edge
32
+ # @param edge_attrs [Hash{ Object => Object }] the hash of the edge attributes
33
+ def add_edge(node_1, node_2, **edge_attrs)
34
+ add_node(node_1)
35
+ add_node(node_2)
36
+ key = new_edge_key(node_1, node_2)
37
+ all_edge_attrs = @adj[node_1][node_2] || {}
38
+ all_edge_attrs[key] = edge_attrs
39
+ @adj[node_1][node_2] = all_edge_attrs
40
+ @pred[node_2][node_1] = all_edge_attrs
41
+ end
42
+
43
+ # TODO: Reduce method complexity
44
+
45
+ # Removes edge from the graph
46
+ #
47
+ # @example
48
+ # graph.remove_edge('Noida', 'Bangalore')
49
+ #
50
+ # @param node_1 [Object] the first node of the edge
51
+ # @param node_2 [Object] the second node of the edge
52
+ def remove_edge(node_1, node_2, key=nil)
53
+ if key.nil?
54
+ super(node_1, node_2)
55
+ return
56
+ end
57
+ raise KeyError, "#{node_1} is not a valid node." unless @nodes.key?(node_1)
58
+ raise KeyError, "#{node_2} is not a valid node" unless @nodes.key?(node_2)
59
+ raise KeyError, 'The given edge is not a valid one.' unless @adj[node_1].key?(node_2)
60
+ raise KeyError, 'The given edge is not a valid one.' unless @adj[node_1][node_2].key?(key)
61
+ @adj[node_1][node_2].delete(key)
62
+ @pred[node_2][node_1].delete(key)
63
+ end
64
+
65
+ # Checks if the the edge consisting of two nodes is present in the graph
66
+ #
67
+ # @example
68
+ # graph.edge?(node_1, node_2)
69
+ #
70
+ # @param node_1 [Object] the first node of the edge to be checked
71
+ # @param node_2 [Object] the second node of the edge to be checked
72
+ # @param key [Integer] the key of the given edge
73
+ def edge?(node_1, node_2, key=nil)
74
+ super(node_1, node_2) if key.nil?
75
+ node?(node_1) && @adj[node_1].key?(node_2) && @adj[node_1][node_2].key?(key)
76
+ end
77
+
78
+ # Returns the undirected version of the graph
79
+ #
80
+ # @example
81
+ # graph.to_undirected
82
+ def to_undirected
83
+ graph = NetworkX::Graph.new(@graph)
84
+ @nodes.each { |node, node_attr| graph.add_node(node, node_attr) }
85
+ @adj.each do |node_1, node_1_edges|
86
+ node_1_edges.each do |node_2, node_1_node_2|
87
+ edge_attrs = {}
88
+ node_1_node_2.each { |_key, attrs| edge_attrs.merge!(attrs) }
89
+ graph.add_edge(node_1, node_2, edge_attrs)
90
+ end
91
+ end
92
+ graph
93
+ end
94
+
95
+ # Returns the directed version of the graph
96
+ #
97
+ # @example
98
+ # graph.to_directed
99
+ def to_directed
100
+ graph = NetworkX::DiGraph.new(@graph)
101
+ @nodes.each { |node, node_attr| graph.add_node(node, node_attr) }
102
+ @adj.each do |node_1, node_1_edges|
103
+ node_1_edges.each do |node_2, node_1_node_2|
104
+ edge_attrs = {}
105
+ node_1_node_2.each { |_key, attrs| edge_attrs.merge!(attrs) }
106
+ graph.add_edge(node_1, node_2, edge_attrs)
107
+ end
108
+ end
109
+ graph
110
+ end
111
+
112
+ # Returns the multigraph version of the graph
113
+ #
114
+ # @example
115
+ # graph.to_multigraph
116
+ def to_multigraph
117
+ graph = NetworkX::MultiGraph.new(@graph)
118
+ @nodes.each { |node, node_attr| graph.add_node(node, node_attr) }
119
+ @adj.each do |node_1, node_1_edges|
120
+ node_1_edges.each_key do |node_2, node_1_node_2|
121
+ edge_attrs = {}
122
+ node_1_node_2.each { |_key, attrs| graph.add_edge(node_1, node_2, attrs) }
123
+ graph.add_edge(node_1, node_2, edge_attrs)
124
+ end
125
+ end
126
+ graph
127
+ end
128
+
129
+ # Returns the reversed version of the graph
130
+ #
131
+ # @example
132
+ # graph.reverse
133
+ def reverse
134
+ new_graph = NetworkX::MultiDiGraph.new(@graph)
135
+ @nodes.each { |node, attrs| new_graph.add_node(node, attrs) }
136
+ @adj.each do |u, u_edges|
137
+ u_edges.each { |v, uv_attrs| uv_attrs.each { |_k, edge_attrs| new_graph.add_edge(v, u, edge_attrs) } }
138
+ end
139
+ new_graph
140
+ end
141
+
142
+ # Returns in-degree of a given node
143
+ #
144
+ # @example
145
+ # graph.in_degree(node)
146
+ #
147
+ # @param node [Object] the node whose in degree is to be calculated
148
+ def in_degree(node)
149
+ @pred[node].values.map(:length).inject(:+)
150
+ end
151
+
152
+ # Returns out-degree of a given node
153
+ #
154
+ # @example
155
+ # graph.out_degree(node)
156
+ #
157
+ # @param node [Object] the node whose out degree is to be calculated
158
+ def out_degree(node)
159
+ @adj[node].values.map(:length).inject(:+)
160
+ end
161
+
162
+ # Returns number of edges
163
+ #
164
+ # @example
165
+ # graph.number_of_edges
166
+ def number_of_edges
167
+ @adj.values.flat_map(&:values).map(&:length).inject(:+)
168
+ end
169
+
170
+ # Returns the size of the graph
171
+ #
172
+ # @example
173
+ # graph.size(true)
174
+ #
175
+ # @param is_weighted [Bool] if true, method returns sum of weights of all edges
176
+ # else returns number of edges
177
+ def size(is_weighted=false)
178
+ if is_weighted
179
+ graph_size = 0
180
+ @adj.each do |_, hash_val|
181
+ hash_val.each { |_, v| v.each { |_, attrs| graph_size += attrs[:weight] if attrs.key?(:weight) } }
182
+ end
183
+ return graph_size
184
+ end
185
+ number_of_edges
186
+ end
187
+
188
+ # TODO: Reduce method length and method complexity
189
+
190
+ # Returns subgraph consisting of given array of nodes
191
+ #
192
+ # @example
193
+ # graph.subgraph(%w[Mumbai Nagpur])
194
+ #
195
+ # @param nodes [Array<Object>] the nodes to be included in the subgraph
196
+ def subgraph(nodes)
197
+ case nodes
198
+ when Array, Set
199
+ sub_graph = NetworkX::MultiDiGraph.new(@graph)
200
+ nodes.each do |u, _|
201
+ raise KeyError, "#{u} does not exist in the current graph!" unless @nodes.key?(u)
202
+ sub_graph.add_node(u, @nodes[u])
203
+ @adj[u].each do |v, edge_val|
204
+ edge_val.each { |_, keyval| sub_graph.add_edge(u, v, keyval) if @adj[u].key?(v) && nodes.include?(v) }
205
+ end
206
+ return sub_graph
207
+ end
208
+ else
209
+ raise ArgumentError, 'Expected Argument to be Array or Set of nodes, '\
210
+ "received #{nodes.class.name} instead."
211
+ end
212
+ end
213
+
214
+ # TODO: Reduce method length and method complexity
215
+
216
+ # Returns subgraph conisting of given edges
217
+ #
218
+ # @example
219
+ # graph.edge_subgraph([%w[Nagpur Wardha], %w[Nagpur Mumbai]])
220
+ #
221
+ # @param edges [Array<Object, Object>] the edges to be included in the subraph
222
+ def edge_subgraph(edges)
223
+ case edges
224
+ when Array, Set
225
+ sub_graph = NetworkX::MultiDiGraph.new(@graph)
226
+ edges.each do |u, v|
227
+ raise KeyError, "Edge between #{u} and #{v} does not exist in the graph!" unless @nodes.key?(u)\
228
+ && @adj[u].key?(v)
229
+ sub_graph.add_node(u, @nodes[u])
230
+ sub_graph.add_node(v, @nodes[v])
231
+ @adj[u][v].each { |_, keyval| sub_graph.add_edge(u, v, keyval) }
232
+ end
233
+ return sub_graph
234
+ else
235
+ raise ArgumentError, 'Expected Argument to be Array or Set of edges, '\
236
+ "received #{edges.class.name} instead."
237
+ end
238
+ end
239
+ end
240
+ end
@@ -0,0 +1,171 @@
1
+ module NetworkX
2
+ # Describes the class for making MultiGraphs
3
+ #
4
+ # @attr_reader adj [Hash{ Object => Hash{ Object => Hash{ Integer => Hash{ Object => Object } } } }]
5
+ # Stores the edges and their attributes in an adjencency list form
6
+ # @attr_reader nodes [Hash{ Object => Hash{ Object => Object } }] Stores the nodes and their attributes
7
+ # @attr_reader graph [Hash{ Object => Object }] Stores the attributes of the gra
8
+ class MultiGraph < Graph
9
+ # Returns a new key
10
+ #
11
+ # @param node_1 [Object] the first node of a given edge
12
+ # @param node_2 [Object] the second node of a given edge
13
+ def new_edge_key(node_1, node_2)
14
+ return 0 if @adj[node_1][node_2].nil?
15
+ key = @adj[node_1][node_2].length
16
+ key += 1 while @adj[node_1][node_2].key?(key)
17
+ key
18
+ end
19
+
20
+ # Adds the respective edge
21
+ #
22
+ # @example Add an edge with attribute name
23
+ # graph.add_edge(node1, node2, name: "Edge1")
24
+ #
25
+ # @example Add an edge with no attribute
26
+ # graph.add_edge("Bangalore", "Chennai")
27
+ #
28
+ # @param node_1 [Object] the first node of the edge
29
+ # @param node_2 [Object] the second node of the edge
30
+ # @param edge_attrs [Hash{ Object => Object }] the hash of the edge attributes
31
+ def add_edge(node_1, node_2, **edge_attrs)
32
+ add_node(node_1)
33
+ add_node(node_2)
34
+ key = new_edge_key(node_1, node_2)
35
+ all_edge_attrs = @adj[node_1][node_2] || {}
36
+ all_edge_attrs[key] = edge_attrs
37
+ @adj[node_1][node_2] = all_edge_attrs
38
+ @adj[node_2][node_1] = all_edge_attrs
39
+ end
40
+
41
+ # TODO: Reduce method complexity
42
+
43
+ # Removes edge from the graph
44
+ #
45
+ # @example
46
+ # graph.remove_edge('Noida', 'Bangalore')
47
+ #
48
+ # @param node_1 [Object] the first node of the edge
49
+ # @param node_2 [Object] the second node of the edge
50
+ def remove_edge(node_1, node_2, key=nil)
51
+ if key.nil?
52
+ super(node_1, node_2)
53
+ return
54
+ end
55
+ raise KeyError, "#{node_1} is not a valid node." unless @nodes.key?(node_1)
56
+ raise KeyError, "#{node_2} is not a valid node" unless @nodes.key?(node_2)
57
+ raise KeyError, 'The given edge is not a valid one.' unless @adj[node_1].key?(node_2)
58
+ raise KeyError, 'The given edge is not a valid one.' unless @adj[node_1][node_2].key?(key)
59
+ @adj[node_1][node_2].delete(key)
60
+ @adj[node_2][node_1].delete(key)
61
+ end
62
+
63
+ # Returns the size of the graph
64
+ #
65
+ # @example
66
+ # graph.size(true)
67
+ #
68
+ # @param is_weighted [Bool] if true, method returns sum of weights of all edges
69
+ # else returns number of edges
70
+ def size(is_weighted=false)
71
+ if is_weighted
72
+ graph_size = 0
73
+ @adj.each do |_, hash_val|
74
+ hash_val.each { |_, v| v.each { |_, attrs| graph_size += attrs[:weight] if attrs.key?(:weight) } }
75
+ end
76
+ return graph_size / 2
77
+ end
78
+ number_of_edges
79
+ end
80
+
81
+ # Returns number of edges
82
+ #
83
+ # @example
84
+ # graph.number_of_edges
85
+ def number_of_edges
86
+ @adj.values.flat_map(&:values).map(&:length).inject(:+) / 2
87
+ end
88
+
89
+ # Checks if the the edge consisting of two nodes is present in the graph
90
+ #
91
+ # @example
92
+ # graph.edge?(node_1, node_2)
93
+ #
94
+ # @param node_1 [Object] the first node of the edge to be checked
95
+ # @param node_2 [Object] the second node of the edge to be checked
96
+ # @param key [Integer] the key of the given edge
97
+ def edge?(node_1, node_2, key=nil)
98
+ super(node_1, node_2) if key.nil?
99
+ node?(node_1) && @adj[node_1].key?(node_2) && @adj[node_1][node_2].key?(key)
100
+ end
101
+
102
+ # Returns the undirected version of the graph
103
+ #
104
+ # @example
105
+ # graph.to_undirected
106
+ def to_undirected
107
+ graph = NetworkX::Graph.new(@graph)
108
+ @nodes.each { |node, node_attr| graph.add_node(node, node_attr) }
109
+ @adj.each do |node_1, node_1_edges|
110
+ node_1_edges.each do |node_2, node_1_node_2|
111
+ edge_attrs = {}
112
+ node_1_node_2.each { |_key, attrs| edge_attrs.merge!(attrs) }
113
+ graph.add_edge(node_1, node_2, edge_attrs)
114
+ end
115
+ end
116
+ graph
117
+ end
118
+
119
+ # TODO: Reduce method complexity and method length
120
+
121
+ # Returns subgraph consisting of given array of nodes
122
+ #
123
+ # @example
124
+ # graph.subgraph(%w[Mumbai Nagpur])
125
+ #
126
+ # @param nodes [Array<Object>] the nodes to be included in the subgraph
127
+ def subgraph(nodes)
128
+ case nodes
129
+ when Array, Set
130
+ sub_graph = NetworkX::MultiGraph.new(@graph)
131
+ nodes.each do |u, _|
132
+ raise KeyError, "#{u} does not exist in the current graph!" unless @nodes.key?(u)
133
+ sub_graph.add_node(u, @nodes[u])
134
+ @adj[u].each do |v, edge_val|
135
+ edge_val.each { |_, keyval| sub_graph.add_edge(u, v, keyval) if @adj[u].key?(v) && nodes.include?(v) }
136
+ end
137
+ return sub_graph
138
+ end
139
+ else
140
+ raise ArgumentError, 'Expected Argument to be Array or Set of nodes, '\
141
+ "received #{nodes.class.name} instead."
142
+ end
143
+ end
144
+
145
+ # TODO: Reduce method complexity and method length
146
+
147
+ # Returns subgraph conisting of given edges
148
+ #
149
+ # @example
150
+ # graph.edge_subgraph([%w[Nagpur Wardha], %w[Nagpur Mumbai]])
151
+ #
152
+ # @param edges [Array<Object, Object>] the edges to be included in the subraph
153
+ def edge_subgraph(edges)
154
+ case edges
155
+ when Array, Set
156
+ sub_graph = NetworkX::MultiGraph.new(@graph)
157
+ edges.each do |u, v|
158
+ raise KeyError, "Edge between #{u} and #{v} does not exist in the graph!" unless @nodes.key?(u)\
159
+ && @adj[u].key?(v)
160
+ sub_graph.add_node(u, @nodes[u])
161
+ sub_graph.add_node(v, @nodes[v])
162
+ @adj[u][v].each { |_, keyval| sub_graph.add_edge(u, v, keyval) }
163
+ end
164
+ return sub_graph
165
+ else
166
+ raise ArgumentError, 'Expected Argument to be Array or Set of edges, '\
167
+ "received #{edges.class.name} instead."
168
+ end
169
+ end
170
+ end
171
+ end