networkx 0.1.0 → 0.2.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.
Files changed (60) hide show
  1. checksums.yaml +5 -5
  2. data/{CODE_OF_CONDUCT.md → .github/CODE_OF_CONDUCT.md} +0 -0
  3. data/{CONTRIBUTING.md → .github/CONTRIBUTING.md} +21 -11
  4. data/.github/ISSUE_TEMPLATE.md +15 -0
  5. data/.github/PULL_REQUEST_TEMPLATE.md +10 -0
  6. data/.github/workflows/ci.yml +17 -0
  7. data/.github/workflows/doc.yml +23 -0
  8. data/.github/workflows/gem-push.yml +45 -0
  9. data/.rspec +0 -1
  10. data/.rubocop.yml +56 -63
  11. data/.yardopts +0 -1
  12. data/README.md +27 -27
  13. data/Rakefile +2 -3
  14. data/lib/networkx/auxillary_functions/cliques.rb +62 -0
  15. data/lib/networkx/auxillary_functions/cycles.rb +114 -0
  16. data/lib/networkx/auxillary_functions/dag.rb +59 -0
  17. data/lib/networkx/auxillary_functions/eccentricity.rb +37 -0
  18. data/lib/networkx/auxillary_functions/mis.rb +23 -0
  19. data/lib/networkx/auxillary_functions/mst.rb +33 -0
  20. data/lib/networkx/auxillary_functions/union_find.rb +104 -0
  21. data/lib/networkx/auxillary_functions/vitality.rb +13 -0
  22. data/lib/networkx/auxillary_functions/wiener.rb +13 -0
  23. data/lib/networkx/converters/to_csv.rb +45 -0
  24. data/lib/networkx/converters/to_json.rb +37 -0
  25. data/lib/networkx/digraph.rb +234 -0
  26. data/lib/networkx/flow/capacityscaling.rb +249 -0
  27. data/lib/networkx/flow/edmondskarp.rb +115 -0
  28. data/lib/networkx/flow/preflowpush.rb +249 -0
  29. data/lib/networkx/flow/shortestaugmentingpath.rb +154 -0
  30. data/lib/networkx/flow/utils.rb +139 -0
  31. data/lib/networkx/graph.rb +448 -0
  32. data/lib/networkx/link_analysis/hits.rb +59 -0
  33. data/lib/networkx/link_analysis/pagerank.rb +89 -0
  34. data/lib/networkx/multidigraph.rb +249 -0
  35. data/lib/networkx/multigraph.rb +199 -0
  36. data/lib/networkx/operators/all.rb +65 -0
  37. data/lib/networkx/operators/binary.rb +222 -0
  38. data/lib/networkx/operators/product.rb +201 -0
  39. data/lib/networkx/operators/unary.rb +17 -0
  40. data/lib/networkx/others/bridges.rb +30 -0
  41. data/lib/networkx/others/generators.rb +237 -0
  42. data/lib/networkx/others/grid_2d_graph.rb +38 -0
  43. data/lib/networkx/others/info.rb +11 -0
  44. data/lib/networkx/others/number_connected_components.rb +17 -0
  45. data/lib/networkx/others/reads.rb +52 -0
  46. data/lib/networkx/shortest_path/astar.rb +73 -0
  47. data/lib/networkx/shortest_path/dense.rb +29 -0
  48. data/lib/networkx/shortest_path/unweighted.rb +136 -0
  49. data/lib/networkx/shortest_path/weighted.rb +417 -0
  50. data/lib/networkx/to_matrix.rb +51 -0
  51. data/lib/networkx/traversals/bfs.rb +110 -0
  52. data/lib/networkx/traversals/dfs.rb +135 -0
  53. data/lib/networkx/traversals/edge_dfs.rb +114 -0
  54. data/lib/networkx/version.rb +1 -1
  55. data/lib/networkx.rb +43 -1
  56. data/networkx.gemspec +14 -12
  57. metadata +118 -62
  58. data/.rspec_formatter.rb +0 -24
  59. data/.travis.yml +0 -18
  60. data/Guardfile +0 -7
@@ -0,0 +1,59 @@
1
+ module NetworkX
2
+ # Returns the descendants of a given node
3
+ #
4
+ # @param graph [Graph, DiGraph, MultiGraph, MultiDiGraph] a graph
5
+ # @param source [Object] node to find descendents of
6
+ #
7
+ # @return [Array<Object>] Array of the descendants
8
+ def self.descendants(graph, source)
9
+ raise ArgumentError, 'Source is not present in the graph!' unless graph.node?(source)
10
+
11
+ des = single_source_shortest_path_length(graph, source).map { |u, _| u }.uniq
12
+ des - [source]
13
+ end
14
+
15
+ # Returns the ancestors of a given node
16
+ #
17
+ # @param graph [Graph, DiGraph, MultiGraph, MultiDiGraph] a graph
18
+ # @param source [Object] node to find ancestors of
19
+ #
20
+ # @return [Array<Object>] Array of the ancestors
21
+ def self.ancestors(graph, source)
22
+ raise ArgumentError, 'Source is not present in the graph!' unless graph.node?(source)
23
+
24
+ anc = single_source_shortest_path_length(graph.reverse, source).map { |u, _| u }.uniq
25
+ anc - [source]
26
+ end
27
+
28
+ # Returns the nodes arranged in the topologically sorted fashion
29
+ #
30
+ # @param graph [DiGraph] a graph
31
+ #
32
+ # @return [Array<Object>] Array of the nodes
33
+ def self.topological_sort(graph)
34
+ raise ArgumentError, 'Topological Sort not defined on undirected graphs!' unless graph.directed?
35
+
36
+ nodes = []
37
+ indegree_map = graph.nodes.each_key.map do |u|
38
+ [u, graph.in_degree(u)] if graph.in_degree(u).positive?
39
+ end.compact.to_h
40
+ zero_indegree = graph.nodes.each_key.select { |u| graph.in_degree(u).zero? }
41
+
42
+ until zero_indegree.empty?
43
+ node = zero_indegree.shift
44
+ raise ArgumentError, 'Graph changed during iteration!' unless graph.nodes.has_key?(node)
45
+
46
+ graph.adj[node].each_key do |child|
47
+ indegree_map[child] -= 1
48
+ if indegree_map[child].zero?
49
+ zero_indegree << child
50
+ indegree_map.delete(child)
51
+ end
52
+ end
53
+ nodes << node
54
+ end
55
+ raise ArgumentError, 'Graph contains cycle or graph changed during iteration!' unless indegree_map.empty?
56
+
57
+ nodes
58
+ end
59
+ end
@@ -0,0 +1,37 @@
1
+ module NetworkX
2
+ # Returns the eccentricity of a particular node or all nodes
3
+ #
4
+ # @param graph [Graph, DiGraph, MultiGraph, MultiDiGraph] a graph
5
+ # @param node [Object] node to find the eccentricity of
6
+ #
7
+ # @return [Array<Numeric>, Numeric] eccentricity/eccentricites of all nodes
8
+ def self.eccentricity(graph, node = nil)
9
+ e = {}
10
+ graph.nodes.each do |u, _|
11
+ length = single_source_shortest_path_length(graph, u)
12
+ l = length.length
13
+ raise ArgumentError, 'Found infinite path length!' unless l == graph.nodes.length
14
+
15
+ e[u] = length.max_by { |a| a[1] }[1]
16
+ end
17
+ node.nil? ? e : e[node]
18
+ end
19
+
20
+ # Returns the diameter of a graph
21
+ #
22
+ # @param graph [Graph, DiGraph, MultiGraph, MultiDiGraph] a graph
23
+ #
24
+ # @return [Numeric] diameter of the graph
25
+ def self.diameter(graph)
26
+ eccentricity(graph).values.max
27
+ end
28
+
29
+ # Returns the radius of a graph
30
+ #
31
+ # @param graph [Graph, DiGraph, MultiGraph, MultiDiGraph] a graph
32
+ #
33
+ # @return [Numeric] radius of the graph
34
+ def self.radius(graph)
35
+ eccentricity(graph).values.min
36
+ end
37
+ end
@@ -0,0 +1,23 @@
1
+ module NetworkX
2
+ # Returns the maximal independent set of a graph
3
+ #
4
+ # @param graph [Graph, DiGraph, MultiGraph, MultiDiGraph] a graph
5
+ # @param nodes [Object] nodes to be considered in the MIS
6
+ #
7
+ # @return [Numeric] radius of the graph
8
+ def self.maximal_independent_set(graph, nodes)
9
+ raise 'The array containing the nodes should be a subset of the graph!' if (graph.nodes.keys - nodes).empty?
10
+
11
+ neighbours = []
12
+ nodes.each { |u| graph.adj[u].each { |v, _| neighbours |= [v] } }
13
+ raise 'Nodes is not an independent set of graph!' if (neighbours - nodes).empty?
14
+
15
+ available_nodes = graph.nodes.keys - (neighbours | nodes)
16
+ until available_nodes.empty?
17
+ node = available_nodes.sample
18
+ nodes << node
19
+ available_nodes -= (graph.adj[node].keys + [node])
20
+ end
21
+ nodes
22
+ end
23
+ end
@@ -0,0 +1,33 @@
1
+ module NetworkX
2
+ # Helper function for the minimum spanning tree
3
+ #
4
+ def self.get_edges_weights(graph)
5
+ edges = []
6
+ graph.adj.each do |u, u_edges|
7
+ u_edges.each do |v, uv_attrs|
8
+ edges << [[u, v], uv_attrs[:weight] || Float::INFINITY]
9
+ end
10
+ end
11
+ edges
12
+ end
13
+
14
+ # Returns the minimum spanning tree of a graph
15
+ #
16
+ # @param graph [Graph, DiGraph] a graph
17
+ #
18
+ # @return [DiGraph, Graph] a minimum spanning tree of the graph
19
+ def self.minimum_spanning_tree(graph)
20
+ mst = Marshal.load(Marshal.dump(graph))
21
+ mst.clear
22
+ edges = get_edges_weights(graph).sort_by { |a| a[1] }
23
+ union_find = UnionFind.new(graph.nodes.keys)
24
+ while edges.any? && mst.nodes.length <= graph.nodes.length
25
+ edge = edges.shift
26
+ unless union_find.connected?(edge[0][0], edge[0][1])
27
+ union_find.union(edge[0][0], edge[0][1])
28
+ mst.add_edge(edge[0][0], edge[0][1], **graph.adj[edge[0][0]][edge[0][1]])
29
+ end
30
+ end
31
+ mst
32
+ end
33
+ end
@@ -0,0 +1,104 @@
1
+ module NetworkX
2
+ # Union Find Tree
3
+ #
4
+ # Reference
5
+ # - [ac-library-rb DSU (CC0)](https://github.com/universato/ac-library-rb/blob/main/lib/dsu.rb)
6
+ # - [Python NetworkX UnionFind](https://networkx.org/documentation/stable/_modules/networkx/utils/union_find.html)
7
+ #
8
+ #
9
+ # @attr_reader parents [Hash{ Object => Object }] Return parent of each element
10
+ # @attr_reader weights [Hash{ Object => Integer }] Return weight of each element
11
+ class UnionFind
12
+ attr_accessor :parents, :weights
13
+
14
+ # Constructor for initializing Union Find Tree
15
+ #
16
+ # @param nodes [?Array[Object]] nodes
17
+ #
18
+ # @return [UnionFind] Union Find Tree
19
+ def initialize(nodes = nil)
20
+ @weights = {}
21
+ @parents = {}
22
+ nodes&.each do |node|
23
+ @weights[node] = 1
24
+ @parents[node] = node
25
+ end
26
+ end
27
+
28
+ # Return the root of node
29
+ #
30
+ # @param node [Object] node
31
+ #
32
+ # @return [Object] root of node, leader of node
33
+ def [](node)
34
+ if @parents.has_key?(node)
35
+ @parents[node] == node ? node : (@parents[node] = self[@parents[node]])
36
+ else
37
+ @weights[node] = 1
38
+ @parents[node] = node
39
+ end
40
+ end
41
+
42
+ # Return the root of node
43
+ #
44
+ # @param node [Object] node
45
+ #
46
+ # @return [Object] root of node, leader of node
47
+ def root(node)
48
+ @parents.has_key?(node) or raise ArgumentError.new, "#{node} is not a node"
49
+
50
+ @parents[node] == node ? node : (@parents[node] = root(@parents[node]))
51
+ end
52
+
53
+ def each(&block)
54
+ @parents.each_key(&block)
55
+ end
56
+
57
+ def to_sets
58
+ each.group_by { |node| root(node) }.values
59
+ end
60
+ alias groups to_sets
61
+
62
+ # Is each root of two nodes the same?
63
+ #
64
+ # @param node1 [Object] node
65
+ # @param node2 [Object] node
66
+ #
67
+ # @return [bool] Is each root of node1 and nodes_2 the same?
68
+ def connected?(node1, node2)
69
+ root(node1) == root(node2)
70
+ end
71
+ alias same? connected?
72
+
73
+ # Unite nodes.
74
+ #
75
+ # @param nodes [Array[Object]] nodes
76
+ #
77
+ # @return [Object | nil] root of united nodes
78
+ def union(*nodes)
79
+ return merge(*nodes) if nodes.size == 2
80
+
81
+ roots = nodes.map { |node| self[node] }.uniq
82
+ return if roots.size == 1
83
+
84
+ roots.sort_by! { |root| @weights[root] }
85
+ root = roots[-1]
86
+ roots[0...-1].each do |r|
87
+ @weights[root] += @weights[r]
88
+ @parents[r] = root
89
+ end
90
+ root
91
+ end
92
+ alias unite union
93
+
94
+ def merge(node1, node2)
95
+ x = self[node1]
96
+ y = self[node2]
97
+ return if x == y
98
+
99
+ x, y = y, x if @weights[x] < @weights[y]
100
+ @weights[x] += @weights[y]
101
+ @parents[y] = x
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,13 @@
1
+ module NetworkX
2
+ # Returns the closeness vitality of a node
3
+ #
4
+ # @param graph [Graph, DiGraph] a graph
5
+ # @param node [Object] node to compute closeness vitality of
6
+ #
7
+ # @return [Numeric] closeness vitality of the given node
8
+ def self.closeness_vitality(graph, node)
9
+ before = wiener_index(graph)
10
+ after = wiener_index(graph.subgraph(graph.nodes.keys - [node]))
11
+ before - after
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module NetworkX
2
+ # Returns the wiener index of the graph
3
+ #
4
+ # @param graph [Graph, DiGraph] a graph
5
+ #
6
+ # @return [Numeric] wiener index of the graph
7
+ def self.wiener_index(graph)
8
+ total = all_pairs_shortest_path_length(graph)
9
+ wiener_ind = 0
10
+ total.to_h.each { |_, distances| distances.to_h.each { |_, val| wiener_ind += val } }
11
+ graph.directed? ? wiener_ind : wiener_ind / 2
12
+ end
13
+ end
@@ -0,0 +1,45 @@
1
+ module NetworkX
2
+ # Saves the graph in a csv file
3
+ #
4
+ # @param graph [Graph, DiGraph, MultiGraph, MultiDiGraph] a graph
5
+ # @param filename [String] filename of the graph
6
+ def self.graph_to_csv(graph, filename = 'graph.csv')
7
+ CSV.open(filename, 'wb') do |csv|
8
+ csv << [graph.class.name]
9
+ csv << ['graph_values']
10
+ csv << graph.graph.keys
11
+ csv << graph.graph.values
12
+ csv << ['graph_nodes']
13
+ graph.nodes.each do |u, attrs|
14
+ node_attrs = [u]
15
+ attrs.each do |k, v|
16
+ node_attrs << k
17
+ node_attrs << v
18
+ end
19
+ csv << node_attrs
20
+ end
21
+ csv << ['graph_edges']
22
+ graph.adj.each do |u, u_edges|
23
+ u_edges.each do |v, uv_attrs|
24
+ if graph.multigraph?
25
+ uv_attrs.each do |key, attrs|
26
+ node_attrs = [u, v, key]
27
+ attrs.each do |k, k_attrs|
28
+ node_attrs << k
29
+ node_attrs << k_attrs
30
+ end
31
+ csv << node_attrs
32
+ end
33
+ else
34
+ node_attrs = [u, v]
35
+ uv_attrs.each do |k, vals|
36
+ node_attrs << k
37
+ node_attrs << vals
38
+ end
39
+ csv << node_attrs
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,37 @@
1
+ module NetworkX
2
+ # Returns a JSON object of the given graph
3
+ #
4
+ # @param graph [Graph, DiGraph, MultiGraph, MultiDiGraph] a graph
5
+ #
6
+ # @return [JSON] json encoded graph
7
+ def self.graph_to_json(graph)
8
+ json_hash = {}
9
+ json_hash[:class] = graph.class.name
10
+ json_hash[:graph] = graph.graph
11
+ json_hash[:nodes] = graph.nodes
12
+ json_hash[:adj] = graph.adj
13
+ json_hash.to_json
14
+ end
15
+
16
+ # Returns a graph from the json encoded graph
17
+ #
18
+ # @param json_str [JSON] json encoded string
19
+ #
20
+ # @return [Graph, DiGraph, MultiGraph, MultiDiGraph] a decoded graph
21
+ def self.json_to_graph(json_str)
22
+ graph_hash = JSON.parse(json_str)
23
+ case json_str['class']
24
+ when 'NetworkX::Graph'
25
+ graph = NetworkX::Graph.new(graph_hash.graph)
26
+ when 'NetworkX::MultiGraph'
27
+ graph = NetworkX::MultiGraph.new(graph_hash.graph)
28
+ when 'NetworkX::DiGraph'
29
+ graph = NetworkX::DiGraph.new(graph_hash.graph)
30
+ when 'NetworkX::MultiDiGraph'
31
+ graph = NetworkX::MultiDiGraph.new(graph_hash.graph)
32
+ end
33
+ graph.adj = graph_hash['adj']
34
+ graph.nodes = graph_hash['nodes']
35
+ graph
36
+ end
37
+ end
@@ -0,0 +1,234 @@
1
+ module NetworkX
2
+ # Describes the class for making Directed 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 pred [Hash{ Object => Hash{ Object => 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 DiGraph < Graph
11
+ attr_reader :adj, :graph, :pred
12
+
13
+ # Constructor for initializing graph
14
+ #
15
+ # @example Initialize a graph with attributes 'type' and 'name'
16
+ # graph = NetworkX::Graph.new(name: "Social Network", type: "undirected")
17
+ #
18
+ # @param graph_attrs [Hash{ Object => Object }] the graph attributes in a hash format
19
+ def initialize(**graph_attrs)
20
+ super(**graph_attrs)
21
+
22
+ @pred = {}
23
+ end
24
+
25
+ # Adds the respective edge
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 node1 [Object] the first node of the edge
34
+ # @param node2 [Object] the second node of the edge
35
+ # @param edge_attrs [Hash{ Object => Object }] the hash of the edge attributes
36
+ def add_edge(node1, node2, **edge_attrs)
37
+ add_node(node1)
38
+ add_node(node2)
39
+
40
+ edge_attrs = (@adj[node1][node2] || {}).merge(edge_attrs)
41
+ @adj[node1][node2] = edge_attrs
42
+ @pred[node2][node1] = edge_attrs
43
+ end
44
+
45
+ # Adds a node and its attributes to the graph
46
+ #
47
+ # @example Add a node with attribute 'type'
48
+ # graph.add_node("Noida", type: "city")
49
+ #
50
+ # @param node [Object] the node object
51
+ # @param node_attrs [Hash{ Object => Object }] the hash of the attributes of the node
52
+ def add_node(node, **node_attrs)
53
+ super(node, **node_attrs)
54
+
55
+ @pred[node] = {} unless @pred.has_key?(node)
56
+ end
57
+
58
+ def nodes(data: true)
59
+ data ? @nodes : @nodes.keys
60
+ end
61
+
62
+ # Removes node from the graph
63
+ #
64
+ # @example
65
+ # graph.remove_node("Noida")
66
+ #
67
+ # @param node [Object] the node to be removed
68
+ def remove_node(node)
69
+ raise KeyError, "Error in deleting node #{node} from Graph." unless @nodes.has_key?(node)
70
+
71
+ neighbours = @adj[node]
72
+ neighbours.each_key { |k| @pred[k].delete(node) }
73
+ @pred[node].each_key do |k|
74
+ @adj[k].delete(node)
75
+ end
76
+
77
+ @pred.delete(node)
78
+ @adj.delete(node)
79
+ @nodes.delete(node)
80
+ end
81
+
82
+ # Removes edge from the graph
83
+ #
84
+ # @example
85
+ # graph.remove_edge('Noida', 'Bangalore')
86
+ #
87
+ # @param node1 [Object] the first node of the edge
88
+ # @param node2 [Object] the second node of the edge
89
+ def remove_edge(node1, node2)
90
+ raise KeyError, "#{node1} is not a valid node." unless @nodes.has_key?(node1)
91
+ raise KeyError, "#{node2} is not a valid node" unless @nodes.has_key?(node2)
92
+ raise KeyError, 'The given edge is not a valid one.' unless @adj[node1].has_key?(node2)
93
+
94
+ @adj[node1].delete(node2)
95
+ @pred[node2].delete(node1)
96
+ end
97
+
98
+ # Clears the graph
99
+ #
100
+ # @example
101
+ # graph.clear
102
+ def clear
103
+ super
104
+
105
+ @pred.clear
106
+ end
107
+
108
+ # Returns number of edges
109
+ #
110
+ # @example
111
+ # graph.number_of_edges
112
+ def number_of_edges
113
+ @adj.values.map(&:length).sum
114
+ end
115
+
116
+ # Returns the size of graph
117
+ #
118
+ # @example
119
+ # graph.size(true)
120
+ #
121
+ # @param is_weighted [Bool] if true, method returns sum of weights of all edges
122
+ # else returns number of edges
123
+ def size(is_weighted = false)
124
+ if is_weighted
125
+ graph_size = 0
126
+ @adj.each do |_, hash_val|
127
+ hash_val.each { |_, v| graph_size += v[:weight] if v.has_key?(:weight) }
128
+ end
129
+ return graph_size
130
+ end
131
+ number_of_edges
132
+ end
133
+
134
+ # Returns in-degree of a given node
135
+ #
136
+ # @example
137
+ # graph.in_degree(node)
138
+ #
139
+ # @param node [Object] the node whose in degree is to be calculated
140
+ def in_degree(node)
141
+ @pred[node].length
142
+ end
143
+
144
+ # Returns out-degree of a given node
145
+ #
146
+ # @example
147
+ # graph.out_degree(node)
148
+ #
149
+ # @param node [Object] the node whose out degree is to be calculated
150
+ def out_degree(node)
151
+ @adj[node].length
152
+ end
153
+
154
+ # Returns the reversed version of the graph
155
+ #
156
+ # @example
157
+ # graph.reverse
158
+ def reverse
159
+ new_graph = NetworkX::DiGraph.new(**@graph)
160
+ @nodes.each { |u, attrs| new_graph.add_node(u, **attrs) }
161
+ @adj.each do |u, edges|
162
+ edges.each { |v, attrs| new_graph.add_edge(v, u, **attrs) }
163
+ end
164
+ new_graph
165
+ end
166
+
167
+ # Returns the undirected version of the graph
168
+ #
169
+ # @example
170
+ # graph.to_undirected
171
+ def to_undirected
172
+ new_graph = NetworkX::Graph.new(**@graph)
173
+ @nodes.each { |u, attrs| new_graph.add_node(u, **attrs) }
174
+ @adj.each do |u, edges|
175
+ edges.each { |v, attrs| new_graph.add_edge(u, v, **attrs) }
176
+ end
177
+ new_graph
178
+ end
179
+
180
+ # Returns subgraph consisting of given array of nodes
181
+ #
182
+ # @example
183
+ # graph.subgraph(%w[Mumbai Nagpur])
184
+ #
185
+ # @param nodes [Array<Object>] the nodes to be included in the subgraph
186
+ def subgraph(nodes)
187
+ case nodes
188
+ when Array, Set
189
+ sub_graph = NetworkX::DiGraph.new(**@graph)
190
+ nodes.each do |u|
191
+ raise KeyError, "#{u} does not exist in the current graph!" unless node?(u)
192
+
193
+ sub_graph.add_node(u, **@nodes[u])
194
+ @adj[u].each do |v, uv_attrs|
195
+ sub_graph.add_edge(u, v, **uv_attrs) if @adj[u].has_key?(v) && nodes.include?(v)
196
+ end
197
+ end
198
+ sub_graph
199
+ else
200
+ raise ArgumentError, 'Expected Argument to be Array or Set of nodes, ' \
201
+ "received #{nodes.class.name} instead."
202
+ end
203
+ end
204
+
205
+ # Returns subgraph consisting of given edges
206
+ #
207
+ # @example
208
+ # graph.edge_subgraph([%w[Nagpur Wardha], %w[Nagpur Mumbai]])
209
+ #
210
+ # @param edges [Array<Object, Object>] the edges to be included in the subraph
211
+ def edge_subgraph(edges)
212
+ case edges
213
+ when Array, Set
214
+ sub_graph = NetworkX::DiGraph.new(**@graph)
215
+ edges.each do |u, v|
216
+ raise KeyError, "Edge between #{u} and #{v} does not exist in the graph!" unless @nodes.has_key?(u) \
217
+ && @adj[u].has_key?(v)
218
+
219
+ sub_graph.add_node(u, **@nodes[u])
220
+ sub_graph.add_node(v, **@nodes[v])
221
+ sub_graph.add_edge(u, v, **@adj[u][v])
222
+ end
223
+ sub_graph
224
+ else
225
+ raise ArgumentError, 'Expected Argument to be Array or Set of edges, ' \
226
+ "received #{edges.class.name} instead."
227
+ end
228
+ end
229
+
230
+ def directed?
231
+ true
232
+ end
233
+ end
234
+ end