rugraph 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. checksums.yaml +7 -0
  2. data/lib/rugraph.rb +372 -0
  3. metadata +60 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: aadae7e823b0900d0b3adf9e3dc2d31ce35418c1
4
+ data.tar.gz: 14550cb1699d8f55b4d2fdcd2492f313582c85f7
5
+ SHA512:
6
+ metadata.gz: add9e065cedb6518b27b801f6cf40b58d75af79c9ffaa44283b0099ed13284c452ea36416c1c5acf0292975ae27e563902208abd16da005ad330fdd935609c47
7
+ data.tar.gz: dfc8d3a96890dcae09c70712dd5cecae1b47715d680f0119c317a6afcafd5dd6081adb52ad9fd6049fa44b28a792a4438d934a66f0efd075d99e440aa122317d
data/lib/rugraph.rb ADDED
@@ -0,0 +1,372 @@
1
+ require 'byebug'
2
+ # Rugraph class representing a Directed graph.
3
+ class Rugraph
4
+ # @return [Rugraph] an empty graph.
5
+ def initialize
6
+ @structure = {}
7
+ end
8
+
9
+ # Creates graph from describing hash. See {#load_from_vertices_hash}
10
+ #
11
+ # @param (see #load_from_vertices_hash)
12
+ # @return [Rugraph] the generated graph
13
+ def self.new_from_vertices_hash(vertices_hash)
14
+ new.load_from_vertices_hash(vertices_hash)
15
+ end
16
+
17
+ # Creates graph from describing array. See {#load_from_edges_array}
18
+ #
19
+ # @param (see #load_from_edges_array)
20
+ # @return [Rugraph] the generated graph
21
+ def self.new_from_edges_array(edges_array)
22
+ new.load_from_edges_array(edges_array)
23
+ end
24
+
25
+ # Adds vertices and edges to the graph from a hash [Hash] formatted
26
+ # as such
27
+ # { node1 => neighboring_nodes_array, node2 => ... }
28
+ #
29
+ # @param vertices_hash [Hash] a hash describing the Rugraph
30
+ # @return [Rugraph] the graph
31
+ def load_from_vertices_hash(vertices_hash)
32
+ @structure = vertices_hash
33
+ self
34
+ end
35
+
36
+ # Adds vertices and edges to the graph from an array [Array] formatted
37
+ # as such
38
+ # [[source_node, destination] [source_node2, destination2], ...]
39
+ #
40
+ # @param edges_array [Array] an array describing the Rugraph
41
+ # @return [Rugraph] the graph
42
+ def load_from_edges_array(edges_array)
43
+ @structure = edges_array
44
+ .each_with_object({}) do |edge, nodes|
45
+ nodes[edge[0]] = [] unless nodes.keys.include? edge[0]
46
+ nodes[edge[0]] << edge[1]
47
+ end
48
+ self
49
+ end
50
+
51
+ # Adds a vertex to the graph. If the vertex already exists, do nothing.
52
+ #
53
+ # @param node [Object] the value of the node to add to the graph
54
+ # @return [Rugraph] the graph
55
+ def add_vertex(node)
56
+ @structure[node] ||= []
57
+ end
58
+
59
+ # (see #add_vertex)
60
+ def add_node(node)
61
+ add_vertex(node)
62
+ end
63
+
64
+ # Adds an edge to the graph. If the vertex already exists, do nothing.
65
+ #
66
+ # @param edge [Array] an array formatted as such [source, dest]
67
+ # @return [Rugraph] the graph
68
+ def add_edge(edge)
69
+ edge.each { |node| add_vertex(node) }
70
+ @structure[edge[0]] << edge[1]
71
+ end
72
+
73
+ # Method returning the neighbors of a node.
74
+ #
75
+ # @param node [Object] the value corresponding to a node in the graph
76
+ # @return [Array, nil] an array of its neighbors if the node exists,
77
+ # nil otherwise
78
+ def neighbors_of(node)
79
+ return nil unless nodes.include?(node)
80
+ @structure[node] || []
81
+ end
82
+
83
+ # Computes the shortest path lengths from a node to all other
84
+ # accessible nodes.
85
+ #
86
+ # @param source [Object] the value of the source node.
87
+ # @return [Hash] a hash of the lengths of shortest path to each other node.
88
+ def shortest_path_lengths(source)
89
+ seen = {}
90
+ level = 0
91
+ nextlevel = { source => 1 }
92
+ until nextlevel.empty?
93
+ thislevel = nextlevel
94
+ nextlevel = {}
95
+ thislevel.each do |v, _|
96
+ if seen[v].nil?
97
+ seen[v] = level
98
+ nextlevel.update(neighbors_of(v).map { |w| [w, nil] }.to_h)
99
+ end
100
+ end
101
+ level += 1
102
+ end
103
+ seen
104
+ end
105
+
106
+ # Computes the shortest path a node.
107
+ #
108
+ # @param source [Object] the value of the source node.
109
+ # @return [Array] an array [pred, seen] containing two hashes :
110
+ # - pred [Hash] a hash of the shortest path to each node
111
+ # - seen [Hash] a hash of the lengths of the shortest path to each node
112
+ def shortest_paths(source)
113
+ level = 0
114
+ nextlevel = [source]
115
+ seen = { source => level }
116
+ pred = { source => [] }
117
+ until nextlevel.empty?
118
+ level += 1
119
+ thislevel = nextlevel
120
+ nextlevel = []
121
+ thislevel.each do |v|
122
+ neighbors_of(v).each do |w|
123
+ next if (seen.keys.include? w) && (seen[w] != level)
124
+ unless seen.keys.include? w
125
+ pred[w] = []
126
+ seen[w] = level
127
+ nextlevel << w
128
+ end
129
+ pred[w] << v
130
+ end
131
+ end
132
+ end
133
+ [pred, seen]
134
+ end
135
+
136
+ # Returns the edges around a node.
137
+ #
138
+ # @params node [Object]
139
+ # @return [Array] an array of the edges.
140
+ def edges_of(node)
141
+ edges_from(node) | edges_to(node)
142
+ end
143
+
144
+ # Returns the edges coming out of a node.
145
+ #
146
+ # @params node [Object]
147
+ # @return [Array] an array of the edges.
148
+ def edges_from(node)
149
+ self[node].map { |n| [node, n] }
150
+ end
151
+
152
+ # Returns the edges coming from a node.
153
+ #
154
+ # @params node [Object]
155
+ # @return [Array] an array of the edges.
156
+ def edges_to(node)
157
+ edges.select { |edge| edge.last == node }
158
+ end
159
+
160
+ # Computes the degree of each node.
161
+ #
162
+ # @return [Enumerator] an enumerator of the degrees of all nodes
163
+ def degrees
164
+ Enumerator.new(@structure.length) do |y|
165
+ nodes.each do |node|
166
+ y << [node, degree(node)]
167
+ end
168
+ end
169
+ end
170
+
171
+ # Computes the degree of a node.
172
+ #
173
+ # @param node [Object] the value of a node.
174
+ # @return [Integer] the degree of the node.
175
+ def degree(node)
176
+ in_degree(node) + out_degree(node)
177
+ end
178
+
179
+ # Computes the incoming degree of a node.
180
+ #
181
+ # @param (see #degree)
182
+ # @return (see #degree)
183
+ def in_degree(node)
184
+ self[node].length
185
+ end
186
+
187
+ # Computes the outcoming degree of a node.
188
+ #
189
+ # @param (see #degree)
190
+ # @return (see #degree)
191
+ def out_degree(node)
192
+ @structure.count { |_source, dest| dest.include? node }
193
+ end
194
+
195
+ # Computes the order (number of nodes) of the graph.
196
+ #
197
+ # @return [Integer] the order of the graph.
198
+ def order
199
+ nodes.length
200
+ end
201
+
202
+ # Returns the nodes in the graph
203
+ #
204
+ # @return [Array] the values of all the nodes in the graph.
205
+ def vertices
206
+ (@structure.keys | @structure.values).flatten.uniq
207
+ end
208
+
209
+ # (see #vertices)
210
+ def nodes
211
+ vertices
212
+ end
213
+
214
+ # Returns the list of all the edges in the graph.
215
+ #
216
+ # @return [Array] an array of pairs of nodes.
217
+ def edges
218
+ @structure.reduce([]) do |acc, node|
219
+ acc + node[1].map { |dest| [node[0], dest] }
220
+ end
221
+ end
222
+
223
+ # Equivalent to {#neighbors_of}
224
+ #
225
+ # @return (see #neighbors_of)
226
+ def [](node)
227
+ neighbors_of(node)
228
+ end
229
+
230
+ # Sets the edges coming out of a node.
231
+ #
232
+ # @params node_array [Array] an array of nodes
233
+ # @params node [node] the concerned node
234
+ # @return the new neighbors of the node
235
+ def []=(node, node_array)
236
+ @structure[node] = node_array
237
+ end
238
+
239
+ # Equivalent to #{add_vertex}.
240
+ #
241
+ # @params (see #add_vertex)
242
+ # @return (see #add_vertex)
243
+ def <<(node)
244
+ add_vertex(node)
245
+ end
246
+
247
+
248
+ # Calculates the betweenness centrality for each node.
249
+ #
250
+ # Based on Brandes algorithm :
251
+ # http://algo.uni-konstanz.de/publications/b-fabc-01.pdf
252
+ #
253
+ # @param graph [Rugraph] a graph
254
+ # @return [Hash] a hash with the nodes as keys and their centrality as values
255
+ def self.betweenness_centrality(graph)
256
+ cb = graph.vertices.map { |v| [v, 0] }.to_h
257
+ graph.vertices.each do |source|
258
+ stack = []
259
+ path = Hash.new { [] }
260
+ sig = Hash.new(0)
261
+ sig[source] = 1.0
262
+ distance = {}
263
+ distance[source] = 0
264
+ queue = []
265
+ queue << source
266
+
267
+ until queue.empty?
268
+ v = queue.shift
269
+ stack << v
270
+
271
+ graph.neighbors_of(v).each do |w|
272
+ unless distance.keys.include? w
273
+ queue << w
274
+ distance[w] = distance[v] + 1
275
+ end
276
+ if distance[w] == distance[v] + 1
277
+ sig[w] += sig[v]
278
+ path[w] += [v]
279
+ end
280
+ end
281
+ end
282
+
283
+ delta = Hash.new(0)
284
+ until stack.empty?
285
+ w = stack.pop
286
+ coeff = (1.0 + delta[w]) / sig[w].to_f
287
+ path[w].each do |n|
288
+ delta[n] += sig[n] * coeff
289
+ end
290
+ cb[w] += delta[w] if w != source
291
+ end
292
+ end
293
+
294
+ cb
295
+ end
296
+
297
+ # Calculate the closeness centrality for each node.
298
+ #
299
+ # @param (see #betweenness_centrality)
300
+ # @return (see #betweenness_centrality)
301
+ def self.closeness_centrality(graph)
302
+ closeness_centrality = {}
303
+
304
+ graph.vertices.each do |n|
305
+ shortest_paths = graph.shortest_path_lengths(n)
306
+ sum = shortest_paths.values.reduce(&:+)
307
+ closeness_centrality[n] = 0.0
308
+ next unless (sum > 0) && (graph.order > 1)
309
+ closeness_centrality[n] = (shortest_paths.length - 1) / sum.to_f
310
+ s = (shortest_paths.length - 1).to_f / (graph.order - 1)
311
+ closeness_centrality[n] *= s
312
+ end
313
+
314
+ closeness_centrality
315
+ end
316
+
317
+ # Calculate the degree centrality for each node.
318
+ #
319
+ # @param (see #betweenness_centrality)
320
+ # @return (see #betweenness_centrality)
321
+ def self.degree_centrality(graph)
322
+ s = 1.0 / (graph.order - 1)
323
+ graph.degrees
324
+ .map { |n, d| [n, d * s] }
325
+ .to_h
326
+ end
327
+
328
+ # Calculate the load centrality for each node.
329
+ #
330
+ # Based on NetworkX's Python implementation.
331
+ #
332
+ # @param (see #betweenness_centrality)
333
+ # @return (see #betweenness_centrality)
334
+ def self.load_centrality(graph)
335
+ betweenness = graph.vertices
336
+ .map { |n| [n, 0.0] }
337
+ .to_h
338
+ betweenness.each do |source, _|
339
+ ubetween = _node_betweenness(graph, source)
340
+ betweenness.merge(ubetween) do |key, v1, v2|
341
+ betweenness[key] = v1 + v2
342
+ end
343
+ end
344
+ end
345
+
346
+ def self._node_betweenness(graph, source)
347
+ (pred, between) = graph.shortest_paths(source)
348
+
349
+ ordered = _ordered_nodes_from_paths(between)
350
+ between.each { |k, _| between[k] = 1.0 }
351
+
352
+ until ordered.empty?
353
+ v = ordered.pop
354
+ next unless pred.include? v
355
+ pred[v].each do |x|
356
+ break if x == source
357
+ between[x] += between[v] / pred[v].size.to_f
358
+ end
359
+ end
360
+
361
+ between.each { |n, _| between[n] -= 1 }
362
+ end
363
+
364
+ def self._ordered_nodes_from_paths(paths)
365
+ paths.to_a
366
+ .sort { |a, b| a.last <=> b.last }
367
+ .map(&:first)
368
+ .flatten
369
+ end
370
+ private_class_method :_node_betweenness
371
+ private_class_method :_ordered_nodes_from_paths
372
+ end
metadata ADDED
@@ -0,0 +1,60 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rugraph
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.4.0
5
+ platform: ruby
6
+ authors:
7
+ - Jérémie Bonal
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-05-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: A graph library implementing basic directed graph functions and some
28
+ algorithms.
29
+ email: jeremie.bonal@gmail.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - lib/rugraph.rb
35
+ homepage: http://rubygems.org/gems/rugraph
36
+ licenses:
37
+ - MIT
38
+ metadata: {}
39
+ post_install_message:
40
+ rdoc_options: []
41
+ require_paths:
42
+ - lib
43
+ required_ruby_version: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ required_rubygems_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ requirements: []
54
+ rubyforge_project:
55
+ rubygems_version: 2.6.1
56
+ signing_key:
57
+ specification_version: 4
58
+ summary: A graph library for Ruby.
59
+ test_files: []
60
+ has_rdoc: