rugraph 0.4.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 (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: