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,249 @@
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 node1 [Object] the first node of a given edge
14
+ # @param node2 [Object] the second node of a given edge
15
+ def new_edge_key(node1, node2)
16
+ return 0 if @adj[node1][node2].nil?
17
+
18
+ key = @adj[node1][node2].length
19
+ key += 1 while @adj[node1][node2].has_key?(key)
20
+ key
21
+ end
22
+
23
+ # Adds the respective edge
24
+ #
25
+ # @example Add an edge with attribute name
26
+ # graph.add_edge(node1, node2, name: "Edge1")
27
+ #
28
+ # @example Add an edge with no attribute
29
+ # graph.add_edge("Bangalore", "Chennai")
30
+ #
31
+ # @param node1 [Object] the first node of the edge
32
+ # @param node2 [Object] the second node of the edge
33
+ # @param edge_attrs [Hash{ Object => Object }] the hash of the edge attributes
34
+ def add_edge(node1, node2, **edge_attrs)
35
+ add_node(node1)
36
+ add_node(node2)
37
+ key = new_edge_key(node1, node2)
38
+ all_edge_attrs = @adj[node1][node2] || {}
39
+ all_edge_attrs[key] = edge_attrs
40
+ @adj[node1][node2] = all_edge_attrs
41
+ @pred[node2][node1] = all_edge_attrs
42
+ end
43
+
44
+ # Removes edge from the graph
45
+ #
46
+ # @example
47
+ # graph.remove_edge('Noida', 'Bangalore')
48
+ #
49
+ # @param node1 [Object] the first node of the edge
50
+ # @param node2 [Object] the second node of the edge
51
+ def remove_edge(node1, node2, key = nil)
52
+ return super(node1, node2) if key.nil?
53
+
54
+ raise KeyError, "#{node1} is not a valid node." unless @nodes.has_key?(node1)
55
+ raise KeyError, "#{node2} is not a valid node" unless @nodes.has_key?(node2)
56
+ raise KeyError, 'The given edge is not a valid one.' unless @adj[node1].has_key?(node2)
57
+ if @adj[node1][node2].none? { |_index, data| data[:key] == key }
58
+ raise KeyError, 'The given edge is not a valid one'
59
+ end
60
+
61
+ @adj[node1][node2].delete_if { |_indx, data| data[:key] == key }
62
+ @pred[node2][node1].delete_if { |_indx, data| data[:key] == 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?(node1, node2)
69
+ #
70
+ # @param node1 [Object] the first node of the edge to be checked
71
+ # @param node2 [Object] the second node of the edge to be checked
72
+ # @param key [Integer] the key of the given edge
73
+ def edge?(node1, node2, key = nil)
74
+ return super(node1, node2) if key.nil?
75
+
76
+ node?(node1) && @adj[node1].has_key?(node2) && @adj[node1][node2].has_key?(key)
77
+ end
78
+
79
+ def has_edge?(node1, node2, key = nil)
80
+ return super(node1, node2) if key.nil?
81
+
82
+ return false unless node?(node1) && @adj[node1].has_key?(node2)
83
+
84
+ @adj[node1][node2].any? { |_index, data| data[:key] == key }
85
+ end
86
+
87
+ # Returns the undirected version of the graph
88
+ #
89
+ # @example
90
+ # graph.to_undirected
91
+ def to_undirected
92
+ graph = NetworkX::Graph.new(**@graph)
93
+ @nodes.each { |node, node_attr| graph.add_node(node, **node_attr) }
94
+ @adj.each do |node1, node1_edges|
95
+ node1_edges.each do |node2, node1_node2|
96
+ edge_attrs = {}
97
+ node1_node2.each { |_key, attrs| edge_attrs.merge!(attrs) }
98
+ graph.add_edge(node1, node2, **edge_attrs)
99
+ end
100
+ end
101
+ graph
102
+ end
103
+
104
+ # Returns the directed version of the graph
105
+ #
106
+ # @example
107
+ # graph.to_directed
108
+ def to_directed
109
+ graph = NetworkX::DiGraph.new(**@graph)
110
+ @nodes.each { |node, node_attr| graph.add_node(node, **node_attr) }
111
+ @adj.each do |node1, node1_edges|
112
+ node1_edges.each do |node2, node1_node2|
113
+ edge_attrs = {}
114
+ node1_node2.each { |_key, attrs| edge_attrs.merge!(attrs) }
115
+ graph.add_edge(node1, node2, **edge_attrs)
116
+ end
117
+ end
118
+ graph
119
+ end
120
+
121
+ # Returns the multigraph version of the graph
122
+ #
123
+ # @example
124
+ # graph.to_multigraph
125
+ def to_multigraph
126
+ graph = NetworkX::MultiGraph.new(**@graph)
127
+ @nodes.each { |node, node_attr| graph.add_node(node, **node_attr) }
128
+ @adj.each do |node1, node2_edges|
129
+ node2_edges.each do |node2, edges|
130
+ edges.each { |_key, attrs| graph.add_edge(node1, node2, **attrs) }
131
+ end
132
+ end
133
+ graph
134
+ end
135
+
136
+ # Returns the reversed version of the graph
137
+ #
138
+ # @example
139
+ # graph.reverse
140
+ def reverse
141
+ new_graph = NetworkX::MultiDiGraph.new(**@graph)
142
+ @nodes.each { |node, attrs| new_graph.add_node(node, **attrs) }
143
+ @adj.each do |u, u_edges|
144
+ u_edges.each { |v, uv_attrs| uv_attrs.each { |_k, edge_attrs| new_graph.add_edge(v, u, **edge_attrs) } }
145
+ end
146
+ new_graph
147
+ end
148
+
149
+ # Returns in-degree of a given node
150
+ #
151
+ # @example
152
+ # graph.in_degree(node)
153
+ #
154
+ # @param node [Object] the node whose in degree is to be calculated
155
+ def in_degree(node)
156
+ @pred[node].values.map(&:length).sum
157
+ end
158
+
159
+ # Returns out-degree of a given node
160
+ #
161
+ # @example
162
+ # graph.out_degree(node)
163
+ #
164
+ # @param node [Object] the node whose out degree is to be calculated
165
+ def out_degree(node)
166
+ @adj[node].values.map(&:length).sum
167
+ end
168
+
169
+ # Returns number of edges
170
+ #
171
+ # @example
172
+ # graph.number_of_edges
173
+ def number_of_edges
174
+ @adj.values.flat_map(&:values).map(&:length).sum
175
+ end
176
+
177
+ # Returns the size of the graph
178
+ #
179
+ # @example
180
+ # graph.size(true)
181
+ #
182
+ # @param is_weighted [Bool] if true, method returns sum of weights of all edges
183
+ # else returns number of edges
184
+ def size(is_weighted = false)
185
+ if is_weighted
186
+ graph_size = 0
187
+ @adj.each do |_, hash_val|
188
+ hash_val.each { |_, v| v.each { |_, attrs| graph_size += attrs[:weight] if attrs.has_key?(:weight) } }
189
+ end
190
+ return graph_size
191
+ end
192
+ number_of_edges
193
+ end
194
+
195
+ # Returns subgraph consisting of given array of nodes
196
+ #
197
+ # @example
198
+ # graph.subgraph(%w[Mumbai Nagpur])
199
+ #
200
+ # @param nodes [Array<Object>] the nodes to be included in the subgraph
201
+ def subgraph(nodes)
202
+ case nodes
203
+ when Array, Set
204
+ sub_graph = NetworkX::MultiDiGraph.new(**@graph)
205
+ nodes.each do |u, _|
206
+ raise KeyError, "#{u} does not exist in the current graph!" unless @nodes.has_key?(u)
207
+
208
+ sub_graph.add_node(u, **@nodes[u])
209
+ @adj[u].each do |v, edge_val|
210
+ edge_val.each { |_, keyval| sub_graph.add_edge(u, v, **keyval) if @adj[u].has_key?(v) && nodes.include?(v) }
211
+ end
212
+ end
213
+ sub_graph
214
+ else
215
+ raise ArgumentError, 'Expected Argument to be Array or Set of nodes, ' \
216
+ "received #{nodes.class.name} instead."
217
+ end
218
+ end
219
+
220
+ # Returns subgraph conisting of given edges
221
+ #
222
+ # @example
223
+ # graph.edge_subgraph([%w[Nagpur Wardha], %w[Nagpur Mumbai]])
224
+ #
225
+ # @param edges [Array<Object, Object>] the edges to be included in the subraph
226
+ def edge_subgraph(edges)
227
+ case edges
228
+ when Array, Set
229
+ sub_graph = NetworkX::MultiDiGraph.new(**@graph)
230
+ edges.each do |u, v|
231
+ raise KeyError, "Edge between #{u} and #{v} does not exist in the graph!" unless @nodes.has_key?(u) \
232
+ && @adj[u].has_key?(v)
233
+
234
+ sub_graph.add_node(u, **@nodes[u])
235
+ sub_graph.add_node(v, **@nodes[v])
236
+ @adj[u][v].each { |_, keyval| sub_graph.add_edge(u, v, **keyval) }
237
+ end
238
+ sub_graph
239
+ else
240
+ raise ArgumentError, 'Expected Argument to be Array or Set of edges, ' \
241
+ "received #{edges.class.name} instead."
242
+ end
243
+ end
244
+
245
+ def multigraph?
246
+ true
247
+ end
248
+ end
249
+ end
@@ -0,0 +1,199 @@
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 node1 [Object] the first node of a given edge
12
+ # @param node2 [Object] the second node of a given edge
13
+ def new_edge_key(node1, node2)
14
+ return 0 if @adj[node1][node2].nil?
15
+
16
+ key = @adj[node1][node2].length
17
+ key += 1 while @adj[node1][node2].has_key?(key)
18
+ key
19
+ end
20
+
21
+ # Adds the respective edge
22
+ #
23
+ # @example Add an edge with attribute name
24
+ # graph.add_edge(node1, node2, name: "Edge1")
25
+ #
26
+ # @example Add an edge with no attribute
27
+ # graph.add_edge("Bangalore", "Chennai")
28
+ #
29
+ # @param node1 [Object] the first node of the edge
30
+ # @param node2 [Object] the second node of the edge
31
+ # @param edge_attrs [Hash{ Object => Object }] the hash of the edge attributes
32
+ def add_edge(node1, node2, **edge_attrs)
33
+ add_node(node1)
34
+ add_node(node2)
35
+ key = new_edge_key(node1, node2)
36
+ all_edge_attrs = @adj[node1][node2] || {}
37
+ all_edge_attrs[key] = edge_attrs
38
+ @adj[node1][node2] = all_edge_attrs
39
+ @adj[node2][node1] = all_edge_attrs
40
+ end
41
+
42
+ # Removes edge from the graph
43
+ #
44
+ # @example
45
+ # graph.remove_edge('Noida', 'Bangalore')
46
+ #
47
+ # @param node1 [Object] the first node of the edge
48
+ # @param node2 [Object] the second node of the edge
49
+ def remove_edge(node1, node2, key = nil)
50
+ return super(node1, node2) if key.nil?
51
+
52
+ raise KeyError, "#{node1} is not a valid node." unless @nodes.has_key?(node1)
53
+ raise KeyError, "#{node2} is not a valid node" unless @nodes.has_key?(node2)
54
+ raise KeyError, 'The given edge is not a valid one.' unless @adj[node1].has_key?(node2)
55
+
56
+ if @adj[node1][node2].none? { |_index, data| data[:key] == key }
57
+ raise KeyError, 'The given edge is not a valid one'
58
+ end
59
+
60
+ @adj[node1][node2].delete_if { |_indx, data| data[:key] == key }
61
+ @adj[node2][node1].delete_if { |_indx, data| data[:key] == key }
62
+ end
63
+
64
+ # Returns the size of the graph
65
+ #
66
+ # @example
67
+ # graph.size(true)
68
+ #
69
+ # @param is_weighted [Bool] if true, method returns sum of weights of all edges
70
+ # else returns number of edges
71
+ def size(is_weighted = false)
72
+ if is_weighted
73
+ graph_size = 0
74
+ @adj.each do |_, hash_val|
75
+ hash_val.each { |_, v| v.each { |_, attrs| graph_size += attrs[:weight] if attrs.has_key?(:weight) } }
76
+ end
77
+ return graph_size / 2
78
+ end
79
+ number_of_edges
80
+ end
81
+
82
+ # Returns number of edges
83
+ #
84
+ # @example
85
+ # graph.number_of_edges
86
+ def number_of_edges
87
+ @adj.values.flat_map(&:values).map(&:length).sum / 2
88
+ end
89
+
90
+ # Checks if the the edge consisting of two nodes is present in the graph
91
+ #
92
+ # @example
93
+ # graph.edge?(node1, node2)
94
+ #
95
+ # @param node1 [Object] the first node of the edge to be checked
96
+ # @param node2 [Object] the second node of the edge to be checked
97
+ # @param key [Integer] the key of the given edge
98
+ def edge?(node1, node2, key = nil)
99
+ return super(node1, node2) if key.nil?
100
+
101
+ node?(node1) && @adj[node1].has_key?(node2) && @adj[node1][node2].has_key?(key)
102
+ end
103
+
104
+ def has_edge?(node1, node2, key = nil)
105
+ return super(node1, node2) if key.nil?
106
+
107
+ return false unless node?(node1) && @adj[node1].has_key?(node2)
108
+
109
+ @adj[node1][node2].any? { |_index, data| data[:key] == key }
110
+ end
111
+
112
+ def each_edge(data: false)
113
+ return enum_for(:each_edge, data: data) unless block_given?
114
+
115
+ @adj.each do |v, ws|
116
+ ws.each do |w, key_and_info|
117
+ next if v > w
118
+
119
+ key_and_info.each do |key, info|
120
+ data ? yield(v, w, key, info) : yield(v, w, key)
121
+ end
122
+ end
123
+ end
124
+ end
125
+
126
+ # Returns the undirected version of the graph
127
+ #
128
+ # @example
129
+ # graph.to_undirected
130
+ def to_undirected
131
+ graph = NetworkX::Graph.new(**@graph)
132
+ @nodes.each { |node, node_attr| graph.add_node(node, **node_attr) }
133
+ @adj.each do |node1, node1_edges|
134
+ node1_edges.each do |node2, node1_node2|
135
+ edge_attrs = {}
136
+ node1_node2.each { |_key, attrs| edge_attrs.merge!(attrs) }
137
+ graph.add_edge(node1, node2, **edge_attrs)
138
+ end
139
+ end
140
+ graph
141
+ end
142
+
143
+ # Returns subgraph consisting of given array of nodes
144
+ #
145
+ # @example
146
+ # graph.subgraph(%w[Mumbai Nagpur])
147
+ #
148
+ # @param nodes [Array<Object>] the nodes to be included in the subgraph
149
+ def subgraph(nodes)
150
+ case nodes
151
+ when Array, Set
152
+ sub_graph = NetworkX::MultiGraph.new(**@graph)
153
+ nodes.each do |u, _|
154
+ raise KeyError, "#{u} does not exist in the current graph!" unless @nodes.has_key?(u)
155
+
156
+ sub_graph.add_node(u, **@nodes[u])
157
+ @adj[u].each do |v, edge_val|
158
+ next if u > v
159
+
160
+ edge_val.each { |_, keyval| sub_graph.add_edge(u, v, **keyval) if @adj[u].has_key?(v) && nodes.include?(v) }
161
+ end
162
+ end
163
+ sub_graph
164
+ else
165
+ raise ArgumentError, 'Expected Argument to be Array or Set of nodes, ' \
166
+ "received #{nodes.class.name} instead."
167
+ end
168
+ end
169
+
170
+ # Returns subgraph conisting of given edges
171
+ #
172
+ # @example
173
+ # graph.edge_subgraph([%w[Nagpur Wardha], %w[Nagpur Mumbai]])
174
+ #
175
+ # @param edges [Array<Object, Object>] the edges to be included in the subraph
176
+ def edge_subgraph(edges)
177
+ case edges
178
+ when Array, Set
179
+ sub_graph = NetworkX::MultiGraph.new(**@graph)
180
+ edges.each do |u, v|
181
+ raise KeyError, "Edge between #{u} and #{v} does not exist in the graph!" unless @nodes.has_key?(u) \
182
+ && @adj[u].has_key?(v)
183
+
184
+ sub_graph.add_node(u, **@nodes[u])
185
+ sub_graph.add_node(v, **@nodes[v])
186
+ @adj[u][v].each { |_, keyval| sub_graph.add_edge(u, v, **keyval) }
187
+ end
188
+ sub_graph
189
+ else
190
+ raise ArgumentError, 'Expected Argument to be Array or Set of edges, ' \
191
+ "received #{edges.class.name} instead."
192
+ end
193
+ end
194
+
195
+ def multigraph?
196
+ true
197
+ end
198
+ end
199
+ end
@@ -0,0 +1,65 @@
1
+ module NetworkX
2
+ # Performs the union of many graphs
3
+ #
4
+ # @param graphs [Array<Graph>, Array<DiGraph>, Array<MultiGraph>, Array<MultiDiGraph>] Array of graphs
5
+ #
6
+ # @return [Graph, DiGraph, MultiGraph, MultiDiGraph] union of all the graphs
7
+ def self.union_all(graphs)
8
+ raise ArgumentError, 'Argument array is empty' if graphs.empty?
9
+
10
+ result = graphs.shift
11
+
12
+ graphs.each do |graph|
13
+ result = NetworkX.union(result, graph)
14
+ end
15
+ result
16
+ end
17
+
18
+ # Performs the disjoint union of many graphs
19
+ #
20
+ # @param graphs [Array<Graph>, Array<DiGraph>, Array<MultiGraph>, Array<MultiDiGraph>] Array of graphs
21
+ #
22
+ # @return [Graph, DiGraph, MultiGraph, MultiDiGraph] disjoint union of all the graphs
23
+ def self.disjoint_union_all(graphs)
24
+ raise ArgumentError, 'Argument array is empty' if graphs.empty?
25
+
26
+ result = graphs.shift
27
+
28
+ graphs.each do |graph|
29
+ result = NetworkX.disjoint_union(result, graph)
30
+ end
31
+ result
32
+ end
33
+
34
+ # Performs the intersection of many graphs
35
+ #
36
+ # @param graphs [Array<Graph>, Array<DiGraph>, Array<MultiGraph>, Array<MultiDiGraph>] Array of graphs
37
+ #
38
+ # @return [Graph, DiGraph, MultiGraph, MultiDiGraph] intersection of all the graphs
39
+ def self.intersection_all(graphs)
40
+ raise ArgumentError, 'Argument array is empty' if graphs.empty?
41
+
42
+ result = graphs.shift
43
+
44
+ graphs.each do |graph|
45
+ result = NetworkX.intersection(result, graph)
46
+ end
47
+ result
48
+ end
49
+
50
+ # Performs the composition of many graphs
51
+ #
52
+ # @param graphs [Array<Graph>, Array<DiGraph>, Array<MultiGraph>, Array<MultiDiGraph>] Array of graphs
53
+ #
54
+ # @return [Graph, DiGraph, MultiGraph, MultiDiGraph] composition of all the graphs
55
+ def self.compose_all(graphs)
56
+ raise ArgumentError, 'Argument array is empty' if graphs.empty?
57
+
58
+ result = graphs.shift
59
+
60
+ graphs.each do |graph|
61
+ result = NetworkX.compose(result, graph)
62
+ end
63
+ result
64
+ end
65
+ end