graphmatcher 0.3.4 → 0.3.5

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 +4 -4
  2. metadata +11 -26
  3. data/lib/graphmatcher.rb +0 -291
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 16aafb8ddede700354cfac5795c95c8f16ecf3c4076b082d4e409b7b211d4310
4
- data.tar.gz: 742e07a51018dae3dc9de0b046a82e9f35b39825784c9f126964685f77366e3e
3
+ metadata.gz: 1a07b061aee6d3c909fa21e7dcc3ed815c556b358f0e233338614f97dc26d90f
4
+ data.tar.gz: ac1149e8f375be540ba8d77d77276f9d309df1dc44c7e7057eef9def8ff38be8
5
5
  SHA512:
6
- metadata.gz: 16f27d89dae6474a68ec4ed01c800f0047f9f5f46967771172a7a3150174359bddd84d4b5d1467410abfe2f8a8e35bf6458aaf1a1a951d4e5b8524d03259a94f
7
- data.tar.gz: bd84006820c720fecfd948113423a90e8aa980ac281e9342a7ac5b38874c4f5e84412d9c5a32ff8a27d29260c8cb01f03b1317f3041a58d994efc6341f1da359
6
+ metadata.gz: be30bea5f4045cefb90288e632e43e4ea9d2472879c2e4d339fef667ddf842f863601064052e969bb1f7e6afa8f3d17daa5457ed52208e311faf76575ada5ca4
7
+ data.tar.gz: 6ce6b7bd8a89fbc44c06079c014353220ed8f5cc3e938040bb7da9eb9b2226d967c11eadef2e1fe50c95989fbfd303d637d90cb057ea948adc3c785ba48ad142
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphmatcher
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.4
4
+ version: 0.3.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Emre Unlu
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-12-19 00:00:00.000000000 Z
11
+ date: 2020-12-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -42,44 +42,30 @@ dependencies:
42
42
  name: rspec
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - "~>"
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: 3.5.0
47
+ version: '0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - "~>"
52
+ - - ">="
53
53
  - !ruby/object:Gem::Version
54
- version: 3.5.0
54
+ version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: ruby-prof
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - "~>"
59
+ - - ">="
60
60
  - !ruby/object:Gem::Version
61
- version: 0.16.2
61
+ version: '0'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - "~>"
67
- - !ruby/object:Gem::Version
68
- version: 0.16.2
69
- - !ruby/object:Gem::Dependency
70
- name: rspec-prof
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - "~>"
74
- - !ruby/object:Gem::Version
75
- version: 0.0.7
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - "~>"
66
+ - - ">="
81
67
  - !ruby/object:Gem::Version
82
- version: 0.0.7
68
+ version: '0'
83
69
  description: An effective subgraph matching gem based on DualIso algorithm of M. Saltz
84
70
  et al.
85
71
  email:
@@ -87,8 +73,7 @@ email:
87
73
  executables: []
88
74
  extensions: []
89
75
  extra_rdoc_files: []
90
- files:
91
- - lib/graphmatcher.rb
76
+ files: []
92
77
  homepage: https://github.com/forvelin/graphmatcher
93
78
  licenses:
94
79
  - MIT
@@ -1,291 +0,0 @@
1
- require 'logger'
2
- # require 'ruby-prof'
3
-
4
- # @query_graph & @data_graph: Arrays which are represented as an
5
- # array such as g=[[[1,2],[3],[3],[]],['x','y','y','z']]. g[0] is
6
- # adjacency array which points children of given vertex of array index,
7
- # -e.g. vertex 0 has 1 and 2 as children- and g[1] is labels for vertices.
8
- # -e.g. vertex 0 has label 'x'.
9
- #
10
- # @limit: Upper limit for number of matches. Procedure terminates after
11
- # reaching to an upper limit.
12
- # @max_allowed_time: Maximum allowed time for procedure to run.
13
- # @self_loops: Boolean value for representing if query is cyclic or not.
14
- class Graphmatcher
15
- @@logger = Logger.new(STDOUT)
16
- @@logger.level = Logger::FATAL
17
-
18
- def initialize(args)
19
- @query_graph = args[:query_graph].to_a
20
- @data_graph = args[:data_graph].to_a
21
- @limit = (args[:limit] || 1).to_i
22
- @max_allowed_time = (args[:max_allowed_time] || 4.000).to_f
23
- @cost_matrix = args[:cost_matrix] || nil
24
- @self_loops = args[:self_loops] || false
25
- validate!
26
- end
27
-
28
- # Function for generating feasible matches for query
29
- # graph based on labels of vertices of data graph.
30
- def label_match
31
- data_labels = @data_graph[1]
32
- query_labels = @query_graph[1]
33
-
34
- feasible = query_labels.map.with_index do |ql, _index|
35
- data_labels.each_index.select { |i| data_labels[i] == ql }
36
- end
37
-
38
- if @cost_matrix
39
-
40
- feasible = assess_cost(feasible, @cost_matrix)
41
-
42
- feasible = feasible.select { |f| f[1] }.map do |feasible_set|
43
- feasible_set.sort_by { |f| f[1] }
44
- end
45
-
46
- feasible = feasible.map do |f_set|
47
- f_set.map do |f|
48
- f[0]
49
- end
50
- end
51
- end
52
-
53
- @@logger.info('Label matches(phi) are: ' + feasible.to_s)
54
- feasible
55
- end
56
-
57
- # Public interface for Graphmatcher class.
58
- #
59
- # @matches: Array of matching indices of query graph in data graph.
60
- def find_matches
61
- @matches = []
62
- @t0 = Time.now.to_f
63
- phi = label_match
64
-
65
- dual_iso(dual_simulation(phi), 0)
66
- # @@logger.info("FINISHED matches=#{@matches}")
67
- if @cost_matrix
68
- # if cost matrix is available, get costs of found matches.
69
-
70
- @matches = assess_cost(@matches, @cost_matrix)
71
-
72
- # sort matches by sum of costs of matched resources.
73
- # MATCHES
74
- # [ [[1,100],[2,10]],[[3,500],[4,800]] ]
75
- # MATCH COSTS
76
- # 110 1300
77
-
78
- # The behaviour here is important !
79
- # Sum of costs vs. max of costs, depends which one is relevant.
80
-
81
- @matches.reject! { |match_set| match_set.map { |e| e[1] }.include?(nil) }
82
-
83
- @matches = @matches.sort_by do |match_set|
84
- match_set.reduce(0) { |sum, e| sum + e[1] }
85
- end
86
-
87
- end
88
- @matches
89
- end
90
-
91
- def get_resource_property(_match, property)
92
- truncated_data_graph = @data_graph.truncate
93
- @matches.map { |match_set| match_set.map { |match| truncated_data_graph[match[0]][2][property] } }
94
- end
95
-
96
- def get_resource_cost(costs, resource_position, query_index)
97
- # costs = { resource_id => { query_index => cost } }
98
- # e.g.
99
- # costs = { 40 => { 0 => 5, 1 => 10 } }
100
- if costs[resource_position][query_index]
101
- cost = (costs[resource_position][query_index])
102
- cost
103
- end
104
- end
105
-
106
- def assess_cost(matches, costs)
107
- # resource_graph =
108
- # [
109
- # [[],[],[],[],[],[],[]], #adj.
110
- # ['SPO2','NRF52','SPO2','NRF52','SPO2','NRF52','SPO2'], #types
111
- # ['img_x','img_y','img_z','img_t','img_z','img_q','img_z'], #images
112
- # ['12','52','25','61','74','95','11'] #resource_id
113
- # ]
114
-
115
- # request_graph =
116
- # [
117
- # [[],[]],
118
- # ['NRF52','NRF52'],
119
- # ['img_y','img_z'],
120
- # ['NODE_A','HUB_A']
121
- # ]
122
-
123
- # costs = {
124
- # 52 => {0 => 0, 1 => 50} , #y
125
- # 61 => {0 => 30, 1 => 70} , #t
126
- # 95 => {0 => 40, 1 => 55} , #q
127
- # }
128
-
129
- # matches = [
130
- # [[1],[2]],[[1],[4]],[[1],[6]],
131
- # [[3],[2]],[[3],[4]],[[3],[6]],
132
- # [[5],[2]],[[5],[4]],[[5],[6]]
133
- # ]
134
-
135
- matches.map do |match_set| # [ [1],[2] ]
136
- match_set.flatten.map.with_index do |match, query_index| # 1
137
- [match, get_resource_cost(costs, match, query_index).to_f]
138
- end
139
- end
140
- end
141
-
142
- # INFO: Function that uses parameter phi -which is generated by label_match-
143
- # to determine which matches of data have expected relations in query graph.
144
- # phi = ...
145
- def dual_simulation(phi)
146
- # One directional adjacency array for data graph and query graphs.
147
- data_children = @data_graph[0]
148
- query_children = @query_graph[0]
149
- # @@logger.info("Data children: #{data_children.to_s}")
150
- # @@logger.info("Query children: #{query_children.to_s}")
151
- changed = true
152
- while changed
153
- changed = false
154
- return nil if (Time.now.to_f - @t0) > @max_allowed_time
155
-
156
- # children = query_edges
157
- # q_index = query_vertex_index
158
- query_children.each_with_index do |children, q_index|
159
- # query_child = query_edge_target
160
- children.each do |query_child|
161
- # Create a temporary phi object.
162
- temp_phi = []
163
- # Loop over candidates of each vertex in data graph.
164
- to_delete = []
165
-
166
- phi[q_index].map do |child| # loop 3
167
- # @@logger.debug("u=#{q_index}, u_c=#{query_child}, child=#{child}")
168
-
169
- # Find intersection of children of 'child' in data graph and
170
- # candidates of 'query child' in data graph.
171
- phi_intersection = data_children[child] & phi[query_child]
172
- # @@logger.debug("datachildren[child]=#{data_children[child]}")
173
- # @@logger.debug("phi[query_child]=#{phi[query_child]}")
174
- # @@logger.debug("Intersection=#{phi_intersection}")
175
- if phi_intersection.nil? || phi_intersection.empty?
176
- to_delete.push(child)
177
- return phi if phi[q_index].empty?
178
-
179
- changed = true
180
- end
181
- temp_phi |= phi_intersection
182
- end
183
-
184
- unless to_delete.empty?
185
- to_delete.each do |td|
186
- phi[q_index].delete(td)
187
- end
188
- end
189
- return phi if temp_phi.flatten.empty?
190
-
191
- changed = true if temp_phi.size < phi[query_child].size
192
- if @self_loops && query_child == q_index
193
- phi[query_child] &= temp_phi
194
- else
195
- # @@logger.debug("phi=#{phi} and phi[#{query_child}]=#{temp_phi}")
196
- phi[query_child] = temp_phi
197
- end
198
- end
199
- end
200
- end
201
- @@logger.info("Returning phi=#{phi}")
202
- phi
203
- end
204
-
205
- # INFO: Function call to collect matches from phi object.
206
- # phi = ...
207
- # depth = ...
208
- # matches = ...
209
- def dual_iso(phi, depth)
210
- if depth == @query_graph[0].length
211
- unless phi.nil? || phi.empty?
212
- @matches <<
213
- if phi.include?([]) # Unable to match this vertex in graph.
214
- [nil]
215
- else
216
- phi
217
- end
218
- end
219
- elsif !(phi.nil? || phi.empty?)
220
- phi[depth].sort_by { |value| @cost_matrix ? (@cost_matrix[value][depth] || Float::INFINITY) : next }.each do |value|
221
- next if contains(phi, depth, value)
222
-
223
- # keys are indices 0...n, values are possible values for that index
224
- phicopy = phi.map(&:clone)
225
- # @@logger.info("phicopy=#{phicopy},depth=#{depth},value=#{value}")
226
- phicopy[depth] = [value]
227
- if @matches.length >= @limit
228
- @@logger.info("FINISHED matches=#{@matches}")
229
- return @matches
230
- end
231
- dual_iso(dual_simulation(phicopy), depth + 1)
232
- end
233
- end
234
- end
235
-
236
- # INFO: Checks if vertex J is contained in any of previous matches.
237
- # TODO: Change with find method.
238
- def contains(phi, depth, vertex_j)
239
- false if depth <= 0
240
- (0..depth - 1).each do |i|
241
- # @@logger.info("phi[#{i}]=#{phi[i]},depth=#{depth},vertex_j=#{vertex_j}")
242
- return true if phi[i].include?(vertex_j)
243
- end
244
- false
245
- end
246
-
247
- # EXPERIMENTAL
248
- # INFO: Produce a GraphViz-compliant directed graph syntax.
249
- # INFO: Needs dot/graphviz tools installed as a dependency.
250
- # TODO: Unable to handle multiple results, color each result different.
251
- # Indices are IDs, labels are labels adjencency array is outgoing edges.
252
- def dot_graph(data, subgraph = nil, prefix = '')
253
- output = ['digraph {']
254
- data.transpose.each_with_index do |node, id|
255
- output <<
256
- ["#{id} [label=\"#{node[1]}##{id}\"]",
257
- "#{id}->{#{node[0].join(' ')}}"].join("\n")
258
- end
259
- if subgraph
260
- subgraph.each_with_index do |node, _id|
261
- output << "#{node} [fontcolor=\"Red\"]"
262
- end
263
- end
264
- output << '}'
265
- tstamp = Time.new.to_i.to_s
266
- File.write("#{prefix}#{tstamp}.dot", output.join("\n"))
267
- dot_produce = ['dot', '-Tpng', "#{prefix}#{tstamp}.dot",
268
- '-o', "#{prefix}#{tstamp}.png"].join(' ')
269
- `#{dot_produce}`
270
- end
271
-
272
- def validate!
273
- unless @query_graph.is_a?(Array) && @data_graph.is_a?(Array)
274
- raise ArgumentError,
275
- 'Type mismatch for graphs in initialization !'
276
- end
277
- unless @limit.is_a?(Numeric) && @max_allowed_time.is_a?(Numeric)
278
- raise ArgumentError,
279
- 'Type mismatch for limit or timeout value in initialization !'
280
- end
281
- unless @query_graph.length >= 2 && @data_graph.length >= 2
282
- raise ArgumentError,
283
- 'Input graphs must have at least two dimensions !'
284
- end
285
- unless @query_graph.map(&:length).uniq.size == 1 &&
286
- @data_graph.map(&:length).uniq.size == 1
287
- raise ArgumentError,
288
- 'Input graphs\' adjencency and label arrays must be sized equal !'
289
- end
290
- end
291
- end