networkx 0.1.0 → 0.1.1

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