nose 0.1.0pre
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 +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
|