networkx 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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