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