nose 0.1.0pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/lib/nose/backend/cassandra.rb +390 -0
  3. data/lib/nose/backend/file.rb +185 -0
  4. data/lib/nose/backend/mongo.rb +242 -0
  5. data/lib/nose/backend.rb +557 -0
  6. data/lib/nose/cost/cassandra.rb +33 -0
  7. data/lib/nose/cost/entity_count.rb +27 -0
  8. data/lib/nose/cost/field_size.rb +31 -0
  9. data/lib/nose/cost/request_count.rb +32 -0
  10. data/lib/nose/cost.rb +68 -0
  11. data/lib/nose/debug.rb +45 -0
  12. data/lib/nose/enumerator.rb +199 -0
  13. data/lib/nose/indexes.rb +239 -0
  14. data/lib/nose/loader/csv.rb +99 -0
  15. data/lib/nose/loader/mysql.rb +199 -0
  16. data/lib/nose/loader/random.rb +48 -0
  17. data/lib/nose/loader/sql.rb +105 -0
  18. data/lib/nose/loader.rb +38 -0
  19. data/lib/nose/model/entity.rb +136 -0
  20. data/lib/nose/model/fields.rb +293 -0
  21. data/lib/nose/model.rb +113 -0
  22. data/lib/nose/parser.rb +202 -0
  23. data/lib/nose/plans/execution_plan.rb +282 -0
  24. data/lib/nose/plans/filter.rb +99 -0
  25. data/lib/nose/plans/index_lookup.rb +302 -0
  26. data/lib/nose/plans/limit.rb +42 -0
  27. data/lib/nose/plans/query_planner.rb +361 -0
  28. data/lib/nose/plans/sort.rb +49 -0
  29. data/lib/nose/plans/update.rb +60 -0
  30. data/lib/nose/plans/update_planner.rb +270 -0
  31. data/lib/nose/plans.rb +135 -0
  32. data/lib/nose/proxy/mysql.rb +275 -0
  33. data/lib/nose/proxy.rb +102 -0
  34. data/lib/nose/query_graph.rb +481 -0
  35. data/lib/nose/random/barbasi_albert.rb +48 -0
  36. data/lib/nose/random/watts_strogatz.rb +50 -0
  37. data/lib/nose/random.rb +391 -0
  38. data/lib/nose/schema.rb +89 -0
  39. data/lib/nose/search/constraints.rb +143 -0
  40. data/lib/nose/search/problem.rb +328 -0
  41. data/lib/nose/search/results.rb +200 -0
  42. data/lib/nose/search.rb +266 -0
  43. data/lib/nose/serialize.rb +747 -0
  44. data/lib/nose/statements/connection.rb +160 -0
  45. data/lib/nose/statements/delete.rb +83 -0
  46. data/lib/nose/statements/insert.rb +146 -0
  47. data/lib/nose/statements/query.rb +161 -0
  48. data/lib/nose/statements/update.rb +101 -0
  49. data/lib/nose/statements.rb +645 -0
  50. data/lib/nose/timing.rb +79 -0
  51. data/lib/nose/util.rb +305 -0
  52. data/lib/nose/workload.rb +244 -0
  53. data/lib/nose.rb +37 -0
  54. data/templates/workload.erb +42 -0
  55. metadata +700 -0
data/lib/nose/proxy.rb ADDED
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NoSE
4
+ # Query processing proxies to transparently execute queries against a backend
5
+ module Proxy
6
+ # A proxy server to interpret our query language and implement query plans
7
+ class ProxyBase
8
+ attr_reader :logger
9
+ def initialize(config, result, backend)
10
+ @logger = Logging.logger['nose::proxy']
11
+
12
+ @result = result
13
+ @backend = backend
14
+ @config = config
15
+
16
+ @continue = true
17
+ end
18
+
19
+ # Start the proxy server
20
+ # @return [void]
21
+ def start
22
+ @logger.info "Starting server on port #{@config[:port]}"
23
+
24
+ server_socket = TCPServer.new('127.0.0.1', @config[:port])
25
+ server_socket.listen(100)
26
+
27
+ @read_sockets = [server_socket]
28
+ @write_sockets = []
29
+ loop do
30
+ break unless @continue && select_connection(server_socket)
31
+ end
32
+ end
33
+
34
+ # @abstract Subclasses should process a new connection
35
+ # on the given socket
36
+ # :nocov:
37
+ # @return [void]
38
+ def handle_connection(_socket)
39
+ fail NotImplementedError
40
+ end
41
+ # :nocov:
42
+
43
+ # @abstract Subclasses should dispose of state associated with the socket
44
+ # :nocov:
45
+ # @return [void]
46
+ def remove_connection(_socket)
47
+ fail NotImplementedError
48
+ end
49
+ # :nocov:
50
+
51
+ # Stop accepting connections
52
+ # @return [void]
53
+ def stop
54
+ @continue = false
55
+ end
56
+
57
+ private
58
+
59
+ # Select sockets which are available to be processed
60
+ # @return [void]
61
+ def select_connection(server_socket)
62
+ read, write, error = IO.select @read_sockets, @write_sockets,
63
+ @read_sockets + @write_sockets, 5
64
+ return true if read.nil?
65
+
66
+ # Check if we have a new incoming connection
67
+ if read.include? server_socket
68
+ accept_connection server_socket
69
+ read.delete server_socket
70
+ elsif error.include? server_socket
71
+ @logger.error 'Server socket died'
72
+ return false
73
+ end
74
+
75
+ # Remove all sockets which have errors
76
+ error.each { |socket| remove_connection socket }
77
+ @read_sockets -= error
78
+ @write_sockets -= error
79
+
80
+ # Handle connections on each available socket
81
+ process_connections read + write
82
+ end
83
+
84
+ # Accept the new connection
85
+ # @return [void]
86
+ def accept_connection(server_socket)
87
+ client_socket, = server_socket.accept
88
+ @read_sockets << client_socket
89
+ @write_sockets << client_socket
90
+ end
91
+
92
+ # Process all pending connections
93
+ # @return [void]
94
+ def process_connections(sockets)
95
+ sockets.each do |socket|
96
+ @write_sockets.delete socket
97
+ @read_sockets.delete socket unless handle_connection socket
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,481 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+
5
+ module NoSE
6
+ # Representations for connected entities which are referenced in a {Query}
7
+ module QueryGraph
8
+ # A single node in a query graph
9
+ class Node
10
+ attr_reader :entity
11
+
12
+ def initialize(entity)
13
+ @entity = entity
14
+ end
15
+
16
+ # :nocov:
17
+ def inspect
18
+ @entity.name
19
+ end
20
+ # :nocov:
21
+
22
+ # Two nodes are equal if they represent the same entity
23
+ def ==(other)
24
+ other.is_a?(Node) && @entity == other.entity
25
+ end
26
+ alias eql? ==
27
+
28
+ def hash
29
+ @entity.name.hash
30
+ end
31
+ end
32
+
33
+ # An edge between two {Node}s in a {Graph}
34
+ class Edge
35
+ attr_reader :from, :to, :key
36
+
37
+ def initialize(from, to, key)
38
+ @from = from
39
+ @to = to
40
+ @key = key
41
+ end
42
+
43
+ # :nocov:
44
+ def inspect
45
+ @key.inspect
46
+ end
47
+ # :nocov:
48
+
49
+ # Edges are equal if the canonical parameters used to construct
50
+ # them are the same
51
+ def ==(other)
52
+ return false unless other.is_a? Edge
53
+ canonical_params == other.canonical_params
54
+ end
55
+ alias eql? ==
56
+
57
+ def hash
58
+ canonical_params.hash
59
+ end
60
+
61
+ # Produce the parameters to initialize the canonical version of this edge
62
+ # (this accounts for different directionality of edges)
63
+ # @return [Array]
64
+ def canonical_params
65
+ if @from.entity.name > @to.entity.name
66
+ [@from.entity.name, @to.entity.name, @key.name]
67
+ else
68
+ [@to.entity.name, @from.entity.name, @key.reverse.name]
69
+ end
70
+ end
71
+ end
72
+
73
+ # A graph identifying entities and relationships involved in a query
74
+ class Graph
75
+ attr_reader :nodes, :entities
76
+
77
+ def initialize(nodes = [], *edges)
78
+ @nodes = Set.new
79
+ @entities = Set.new
80
+ nodes.each { |n| add_node n }
81
+ @edges = {}
82
+
83
+ edges.each { |edge| add_edge(*edge) }
84
+ end
85
+
86
+ # :nocov:
87
+ def inspect
88
+ "Graph(nodes: #{@nodes.map(&:inspect).join(', ')}, " \
89
+ "edges: #{@edges.inspect})"
90
+ end
91
+ # :nocov:
92
+
93
+ # Graphs are equal if they have the same nodes and edges
94
+ def ==(other)
95
+ return false unless other.is_a? Graph
96
+ @nodes == other.nodes && unique_edges == other.unique_edges
97
+ end
98
+ alias eql? ==
99
+
100
+ def hash
101
+ [@nodes, unique_edges.map(&:canonical_params).sort!].hash
102
+ end
103
+
104
+ # The total number of nodes in the graph
105
+ # @return [Integer]
106
+ def size
107
+ @nodes.size
108
+ end
109
+
110
+ # Check if the graph is empty
111
+ # @return [Boolean]
112
+ def empty?
113
+ @nodes.empty?
114
+ end
115
+
116
+ # Duplicate graphs ensuring that edges are correctly copied
117
+ # @return [Graph]
118
+ def dup
119
+ new_graph = super
120
+
121
+ new_graph.instance_variable_set :@nodes, @nodes.dup
122
+ new_edges = Hash[@edges.map do |k, v|
123
+ [k, v.dup]
124
+ end]
125
+ new_graph.instance_variable_set :@edges, new_edges
126
+ new_graph.instance_variable_set :@unique_edges, nil
127
+
128
+ new_graph
129
+ end
130
+
131
+ # Produce an array of entities in the desired join order
132
+ # @return [Array<Entity>]
133
+ def join_order(eq_fields)
134
+ return [@nodes.first.entity] if @nodes.size == 1
135
+
136
+ # Start with a leaf entity which has an equality predicate
137
+ # and the lowest overall count of all such entities
138
+ entities = @entities.dup
139
+ leaf_entities = entities.select { |e| leaf_entity?(e) }
140
+ join_order = [leaf_entities.select do |entity|
141
+ eq_fields.map(&:parent).include?(entity)
142
+ end.min_by(&:count)].compact
143
+ join_order = [leaf_entities.min_by(&:count)] if join_order.empty?
144
+ entities.delete join_order.first
145
+
146
+ # Keep going until we have joined all entities
147
+ until entities.empty?
148
+ # Try to continue from the last entity
149
+ next_entities = edges_for_entity(join_order.last).map do |edge|
150
+ edge.to.entity
151
+ end.to_set
152
+
153
+ # Otherwise look for a new branch from the existing entities
154
+ if (next_entities & entities).empty?
155
+ next_entities = join_order.reduce(Set.new) do |edges, entity|
156
+ edges.union(edges_for_entity(entity))
157
+ end.map { |edge| edge.to.entity }.to_set
158
+ end
159
+
160
+ # Pick the entity with the smallest count, remove it, and keep going
161
+ next_entity = (next_entities & entities).min_by(&:count)
162
+ join_order << next_entity
163
+ entities.delete next_entity
164
+ end
165
+
166
+ join_order
167
+ end
168
+
169
+ # Find the node corresponding to a given entity in the graph
170
+ # @return [Node]
171
+ def entity_node(entity)
172
+ @nodes.find { |n| n.entity == entity }
173
+ end
174
+
175
+ # Check if the graph includes the given entity
176
+ # @return [Boolean]
177
+ def include_entity?(entity)
178
+ !entity_node(entity).nil?
179
+ end
180
+
181
+ # Check if this entity is a leaf in the graph (at most one edge)
182
+ # @return [Boolean]
183
+ def leaf_entity?(entity)
184
+ node = entity_node(entity)
185
+ return false if node.nil?
186
+ !@edges.key?(node) || @edges[node].size <= 1
187
+ end
188
+
189
+ # Produce a path in the graph between two nodes
190
+ # @return [KeyPath]
191
+ def path_between(node1, node2)
192
+ node1 = find_entity_node node1
193
+ node2 = find_entity_node node2
194
+ keys = path_between_visit [node1.entity.id_field], [node1], node2
195
+
196
+ KeyPath.new keys
197
+ end
198
+
199
+ # Find the node which corresponds to a given entity
200
+ def find_entity_node(entity)
201
+ @nodes.find { |n| n.entity == entity }
202
+ end
203
+
204
+ # Add a new node to the graph
205
+ # @return [Node] the new node which was added
206
+ def add_node(node)
207
+ if node.is_a? Entity
208
+ existing = find_entity_node node
209
+ if existing.nil?
210
+ node = Node.new(node)
211
+ @nodes.add node
212
+ @entities.add node.entity
213
+ else
214
+ node = existing
215
+ end
216
+ elsif !@nodes.include? node
217
+ @nodes.add node
218
+ @entities.add node.entity
219
+ end
220
+
221
+ node
222
+ end
223
+
224
+ # Add a new edge betwene two nodes in the graph
225
+ # @return [void]
226
+ def add_edge(node1, node2, key)
227
+ node1 = add_node node1
228
+ node2 = add_node node2
229
+
230
+ @edges[node1] = Set.new unless @edges.key? node1
231
+ @edges[node1].add Edge.new(node1, node2, key)
232
+ @edges[node2] = Set.new unless @edges.key? node2
233
+ @edges[node2].add Edge.new(node2, node1, key.reverse)
234
+
235
+ @unique_edges = nil
236
+ end
237
+
238
+ # Prune nodes not reachable from a given starting node
239
+ # @return [void]
240
+ def prune(start)
241
+ to_visit = [start]
242
+ reachable = Set.new([start])
243
+
244
+ # Determine which nodes are reachable
245
+ until to_visit.empty?
246
+ discovered = Set.new
247
+ to_visit.each do |node|
248
+ next unless @edges.key? node
249
+ discovered += @edges[node].map(&:to).to_set
250
+ end
251
+ to_visit = discovered - reachable
252
+ reachable += discovered
253
+ end
254
+
255
+ remove_nodes @nodes - reachable
256
+ end
257
+
258
+ # Remove nodes (or entities) from the graph
259
+ def remove_nodes(nodes)
260
+ # Find all the nodes to be removed if needed
261
+ nodes.map! { |n| n.is_a?(Node) ? n : find_entity_node(n) }
262
+
263
+ # Remove any nodes and edges which are not reachable
264
+ @edges.reject! { |node| nodes.include? node }
265
+ @edges.each do |_, edges|
266
+ edges.reject! do |edge|
267
+ nodes.include?(edge.to) || nodes.include?(edge.from)
268
+ end
269
+ end
270
+ @edges.reject! { |_, edges| edges.empty? }
271
+ @nodes -= nodes.to_set
272
+ @entities -= nodes.map(&:entity)
273
+
274
+ @unique_edges = nil
275
+ end
276
+
277
+ # Construct a list of all unique edges in the graph
278
+ # @reutrn [Array<Edge>]
279
+ def unique_edges
280
+ # We memoize this calculation so check if it has already been computed
281
+ return @unique_edges unless @unique_edges.nil?
282
+
283
+ all_edges = @edges.values.reduce(&:union).to_a
284
+ all_edges.uniq!(&:canonical_params)
285
+
286
+ @unique_edges = all_edges.to_set
287
+ end
288
+
289
+ # Produce an enumerator which yields all subgraphs of this graph
290
+ # @return [Set<Graph>]
291
+ def subgraphs(recursive = true)
292
+ # We have no subgraphs if there is only one node
293
+ return [self] if @nodes.size == 1
294
+
295
+ all_edges = unique_edges
296
+ all_subgraphs = Set.new([self])
297
+ all_edges.each do |remove_edge|
298
+ # Construct new graphs from either side of the cut edge
299
+ graph1 = Graph.new [remove_edge.from]
300
+ graph2 = Graph.new [remove_edge.to]
301
+ all_edges.each do |edge|
302
+ next if edge == remove_edge
303
+
304
+ graph1.add_edge edge.from, edge.to, edge.key
305
+ graph2.add_edge edge.from, edge.to, edge.key
306
+ end
307
+
308
+ # Prune the graphs before yielding them and their subgraphs
309
+ graph1.prune remove_edge.from
310
+ all_subgraphs.add graph1
311
+ all_subgraphs += graph1.subgraphs if recursive
312
+
313
+ graph2.prune remove_edge.to
314
+ all_subgraphs.add graph2
315
+ all_subgraphs += graph2.subgraphs if recursive
316
+ end
317
+
318
+ all_subgraphs.to_set
319
+ end
320
+
321
+ # Construct a graph from a KeyPath
322
+ # @return [Graph]
323
+ def self.from_path(path)
324
+ return Graph.new if path.empty?
325
+
326
+ path = path.entries if path.is_a?(KeyPath)
327
+ graph = Graph.new
328
+ prev_node = graph.add_node path.first.parent
329
+ path[1..-1].each do |key|
330
+ next_node = graph.add_node key.entity
331
+ graph.add_edge prev_node, next_node, key
332
+ prev_node = next_node
333
+ end
334
+
335
+ graph
336
+ end
337
+
338
+ # Convert this graph into a path if possible
339
+ # @return [KeyPath]
340
+ # @raise [InvalidPathException]
341
+ def to_path(start_entity)
342
+ return KeyPath.new if @nodes.empty?
343
+
344
+ start = @nodes.find { |n| n.entity == start_entity }
345
+
346
+ fail InvalidPathException, 'Need start for path conversion' \
347
+ if start.nil?
348
+ keys = [start.entity.id_field]
349
+ entities = Set.new [start.entity]
350
+
351
+ edges = edges_for_entity start.entity
352
+ until edges.empty?
353
+ new_entities = edges.map { |e| e.to.entity }.to_set.delete_if do |n|
354
+ entities.include?(n)
355
+ end
356
+ break if new_entities.empty?
357
+ fail InvalidPathException, 'Graph cannot be converted to path' \
358
+ if new_entities.size > 1
359
+ edge = edges.find { |e| !entities.include? e.to.entity }
360
+ keys << edge.key
361
+ entities.add edge.to.entity
362
+ edges = edges_for_entity edge.to.entity
363
+ end
364
+
365
+ KeyPath.new keys
366
+ end
367
+
368
+ # Produce a path through the graph of maximum length
369
+ # @return [KeyPath]
370
+ def longest_path
371
+ return KeyPath.new [@nodes.first.entity.id_field] if @nodes.size == 1
372
+
373
+ longest_path = []
374
+ @nodes.each do |node|
375
+ next unless leaf_entity?(node.entity)
376
+
377
+ longest_path = longest_path_visit node, Set.new([node]), [],
378
+ longest_path
379
+ end
380
+
381
+ KeyPath.new [longest_path.first.from.entity.id_field] +
382
+ longest_path.map(&:key)
383
+ end
384
+
385
+ # Output an image of the query graph
386
+ # @return [void]
387
+ def output(format, filename)
388
+ graph = GraphViz.new :G, type: :graph
389
+ nodes = Hash[@nodes.map do |node|
390
+ [node, graph.add_nodes(node.entity.name)]
391
+ end]
392
+
393
+ @edges.each do |_, edges|
394
+ edges.each do |edge|
395
+ graph.add_edges nodes[edge.from], nodes[edge.to],
396
+ label: edge.key.name
397
+ end
398
+ end
399
+
400
+ graph.output(**{ format => filename })
401
+ end
402
+
403
+ # Split this graph into multiple graphs at the given
404
+ # entity, optionally removing the corresponding node
405
+ # return [Array<Graph>]
406
+ def split(entity, keep = false)
407
+ # Simple case with one node
408
+ return keep ? [dup] : [] if size == 1
409
+
410
+ # Find the node corresponding to the entity to remove
411
+ remove_node = @nodes.find { |n| n.entity == entity }
412
+
413
+ # For each edge from this entity, build a new graph with
414
+ # the entity removed and explore the different paths
415
+ @edges[remove_node].map do |edge|
416
+ new_graph = dup
417
+ remove_nodes = (@edges[remove_node] - [edge]).map(&:to)
418
+ remove_nodes << remove_node unless keep
419
+ new_graph.remove_nodes remove_nodes
420
+ new_graph.prune edge.to
421
+
422
+ new_graph
423
+ end
424
+ end
425
+
426
+ # Produce the keys for all edges leaving the given entity
427
+ # @return [Array<Fields::ForeignKeyField>]
428
+ def keys_from_entity(entity)
429
+ edges_for_entity(entity).map(&:key)
430
+ end
431
+
432
+ private
433
+
434
+ # Helper to find the longest path in the graph starting at a given
435
+ # node and not exploring nodes which have been visited
436
+ # We keep track of the visited edges and update the longest path
437
+ # @return [Array<Edge>]
438
+ def longest_path_visit(node, visited_nodes, edges, longest_path)
439
+ # Find new edges we may want to traverse
440
+ new_edges = @edges[node].reject { |e| visited_nodes.include? e.to }
441
+
442
+ # If we reached the end of a path, see if we found a longer one
443
+ if new_edges.empty?
444
+ return edges.length > longest_path.length ? edges : longest_path
445
+ end
446
+
447
+ new_edges.each do |edge|
448
+ longest_path = longest_path_visit edge.to,
449
+ visited_nodes.dup.add(edge.to),
450
+ edges.dup << edge, longest_path
451
+ end
452
+
453
+ longest_path
454
+ end
455
+
456
+ # Return all the edges starting at a given entity
457
+ # @return [Array<Edge>]
458
+ def edges_for_entity(entity)
459
+ pair = @edges.find { |n, _| n.entity == entity }
460
+ pair.nil? ? Set.new : pair.last
461
+ end
462
+
463
+ # Helper for path_between to recursively visit nodes in the graph
464
+ # @return [void]
465
+ def path_between_visit(keys, nodes, end_node)
466
+ return keys if nodes.last == end_node
467
+ return nil unless @edges.key? nodes.last
468
+
469
+ @edges[nodes.last].lazy.map do |edge|
470
+ next if nodes.include? edge.to
471
+ path_between_visit keys.dup << edge.key,
472
+ nodes.dup << edge.to, end_node
473
+ end.reject(&:nil?).first
474
+ end
475
+ end
476
+
477
+ # Thrown when trying to convert a graph which is not a path
478
+ class InvalidPathException < StandardError
479
+ end
480
+ end
481
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NoSE
4
+ module Random
5
+ # Generates a random graph using the Barbasi-Albert model
6
+ class BarbasiAlbertNetwork < Network
7
+ def initialize(params = {})
8
+ super params
9
+
10
+ # We assume for now that m0 = m = 2
11
+
12
+ create_entities
13
+ add_foreign_keys
14
+ end
15
+
16
+ private
17
+
18
+ # Create all the entities in the graph and their connections
19
+ # @return [void]
20
+ def create_entities
21
+ # Add the initial one or two entities
22
+ @entities = [create_entity(0)]
23
+ return if @nodes_nb == 1
24
+
25
+ @entities << create_entity(1)
26
+ add_link 0, 1
27
+ return if @nodes_nb == 2
28
+
29
+ @entities << create_entity(2)
30
+ add_link 2, 0
31
+ add_link 2, 1
32
+ return if @nodes_nb == 3
33
+
34
+ # Add and connect more entities as needed
35
+ 3.upto(@nodes_nb - 1).each do |node|
36
+ @entities << create_entity(node)
37
+ pick = Pickup.new(0.upto(node - 1).to_a,
38
+ key_func: ->(n) { n },
39
+ weight_func: ->(n) { @neighbours[n].size },
40
+ uniq: true)
41
+ pick.pick(2).each do |node2|
42
+ add_link node, node2
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NoSE
4
+ module Random
5
+ # Generates a random graph using the Watts-Strogatz model
6
+ class WattsStrogatzNetwork < Network
7
+ def initialize(params = {})
8
+ super params
9
+
10
+ @beta = params.fetch :beta, 0.5
11
+ @node_degree = params.fetch :node_degree, 2
12
+ @nodes = 0..(@nodes_nb - 1)
13
+
14
+ @entities = @nodes.map do |node|
15
+ create_entity node
16
+ end
17
+
18
+ build_initial_links
19
+ rewire_links
20
+ add_foreign_keys
21
+ end
22
+
23
+ private
24
+
25
+ # Set up the initial links between all nodes
26
+ # @return [void]
27
+ def build_initial_links
28
+ @nodes.each do |node|
29
+ (@node_degree / 2).times do |i|
30
+ add_link node, (node + i + 1) % @nodes_nb
31
+ end
32
+ end
33
+ end
34
+
35
+ # Rewire all links between nodes
36
+ # @return [void]
37
+ def rewire_links
38
+ (@node_degree / 2).times do |i|
39
+ @nodes.each do |node|
40
+ next unless rand < @beta
41
+
42
+ neighbour = (node + i + 1) % @nodes_nb
43
+ remove_link node, neighbour
44
+ add_link node, new_neighbour(node, neighbour)
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end