Pattern matching: return all the matches (instead of the first one found)
This commit is contained in:
parent
a7148d455b
commit
7ece331efb
3 changed files with 102 additions and 37 deletions
|
|
@ -31,31 +31,34 @@ if __name__ == '__main__':
|
||||||
"""
|
"""
|
||||||
The main function called when running from the command line.
|
The main function called when running from the command line.
|
||||||
"""
|
"""
|
||||||
nr_of_vertices = 50
|
nr_of_vertices = 10
|
||||||
nr_of_diff_types_v = 10
|
nr_of_diff_types_v = 0
|
||||||
nr_of_edges = 150
|
nr_of_edges = 20
|
||||||
nr_of_diff_types_e = 10
|
nr_of_diff_types_e = 0
|
||||||
|
|
||||||
dv = [random.randint(0, nr_of_diff_types_v) for _ in range(nr_of_vertices)]
|
dv = [random.randint(0, nr_of_diff_types_v) for _ in range(nr_of_vertices)]
|
||||||
# dv = np.random.random_integers(0, nr_of_diff_types_v, nr_of_vertices)
|
|
||||||
de = [random.randint(0, nr_of_diff_types_e) for _ in range(nr_of_edges)]
|
de = [random.randint(0, nr_of_diff_types_e) for _ in range(nr_of_edges)]
|
||||||
# de = np.random.random_integers(0, nr_of_diff_types_e, nr_of_edges)
|
|
||||||
dc_inc = [random.randint(0, nr_of_vertices-1) for _ in range(nr_of_edges)]
|
dc_inc = [random.randint(0, nr_of_vertices-1) for _ in range(nr_of_edges)]
|
||||||
# dc_inc = np.random.random_integers(0, nr_of_vertices-1, nr_of_edges)
|
|
||||||
dc_out = [random.randint(0, nr_of_vertices-1) for _ in range(nr_of_edges)]
|
dc_out = [random.randint(0, nr_of_vertices-1) for _ in range(nr_of_edges)]
|
||||||
# dc_out = np.random.random_integers(0, nr_of_vertices-1, nr_of_edges)
|
|
||||||
|
|
||||||
# override random graph by copy pasting output from terminal
|
# override random graph by copy pasting output from terminal
|
||||||
dv = [ 10,5,4,0,8,6,8,0,4,8,5,5,7,0,10,0,5,6,10,4,0,3,0,8,2,7,5,8,1,0,2,10,0,0,1,6,8,4,7,6,4,2,10,10,6,4,6,0,2,7 ]
|
# dv = [ 10,5,4,0,8,6,8,0,4,8,5,5,7,0,10,0,5,6,10,4,0,3,0,8,2,7,5,8,1,0,2,10,0,0,1,6,8,4,7,6,4,2,10,10,6,4,6,0,2,7 ]
|
||||||
de = [ 8,10,8,1,6,7,4,3,5,2,0,0,9,6,0,3,8,3,2,7,2,3,10,8,10,8,10,2,5,5,10,6,7,5,1,2,1,2,2,3,7,7,2,1,7,2,9,10,8,1,9,4,1,3,1,1,8,2,2,9,10,9,1,9,4,10,10,10,9,3,5,3,6,6,9,1,2,6,3,2,4,10,9,6,5,6,2,4,3,2,4,10,6,2,8,8,0,5,1,7,3,4,3,8,7,3,0,8,3,3,8,5,10,5,9,3,1,10,3,2,6,3,10,0,5,10,9,10,0,1,4,7,10,3,1,9,1,2,3,7,4,3,7,8,8,4,5,10,1,4 ]
|
# de = [ 8,10,8,1,6,7,4,3,5,2,0,0,9,6,0,3,8,3,2,7,2,3,10,8,10,8,10,2,5,5,10,6,7,5,1,2,1,2,2,3,7,7,2,1,7,2,9,10,8,1,9,4,1,3,1,1,8,2,2,9,10,9,1,9,4,10,10,10,9,3,5,3,6,6,9,1,2,6,3,2,4,10,9,6,5,6,2,4,3,2,4,10,6,2,8,8,0,5,1,7,3,4,3,8,7,3,0,8,3,3,8,5,10,5,9,3,1,10,3,2,6,3,10,0,5,10,9,10,0,1,4,7,10,3,1,9,1,2,3,7,4,3,7,8,8,4,5,10,1,4 ]
|
||||||
dc_inc = [ 0,25,18,47,22,25,16,45,38,25,5,45,15,44,17,46,6,17,35,8,16,29,48,47,25,34,4,20,24,1,47,44,8,25,32,3,16,6,33,21,6,13,41,10,17,25,21,33,31,30,5,4,45,26,16,42,12,25,29,3,32,30,14,26,11,13,7,13,3,43,43,22,48,37,20,28,15,40,19,33,43,16,49,36,11,25,9,42,3,22,16,40,42,44,27,30,1,18,10,35,19,6,9,43,37,38,45,19,41,14,37,45,0,31,29,31,24,20,44,46,8,45,43,3,38,38,35,12,19,45,7,34,20,28,12,17,45,17,35,49,20,21,49,1,35,38,38,36,33,30 ]
|
# dc_inc = [ 0,25,18,47,22,25,16,45,38,25,5,45,15,44,17,46,6,17,35,8,16,29,48,47,25,34,4,20,24,1,47,44,8,25,32,3,16,6,33,21,6,13,41,10,17,25,21,33,31,30,5,4,45,26,16,42,12,25,29,3,32,30,14,26,11,13,7,13,3,43,43,22,48,37,20,28,15,40,19,33,43,16,49,36,11,25,9,42,3,22,16,40,42,44,27,30,1,18,10,35,19,6,9,43,37,38,45,19,41,14,37,45,0,31,29,31,24,20,44,46,8,45,43,3,38,38,35,12,19,45,7,34,20,28,12,17,45,17,35,49,20,21,49,1,35,38,38,36,33,30 ]
|
||||||
dc_out = [ 9,2,49,49,37,33,16,21,5,46,4,15,9,6,14,22,16,33,23,21,15,31,37,23,47,3,30,26,35,9,29,21,39,32,22,43,5,9,41,30,31,30,37,33,31,34,23,22,34,26,44,36,38,33,48,5,9,34,13,7,48,41,43,26,26,7,12,6,12,28,22,8,29,22,24,27,16,4,31,41,32,15,19,20,38,0,26,18,43,46,40,17,29,14,34,14,32,17,32,47,16,45,7,4,35,22,42,11,38,2,0,29,4,38,17,44,9,23,5,10,31,17,1,11,16,5,37,27,35,32,45,16,18,1,14,4,42,24,43,31,21,38,6,34,39,46,20,1,38,47 ]
|
# dc_out = [ 9,2,49,49,37,33,16,21,5,46,4,15,9,6,14,22,16,33,23,21,15,31,37,23,47,3,30,26,35,9,29,21,39,32,22,43,5,9,41,30,31,30,37,33,31,34,23,22,34,26,44,36,38,33,48,5,9,34,13,7,48,41,43,26,26,7,12,6,12,28,22,8,29,22,24,27,16,4,31,41,32,15,19,20,38,0,26,18,43,46,40,17,29,14,34,14,32,17,32,47,16,45,7,4,35,22,42,11,38,2,0,29,4,38,17,44,9,23,5,10,31,17,1,11,16,5,37,27,35,32,45,16,18,1,14,4,42,24,43,31,21,38,6,34,39,46,20,1,38,47 ]
|
||||||
|
|
||||||
|
dv = [0, 1, 0, 1, 0]
|
||||||
|
de = [0, 0, 0]
|
||||||
|
dc_inc = [0, 2, 4]
|
||||||
|
dc_out = [1, 3, 3]
|
||||||
|
|
||||||
gg = GraphGenerator(dv, de, dc_inc, dc_out, debug)
|
gg = GraphGenerator(dv, de, dc_inc, dc_out, debug)
|
||||||
|
|
||||||
graph = gg.getRandomGraph()
|
graph = gg.getRandomGraph()
|
||||||
pattern = gg.getRandomPattern(5, 15, debug=debug)
|
|
||||||
|
|
||||||
|
print(graph.vertices)
|
||||||
|
pattern = gg.getRandomPattern(3, 15, debug=debug)
|
||||||
|
print(pattern.vertices)
|
||||||
|
|
||||||
# override random pattern by copy pasting output from terminal to create
|
# override random pattern by copy pasting output from terminal to create
|
||||||
# pattern, paste it in the createConstantPattern function in the generator.py
|
# pattern, paste it in the createConstantPattern function in the generator.py
|
||||||
|
|
@ -70,10 +73,12 @@ if __name__ == '__main__':
|
||||||
#PM = PatternMatching('SP')
|
#PM = PatternMatching('SP')
|
||||||
# PM = PatternMatching('Ullmann')
|
# PM = PatternMatching('Ullmann')
|
||||||
PM = PatternMatching('VF2')
|
PM = PatternMatching('VF2')
|
||||||
v,e = PM.match(pattern, graph)
|
matches = PM.match(pattern, graph)
|
||||||
|
print("found", len(matches), "matches:", matches)
|
||||||
|
|
||||||
# regenerate graph, to show matched pattern
|
# regenerate graph, to show matched pattern
|
||||||
graphToDot.printGraph('randomGraph.dot', graph, v, e)
|
for i, (v,e) in enumerate(matches):
|
||||||
|
graphToDot.printGraph(f'randomGraph-{i}.dot', graph, v, e)
|
||||||
|
|
||||||
if debug:
|
if debug:
|
||||||
print(len(v))
|
print(len(v))
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ class PatternMatching(object):
|
||||||
self.result = None
|
self.result = None
|
||||||
self.previous = []
|
self.previous = []
|
||||||
self.optimize = optimize
|
self.optimize = optimize
|
||||||
|
self.results = []
|
||||||
|
|
||||||
def match(self, pattern, graph):
|
def match(self, pattern, graph):
|
||||||
"""
|
"""
|
||||||
|
|
@ -68,6 +69,7 @@ class PatternMatching(object):
|
||||||
self.bound_vertices = {}
|
self.bound_vertices = {}
|
||||||
self.bound_edges = {}
|
self.bound_edges = {}
|
||||||
self.result = None
|
self.result = None
|
||||||
|
self.results = []
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
@ -845,7 +847,21 @@ class PatternMatching(object):
|
||||||
"""
|
"""
|
||||||
# all candidate pair (n, m) represent graph x pattern
|
# all candidate pair (n, m) represent graph x pattern
|
||||||
|
|
||||||
|
candidate = frozenset(itertools.chain(
|
||||||
|
((i, j) for i,j in VF2_obj.mapping.items()),
|
||||||
|
# ((self.reverseMapH[i], self.reverseMapP[j]) for i,j in VF2_obj.mapping.items()),
|
||||||
|
[(h[n],p[m])],
|
||||||
|
))
|
||||||
|
|
||||||
|
if candidate in self.alreadyVisited:
|
||||||
|
# print(self.indent*" ", "candidate:", candidate)
|
||||||
|
# for match in self.alreadyVisited.get(index_M, []):
|
||||||
|
# if match == candidate:
|
||||||
|
return False # already visited this (partial) match -> skip
|
||||||
|
|
||||||
|
|
||||||
if feasibilityTest(H, P, h, p, VF2_obj, n, m):
|
if feasibilityTest(H, P, h, p, VF2_obj, n, m):
|
||||||
|
print(self.indent*" ","adding to match:", n, "->", m)
|
||||||
# adapt VF2_obj
|
# adapt VF2_obj
|
||||||
VF2_obj.core_graph[n] = True
|
VF2_obj.core_graph[n] = True
|
||||||
VF2_obj.core_pattern[m] = True
|
VF2_obj.core_pattern[m] = True
|
||||||
|
|
@ -855,17 +871,30 @@ class PatternMatching(object):
|
||||||
addOutNeighbours(P[m], VF2_obj.N_out_pattern, index_M)
|
addOutNeighbours(P[m], VF2_obj.N_out_pattern, index_M)
|
||||||
addIncNeighbours(P, m, VF2_obj.N_inc_pattern, index_M)
|
addIncNeighbours(P, m, VF2_obj.N_inc_pattern, index_M)
|
||||||
|
|
||||||
if findM(H, P, h, p, VF2_obj, index_M + 1):
|
if index_M > 0:
|
||||||
return True
|
# remember our partial match (shallow copy) so we don't visit it again
|
||||||
|
self.alreadyVisited.add(frozenset([ (i, j) for i,j in VF2_obj.mapping.items()]))
|
||||||
|
# self.alreadyVisited.setdefault(index_M, set()).add(frozenset([ (self.reverseMapH[i], self.reverseMapP[j]) for i,j in VF2_obj.mapping.items()]))
|
||||||
|
# print(self.alreadyVisited)
|
||||||
|
|
||||||
# else, cleanup, adapt VF2_obj
|
self.indent += 1
|
||||||
VF2_obj.core_graph[n] = False
|
if findM(H, P, h, p, VF2_obj, index_M + 1):
|
||||||
VF2_obj.core_pattern[m] = False
|
# return True
|
||||||
del VF2_obj.mapping[h[n]]
|
print(self.indent*" ","found match", len(self.results), ", continuing...")
|
||||||
delNeighbours(VF2_obj.N_out_graph, index_M)
|
self.indent -= 1
|
||||||
delNeighbours(VF2_obj.N_inc_graph, index_M)
|
|
||||||
delNeighbours(VF2_obj.N_out_pattern, index_M)
|
if True:
|
||||||
delNeighbours(VF2_obj.N_inc_pattern, index_M)
|
# else:
|
||||||
|
print(self.indent*" ","backtracking... remove", n, "->", m)
|
||||||
|
|
||||||
|
# else, backtrack, adapt VF2_obj
|
||||||
|
VF2_obj.core_graph[n] = False
|
||||||
|
VF2_obj.core_pattern[m] = False
|
||||||
|
del VF2_obj.mapping[h[n]]
|
||||||
|
delNeighbours(VF2_obj.N_out_graph, index_M)
|
||||||
|
delNeighbours(VF2_obj.N_inc_graph, index_M)
|
||||||
|
delNeighbours(VF2_obj.N_out_pattern, index_M)
|
||||||
|
delNeighbours(VF2_obj.N_inc_pattern, index_M)
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
@ -879,12 +908,15 @@ class PatternMatching(object):
|
||||||
# skip graph vertices that are not in VF2_obj.N_out_graph
|
# skip graph vertices that are not in VF2_obj.N_out_graph
|
||||||
# (or already matched)
|
# (or already matched)
|
||||||
if N_graph[n] == -1 or VF2_obj.core_graph[n]:
|
if N_graph[n] == -1 or VF2_obj.core_graph[n]:
|
||||||
|
# print(self.indent*" "," skipping")
|
||||||
continue
|
continue
|
||||||
|
print(self.indent*" "," n:", n)
|
||||||
for m in range(0, len(N_pattern)):
|
for m in range(0, len(N_pattern)):
|
||||||
# skip graph vertices that are not in VF2_obj.N_out_pattern
|
# skip graph vertices that are not in VF2_obj.N_out_pattern
|
||||||
# (or already matched)
|
# (or already matched)
|
||||||
if N_pattern[m] == -1 or VF2_obj.core_pattern[m]:
|
if N_pattern[m] == -1 or VF2_obj.core_pattern[m]:
|
||||||
continue
|
continue
|
||||||
|
print(self.indent*" "," m:", m)
|
||||||
if matchPhase(H, P, h, p, index_M, VF2_obj, n, m):
|
if matchPhase(H, P, h, p, index_M, VF2_obj, n, m):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
@ -899,35 +931,48 @@ class PatternMatching(object):
|
||||||
# skip vertices that are connected to the graph
|
# skip vertices that are connected to the graph
|
||||||
# (or already matched)
|
# (or already matched)
|
||||||
if not (VF2_obj.N_out_graph[n] == -1 and VF2_obj.N_inc_graph[n] == -1) or VF2_obj.core_graph[n]:
|
if not (VF2_obj.N_out_graph[n] == -1 and VF2_obj.N_inc_graph[n] == -1) or VF2_obj.core_graph[n]:
|
||||||
|
# print(self.indent*" "," skipping")
|
||||||
continue
|
continue
|
||||||
|
print(" n:", n)
|
||||||
for m in range(0, len(VF2_obj.N_out_pattern)):
|
for m in range(0, len(VF2_obj.N_out_pattern)):
|
||||||
# skip vertices that are connected to the graph
|
# skip vertices that are connected to the graph
|
||||||
# (or already matched)
|
# (or already matched)
|
||||||
if not (VF2_obj.N_out_pattern[m] == -1 and VF2_obj.N_inc_pattern[m] == -1) or VF2_obj.core_pattern[m]:
|
if not (VF2_obj.N_out_pattern[m] == -1 and VF2_obj.N_inc_pattern[m] == -1) or VF2_obj.core_pattern[m]:
|
||||||
|
# print(self.indent*" "," skipping")
|
||||||
continue
|
continue
|
||||||
|
print(self.indent*" "," m:", m)
|
||||||
if matchPhase(H, P, h, p, index_M, VF2_obj, n, m):
|
if matchPhase(H, P, h, p, index_M, VF2_obj, n, m):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
print(self.indent*" ","index_M:", index_M)
|
||||||
|
|
||||||
# We are at the end, we found an candidate.
|
# We are at the end, we found an candidate.
|
||||||
if index_M == len(p):
|
if index_M == len(p):
|
||||||
|
print(self.indent*" ","end...")
|
||||||
bound_graph_vertices = {}
|
bound_graph_vertices = {}
|
||||||
for vertex_bound, _ in VF2_obj.mapping.items():
|
for vertex_bound, _ in VF2_obj.mapping.items():
|
||||||
bound_graph_vertices.setdefault(vertex_bound.type, set()).add(vertex_bound)
|
bound_graph_vertices.setdefault(vertex_bound.type, set()).add(vertex_bound)
|
||||||
|
|
||||||
self.result = self.matchNaive(vertices=bound_graph_vertices, edges=self.graph.edges)
|
self.result = self.matchNaive(vertices=bound_graph_vertices, edges=self.graph.edges)
|
||||||
|
if self.result != None:
|
||||||
|
self.results.append(self.result)
|
||||||
return self.result != None
|
return self.result != None
|
||||||
|
|
||||||
# try the candidates is the preffered order
|
if index_M > 0:
|
||||||
# first try the adjacent vertices connected via the outgoing edges.
|
# try the candidates is the preffered order
|
||||||
if preferred(H, P, h, p, index_M, VF2_obj, VF2_obj.N_out_graph, VF2_obj.N_out_pattern):
|
# first try the adjacent vertices connected via the outgoing edges.
|
||||||
return True
|
print(self.indent*" ","preferred L1")
|
||||||
|
if preferred(H, P, h, p, index_M, VF2_obj, VF2_obj.N_out_graph, VF2_obj.N_out_pattern):
|
||||||
|
return True
|
||||||
|
|
||||||
# then try the adjacent vertices connected via the incoming edges.
|
print(self.indent*" ","preferred L2")
|
||||||
if preferred(H, P, h, p, index_M, VF2_obj, VF2_obj.N_inc_graph, VF2_obj.N_inc_pattern):
|
# then try the adjacent vertices connected via the incoming edges.
|
||||||
return True
|
if preferred(H, P, h, p, index_M, VF2_obj, VF2_obj.N_inc_graph, VF2_obj.N_inc_pattern):
|
||||||
|
return True
|
||||||
|
|
||||||
|
print(self.indent*" ","leastPreferred")
|
||||||
# and lastly, try the vertices not connected to the currently matched vertices
|
# and lastly, try the vertices not connected to the currently matched vertices
|
||||||
if leastPreferred(H, P, h, p, index_M, VF2_obj):
|
if leastPreferred(H, P, h, p, index_M, VF2_obj):
|
||||||
return True
|
return True
|
||||||
|
|
@ -937,11 +982,23 @@ class PatternMatching(object):
|
||||||
|
|
||||||
# create adjecency matrix of the graph
|
# create adjecency matrix of the graph
|
||||||
H, h = self.createAdjacencyMatrixMap(self.graph)
|
H, h = self.createAdjacencyMatrixMap(self.graph)
|
||||||
|
print("adjacency:", H)
|
||||||
|
print("h:", len(h))
|
||||||
# create adjecency matrix of the pattern
|
# create adjecency matrix of the pattern
|
||||||
P, p = self.createAdjacencyMatrixMap(self.pattern)
|
P, p = self.createAdjacencyMatrixMap(self.pattern)
|
||||||
|
|
||||||
VF2_obj = VF2_Obj(len(h), len(p))
|
VF2_obj = VF2_Obj(len(h), len(p))
|
||||||
|
|
||||||
|
self.indent = 0
|
||||||
|
|
||||||
|
# Only for debugging:
|
||||||
|
self.reverseMapH = { h[i] : i for i in range(len(h))}
|
||||||
|
self.reverseMapP = { p[i] : i for i in range(len(p))}
|
||||||
|
|
||||||
|
# Set of partial matches already explored - prevents us from producing the same match multiple times
|
||||||
|
# Encoded as a mapping from match size to the partial match
|
||||||
|
self.alreadyVisited = set()
|
||||||
|
|
||||||
findM(H, P, h, p, VF2_obj)
|
findM(H, P, h, p, VF2_obj)
|
||||||
|
|
||||||
return self.result
|
# return self.results
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
|
rm *.svg
|
||||||
|
rm *.dot
|
||||||
python main.py
|
python main.py
|
||||||
dot randomGraph.dot -Tsvg > randomGraph.svg
|
# dot randomGraph.dot -Tsvg > randomGraph.svg
|
||||||
dot randomPattern.dot -Tsvg > randomPattern.svg
|
for filename in randomGraph-*.dot; do
|
||||||
|
dot $filename -Tsvg > $filename.svg
|
||||||
|
done
|
||||||
|
|
||||||
firefox randomGraph.svg
|
firefox *.svg
|
||||||
firefox randomPattern.svg
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue