nose 0.1.0pre
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/nose/backend/cassandra.rb +390 -0
- data/lib/nose/backend/file.rb +185 -0
- data/lib/nose/backend/mongo.rb +242 -0
- data/lib/nose/backend.rb +557 -0
- data/lib/nose/cost/cassandra.rb +33 -0
- data/lib/nose/cost/entity_count.rb +27 -0
- data/lib/nose/cost/field_size.rb +31 -0
- data/lib/nose/cost/request_count.rb +32 -0
- data/lib/nose/cost.rb +68 -0
- data/lib/nose/debug.rb +45 -0
- data/lib/nose/enumerator.rb +199 -0
- data/lib/nose/indexes.rb +239 -0
- data/lib/nose/loader/csv.rb +99 -0
- data/lib/nose/loader/mysql.rb +199 -0
- data/lib/nose/loader/random.rb +48 -0
- data/lib/nose/loader/sql.rb +105 -0
- data/lib/nose/loader.rb +38 -0
- data/lib/nose/model/entity.rb +136 -0
- data/lib/nose/model/fields.rb +293 -0
- data/lib/nose/model.rb +113 -0
- data/lib/nose/parser.rb +202 -0
- data/lib/nose/plans/execution_plan.rb +282 -0
- data/lib/nose/plans/filter.rb +99 -0
- data/lib/nose/plans/index_lookup.rb +302 -0
- data/lib/nose/plans/limit.rb +42 -0
- data/lib/nose/plans/query_planner.rb +361 -0
- data/lib/nose/plans/sort.rb +49 -0
- data/lib/nose/plans/update.rb +60 -0
- data/lib/nose/plans/update_planner.rb +270 -0
- data/lib/nose/plans.rb +135 -0
- data/lib/nose/proxy/mysql.rb +275 -0
- data/lib/nose/proxy.rb +102 -0
- data/lib/nose/query_graph.rb +481 -0
- data/lib/nose/random/barbasi_albert.rb +48 -0
- data/lib/nose/random/watts_strogatz.rb +50 -0
- data/lib/nose/random.rb +391 -0
- data/lib/nose/schema.rb +89 -0
- data/lib/nose/search/constraints.rb +143 -0
- data/lib/nose/search/problem.rb +328 -0
- data/lib/nose/search/results.rb +200 -0
- data/lib/nose/search.rb +266 -0
- data/lib/nose/serialize.rb +747 -0
- data/lib/nose/statements/connection.rb +160 -0
- data/lib/nose/statements/delete.rb +83 -0
- data/lib/nose/statements/insert.rb +146 -0
- data/lib/nose/statements/query.rb +161 -0
- data/lib/nose/statements/update.rb +101 -0
- data/lib/nose/statements.rb +645 -0
- data/lib/nose/timing.rb +79 -0
- data/lib/nose/util.rb +305 -0
- data/lib/nose/workload.rb +244 -0
- data/lib/nose.rb +37 -0
- data/templates/workload.erb +42 -0
- 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
|