networkx 0.1.1 → 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} +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/.github/workflows/gem-push.yml +45 -0
  9. data/.rspec +0 -1
  10. data/.rubocop.yml +57 -71
  11. data/.yardopts +0 -1
  12. data/README.md +27 -27
  13. data/Rakefile +2 -3
  14. data/lib/networkx/auxillary_functions/cliques.rb +9 -12
  15. data/lib/networkx/auxillary_functions/cycles.rb +17 -7
  16. data/lib/networkx/auxillary_functions/dag.rb +10 -5
  17. data/lib/networkx/auxillary_functions/eccentricity.rb +2 -1
  18. data/lib/networkx/auxillary_functions/mis.rb +2 -2
  19. data/lib/networkx/auxillary_functions/mst.rb +1 -3
  20. data/lib/networkx/auxillary_functions/union_find.rb +92 -12
  21. data/lib/networkx/auxillary_functions/wiener.rb +1 -1
  22. data/lib/networkx/converters/to_csv.rb +1 -3
  23. data/lib/networkx/converters/to_json.rb +0 -2
  24. data/lib/networkx/digraph.rb +55 -49
  25. data/lib/networkx/flow/capacityscaling.rb +29 -35
  26. data/lib/networkx/flow/edmondskarp.rb +17 -15
  27. data/lib/networkx/flow/preflowpush.rb +29 -32
  28. data/lib/networkx/flow/shortestaugmentingpath.rb +17 -20
  29. data/lib/networkx/flow/utils.rb +6 -27
  30. data/lib/networkx/graph.rb +179 -72
  31. data/lib/networkx/link_analysis/hits.rb +9 -9
  32. data/lib/networkx/link_analysis/pagerank.rb +48 -6
  33. data/lib/networkx/multidigraph.rb +90 -81
  34. data/lib/networkx/multigraph.rb +91 -63
  35. data/lib/networkx/operators/all.rb +8 -4
  36. data/lib/networkx/operators/binary.rb +106 -128
  37. data/lib/networkx/operators/product.rb +61 -64
  38. data/lib/networkx/operators/unary.rb +1 -1
  39. data/lib/networkx/others/bridges.rb +30 -0
  40. data/lib/networkx/others/generators.rb +237 -0
  41. data/lib/networkx/others/grid_2d_graph.rb +38 -0
  42. data/lib/networkx/others/info.rb +11 -0
  43. data/lib/networkx/others/number_connected_components.rb +17 -0
  44. data/lib/networkx/others/reads.rb +52 -0
  45. data/lib/networkx/shortest_path/astar.rb +10 -8
  46. data/lib/networkx/shortest_path/dense.rb +1 -3
  47. data/lib/networkx/shortest_path/unweighted.rb +13 -16
  48. data/lib/networkx/shortest_path/weighted.rb +51 -42
  49. data/lib/networkx/to_matrix.rb +2 -3
  50. data/lib/networkx/traversals/bfs.rb +54 -2
  51. data/lib/networkx/traversals/dfs.rb +62 -6
  52. data/lib/networkx/traversals/edge_dfs.rb +36 -12
  53. data/lib/networkx/version.rb +1 -1
  54. data/lib/networkx.rb +7 -1
  55. data/networkx.gemspec +12 -13
  56. metadata +71 -81
  57. data/.rspec_formatter.rb +0 -24
  58. data/.travis.yml +0 -18
  59. data/Guardfile +0 -7
  60. 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,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
- raise ArgumentError, 'Init array needs to have same length as number of graph nodes!'\
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].inject(:+) * 1.0) }
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