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,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