networkx 0.1.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) 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} +20 -10
  4. data/{ISSUE_TEMPLATE.md → .github/ISSUE_TEMPLATE.md} +1 -1
  5. data/{PULL_REQUEST_TEMPLATE.md → .github/PULL_REQUEST_TEMPLATE.md} +2 -4
  6. data/.github/workflows/ci.yml +17 -0
  7. data/.github/workflows/doc.yml +23 -0
  8. data/.rspec +0 -1
  9. data/.rubocop.yml +57 -71
  10. data/.yardopts +0 -1
  11. data/README.md +32 -34
  12. data/Rakefile +2 -3
  13. data/lib/networkx/auxillary_functions/cliques.rb +9 -12
  14. data/lib/networkx/auxillary_functions/cycles.rb +17 -7
  15. data/lib/networkx/auxillary_functions/dag.rb +10 -5
  16. data/lib/networkx/auxillary_functions/eccentricity.rb +2 -1
  17. data/lib/networkx/auxillary_functions/mis.rb +2 -2
  18. data/lib/networkx/auxillary_functions/mst.rb +1 -3
  19. data/lib/networkx/auxillary_functions/union_find.rb +92 -12
  20. data/lib/networkx/auxillary_functions/wiener.rb +1 -1
  21. data/lib/networkx/converters/to_csv.rb +1 -3
  22. data/lib/networkx/converters/to_json.rb +0 -2
  23. data/lib/networkx/digraph.rb +55 -49
  24. data/lib/networkx/flow/capacityscaling.rb +29 -35
  25. data/lib/networkx/flow/edmondskarp.rb +17 -15
  26. data/lib/networkx/flow/preflowpush.rb +29 -32
  27. data/lib/networkx/flow/shortestaugmentingpath.rb +17 -20
  28. data/lib/networkx/flow/utils.rb +6 -27
  29. data/lib/networkx/graph.rb +179 -72
  30. data/lib/networkx/link_analysis/hits.rb +9 -9
  31. data/lib/networkx/link_analysis/pagerank.rb +29 -31
  32. data/lib/networkx/multidigraph.rb +90 -81
  33. data/lib/networkx/multigraph.rb +91 -63
  34. data/lib/networkx/operators/all.rb +8 -4
  35. data/lib/networkx/operators/binary.rb +106 -128
  36. data/lib/networkx/operators/product.rb +61 -64
  37. data/lib/networkx/operators/unary.rb +1 -1
  38. data/lib/networkx/others/bridges.rb +30 -0
  39. data/lib/networkx/others/generators.rb +237 -0
  40. data/lib/networkx/others/grid_2d_graph.rb +38 -0
  41. data/lib/networkx/others/info.rb +11 -0
  42. data/lib/networkx/others/number_connected_components.rb +17 -0
  43. data/lib/networkx/others/reads.rb +52 -0
  44. data/lib/networkx/shortest_path/astar.rb +10 -8
  45. data/lib/networkx/shortest_path/dense.rb +1 -3
  46. data/lib/networkx/shortest_path/unweighted.rb +13 -16
  47. data/lib/networkx/shortest_path/weighted.rb +51 -42
  48. data/lib/networkx/to_matrix.rb +2 -3
  49. data/lib/networkx/traversals/bfs.rb +54 -2
  50. data/lib/networkx/traversals/dfs.rb +62 -6
  51. data/lib/networkx/traversals/edge_dfs.rb +36 -12
  52. data/lib/networkx/version.rb +1 -1
  53. data/lib/networkx.rb +7 -1
  54. data/networkx.gemspec +17 -14
  55. metadata +74 -84
  56. data/.rspec_formatter.rb +0 -24
  57. data/.travis.yml +0 -18
  58. data/Guardfile +0 -7
  59. data/RELEASE_POLICY.md +0 -20
@@ -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, :nodes, :graph
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 node_1 [Object] the first node of the edge
34
- # @param node_2 [Object] the second node of the edge
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(node_1, node_2, **edge_attrs)
37
- add_node(node_1)
38
- add_node(node_2)
33
+ def add_edge(node1, node2, **edge_attrs)
34
+ add_node(node1)
35
+ add_node(node2)
39
36
 
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
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 { |node_1, node_2, **attrs| add_edge(node_1, node_2, attrs) }
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.key?(node)
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, **node_attrs| add_node(node, node_attrs) }
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 or Set of nodes, '\
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.key?(node)
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 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
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 { |node_1, node_2| remove_edge(node_1, node_2) }
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 node_1 [Object] the first node of the edge
158
- # @param node_2 [Object] the second node of the edge
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(node_1, node_2, weight)
161
- add_edge(node_1, node_2, weight: weight)
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
- (edges.transpose << weights).transpose.each do |node_1, node_2, weight|
178
- add_weighted_edge(node_1, node_2, weight)
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?(node_1)
280
+ # graph.node?(node1)
196
281
  #
197
282
  # @param node [Object] the node to be checked
198
283
  def node?(node)
199
- @nodes.key?(node)
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?(node_1, node_2)
291
+ # graph.edge?(node1, node2)
206
292
  #
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)
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(node_1, node_2)
315
+ # graph.get_edge_data(node1, node2)
228
316
  #
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]
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).inject(:+) / 2
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.key?(:weight) }
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(@graph)
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.key?(u)
295
- sub_graph.add_node(u, @nodes[u])
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].key?(v) && nodes.include?(v)
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(@graph)
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.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])
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
- return sub_graph
414
+ sub_graph
327
415
  else
328
- raise ArgumentError, 'Expected Argument to be Array or Set of edges, '\
329
- "received #{edges.class.name} instead."
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.inject(:+)
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 }.inject(:+) < tol
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 [NMatrix] authority matrix for the graph
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.dot matrix
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 [NMatrix] hub matrix for the graph
54
+ # @return [Matrix] hub matrix for the graph
55
55
  def self.hub_matrix(graph)
56
56
  matrix, = to_matrix(graph, 0)
57
- matrix.dot matrix.transpose
57
+ matrix * matrix.transpose
58
58
  end
59
59
  end
@@ -1,47 +1,45 @@
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
7
- # @param init [Array<Numeric>] initial pagerank values for the nodes
8
5
  # @param alpha [Numeric] the alpha value to compute the pagerank
9
6
  # @param eps [Numeric] tolerence to check for convergence
10
7
  # @param max_iter [Integer] max iterations for the pagerank algorithm to run
11
8
  #
12
- # @return [Array<Numeric>] pagerank values of the graph
13
- def self.pagerank(graph, init, alpha=0.85, eps=1e-4, max_iter=100)
14
- dim = graph.nodes.length
15
- raise ArgumentError, 'Init array needs to have same length as number of graph nodes!'\
16
- unless dim == init.length
17
- matrix = []
18
- elem_ind = {}
19
- p = []
20
- curr = init.values
21
- init.keys.each_with_index { |n, i| elem_ind[n] = i }
22
- graph.adj.each do |_u, u_edges|
23
- adj_arr = Array.new(dim, 0)
24
- u_edges.each do |v, _|
25
- adj_arr[elem_ind[v]] = 1
26
- end
27
- matrix << adj_arr
9
+ # @return [Hash of Object => Float] pagerank values of the graph
10
+ def self.pagerank(graph, alpha: 0.85, personalization: nil, eps: 1e-6, max_iter: 100)
11
+ n = graph.number_of_nodes
12
+
13
+ matrix, index_to_node = NetworkX.to_matrix(graph, 0)
14
+
15
+ index_from_node = index_to_node.invert
16
+
17
+ probabilities = Array.new(n) do |i|
18
+ total = matrix.row(i).sum
19
+ (matrix.row(i) / total.to_f).to_a
28
20
  end
29
- (0..(dim - 1)).each do |i|
30
- p[i] = []
31
- (0..(dim - 1)).each { |j| p[i][j] = matrix[i][j] / (matrix[i].inject(:+) * 1.0) }
21
+
22
+ curr = personalization
23
+ unless curr
24
+ curr = Array.new(n)
25
+ graph.each_node{|node| curr[index_from_node[node]] = 1.0 / n }
32
26
  end
33
27
 
34
- max_iter.times do |_|
28
+ max_iter.times do
35
29
  prev = curr.clone
36
- dim.times do |i|
37
- ip = 0
38
- dim.times { |j| ip += p.transpose[i][j] * prev[j] }
39
- curr[i] = (alpha * ip) + (1 - alpha) / (dim * 1.0)
30
+
31
+ n.times do |i|
32
+ ip = 0.0
33
+ n.times do |j|
34
+ ip += probabilities[j][i] * prev[j]
35
+ end
36
+ curr[i] = (alpha * ip) + ((1.0 - alpha) / n * 1.0)
40
37
  end
41
- err = 0
42
- dim.times { |i| err += (prev[i] - curr[i]).abs }
43
- return curr if err < eps
38
+
39
+ err = (0...n).map{|i| (prev[i] - curr[i]).abs }.sum
40
+ return (0...n).map{|i| [index_to_node[i], curr[i]] }.sort.to_h if err < eps
44
41
  end
45
- raise ArgumentError, 'PageRank failed to converge!'
42
+ warn "pagerank() failed within #{max_iter} iterations. Please inclease max_iter: or loosen eps:"
43
+ (0...n).map{|i| [index_to_node[i], curr[i]] }.sort.to_h
46
44
  end
47
45
  end