networkx 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/{CODE_OF_CONDUCT.md → .github/CODE_OF_CONDUCT.md} +0 -0
- data/{CONTRIBUTING.md → .github/CONTRIBUTING.md} +20 -10
- data/{ISSUE_TEMPLATE.md → .github/ISSUE_TEMPLATE.md} +1 -1
- data/{PULL_REQUEST_TEMPLATE.md → .github/PULL_REQUEST_TEMPLATE.md} +2 -4
- data/.github/workflows/ci.yml +17 -0
- data/.github/workflows/doc.yml +23 -0
- data/.github/workflows/gem-push.yml +45 -0
- data/.rspec +0 -1
- data/.rubocop.yml +57 -71
- data/.yardopts +0 -1
- data/README.md +27 -27
- data/Rakefile +2 -3
- data/lib/networkx/auxillary_functions/cliques.rb +9 -12
- data/lib/networkx/auxillary_functions/cycles.rb +17 -7
- data/lib/networkx/auxillary_functions/dag.rb +10 -5
- data/lib/networkx/auxillary_functions/eccentricity.rb +2 -1
- data/lib/networkx/auxillary_functions/mis.rb +2 -2
- data/lib/networkx/auxillary_functions/mst.rb +1 -3
- data/lib/networkx/auxillary_functions/union_find.rb +92 -12
- data/lib/networkx/auxillary_functions/wiener.rb +1 -1
- data/lib/networkx/converters/to_csv.rb +1 -3
- data/lib/networkx/converters/to_json.rb +0 -2
- data/lib/networkx/digraph.rb +55 -49
- data/lib/networkx/flow/capacityscaling.rb +29 -35
- data/lib/networkx/flow/edmondskarp.rb +17 -15
- data/lib/networkx/flow/preflowpush.rb +29 -32
- data/lib/networkx/flow/shortestaugmentingpath.rb +17 -20
- data/lib/networkx/flow/utils.rb +6 -27
- data/lib/networkx/graph.rb +179 -72
- data/lib/networkx/link_analysis/hits.rb +9 -9
- data/lib/networkx/link_analysis/pagerank.rb +48 -6
- data/lib/networkx/multidigraph.rb +90 -81
- data/lib/networkx/multigraph.rb +91 -63
- data/lib/networkx/operators/all.rb +8 -4
- data/lib/networkx/operators/binary.rb +106 -128
- data/lib/networkx/operators/product.rb +61 -64
- data/lib/networkx/operators/unary.rb +1 -1
- data/lib/networkx/others/bridges.rb +30 -0
- data/lib/networkx/others/generators.rb +237 -0
- data/lib/networkx/others/grid_2d_graph.rb +38 -0
- data/lib/networkx/others/info.rb +11 -0
- data/lib/networkx/others/number_connected_components.rb +17 -0
- data/lib/networkx/others/reads.rb +52 -0
- data/lib/networkx/shortest_path/astar.rb +10 -8
- data/lib/networkx/shortest_path/dense.rb +1 -3
- data/lib/networkx/shortest_path/unweighted.rb +13 -16
- data/lib/networkx/shortest_path/weighted.rb +51 -42
- data/lib/networkx/to_matrix.rb +2 -3
- data/lib/networkx/traversals/bfs.rb +54 -2
- data/lib/networkx/traversals/dfs.rb +62 -6
- data/lib/networkx/traversals/edge_dfs.rb +36 -12
- data/lib/networkx/version.rb +1 -1
- data/lib/networkx.rb +7 -1
- data/networkx.gemspec +12 -13
- metadata +71 -81
- data/.rspec_formatter.rb +0 -24
- data/.travis.yml +0 -18
- data/Guardfile +0 -7
- data/RELEASE_POLICY.md +0 -20
data/lib/networkx/graph.rb
CHANGED
@@ -3,10 +3,9 @@ module NetworkX
|
|
3
3
|
#
|
4
4
|
# @attr_reader adj [Hash{ Object => Hash{ Object => Hash{ Object => Object } } }]
|
5
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
6
|
# @attr_reader graph [Hash{ Object => Object }] Stores the attributes of the graph
|
8
7
|
class Graph
|
9
|
-
attr_reader :adj, :
|
8
|
+
attr_reader :adj, :graph
|
10
9
|
|
11
10
|
# Constructor for initializing graph
|
12
11
|
#
|
@@ -17,8 +16,6 @@ module NetworkX
|
|
17
16
|
def initialize(**graph_attrs)
|
18
17
|
@nodes = {}
|
19
18
|
@adj = {}
|
20
|
-
@graph = {}
|
21
|
-
|
22
19
|
@graph = graph_attrs
|
23
20
|
end
|
24
21
|
|
@@ -30,16 +27,16 @@ module NetworkX
|
|
30
27
|
# @example Add an edge with no attribute
|
31
28
|
# graph.add_edge("Bangalore", "Chennai")
|
32
29
|
#
|
33
|
-
# @param
|
34
|
-
# @param
|
30
|
+
# @param node1 [Object] the first node of the edge
|
31
|
+
# @param node2 [Object] the second node of the edge
|
35
32
|
# @param edge_attrs [Hash{ Object => Object }] the hash of the edge attributes
|
36
|
-
def add_edge(
|
37
|
-
add_node(
|
38
|
-
add_node(
|
33
|
+
def add_edge(node1, node2, **edge_attrs)
|
34
|
+
add_node(node1)
|
35
|
+
add_node(node2)
|
39
36
|
|
40
|
-
edge_attrs = (@adj[
|
41
|
-
@adj[
|
42
|
-
@adj[
|
37
|
+
edge_attrs = (@adj[node1][node2] || {}).merge(edge_attrs)
|
38
|
+
@adj[node1][node2] = edge_attrs
|
39
|
+
@adj[node2][node1] = edge_attrs
|
43
40
|
end
|
44
41
|
|
45
42
|
# Adds multiple edges from an array
|
@@ -50,13 +47,17 @@ module NetworkX
|
|
50
47
|
def add_edges(edges)
|
51
48
|
case edges
|
52
49
|
when Array
|
53
|
-
edges.each { |
|
50
|
+
edges.each { |node1, node2, attrs| add_edge(node1, node2, **(attrs || {})) }
|
54
51
|
else
|
55
|
-
raise ArgumentError, 'Expected argument to be an Array of edges, '\
|
52
|
+
raise ArgumentError, 'Expected argument to be an Array of edges, ' \
|
56
53
|
"received #{edges.class.name} instead."
|
57
54
|
end
|
58
55
|
end
|
59
56
|
|
57
|
+
def add_edges_from(rng)
|
58
|
+
rng.each { |node| add_edge(*node) }
|
59
|
+
end
|
60
|
+
|
60
61
|
# Adds a node and its attributes to the graph
|
61
62
|
#
|
62
63
|
# @example Add a node with attribute 'type'
|
@@ -65,7 +66,7 @@ module NetworkX
|
|
65
66
|
# @param node [Object] the node object
|
66
67
|
# @param node_attrs [Hash{ Object => Object }] the hash of the attributes of the node
|
67
68
|
def add_node(node, **node_attrs)
|
68
|
-
if @nodes.
|
69
|
+
if @nodes.has_key?(node)
|
69
70
|
@nodes[node].merge!(node_attrs)
|
70
71
|
else
|
71
72
|
@adj[node] = {}
|
@@ -82,13 +83,31 @@ module NetworkX
|
|
82
83
|
def add_nodes(nodes)
|
83
84
|
case nodes
|
84
85
|
when Set, Array
|
85
|
-
nodes.each { |node,
|
86
|
+
nodes.each { |node, node_attrs| add_node(node, **(node_attrs || {})) }
|
87
|
+
when Range
|
88
|
+
nodes.each { |node| add_node(node) }
|
86
89
|
else
|
87
|
-
raise ArgumentError, 'Expected argument to be an Array
|
90
|
+
raise ArgumentError, 'Expected argument to be an Array/Set/Range of nodes, ' \
|
88
91
|
"received #{nodes.class.name} instead."
|
89
92
|
end
|
90
93
|
end
|
91
94
|
|
95
|
+
# [TODO][EXPERIMENTAL]
|
96
|
+
#
|
97
|
+
# @param nodes_for_adding [Array | Range | String] nodes
|
98
|
+
def add_nodes_from(nodes_for_adding)
|
99
|
+
case nodes_for_adding
|
100
|
+
when String
|
101
|
+
nodes_for_adding.each_char { |node| add_node(node) }
|
102
|
+
else
|
103
|
+
nodes_for_adding.each { |node| add_node(node) }
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def add_path(paths)
|
108
|
+
paths.each_cons(2){|x, y| add_edge(x, y) }
|
109
|
+
end
|
110
|
+
|
92
111
|
# Removes node from the graph
|
93
112
|
#
|
94
113
|
# @example
|
@@ -96,7 +115,8 @@ module NetworkX
|
|
96
115
|
#
|
97
116
|
# @param node [Object] the node to be removed
|
98
117
|
def remove_node(node)
|
99
|
-
raise KeyError, "Error in deleting node #{node} from Graph." unless @nodes.
|
118
|
+
raise KeyError, "Error in deleting node #{node} from Graph." unless @nodes.has_key?(node)
|
119
|
+
|
100
120
|
@adj[node].each_key { |k| @adj[k].delete(node) }
|
101
121
|
@adj.delete(node)
|
102
122
|
@nodes.delete(node)
|
@@ -113,24 +133,26 @@ module NetworkX
|
|
113
133
|
when Set, Array
|
114
134
|
nodes.each { |node| remove_node(node) }
|
115
135
|
else
|
116
|
-
raise ArgumentError, 'Expected argument to be an Array or Set of nodes, '\
|
136
|
+
raise ArgumentError, 'Expected argument to be an Array or Set of nodes, ' \
|
117
137
|
"received #{nodes.class.name} instead."
|
118
138
|
end
|
119
139
|
end
|
140
|
+
alias remove_nodes_from remove_nodes
|
120
141
|
|
121
142
|
# Removes edge from the graph
|
122
143
|
#
|
123
144
|
# @example
|
124
145
|
# graph.remove_edge('Noida', 'Bangalore')
|
125
146
|
#
|
126
|
-
# @param
|
127
|
-
# @param
|
128
|
-
def remove_edge(
|
129
|
-
raise KeyError, "#{
|
130
|
-
raise KeyError, "#{
|
131
|
-
raise KeyError, 'The given edge is not a valid one.' unless @adj[
|
132
|
-
|
133
|
-
@adj[
|
147
|
+
# @param node1 [Object] the first node of the edge
|
148
|
+
# @param node2 [Object] the second node of the edge
|
149
|
+
def remove_edge(node1, node2)
|
150
|
+
raise KeyError, "#{node1} is not a valid node." unless @nodes.has_key?(node1)
|
151
|
+
raise KeyError, "#{node2} is not a valid node" unless @nodes.has_key?(node2)
|
152
|
+
raise KeyError, 'The given edge is not a valid one.' unless @adj[node1].has_key?(node2)
|
153
|
+
|
154
|
+
@adj[node1].delete(node2)
|
155
|
+
@adj[node2].delete(node1) if node1 != node2
|
134
156
|
end
|
135
157
|
|
136
158
|
# Removes multiple edges from the graph
|
@@ -142,23 +164,24 @@ module NetworkX
|
|
142
164
|
def remove_edges(edges)
|
143
165
|
case edges
|
144
166
|
when Array, Set
|
145
|
-
edges.each { |
|
167
|
+
edges.each { |node1, node2| remove_edge(node1, node2) }
|
146
168
|
else
|
147
|
-
raise ArgumentError, 'Expected Arguement to be Array or Set of edges, '\
|
169
|
+
raise ArgumentError, 'Expected Arguement to be Array or Set of edges, ' \
|
148
170
|
"received #{edges.class.name} instead."
|
149
171
|
end
|
150
172
|
end
|
173
|
+
alias remove_edges_from remove_edges
|
151
174
|
|
152
175
|
# Adds weighted edge
|
153
176
|
#
|
154
177
|
# @example
|
155
178
|
# graph.add_weighted_edge('Noida', 'Bangalore', 1000)
|
156
179
|
#
|
157
|
-
# @param
|
158
|
-
# @param
|
180
|
+
# @param node1 [Object] the first node of the edge
|
181
|
+
# @param node2 [Object] the second node of the edge
|
159
182
|
# @param weight [Integer] the weight value
|
160
|
-
def add_weighted_edge(
|
161
|
-
add_edge(
|
183
|
+
def add_weighted_edge(node1, node2, weight)
|
184
|
+
add_edge(node1, node2, weight: weight)
|
162
185
|
end
|
163
186
|
|
164
187
|
# Adds multiple weighted edges
|
@@ -170,12 +193,74 @@ module NetworkX
|
|
170
193
|
# @param edges [Array<Object, Object>] the array of edges
|
171
194
|
# @param weights [Array<Integer>] the array of weights
|
172
195
|
def add_weighted_edges(edges, weights)
|
173
|
-
raise ArgumentError, 'edges and weights array must have equal number of elements.'\
|
196
|
+
raise ArgumentError, 'edges and weights array must have equal number of elements.' \
|
174
197
|
unless edges.size == weights.size
|
175
|
-
raise ArgumentError, 'edges and weight must be given in an Array.'\
|
198
|
+
raise ArgumentError, 'edges and weight must be given in an Array.' \
|
176
199
|
unless edges.is_a?(Array) && weights.is_a?(Array)
|
177
|
-
|
178
|
-
|
200
|
+
|
201
|
+
(edges.transpose << weights).transpose.each do |node1, node2, weight|
|
202
|
+
add_weighted_edge(node1, node2, weight)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
# [TODO][EXPERIMENTAL]
|
207
|
+
#
|
208
|
+
# @param edges [[Object, Object, Integer|Float]] the weight of edge
|
209
|
+
# @param weight [Symbol] weight name key. default key is `:weight``
|
210
|
+
def add_weighted_edges_from(edges, weight: :weight)
|
211
|
+
edges.each do |s, t, w|
|
212
|
+
add_edge(s, t, **{weight => w})
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
# [TODO] Current default of `data` is true.
|
217
|
+
# [Alert] Change the default in the futher. Please specify the `data`.
|
218
|
+
#
|
219
|
+
# @param data [bool] true if you want data of each edge
|
220
|
+
#
|
221
|
+
# @return [Hash | Array] if data is true, it returns hash including data.
|
222
|
+
# otherwise, simple nodes array.
|
223
|
+
def nodes(data: true)
|
224
|
+
if data
|
225
|
+
@nodes
|
226
|
+
else
|
227
|
+
@nodes.keys
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
def each_node(data: false, &block)
|
232
|
+
return enum_for(:each_node, data: data) unless block_given?
|
233
|
+
|
234
|
+
data ? @nodes.each(&block) : @nodes.each_key(&block)
|
235
|
+
end
|
236
|
+
|
237
|
+
# [TODO][EXPERIMENTAL]
|
238
|
+
#
|
239
|
+
# @param data [bool] true if you want data of each edge
|
240
|
+
#
|
241
|
+
# @return [Array[[Object, Object]]] edges array
|
242
|
+
def edges(data: false)
|
243
|
+
each_edge(data: data).to_a
|
244
|
+
end
|
245
|
+
|
246
|
+
# [TODO][EXPERIMENTAL]
|
247
|
+
#
|
248
|
+
# @param data [bool] true if you want data of each edge
|
249
|
+
def each_edge(data: false)
|
250
|
+
return enum_for(:each_edge, data: data) unless block_given?
|
251
|
+
|
252
|
+
h = {}
|
253
|
+
@adj.each do |v, ws|
|
254
|
+
ws.each do |w, info|
|
255
|
+
next if v > w
|
256
|
+
|
257
|
+
h[[v, w, info]] = true
|
258
|
+
end
|
259
|
+
end
|
260
|
+
if data
|
261
|
+
h.each { |(v, w, info), _true| yield(v, w, info) }
|
262
|
+
else
|
263
|
+
h.each { |(v, w, _info), _true| yield(v, w) }
|
179
264
|
end
|
180
265
|
end
|
181
266
|
|
@@ -192,23 +277,25 @@ module NetworkX
|
|
192
277
|
# Checks if a node is present in the graph
|
193
278
|
#
|
194
279
|
# @example
|
195
|
-
# graph.node?(
|
280
|
+
# graph.node?(node1)
|
196
281
|
#
|
197
282
|
# @param node [Object] the node to be checked
|
198
283
|
def node?(node)
|
199
|
-
@nodes.
|
284
|
+
@nodes.has_key?(node)
|
200
285
|
end
|
286
|
+
alias has_node? node?
|
201
287
|
|
202
288
|
# Checks if the the edge consisting of two nodes is present in the graph
|
203
289
|
#
|
204
290
|
# @example
|
205
|
-
# graph.edge?(
|
291
|
+
# graph.edge?(node1, node2)
|
206
292
|
#
|
207
|
-
# @param
|
208
|
-
# @param
|
209
|
-
def edge?(
|
210
|
-
node?(
|
293
|
+
# @param node1 [Object] the first node of the edge to be checked
|
294
|
+
# @param node2 [Object] the second node of the edge to be checked
|
295
|
+
def edge?(node1, node2)
|
296
|
+
node?(node1) && @adj[node1].has_key?(node2)
|
211
297
|
end
|
298
|
+
alias has_edge? edge?
|
212
299
|
|
213
300
|
# Gets the node data
|
214
301
|
#
|
@@ -218,19 +305,21 @@ module NetworkX
|
|
218
305
|
# @param node [Object] the node whose data is to be fetched
|
219
306
|
def get_node_data(node)
|
220
307
|
raise ArgumentError, 'No such node exists!' unless node?(node)
|
308
|
+
|
221
309
|
@nodes[node]
|
222
310
|
end
|
223
311
|
|
224
312
|
# Gets the edge data
|
225
313
|
#
|
226
314
|
# @example
|
227
|
-
# graph.get_edge_data(
|
315
|
+
# graph.get_edge_data(node1, node2)
|
228
316
|
#
|
229
|
-
# @param
|
230
|
-
# @param
|
231
|
-
def get_edge_data(
|
232
|
-
raise KeyError, 'No such edge exists!' unless node?(
|
233
|
-
|
317
|
+
# @param node1 [Object] the first node of the edge
|
318
|
+
# @param node2 [Object] the second node of the edge
|
319
|
+
def get_edge_data(node1, node2)
|
320
|
+
raise KeyError, 'No such edge exists!' unless node?(node1) && node?(node2)
|
321
|
+
|
322
|
+
@adj[node1][node2]
|
234
323
|
end
|
235
324
|
|
236
325
|
# Retus a hash of neighbours of a node
|
@@ -241,6 +330,7 @@ module NetworkX
|
|
241
330
|
# @param node [Object] the node whose neighbours are to be fetched
|
242
331
|
def neighbours(node)
|
243
332
|
raise KeyError, 'No such node exists!' unless node?(node)
|
333
|
+
|
244
334
|
@adj[node]
|
245
335
|
end
|
246
336
|
|
@@ -257,7 +347,7 @@ module NetworkX
|
|
257
347
|
# @example
|
258
348
|
# graph.number_of_edges
|
259
349
|
def number_of_edges
|
260
|
-
@adj.values.map(&:length).
|
350
|
+
@adj.values.map(&:length).sum / 2
|
261
351
|
end
|
262
352
|
|
263
353
|
# Returns the size of the graph
|
@@ -267,19 +357,17 @@ module NetworkX
|
|
267
357
|
#
|
268
358
|
# @param is_weighted [Bool] if true, method returns sum of weights of all edges
|
269
359
|
# else returns number of edges
|
270
|
-
def size(is_weighted=false)
|
360
|
+
def size(is_weighted = false)
|
271
361
|
if is_weighted
|
272
362
|
graph_size = 0
|
273
363
|
@adj.each do |_, hash_val|
|
274
|
-
hash_val.each { |_, v| graph_size += v[:weight] if v.
|
364
|
+
hash_val.each { |_, v| graph_size += v[:weight] if v.has_key?(:weight) }
|
275
365
|
end
|
276
366
|
return graph_size / 2
|
277
367
|
end
|
278
368
|
number_of_edges
|
279
369
|
end
|
280
370
|
|
281
|
-
# TODO: Reduce method length and method complexity
|
282
|
-
|
283
371
|
# Returns subgraph consisting of given array of nodes
|
284
372
|
#
|
285
373
|
# @example
|
@@ -289,23 +377,22 @@ module NetworkX
|
|
289
377
|
def subgraph(nodes)
|
290
378
|
case nodes
|
291
379
|
when Array, Set
|
292
|
-
sub_graph = NetworkX::Graph.new(
|
380
|
+
sub_graph = NetworkX::Graph.new(**@graph)
|
293
381
|
nodes.each do |u, _|
|
294
|
-
raise KeyError, "#{u} does not exist in the current graph!" unless @nodes.
|
295
|
-
|
382
|
+
raise KeyError, "#{u} does not exist in the current graph!" unless @nodes.has_key?(u)
|
383
|
+
|
384
|
+
sub_graph.add_node(u, **@nodes[u])
|
296
385
|
@adj[u].each do |v, edge_val|
|
297
|
-
sub_graph.add_edge(u, v, edge_val) if @adj[u].
|
386
|
+
sub_graph.add_edge(u, v, **edge_val) if @adj[u].has_key?(v) && nodes.include?(v)
|
298
387
|
end
|
299
|
-
return sub_graph
|
300
388
|
end
|
389
|
+
sub_graph
|
301
390
|
else
|
302
|
-
raise ArgumentError, 'Expected Argument to be Array or Set of nodes, '\
|
391
|
+
raise ArgumentError, 'Expected Argument to be Array or Set of nodes, ' \
|
303
392
|
"received #{nodes.class.name} instead."
|
304
393
|
end
|
305
394
|
end
|
306
395
|
|
307
|
-
# TODO: Reduce method length and method complexity
|
308
|
-
|
309
396
|
# Returns subgraph conisting of given edges
|
310
397
|
#
|
311
398
|
# @example
|
@@ -315,21 +402,41 @@ module NetworkX
|
|
315
402
|
def edge_subgraph(edges)
|
316
403
|
case edges
|
317
404
|
when Array, Set
|
318
|
-
sub_graph = NetworkX::Graph.new(
|
405
|
+
sub_graph = NetworkX::Graph.new(**@graph)
|
319
406
|
edges.each do |u, v|
|
320
|
-
raise KeyError, "Edge between #{u} and #{v} does not exist in the graph!" unless @nodes.
|
321
|
-
&& @adj[u].
|
322
|
-
|
323
|
-
sub_graph.add_node(
|
324
|
-
sub_graph.
|
407
|
+
raise KeyError, "Edge between #{u} and #{v} does not exist in the graph!" unless @nodes.has_key?(u) \
|
408
|
+
&& @adj[u].has_key?(v)
|
409
|
+
|
410
|
+
sub_graph.add_node(u, **@nodes[u])
|
411
|
+
sub_graph.add_node(v, **@nodes[v])
|
412
|
+
sub_graph.add_edge(u, v, **@adj[u][v])
|
325
413
|
end
|
326
|
-
|
414
|
+
sub_graph
|
327
415
|
else
|
328
|
-
raise ArgumentError, 'Expected Argument to be Array or Set of edges, '\
|
329
|
-
|
416
|
+
raise ArgumentError, 'Expected Argument to be Array or Set of edges, ' \
|
417
|
+
"received #{edges.class.name} instead."
|
330
418
|
end
|
331
419
|
end
|
332
420
|
|
421
|
+
# [EXPERIMENTAL]
|
422
|
+
def degree(nodes = nil)
|
423
|
+
if nodes.nil?
|
424
|
+
@adj.transform_values(&:size)
|
425
|
+
else
|
426
|
+
res = {}
|
427
|
+
nodes.each { |node| res[node] = @adj[node].size }
|
428
|
+
res
|
429
|
+
end
|
430
|
+
end
|
431
|
+
|
432
|
+
def info
|
433
|
+
info = ''
|
434
|
+
info << "Type: #{self.class}\n"
|
435
|
+
info << "Number of nodes: #{number_of_nodes}\n"
|
436
|
+
info << "Number of edges: #{number_of_edges}\n"
|
437
|
+
info
|
438
|
+
end
|
439
|
+
|
333
440
|
def multigraph?
|
334
441
|
['NetworkX::MultiGraph', 'NetworkX::MultiDiGraph'].include?(self.class.name)
|
335
442
|
end
|
@@ -1,6 +1,4 @@
|
|
1
1
|
module NetworkX
|
2
|
-
# TODO: Reduce method length and method complexity
|
3
|
-
|
4
2
|
# Computes hits and authority scores for all the graphs
|
5
3
|
#
|
6
4
|
# @param graph [Graph, DiGraph] a graph
|
@@ -9,10 +7,11 @@ module NetworkX
|
|
9
7
|
# @param nstart [Array<Numeric>] starting hub values for the nodes
|
10
8
|
#
|
11
9
|
# @return [Array<Numeric, Numeric>] hits and authority scores
|
12
|
-
def self.hits(graph, max_iter=100, tol=1e-8, nstart)
|
10
|
+
def self.hits(graph, max_iter = 100, tol = 1e-8, nstart)
|
13
11
|
return [{}, {}] if graph.nodes.empty?
|
12
|
+
|
14
13
|
h = nstart
|
15
|
-
sum = h.values.
|
14
|
+
sum = h.values.sum
|
16
15
|
h.each_key { |k| h[k] /= (sum * 1.0) }
|
17
16
|
i = 0
|
18
17
|
a = {}
|
@@ -30,8 +29,9 @@ module NetworkX
|
|
30
29
|
h.each_key { |k| h[k] /= smax }
|
31
30
|
smax = a.values.max
|
32
31
|
a.each_key { |k| a[k] /= smax }
|
33
|
-
break if h.keys.map { |k| (h[k] - hlast[k]).abs }.
|
32
|
+
break if h.keys.map { |k| (h[k] - hlast[k]).abs }.sum < tol
|
34
33
|
raise ArgumentError, 'Power Iteration failed to converge!' if i > max_iter
|
34
|
+
|
35
35
|
i += 1
|
36
36
|
end
|
37
37
|
[h, a]
|
@@ -41,19 +41,19 @@ module NetworkX
|
|
41
41
|
#
|
42
42
|
# @param graph [Graph, DiGraph] a graph
|
43
43
|
#
|
44
|
-
# @return [
|
44
|
+
# @return [Matrix] authority matrix for the graph
|
45
45
|
def self.authority_matrix(graph)
|
46
46
|
matrix, = to_matrix(graph, 0)
|
47
|
-
matrix.transpose
|
47
|
+
matrix.transpose * matrix
|
48
48
|
end
|
49
49
|
|
50
50
|
# Computes hub matrix for the graph
|
51
51
|
#
|
52
52
|
# @param graph [Graph, DiGraph] a graph
|
53
53
|
#
|
54
|
-
# @return [
|
54
|
+
# @return [Matrix] hub matrix for the graph
|
55
55
|
def self.hub_matrix(graph)
|
56
56
|
matrix, = to_matrix(graph, 0)
|
57
|
-
matrix
|
57
|
+
matrix * matrix.transpose
|
58
58
|
end
|
59
59
|
end
|
@@ -1,6 +1,4 @@
|
|
1
1
|
module NetworkX
|
2
|
-
# TODO: Reduce method length and method complexity
|
3
|
-
|
4
2
|
# Computes pagerank values for the graph
|
5
3
|
#
|
6
4
|
# @param graph [Graph] a graph
|
@@ -10,10 +8,17 @@ module NetworkX
|
|
10
8
|
# @param max_iter [Integer] max iterations for the pagerank algorithm to run
|
11
9
|
#
|
12
10
|
# @return [Array<Numeric>] pagerank values of the graph
|
13
|
-
def self.pagerank(graph, init, alpha=0.85, eps=1e-4, max_iter=100)
|
11
|
+
def self.pagerank(graph, init = nil, alpha = 0.85, eps = 1e-4, max_iter = 100)
|
14
12
|
dim = graph.nodes.length
|
15
|
-
|
13
|
+
if init.nil?
|
14
|
+
init = graph.nodes(data: false).to_h{ |i| [i, 1.0 / dim] }
|
15
|
+
else
|
16
|
+
s = init.values.sum.to_f
|
17
|
+
init = init.transform_values { |v| v / s }
|
18
|
+
end
|
19
|
+
raise ArgumentError, 'Init array needs to have same length as number of graph nodes!' \
|
16
20
|
unless dim == init.length
|
21
|
+
|
17
22
|
matrix = []
|
18
23
|
elem_ind = {}
|
19
24
|
p = []
|
@@ -28,7 +33,7 @@ module NetworkX
|
|
28
33
|
end
|
29
34
|
(0..(dim - 1)).each do |i|
|
30
35
|
p[i] = []
|
31
|
-
(0..(dim - 1)).each { |j| p[i][j] = matrix[i][j] / (matrix[i].
|
36
|
+
(0..(dim - 1)).each { |j| p[i][j] = matrix[i][j] / (matrix[i].sum * 1.0) }
|
32
37
|
end
|
33
38
|
|
34
39
|
max_iter.times do |_|
|
@@ -36,7 +41,7 @@ module NetworkX
|
|
36
41
|
dim.times do |i|
|
37
42
|
ip = 0
|
38
43
|
dim.times { |j| ip += p.transpose[i][j] * prev[j] }
|
39
|
-
curr[i] = (alpha * ip) + (1 - alpha) / (dim * 1.0)
|
44
|
+
curr[i] = (alpha * ip) + ((1 - alpha) / (dim * 1.0))
|
40
45
|
end
|
41
46
|
err = 0
|
42
47
|
dim.times { |i| err += (prev[i] - curr[i]).abs }
|
@@ -44,4 +49,41 @@ module NetworkX
|
|
44
49
|
end
|
45
50
|
raise ArgumentError, 'PageRank failed to converge!'
|
46
51
|
end
|
52
|
+
|
53
|
+
def self.pagerank2(graph, alpha: 0.85, personalization: nil, eps: 1e-6, max_iter: 100)
|
54
|
+
n = graph.number_of_nodes
|
55
|
+
|
56
|
+
matrix, index_to_node = NetworkX.to_matrix(graph, 0)
|
57
|
+
|
58
|
+
# index_to_node = {0=>0, 1=>1, 2=>2, 3=>3}
|
59
|
+
|
60
|
+
index_from_node = index_to_node.invert
|
61
|
+
|
62
|
+
probabilities = Array.new(n) do |i|
|
63
|
+
total = matrix.row(i).sum
|
64
|
+
(matrix.row(i) / total.to_f).to_a
|
65
|
+
end
|
66
|
+
|
67
|
+
curr = personalization
|
68
|
+
unless curr
|
69
|
+
curr = Array.new(n)
|
70
|
+
graph.each_node{|node| curr[index_from_node[node]] = 1.0 / n }
|
71
|
+
end
|
72
|
+
|
73
|
+
max_iter.times do
|
74
|
+
prev = curr.clone
|
75
|
+
|
76
|
+
n.times do |i|
|
77
|
+
ip = 0.0
|
78
|
+
n.times do |j|
|
79
|
+
ip += probabilities[j][i] * prev[j]
|
80
|
+
end
|
81
|
+
curr[i] = (alpha * ip) + ((1.0 - alpha) / n * 1.0)
|
82
|
+
end
|
83
|
+
|
84
|
+
err = (0...n).map{|i| (prev[i] - curr[i]).abs }.sum
|
85
|
+
return (0...n).map{|i| [index_to_node[i], curr[i]] }.sort.to_h if err < eps
|
86
|
+
end
|
87
|
+
(0...n).map{|i| [index_to_node[i], curr[i]] }.sort.to_h
|
88
|
+
end
|
47
89
|
end
|