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