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,157 @@
1
+ # TODO: Reduce module length
2
+
3
+ module NetworkX
4
+ # TODO: Reduce method complexity and method length
5
+
6
+ # Helper function for running the shortest augmenting path algorithm
7
+ def self.shortest_augmenting_path_impl(graph, source, target, residual, two_phase, cutoff)
8
+ raise ArgumentError, 'Source is not in the graph!' unless graph.nodes.key?(source)
9
+ raise ArgumentError, 'Target is not in the graph!' unless graph.nodes.key?(target)
10
+ raise ArgumentError, 'Source and Target are the same!' if source == target
11
+
12
+ residual = residual.nil? ? build_residual_network(graph) : residual
13
+ r_nodes = residual.nodes
14
+ r_pred = residual.pred
15
+ r_adj = residual.adj
16
+
17
+ r_adj.each_value do |u_edges|
18
+ u_edges.each_value do |attrs|
19
+ attrs[:flow] = 0
20
+ end
21
+ end
22
+
23
+ heights = {target => 0}
24
+ q = [[target, 0]]
25
+
26
+ until q.empty?
27
+ u, height = q.shift
28
+ height += 1
29
+ r_pred[u].each do |v, attrs|
30
+ if !heights.key?(v) && attrs[:flow] < attrs[:capacity]
31
+ heights[v] = height
32
+ q << [v, height]
33
+ end
34
+ end
35
+ end
36
+
37
+ unless heights.key?(source)
38
+ residual.graph[:flow_value] = 0
39
+ return residual
40
+ end
41
+
42
+ n = graph.nodes.length
43
+ m = residual.size / 2
44
+
45
+ r_nodes.each do |node, attrs|
46
+ attrs[:height] = heights.key?(node) ? heights[node] : n
47
+ attrs[:curr_edge] = CurrentEdge.new(r_adj[node])
48
+ end
49
+
50
+ counts = Array.new(2 * n - 1, 0)
51
+ counts.fill(0)
52
+ r_nodes.each_value { |attrs| counts[attrs[:height]] += 1 }
53
+ inf = graph.graph[:inf]
54
+
55
+ cutoff = Float::INFINITY if cutoff.nil?
56
+ flow_value = 0
57
+ path = [source]
58
+ u = source
59
+ d = two_phase ? n : [m ** 0.5, 2 * n ** (2. / 3)].min.floor
60
+ done = r_nodes[source][:height] >= d
61
+
62
+ until done
63
+ height = r_nodes[u][:height]
64
+ curr_edge = r_nodes[u][:curr_edge]
65
+
66
+ loop do
67
+ v, attr = curr_edge.get
68
+ if height == r_nodes[v][:height] + 1 && attr[:flow] < attr[:capacity]
69
+ path << v
70
+ u = v
71
+ break
72
+ end
73
+ begin
74
+ curr_edge.move_to_next
75
+ rescue StopIteration
76
+ if counts[height].zero?
77
+ residual.graph[:flow_value] = flow_value
78
+ return residual
79
+ end
80
+ height = relabel(u, n, r_adj, r_nodes)
81
+ if u == source && height >= d
82
+ if !two_phase
83
+ residual.graph[:flow_value] = flow_value
84
+ return residual
85
+ else
86
+ done = true
87
+ break
88
+ end
89
+ end
90
+ counts[height] += 1
91
+ r_nodes[u][:height] = height
92
+ unless u == source
93
+ path.pop
94
+ u = path[-1]
95
+ break
96
+ end
97
+ end
98
+ end
99
+ next unless u == target
100
+ flow_value += augment(path, inf, r_adj)
101
+ if flow_value >= cutoff
102
+ residual.graph[:flow_value] = flow_value
103
+ return residual
104
+ end
105
+ end
106
+ flow_value += edmondskarp_core(residual, source, target, cutoff - flow_value)
107
+ residual.graph[:flow_value] = flow_value
108
+ residual
109
+ end
110
+
111
+ # TODO: Reduce method complexity and method length
112
+
113
+ # Helper function for augmenting flow
114
+ def augment(path, inf, r_adj)
115
+ flow = inf
116
+ temp_path = path.clone
117
+ u = temp_path.shift
118
+ temp_path.each do |v|
119
+ attr = r_adj[u][v]
120
+ flow = [flow, attr[:capacity] - attr[:flow]].min
121
+ u = v
122
+ end
123
+ raise ArgumentError, 'Infinite capacity path!' if flow * 2 > inf
124
+ temp_path = path.clone
125
+ u = temp_path.shift
126
+ temp_path.each do |v|
127
+ r_adj[u][v][:flow] += flow
128
+ r_adj[v][u][:flow] -= flow
129
+ u = v
130
+ end
131
+ flow
132
+ end
133
+
134
+ # Helper function to relable a node to create a permissible edge
135
+ def self.relabel(node, num, r_adj, r_nodes)
136
+ height = num - 1
137
+ r_adj[node].each do |v, attrs|
138
+ height = [height, r_nodes[v][:height]].min if attrs[:flow] < attrs[:capacity]
139
+ end
140
+ height + 1
141
+ end
142
+
143
+ # Computes max flow using shortest augmenting path algorithm
144
+ #
145
+ # @param graph [DiGraph] a graph
146
+ # @param source [Object] source node
147
+ # @param target [Object] target node
148
+ # @param residual [DiGraph, nil] residual graph
149
+ # @param value_only [Boolean] if true, compute only the maximum flow value
150
+ # @param two_phase [Boolean] if true, two phase variant is used
151
+ # @param cutoff [Numeric] cutoff value for the algorithm
152
+ #
153
+ # @return [DiGraph] a residual graph containing the flow values
154
+ def self.shortest_augmenting_path(graph, source, target, residual=nil, _value_only=false, two_phase=false, cutoff=nil)
155
+ shortest_augmenting_path_impl(graph, source, target, residual, two_phase, cutoff)
156
+ end
157
+ end
@@ -0,0 +1,160 @@
1
+ module NetworkX
2
+ # Helper class for preflow push algorithm
3
+ class CurrentEdge
4
+ attr_reader :curr, :edges
5
+
6
+ def initialize(edges)
7
+ @edges = edges
8
+ @index = {}
9
+ @n = edges.length
10
+ @curr = 0
11
+ edges.each_with_index { |(key, _value), idx| @index[idx] = key }
12
+ end
13
+
14
+ def get
15
+ [@index[@curr], @edges[@index[@curr]]]
16
+ end
17
+
18
+ def move_to_next
19
+ @temp = @curr
20
+ @curr = (@curr + 1) % @n
21
+ raise StopIteration if @temp == @n - 1
22
+ end
23
+ end
24
+
25
+ # Helper class for preflow push algorithm
26
+ class Level
27
+ attr_reader :inactive, :active
28
+
29
+ def initialize
30
+ @inactive = Set.new
31
+ @active = Set.new
32
+ end
33
+ end
34
+
35
+ # Helper class for preflow push algorithm
36
+ class GlobalRelabelThreshold
37
+ def initialize(num_1, num_2, freq)
38
+ freq = freq.nil? ? Float::INFINITY : freq
39
+ @threshold = (num_1 + num_2) / freq
40
+ @work = 0
41
+ end
42
+
43
+ def add_work(work)
44
+ @work += work
45
+ end
46
+
47
+ def reached?
48
+ @work >= @threshold
49
+ end
50
+
51
+ def clear_work
52
+ @work = 0
53
+ end
54
+ end
55
+
56
+ # TODO: Reduce method complexity and method length
57
+
58
+ # Builds a residual graph from a constituent graph
59
+ #
60
+ # @param graph [DiGraph] a graph
61
+ #
62
+ # @return [DiGraph] residual graph
63
+ def self.build_residual_network(graph)
64
+ raise NotImplementedError, 'MultiGraph and MultiDiGraph not supported!' if graph.multigraph?
65
+
66
+ r_network = NetworkX::DiGraph.new(inf: 0, flow_value: 0)
67
+ r_network.add_nodes(graph.nodes.keys)
68
+ inf = Float::INFINITY
69
+ edge_list = []
70
+
71
+ graph.adj.each do |u, u_edges|
72
+ require 'spec_helper'
73
+ RSpec.describe NetworkX::DiGraph do
74
+ subject { graph }
75
+
76
+ let(:graph) { described_class.new }
77
+
78
+ before do
79
+ graph.add_edge(1, 2)
80
+ graph.add_edge(2, 4)
81
+ end
82
+
83
+ context 'when capacity_scaling is called' do
84
+ subject { NetworkX.capacity_scaling(graph) }
85
+
86
+ it { is_expected.to eq([0, {1=>{2=>0}, 2=>{4=>0}, 4=>{}}]) }
87
+ end
88
+ end
89
+
90
+ u_edges.each do |v, uv_attrs|
91
+ edge_list << [u, v, uv_attrs] if (uv_attrs[:capacity] || inf) > 0 && u != v
92
+ end
93
+ end
94
+
95
+ inf_chk = 3 * edge_list.inject(0) do |result, arr|
96
+ arr[2].key?(:capacity) && arr[2][:capacity] != inf ? (result + arr[2][:capacity]) : result
97
+ end
98
+ inf = inf_chk.zero? ? 1 : inf_chk
99
+
100
+ if graph.directed?
101
+ edge_list.each do |u, v, attrs|
102
+ r = [attrs[:capacity] || inf, inf].min
103
+ if r_network.adj[u][v].nil?
104
+ r_network.add_edge(u, v, capacity: r)
105
+ r_network.add_edge(v, u, capacity: 0)
106
+ else
107
+ r_network[u][v][:capacity] = r
108
+ end
109
+ end
110
+ else
111
+ edge_list.each do |u, v, attrs|
112
+ r = [attrs[:capacity] || inf, inf].min
113
+ r_network.add_edge(u, v, capacity: r)
114
+ r_network.add_edge(v, u, capacity: r)
115
+ end
116
+ end
117
+ r_network.graph[:inf] = inf
118
+ r_network
119
+ end
120
+
121
+ # TODO: Reduce method complexity and method length
122
+
123
+ # Detects unboundedness in a graph, raises exception when
124
+ # infinite capacity flow is found
125
+ #
126
+ # @param r_network [DiGraph] a residual graph
127
+ # @param source [Object] source node
128
+ # @param target [Object] target node
129
+ def self.detect_unboundedness(r_network, source, target)
130
+ q = [source]
131
+ seen = Set.new([source])
132
+ inf = r_network.graph[:inf]
133
+ until q.empty?
134
+ u = q.shift
135
+ r_network.adj[u].each do |v, uv_attrs|
136
+ next unless uv_attrs[:capacity] == inf && !seen.include?(v)
137
+ raise ArgumentError, 'Infinite capacity flow!' if v == target
138
+ seen << v
139
+ q << v
140
+ end
141
+ end
142
+ end
143
+
144
+ # Build flow dictionary of a graph from its residual graph
145
+ #
146
+ # @param graph [DiGraph] a graph
147
+ # @param residual [DiGraph] residual graph
148
+ #
149
+ # @return [Hash{ Object => Hash{ Object => Numeric }] flowdict containing all
150
+ # the flow values in the edges
151
+ def self.build_flow_dict(graph, residual)
152
+ flow_dict = {}
153
+ graph.edges.each do |u, u_edges|
154
+ flow_dict[u] = {}
155
+ u_edges.each_key { |v| flow_dict[u][v] = 0 }
156
+ u_edges.each_key { |v| flow_dict[u][v] = residual[u][v][:flow] if residual[u][v][:flow] > 0 }
157
+ end
158
+ flow_dict
159
+ end
160
+ end
@@ -0,0 +1,341 @@
1
+ module NetworkX
2
+ # Describes the class for making Undirected Graphs
3
+ #
4
+ # @attr_reader adj [Hash{ Object => Hash{ Object => 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 graph
8
+ class Graph
9
+ attr_reader :adj, :nodes, :graph
10
+
11
+ # Constructor for initializing graph
12
+ #
13
+ # @example Initialize a graph with attributes 'type' and 'name'
14
+ # graph = NetworkX::Graph.new(name: "Social Network", type: "undirected")
15
+ #
16
+ # @param graph_attrs [Hash{ Object => Object }] the graph attributes in a hash format
17
+ def initialize(**graph_attrs)
18
+ @nodes = {}
19
+ @adj = {}
20
+ @graph = {}
21
+
22
+ @graph = graph_attrs
23
+ end
24
+
25
+ # Adds the respective edges
26
+ #
27
+ # @example Add an edge with attribute name
28
+ # graph.add_edge(node1, node2, name: "Edge1")
29
+ #
30
+ # @example Add an edge with no attribute
31
+ # graph.add_edge("Bangalore", "Chennai")
32
+ #
33
+ # @param node_1 [Object] the first node of the edge
34
+ # @param node_2 [Object] the second node of the edge
35
+ # @param edge_attrs [Hash{ Object => Object }] the hash of the edge attributes
36
+ def add_edge(node_1, node_2, **edge_attrs)
37
+ add_node(node_1)
38
+ add_node(node_2)
39
+
40
+ edge_attrs = (@adj[node_1][node_2] || {}).merge(edge_attrs)
41
+ @adj[node_1][node_2] = edge_attrs
42
+ @adj[node_2][node_1] = edge_attrs
43
+ end
44
+
45
+ # Adds multiple edges from an array
46
+ #
47
+ # @example Add multiple edges without any attributes
48
+ # graph.add_edges([['Nagpur', 'Kgp'], ['Noida', 'Kgp']])
49
+ # @param edges [Array<Object, Object>]
50
+ def add_edges(edges)
51
+ case edges
52
+ when Array
53
+ edges.each { |node_1, node_2, **attrs| add_edge(node_1, node_2, attrs) }
54
+ else
55
+ raise ArgumentError, 'Expected argument to be an Array of edges, '\
56
+ "received #{edges.class.name} instead."
57
+ end
58
+ end
59
+
60
+ # Adds a node and its attributes to the graph
61
+ #
62
+ # @example Add a node with attribute 'type'
63
+ # graph.add_node("Noida", type: "city")
64
+ #
65
+ # @param node [Object] the node object
66
+ # @param node_attrs [Hash{ Object => Object }] the hash of the attributes of the node
67
+ def add_node(node, **node_attrs)
68
+ if @nodes.key?(node)
69
+ @nodes[node].merge!(node_attrs)
70
+ else
71
+ @adj[node] = {}
72
+ @nodes[node] = node_attrs
73
+ end
74
+ end
75
+
76
+ # Adds multiple nodes to the graph
77
+ #
78
+ # @example Adds multiple nodes with attribute 'type'
79
+ # graph.add_nodes([["Noida", type: "city"], ["Kgp", type: "town"]])
80
+ #
81
+ # @param nodes [Array<Object, Hash{ Object => Object }>] the Array of pair containing nodes and its attributes
82
+ def add_nodes(nodes)
83
+ case nodes
84
+ when Set, Array
85
+ nodes.each { |node, **node_attrs| add_node(node, node_attrs) }
86
+ else
87
+ raise ArgumentError, 'Expected argument to be an Array or Set of nodes, '\
88
+ "received #{nodes.class.name} instead."
89
+ end
90
+ end
91
+
92
+ # Removes node from the graph
93
+ #
94
+ # @example
95
+ # graph.remove_node("Noida")
96
+ #
97
+ # @param node [Object] the node to be removed
98
+ def remove_node(node)
99
+ raise KeyError, "Error in deleting node #{node} from Graph." unless @nodes.key?(node)
100
+ @adj[node].each_key { |k| @adj[k].delete(node) }
101
+ @adj.delete(node)
102
+ @nodes.delete(node)
103
+ end
104
+
105
+ # Removes multiple nodes from the graph
106
+ #
107
+ # @example
108
+ # graph.remove_nodes(["Noida", "Bangalore"])
109
+ #
110
+ # @param nodes [Array<Object>] the array of nodes to be removed
111
+ def remove_nodes(nodes)
112
+ case nodes
113
+ when Set, Array
114
+ nodes.each { |node| remove_node(node) }
115
+ else
116
+ raise ArgumentError, 'Expected argument to be an Array or Set of nodes, '\
117
+ "received #{nodes.class.name} instead."
118
+ end
119
+ end
120
+
121
+ # Removes edge from the graph
122
+ #
123
+ # @example
124
+ # graph.remove_edge('Noida', 'Bangalore')
125
+ #
126
+ # @param node_1 [Object] the first node of the edge
127
+ # @param node_2 [Object] the second node of the edge
128
+ def remove_edge(node_1, node_2)
129
+ raise KeyError, "#{node_1} is not a valid node." unless @nodes.key?(node_1)
130
+ raise KeyError, "#{node_2} is not a valid node" unless @nodes.key?(node_2)
131
+ raise KeyError, 'The given edge is not a valid one.' unless @adj[node_1].key?(node_2)
132
+ @adj[node_1].delete(node_2)
133
+ @adj[node_2].delete(node_1) if node_1 != node_2
134
+ end
135
+
136
+ # Removes multiple edges from the graph
137
+ #
138
+ # @example
139
+ # graph.remove_edges([%w[Noida Bangalore], %w[Bangalore Chennai]])
140
+ #
141
+ # @param edges [Array<Object>] the array of edges to be removed
142
+ def remove_edges(edges)
143
+ case edges
144
+ when Array, Set
145
+ edges.each { |node_1, node_2| remove_edge(node_1, node_2) }
146
+ else
147
+ raise ArgumentError, 'Expected Arguement to be Array or Set of edges, '\
148
+ "received #{edges.class.name} instead."
149
+ end
150
+ end
151
+
152
+ # Adds weighted edge
153
+ #
154
+ # @example
155
+ # graph.add_weighted_edge('Noida', 'Bangalore', 1000)
156
+ #
157
+ # @param node_1 [Object] the first node of the edge
158
+ # @param node_2 [Object] the second node of the edge
159
+ # @param weight [Integer] the weight value
160
+ def add_weighted_edge(node_1, node_2, weight)
161
+ add_edge(node_1, node_2, weight: weight)
162
+ end
163
+
164
+ # Adds multiple weighted edges
165
+ #
166
+ # @example
167
+ # graph.add_weighted_edges([['Noida', 'Bangalore'],
168
+ # ['Noida', 'Nagpur']], [1000, 2000])
169
+ #
170
+ # @param edges [Array<Object, Object>] the array of edges
171
+ # @param weights [Array<Integer>] the array of weights
172
+ def add_weighted_edges(edges, weights)
173
+ raise ArgumentError, 'edges and weights array must have equal number of elements.'\
174
+ unless edges.size == weights.size
175
+ raise ArgumentError, 'edges and weight must be given in an Array.'\
176
+ unless edges.is_a?(Array) && weights.is_a?(Array)
177
+ (edges.transpose << weights).transpose.each do |node_1, node_2, weight|
178
+ add_weighted_edge(node_1, node_2, weight)
179
+ end
180
+ end
181
+
182
+ # Clears the graph
183
+ #
184
+ # @example
185
+ # graph.clear
186
+ def clear
187
+ @adj.clear
188
+ @nodes.clear
189
+ @graph.clear
190
+ end
191
+
192
+ # Checks if a node is present in the graph
193
+ #
194
+ # @example
195
+ # graph.node?(node_1)
196
+ #
197
+ # @param node [Object] the node to be checked
198
+ def node?(node)
199
+ @nodes.key?(node)
200
+ end
201
+
202
+ # Checks if the the edge consisting of two nodes is present in the graph
203
+ #
204
+ # @example
205
+ # graph.edge?(node_1, node_2)
206
+ #
207
+ # @param node_1 [Object] the first node of the edge to be checked
208
+ # @param node_2 [Object] the second node of the edge to be checked
209
+ def edge?(node_1, node_2)
210
+ node?(node_1) && @adj[node_1].key?(node_2)
211
+ end
212
+
213
+ # Gets the node data
214
+ #
215
+ # @example
216
+ # graph.get_node_data(node)
217
+ #
218
+ # @param node [Object] the node whose data is to be fetched
219
+ def get_node_data(node)
220
+ raise ArgumentError, 'No such node exists!' unless node?(node)
221
+ @nodes[node]
222
+ end
223
+
224
+ # Gets the edge data
225
+ #
226
+ # @example
227
+ # graph.get_edge_data(node_1, node_2)
228
+ #
229
+ # @param node_1 [Object] the first node of the edge
230
+ # @param node_2 [Object] the second node of the edge
231
+ def get_edge_data(node_1, node_2)
232
+ raise KeyError, 'No such edge exists!' unless node?(node_1) && edge?(node_2)
233
+ @adj[node_1][node_2]
234
+ end
235
+
236
+ # Retus a hash of neighbours of a node
237
+ #
238
+ # @example
239
+ # graph.neighbours(node)
240
+ #
241
+ # @param node [Object] the node whose neighbours are to be fetched
242
+ def neighbours(node)
243
+ raise KeyError, 'No such node exists!' unless node?(node)
244
+ @adj[node]
245
+ end
246
+
247
+ # Returns number of nodes
248
+ #
249
+ # @example
250
+ # graph.number_of_nodes
251
+ def number_of_nodes
252
+ @nodes.length
253
+ end
254
+
255
+ # Returns number of edges
256
+ #
257
+ # @example
258
+ # graph.number_of_edges
259
+ def number_of_edges
260
+ @adj.values.map(&:length).inject(:+) / 2
261
+ end
262
+
263
+ # Returns the size of the graph
264
+ #
265
+ # @example
266
+ # graph.size(true)
267
+ #
268
+ # @param is_weighted [Bool] if true, method returns sum of weights of all edges
269
+ # else returns number of edges
270
+ def size(is_weighted=false)
271
+ if is_weighted
272
+ graph_size = 0
273
+ @adj.each do |_, hash_val|
274
+ hash_val.each { |_, v| graph_size += v[:weight] if v.key?(:weight) }
275
+ end
276
+ return graph_size / 2
277
+ end
278
+ number_of_edges
279
+ end
280
+
281
+ # TODO: Reduce method length and method complexity
282
+
283
+ # Returns subgraph consisting of given array of nodes
284
+ #
285
+ # @example
286
+ # graph.subgraph(%w[Mumbai Nagpur])
287
+ #
288
+ # @param nodes [Array<Object>] the nodes to be included in the subgraph
289
+ def subgraph(nodes)
290
+ case nodes
291
+ when Array, Set
292
+ sub_graph = NetworkX::Graph.new(@graph)
293
+ nodes.each do |u, _|
294
+ raise KeyError, "#{u} does not exist in the current graph!" unless @nodes.key?(u)
295
+ sub_graph.add_node(u, @nodes[u])
296
+ @adj[u].each do |v, edge_val|
297
+ sub_graph.add_edge(u, v, edge_val) if @adj[u].key?(v) && nodes.include?(v)
298
+ end
299
+ return sub_graph
300
+ end
301
+ else
302
+ raise ArgumentError, 'Expected Argument to be Array or Set of nodes, '\
303
+ "received #{nodes.class.name} instead."
304
+ end
305
+ end
306
+
307
+ # TODO: Reduce method length and method complexity
308
+
309
+ # Returns subgraph conisting of given edges
310
+ #
311
+ # @example
312
+ # graph.edge_subgraph([%w[Nagpur Wardha], %w[Nagpur Mumbai]])
313
+ #
314
+ # @param edges [Array<Object, Object>] the edges to be included in the subraph
315
+ def edge_subgraph(edges)
316
+ case edges
317
+ when Array, Set
318
+ sub_graph = NetworkX::Graph.new(@graph)
319
+ edges.each do |u, v|
320
+ raise KeyError, "Edge between #{u} and #{v} does not exist in the graph!" unless @nodes.key?(u)\
321
+ && @adj[u].key?(v)
322
+ sub_graph.add_node(u, @nodes[u])
323
+ sub_graph.add_node(v, @nodes[v])
324
+ sub_graph.add_edge(u, v, @adj[u][v])
325
+ end
326
+ return sub_graph
327
+ else
328
+ raise ArgumentError, 'Expected Argument to be Array or Set of edges, '\
329
+ "received #{edges.class.name} instead."
330
+ end
331
+ end
332
+
333
+ def multigraph?
334
+ ['NetworkX::MultiGraph', 'NetworkX::MultiDiGraph'].include?(self.class.name)
335
+ end
336
+
337
+ def directed?
338
+ ['NetworkX::DiGraph', 'NetworkX::MultiDiGraph'].include?(self.class.name)
339
+ end
340
+ end
341
+ end