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,448 @@
1
+ module NetworkX
2
+ # Describes the class for making Undirected 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 graph [Hash{ Object => Object }] Stores the attributes of the graph
7
+ class Graph
8
+ attr_reader :adj, :graph
9
+
10
+ # Constructor for initializing graph
11
+ #
12
+ # @example Initialize a graph with attributes 'type' and 'name'
13
+ # graph = NetworkX::Graph.new(name: "Social Network", type: "undirected")
14
+ #
15
+ # @param graph_attrs [Hash{ Object => Object }] the graph attributes in a hash format
16
+ def initialize(**graph_attrs)
17
+ @nodes = {}
18
+ @adj = {}
19
+ @graph = graph_attrs
20
+ end
21
+
22
+ # Adds the respective edges
23
+ #
24
+ # @example Add an edge with attribute name
25
+ # graph.add_edge(node1, node2, name: "Edge1")
26
+ #
27
+ # @example Add an edge with no attribute
28
+ # graph.add_edge("Bangalore", "Chennai")
29
+ #
30
+ # @param node1 [Object] the first node of the edge
31
+ # @param node2 [Object] the second node of the edge
32
+ # @param edge_attrs [Hash{ Object => Object }] the hash of the edge attributes
33
+ def add_edge(node1, node2, **edge_attrs)
34
+ add_node(node1)
35
+ add_node(node2)
36
+
37
+ edge_attrs = (@adj[node1][node2] || {}).merge(edge_attrs)
38
+ @adj[node1][node2] = edge_attrs
39
+ @adj[node2][node1] = edge_attrs
40
+ end
41
+
42
+ # Adds multiple edges from an array
43
+ #
44
+ # @example Add multiple edges without any attributes
45
+ # graph.add_edges([['Nagpur', 'Kgp'], ['Noida', 'Kgp']])
46
+ # @param edges [Array<Object, Object>]
47
+ def add_edges(edges)
48
+ case edges
49
+ when Array
50
+ edges.each { |node1, node2, attrs| add_edge(node1, node2, **(attrs || {})) }
51
+ else
52
+ raise ArgumentError, 'Expected argument to be an Array of edges, ' \
53
+ "received #{edges.class.name} instead."
54
+ end
55
+ end
56
+
57
+ def add_edges_from(rng)
58
+ rng.each { |node| add_edge(*node) }
59
+ end
60
+
61
+ # Adds a node and its attributes to the graph
62
+ #
63
+ # @example Add a node with attribute 'type'
64
+ # graph.add_node("Noida", type: "city")
65
+ #
66
+ # @param node [Object] the node object
67
+ # @param node_attrs [Hash{ Object => Object }] the hash of the attributes of the node
68
+ def add_node(node, **node_attrs)
69
+ if @nodes.has_key?(node)
70
+ @nodes[node].merge!(node_attrs)
71
+ else
72
+ @adj[node] = {}
73
+ @nodes[node] = node_attrs
74
+ end
75
+ end
76
+
77
+ # Adds multiple nodes to the graph
78
+ #
79
+ # @example Adds multiple nodes with attribute 'type'
80
+ # graph.add_nodes([["Noida", type: "city"], ["Kgp", type: "town"]])
81
+ #
82
+ # @param nodes [Array<Object, Hash{ Object => Object }>] the Array of pair containing nodes and its attributes
83
+ def add_nodes(nodes)
84
+ case nodes
85
+ when Set, Array
86
+ nodes.each { |node, node_attrs| add_node(node, **(node_attrs || {})) }
87
+ when Range
88
+ nodes.each { |node| add_node(node) }
89
+ else
90
+ raise ArgumentError, 'Expected argument to be an Array/Set/Range of nodes, ' \
91
+ "received #{nodes.class.name} instead."
92
+ end
93
+ end
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
+
111
+ # Removes node from the graph
112
+ #
113
+ # @example
114
+ # graph.remove_node("Noida")
115
+ #
116
+ # @param node [Object] the node to be removed
117
+ def remove_node(node)
118
+ raise KeyError, "Error in deleting node #{node} from Graph." unless @nodes.has_key?(node)
119
+
120
+ @adj[node].each_key { |k| @adj[k].delete(node) }
121
+ @adj.delete(node)
122
+ @nodes.delete(node)
123
+ end
124
+
125
+ # Removes multiple nodes from the graph
126
+ #
127
+ # @example
128
+ # graph.remove_nodes(["Noida", "Bangalore"])
129
+ #
130
+ # @param nodes [Array<Object>] the array of nodes to be removed
131
+ def remove_nodes(nodes)
132
+ case nodes
133
+ when Set, Array
134
+ nodes.each { |node| remove_node(node) }
135
+ else
136
+ raise ArgumentError, 'Expected argument to be an Array or Set of nodes, ' \
137
+ "received #{nodes.class.name} instead."
138
+ end
139
+ end
140
+ alias remove_nodes_from remove_nodes
141
+
142
+ # Removes edge from the graph
143
+ #
144
+ # @example
145
+ # graph.remove_edge('Noida', 'Bangalore')
146
+ #
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
156
+ end
157
+
158
+ # Removes multiple edges from the graph
159
+ #
160
+ # @example
161
+ # graph.remove_edges([%w[Noida Bangalore], %w[Bangalore Chennai]])
162
+ #
163
+ # @param edges [Array<Object>] the array of edges to be removed
164
+ def remove_edges(edges)
165
+ case edges
166
+ when Array, Set
167
+ edges.each { |node1, node2| remove_edge(node1, node2) }
168
+ else
169
+ raise ArgumentError, 'Expected Arguement to be Array or Set of edges, ' \
170
+ "received #{edges.class.name} instead."
171
+ end
172
+ end
173
+ alias remove_edges_from remove_edges
174
+
175
+ # Adds weighted edge
176
+ #
177
+ # @example
178
+ # graph.add_weighted_edge('Noida', 'Bangalore', 1000)
179
+ #
180
+ # @param node1 [Object] the first node of the edge
181
+ # @param node2 [Object] the second node of the edge
182
+ # @param weight [Integer] the weight value
183
+ def add_weighted_edge(node1, node2, weight)
184
+ add_edge(node1, node2, weight: weight)
185
+ end
186
+
187
+ # Adds multiple weighted edges
188
+ #
189
+ # @example
190
+ # graph.add_weighted_edges([['Noida', 'Bangalore'],
191
+ # ['Noida', 'Nagpur']], [1000, 2000])
192
+ #
193
+ # @param edges [Array<Object, Object>] the array of edges
194
+ # @param weights [Array<Integer>] the array of weights
195
+ def add_weighted_edges(edges, weights)
196
+ raise ArgumentError, 'edges and weights array must have equal number of elements.' \
197
+ unless edges.size == weights.size
198
+ raise ArgumentError, 'edges and weight must be given in an Array.' \
199
+ unless edges.is_a?(Array) && weights.is_a?(Array)
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) }
264
+ end
265
+ end
266
+
267
+ # Clears the graph
268
+ #
269
+ # @example
270
+ # graph.clear
271
+ def clear
272
+ @adj.clear
273
+ @nodes.clear
274
+ @graph.clear
275
+ end
276
+
277
+ # Checks if a node is present in the graph
278
+ #
279
+ # @example
280
+ # graph.node?(node1)
281
+ #
282
+ # @param node [Object] the node to be checked
283
+ def node?(node)
284
+ @nodes.has_key?(node)
285
+ end
286
+ alias has_node? node?
287
+
288
+ # Checks if the the edge consisting of two nodes is present in the graph
289
+ #
290
+ # @example
291
+ # graph.edge?(node1, node2)
292
+ #
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)
297
+ end
298
+ alias has_edge? edge?
299
+
300
+ # Gets the node data
301
+ #
302
+ # @example
303
+ # graph.get_node_data(node)
304
+ #
305
+ # @param node [Object] the node whose data is to be fetched
306
+ def get_node_data(node)
307
+ raise ArgumentError, 'No such node exists!' unless node?(node)
308
+
309
+ @nodes[node]
310
+ end
311
+
312
+ # Gets the edge data
313
+ #
314
+ # @example
315
+ # graph.get_edge_data(node1, node2)
316
+ #
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]
323
+ end
324
+
325
+ # Retus a hash of neighbours of a node
326
+ #
327
+ # @example
328
+ # graph.neighbours(node)
329
+ #
330
+ # @param node [Object] the node whose neighbours are to be fetched
331
+ def neighbours(node)
332
+ raise KeyError, 'No such node exists!' unless node?(node)
333
+
334
+ @adj[node]
335
+ end
336
+
337
+ # Returns number of nodes
338
+ #
339
+ # @example
340
+ # graph.number_of_nodes
341
+ def number_of_nodes
342
+ @nodes.length
343
+ end
344
+
345
+ # Returns number of edges
346
+ #
347
+ # @example
348
+ # graph.number_of_edges
349
+ def number_of_edges
350
+ @adj.values.map(&:length).sum / 2
351
+ end
352
+
353
+ # Returns the size of the graph
354
+ #
355
+ # @example
356
+ # graph.size(true)
357
+ #
358
+ # @param is_weighted [Bool] if true, method returns sum of weights of all edges
359
+ # else returns number of edges
360
+ def size(is_weighted = false)
361
+ if is_weighted
362
+ graph_size = 0
363
+ @adj.each do |_, hash_val|
364
+ hash_val.each { |_, v| graph_size += v[:weight] if v.has_key?(:weight) }
365
+ end
366
+ return graph_size / 2
367
+ end
368
+ number_of_edges
369
+ end
370
+
371
+ # Returns subgraph consisting of given array of nodes
372
+ #
373
+ # @example
374
+ # graph.subgraph(%w[Mumbai Nagpur])
375
+ #
376
+ # @param nodes [Array<Object>] the nodes to be included in the subgraph
377
+ def subgraph(nodes)
378
+ case nodes
379
+ when Array, Set
380
+ sub_graph = NetworkX::Graph.new(**@graph)
381
+ nodes.each do |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])
385
+ @adj[u].each do |v, edge_val|
386
+ sub_graph.add_edge(u, v, **edge_val) if @adj[u].has_key?(v) && nodes.include?(v)
387
+ end
388
+ end
389
+ sub_graph
390
+ else
391
+ raise ArgumentError, 'Expected Argument to be Array or Set of nodes, ' \
392
+ "received #{nodes.class.name} instead."
393
+ end
394
+ end
395
+
396
+ # Returns subgraph conisting of given edges
397
+ #
398
+ # @example
399
+ # graph.edge_subgraph([%w[Nagpur Wardha], %w[Nagpur Mumbai]])
400
+ #
401
+ # @param edges [Array<Object, Object>] the edges to be included in the subraph
402
+ def edge_subgraph(edges)
403
+ case edges
404
+ when Array, Set
405
+ sub_graph = NetworkX::Graph.new(**@graph)
406
+ edges.each do |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])
413
+ end
414
+ sub_graph
415
+ else
416
+ raise ArgumentError, 'Expected Argument to be Array or Set of edges, ' \
417
+ "received #{edges.class.name} instead."
418
+ end
419
+ end
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
+
440
+ def multigraph?
441
+ ['NetworkX::MultiGraph', 'NetworkX::MultiDiGraph'].include?(self.class.name)
442
+ end
443
+
444
+ def directed?
445
+ ['NetworkX::DiGraph', 'NetworkX::MultiDiGraph'].include?(self.class.name)
446
+ end
447
+ end
448
+ end
@@ -0,0 +1,59 @@
1
+ module NetworkX
2
+ # Computes hits and authority scores for all the graphs
3
+ #
4
+ # @param graph [Graph, DiGraph] a graph
5
+ # @param max_iter [Integer] max iterations to run the hits algorithm
6
+ # @param tol [Numeric] tolerences to cut off the loop
7
+ # @param nstart [Array<Numeric>] starting hub values for the nodes
8
+ #
9
+ # @return [Array<Numeric, Numeric>] hits and authority scores
10
+ def self.hits(graph, max_iter = 100, tol = 1e-8, nstart)
11
+ return [{}, {}] if graph.nodes.empty?
12
+
13
+ h = nstart
14
+ sum = h.values.sum
15
+ h.each_key { |k| h[k] /= (sum * 1.0) }
16
+ i = 0
17
+ a = {}
18
+
19
+ loop do
20
+ hlast = Marshal.load(Marshal.dump(h))
21
+ h, a = {}, {}
22
+ hlast.each do |k, _v|
23
+ h[k] = 0
24
+ a[k] = 0
25
+ end
26
+ h.each_key { |k| graph.adj[k].each { |nbr, attrs| a[k] += hlast[nbr] * (attrs[:weight] || 1) } }
27
+ h.each_key { |k| graph.adj[k].each { |nbr, attrs| h[k] += a[nbr] * (attrs[:weight] || 1) } }
28
+ smax = h.values.max
29
+ h.each_key { |k| h[k] /= smax }
30
+ smax = a.values.max
31
+ a.each_key { |k| a[k] /= smax }
32
+ break if h.keys.map { |k| (h[k] - hlast[k]).abs }.sum < tol
33
+ raise ArgumentError, 'Power Iteration failed to converge!' if i > max_iter
34
+
35
+ i += 1
36
+ end
37
+ [h, a]
38
+ end
39
+
40
+ # Computes authority matrix for the graph
41
+ #
42
+ # @param graph [Graph, DiGraph] a graph
43
+ #
44
+ # @return [Matrix] authority matrix for the graph
45
+ def self.authority_matrix(graph)
46
+ matrix, = to_matrix(graph, 0)
47
+ matrix.transpose * matrix
48
+ end
49
+
50
+ # Computes hub matrix for the graph
51
+ #
52
+ # @param graph [Graph, DiGraph] a graph
53
+ #
54
+ # @return [Matrix] hub matrix for the graph
55
+ def self.hub_matrix(graph)
56
+ matrix, = to_matrix(graph, 0)
57
+ matrix * matrix.transpose
58
+ end
59
+ end
@@ -0,0 +1,89 @@
1
+ module NetworkX
2
+ # Computes pagerank values for the graph
3
+ #
4
+ # @param graph [Graph] a graph
5
+ # @param init [Array<Numeric>] initial pagerank values for the nodes
6
+ # @param alpha [Numeric] the alpha value to compute the pagerank
7
+ # @param eps [Numeric] tolerence to check for convergence
8
+ # @param max_iter [Integer] max iterations for the pagerank algorithm to run
9
+ #
10
+ # @return [Array<Numeric>] pagerank values of the graph
11
+ def self.pagerank(graph, init = nil, alpha = 0.85, eps = 1e-4, max_iter = 100)
12
+ dim = graph.nodes.length
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!' \
20
+ unless dim == init.length
21
+
22
+ matrix = []
23
+ elem_ind = {}
24
+ p = []
25
+ curr = init.values
26
+ init.keys.each_with_index { |n, i| elem_ind[n] = i }
27
+ graph.adj.each do |_u, u_edges|
28
+ adj_arr = Array.new(dim, 0)
29
+ u_edges.each do |v, _|
30
+ adj_arr[elem_ind[v]] = 1
31
+ end
32
+ matrix << adj_arr
33
+ end
34
+ (0..(dim - 1)).each do |i|
35
+ p[i] = []
36
+ (0..(dim - 1)).each { |j| p[i][j] = matrix[i][j] / (matrix[i].sum * 1.0) }
37
+ end
38
+
39
+ max_iter.times do |_|
40
+ prev = curr.clone
41
+ dim.times do |i|
42
+ ip = 0
43
+ dim.times { |j| ip += p.transpose[i][j] * prev[j] }
44
+ curr[i] = (alpha * ip) + ((1 - alpha) / (dim * 1.0))
45
+ end
46
+ err = 0
47
+ dim.times { |i| err += (prev[i] - curr[i]).abs }
48
+ return curr if err < eps
49
+ end
50
+ raise ArgumentError, 'PageRank failed to converge!'
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
89
+ end