(Re)Move some files
This commit is contained in:
parent
175edb64d9
commit
9faa5770a8
10 changed files with 4 additions and 1419 deletions
|
|
@ -6,12 +6,11 @@ from uuid import UUID
|
||||||
from services.scd import SCD
|
from services.scd import SCD
|
||||||
from framework.conformance import Conformance
|
from framework.conformance import Conformance
|
||||||
from services.od import OD
|
from services.od import OD
|
||||||
|
from transformation.matcher import mvs_adapter
|
||||||
from transformation.ramify import ramify
|
from transformation.ramify import ramify
|
||||||
from transformation import rewriter
|
from transformation import rewriter
|
||||||
from services.bottom.V0 import Bottom
|
from services.bottom.V0 import Bottom
|
||||||
from services.primitives.integer_type import Integer
|
from services.primitives.integer_type import Integer
|
||||||
from pattern_matching import mvs_adapter
|
|
||||||
from pattern_matching.matcher import MatcherVF2
|
|
||||||
from concrete_syntax import plantuml
|
from concrete_syntax import plantuml
|
||||||
from concrete_syntax.textual_od import parser, renderer
|
from concrete_syntax.textual_od import parser, renderer
|
||||||
|
|
||||||
|
|
@ -91,10 +90,8 @@ Bear_inh_Animal:Inheritance (Bear -> Animal)
|
||||||
dsl_m_cs = """
|
dsl_m_cs = """
|
||||||
george :Man
|
george :Man
|
||||||
weight = 80
|
weight = 80
|
||||||
|
bear1:Bear
|
||||||
bear1 :Bear
|
bear2:Bear
|
||||||
bear2 :Bear
|
|
||||||
|
|
||||||
:afraidOf (george -> bear1)
|
:afraidOf (george -> bear1)
|
||||||
:afraidOf (george -> bear2)
|
:afraidOf (george -> bear2)
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -1,286 +0,0 @@
|
||||||
import time
|
|
||||||
|
|
||||||
import matcher as j # joeri's matcher
|
|
||||||
import graph as sgraph # sten's graph
|
|
||||||
import patternMatching as s # sten's matcher
|
|
||||||
import generator
|
|
||||||
|
|
||||||
def j_to_s(j):
|
|
||||||
s = sgraph.Graph()
|
|
||||||
m = {}
|
|
||||||
for jv in j.vtxs:
|
|
||||||
sv = s.addCreateVertex(jv.value) # value becomes type
|
|
||||||
m[jv] = sv
|
|
||||||
for je in j.edges:
|
|
||||||
s.addCreateEdge(m[je.src], m[je.tgt], "e") # only one type
|
|
||||||
return s
|
|
||||||
|
|
||||||
def s_to_j(s):
|
|
||||||
jg = j.Graph()
|
|
||||||
jg.vtxs = [ j.Vertex(typ) for (typ,svs) in s.vertices.items() for sv in svs ]
|
|
||||||
m = { sv : jg.vtxs[i] for svs in s.vertices.values() for i,sv in enumerate(svs) }
|
|
||||||
jg.edges = [j.Edge(m[se.src], m[se.tgt]) for ses in s.edges.values() for se in ses ]
|
|
||||||
return j
|
|
||||||
|
|
||||||
|
|
||||||
def run_benchmark(jhost, jguest, shost, sguest, expected=None):
|
|
||||||
j_durations = 0
|
|
||||||
s_durations = 0
|
|
||||||
|
|
||||||
# benchmark Joeri
|
|
||||||
m = j.MatcherVF2(host, guest,
|
|
||||||
lambda g_vtx, h_vtx: g_vtx.value == h_vtx.value) # all vertices can be matched
|
|
||||||
iterations = 50
|
|
||||||
print(" Patience (joeri)...")
|
|
||||||
for n in range(iterations):
|
|
||||||
time_start = time.perf_counter_ns()
|
|
||||||
matches = [mm for mm in m.match()]
|
|
||||||
time_end = time.perf_counter_ns()
|
|
||||||
duration = time_end - time_start
|
|
||||||
j_durations += duration
|
|
||||||
print(f' {iterations} iterations, took {j_durations/1000000:.3f} ms, {j_durations/iterations/1000000:.3f} ms per iteration')
|
|
||||||
if expected == None:
|
|
||||||
print(f" {len(matches)} matches")
|
|
||||||
else:
|
|
||||||
if len(matches) == expected:
|
|
||||||
print(" correct (probably)")
|
|
||||||
else:
|
|
||||||
print(f" WRONG! expected: {expected}, got: {len(matches)}")
|
|
||||||
# print([m.mapping_vtxs for m in matches])
|
|
||||||
# print([m.mapping_edges for m in matches])
|
|
||||||
|
|
||||||
# benchmark Sten
|
|
||||||
m = s.PatternMatching()
|
|
||||||
print(" Patience (sten)...")
|
|
||||||
for n in range(iterations):
|
|
||||||
time_start = time.perf_counter_ns()
|
|
||||||
matches = [mm for mm in m.matchVF2(sguest, shost)]
|
|
||||||
time_end = time.perf_counter_ns()
|
|
||||||
duration = time_end - time_start
|
|
||||||
s_durations += duration
|
|
||||||
print(f' {iterations} iterations, took {s_durations/1000000:.3f} ms, {s_durations/iterations/1000000:.3f} ms per iteration')
|
|
||||||
if expected == None:
|
|
||||||
print(f" {len(matches)} matches")
|
|
||||||
else:
|
|
||||||
if len(matches) == expected:
|
|
||||||
print(" correct (probably)")
|
|
||||||
else:
|
|
||||||
print(f" WRONG! expected: {expected}, got: {len(matches)}")
|
|
||||||
# print(matches)
|
|
||||||
|
|
||||||
print(f" joeri is {s_durations/j_durations:.2f} times faster")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
|
|
||||||
print("\nBENCHMARK: small graph, simple pattern")
|
|
||||||
|
|
||||||
host = j.Graph()
|
|
||||||
host.vtxs = [j.Vertex(0), j.Vertex(0), j.Vertex(0), j.Vertex(0)]
|
|
||||||
host.edges = [
|
|
||||||
j.Edge(host.vtxs[0], host.vtxs[1]),
|
|
||||||
j.Edge(host.vtxs[1], host.vtxs[2]),
|
|
||||||
j.Edge(host.vtxs[2], host.vtxs[0]),
|
|
||||||
j.Edge(host.vtxs[2], host.vtxs[3]),
|
|
||||||
j.Edge(host.vtxs[3], host.vtxs[2]),
|
|
||||||
]
|
|
||||||
|
|
||||||
guest = j.Graph()
|
|
||||||
guest.vtxs = [
|
|
||||||
j.Vertex(0),
|
|
||||||
j.Vertex(0)]
|
|
||||||
guest.edges = [
|
|
||||||
# Look for a simple loop:
|
|
||||||
j.Edge(guest.vtxs[0], guest.vtxs[1]),
|
|
||||||
j.Edge(guest.vtxs[1], guest.vtxs[0]),
|
|
||||||
]
|
|
||||||
|
|
||||||
# because of the symmetry in our pattern, there will be 2 matches
|
|
||||||
|
|
||||||
run_benchmark(host, guest, j_to_s(host), j_to_s(guest), expected=2)
|
|
||||||
|
|
||||||
#######################################################################
|
|
||||||
|
|
||||||
print("\nBENCHMARK: larger graph, simple pattern")
|
|
||||||
|
|
||||||
host = j.Graph()
|
|
||||||
host.vtxs = [
|
|
||||||
j.Vertex('triangle'), # 0
|
|
||||||
j.Vertex('square'), # 1
|
|
||||||
j.Vertex('square'), # 2
|
|
||||||
j.Vertex('circle'), # 3
|
|
||||||
j.Vertex('circle'), # 4
|
|
||||||
j.Vertex('circle'), # 5
|
|
||||||
]
|
|
||||||
host.edges = [
|
|
||||||
# not a match:
|
|
||||||
j.Edge(host.vtxs[0], host.vtxs[5]),
|
|
||||||
j.Edge(host.vtxs[5], host.vtxs[0]),
|
|
||||||
|
|
||||||
# will be a match:
|
|
||||||
j.Edge(host.vtxs[1], host.vtxs[5]),
|
|
||||||
j.Edge(host.vtxs[5], host.vtxs[1]),
|
|
||||||
|
|
||||||
# noise:
|
|
||||||
j.Edge(host.vtxs[1], host.vtxs[2]),
|
|
||||||
|
|
||||||
# will be a match:
|
|
||||||
j.Edge(host.vtxs[2], host.vtxs[4]),
|
|
||||||
j.Edge(host.vtxs[4], host.vtxs[2]),
|
|
||||||
|
|
||||||
# noise:
|
|
||||||
j.Edge(host.vtxs[0], host.vtxs[1]),
|
|
||||||
j.Edge(host.vtxs[0], host.vtxs[3]),
|
|
||||||
j.Edge(host.vtxs[0], host.vtxs[0]),
|
|
||||||
j.Edge(host.vtxs[1], host.vtxs[1]),
|
|
||||||
|
|
||||||
# will be a match:
|
|
||||||
j.Edge(host.vtxs[3], host.vtxs[2]),
|
|
||||||
j.Edge(host.vtxs[2], host.vtxs[3]),
|
|
||||||
]
|
|
||||||
|
|
||||||
guest = j.Graph()
|
|
||||||
guest.vtxs = [
|
|
||||||
j.Vertex('square'), # 0
|
|
||||||
j.Vertex('circle')] # 1
|
|
||||||
guest.edges = [
|
|
||||||
j.Edge(guest.vtxs[0], guest.vtxs[1]),
|
|
||||||
j.Edge(guest.vtxs[1], guest.vtxs[0]),
|
|
||||||
]
|
|
||||||
|
|
||||||
# should give 3 matches
|
|
||||||
|
|
||||||
run_benchmark(host, guest, j_to_s(host), j_to_s(guest), expected=3)
|
|
||||||
|
|
||||||
#######################################################################
|
|
||||||
|
|
||||||
print("\nBENCHMARK: same as before, but with larger pattern")
|
|
||||||
|
|
||||||
host = j.Graph()
|
|
||||||
host.vtxs = [
|
|
||||||
j.Vertex('triangle'), # 0
|
|
||||||
j.Vertex('square'), # 1
|
|
||||||
j.Vertex('square'), # 2
|
|
||||||
j.Vertex('circle'), # 3
|
|
||||||
j.Vertex('circle'), # 4
|
|
||||||
j.Vertex('circle'), # 5
|
|
||||||
]
|
|
||||||
host.edges = [
|
|
||||||
# not a match:
|
|
||||||
j.Edge(host.vtxs[0], host.vtxs[5]),
|
|
||||||
j.Edge(host.vtxs[5], host.vtxs[0]),
|
|
||||||
|
|
||||||
# will be a match:
|
|
||||||
j.Edge(host.vtxs[1], host.vtxs[5]),
|
|
||||||
j.Edge(host.vtxs[5], host.vtxs[1]),
|
|
||||||
|
|
||||||
# noise:
|
|
||||||
j.Edge(host.vtxs[1], host.vtxs[2]),
|
|
||||||
|
|
||||||
# will be a match:
|
|
||||||
j.Edge(host.vtxs[2], host.vtxs[4]),
|
|
||||||
j.Edge(host.vtxs[4], host.vtxs[2]),
|
|
||||||
|
|
||||||
# noise:
|
|
||||||
j.Edge(host.vtxs[0], host.vtxs[1]),
|
|
||||||
j.Edge(host.vtxs[0], host.vtxs[3]),
|
|
||||||
j.Edge(host.vtxs[0], host.vtxs[0]),
|
|
||||||
j.Edge(host.vtxs[1], host.vtxs[1]),
|
|
||||||
|
|
||||||
# will be a match:
|
|
||||||
j.Edge(host.vtxs[3], host.vtxs[2]),
|
|
||||||
j.Edge(host.vtxs[2], host.vtxs[3]),
|
|
||||||
]
|
|
||||||
|
|
||||||
guest = j.Graph()
|
|
||||||
guest.vtxs = [
|
|
||||||
j.Vertex('square'), # 0
|
|
||||||
j.Vertex('circle'), # 1
|
|
||||||
j.Vertex('square')] # 2
|
|
||||||
guest.edges = [
|
|
||||||
j.Edge(guest.vtxs[0], guest.vtxs[1]),
|
|
||||||
j.Edge(guest.vtxs[1], guest.vtxs[0]),
|
|
||||||
j.Edge(guest.vtxs[2], guest.vtxs[0]),
|
|
||||||
]
|
|
||||||
|
|
||||||
# this time, only 2 matches
|
|
||||||
|
|
||||||
run_benchmark(host, guest, j_to_s(host), j_to_s(guest), expected=2)
|
|
||||||
|
|
||||||
#######################################################################
|
|
||||||
|
|
||||||
print("\nBENCHMARK: disconnected pattern")
|
|
||||||
|
|
||||||
host = j.Graph()
|
|
||||||
host.vtxs = [
|
|
||||||
j.Vertex('triangle'), # 0
|
|
||||||
j.Vertex('square'), # 1
|
|
||||||
j.Vertex('square'), # 2
|
|
||||||
j.Vertex('circle'), # 3
|
|
||||||
j.Vertex('circle'), # 4
|
|
||||||
j.Vertex('circle'), # 5
|
|
||||||
j.Vertex('bear'),
|
|
||||||
j.Vertex('bear'),
|
|
||||||
]
|
|
||||||
host.edges = [
|
|
||||||
# not a match:
|
|
||||||
j.Edge(host.vtxs[0], host.vtxs[5]),
|
|
||||||
j.Edge(host.vtxs[5], host.vtxs[0]),
|
|
||||||
|
|
||||||
# will be a match:
|
|
||||||
j.Edge(host.vtxs[1], host.vtxs[5]),
|
|
||||||
j.Edge(host.vtxs[5], host.vtxs[1]),
|
|
||||||
|
|
||||||
# noise:
|
|
||||||
j.Edge(host.vtxs[1], host.vtxs[2]),
|
|
||||||
|
|
||||||
# will be a match:
|
|
||||||
j.Edge(host.vtxs[2], host.vtxs[4]),
|
|
||||||
j.Edge(host.vtxs[4], host.vtxs[2]),
|
|
||||||
|
|
||||||
# noise:
|
|
||||||
j.Edge(host.vtxs[0], host.vtxs[1]),
|
|
||||||
j.Edge(host.vtxs[0], host.vtxs[3]),
|
|
||||||
j.Edge(host.vtxs[0], host.vtxs[0]),
|
|
||||||
j.Edge(host.vtxs[1], host.vtxs[1]),
|
|
||||||
|
|
||||||
# will be a match:
|
|
||||||
j.Edge(host.vtxs[3], host.vtxs[2]),
|
|
||||||
j.Edge(host.vtxs[2], host.vtxs[3]),
|
|
||||||
]
|
|
||||||
|
|
||||||
guest = j.Graph()
|
|
||||||
guest.vtxs = [
|
|
||||||
j.Vertex('square'), # 0
|
|
||||||
j.Vertex('circle'), # 1
|
|
||||||
j.Vertex('bear')]
|
|
||||||
guest.edges = [
|
|
||||||
j.Edge(guest.vtxs[0], guest.vtxs[1]),
|
|
||||||
j.Edge(guest.vtxs[1], guest.vtxs[0]),
|
|
||||||
]
|
|
||||||
|
|
||||||
# the 'bear' in our pattern can be matched with any of the two bears in the graph, effectively doubling the number of matches
|
|
||||||
|
|
||||||
run_benchmark(host, guest, j_to_s(host), j_to_s(guest), expected=6)
|
|
||||||
|
|
||||||
#######################################################################
|
|
||||||
|
|
||||||
print("\nBENCHMARK: larger graph")
|
|
||||||
|
|
||||||
shost, sguest = generator.get_large_host_and_guest()
|
|
||||||
run_benchmark(s_to_j(shost), s_to_j(sguest), shost, sguest)
|
|
||||||
|
|
||||||
#######################################################################
|
|
||||||
|
|
||||||
print("\nBENCHMARK: large random graph")
|
|
||||||
|
|
||||||
import random
|
|
||||||
random.seed(0)
|
|
||||||
|
|
||||||
shost, sguest = generator.get_random_host_and_guest(
|
|
||||||
nr_vtxs = 10,
|
|
||||||
nr_vtx_types = 0,
|
|
||||||
nr_edges = 20,
|
|
||||||
nr_edge_types = 0,
|
|
||||||
)
|
|
||||||
run_benchmark(s_to_j(shost), s_to_j(sguest), shost, sguest)
|
|
||||||
|
|
||||||
|
|
@ -1,232 +0,0 @@
|
||||||
# coding: utf-8
|
|
||||||
|
|
||||||
"""
|
|
||||||
Author: Sten Vercamman
|
|
||||||
Univeristy of Antwerp
|
|
||||||
|
|
||||||
Example code for paper: Efficient model transformations for novices
|
|
||||||
url: http://msdl.cs.mcgill.ca/people/hv/teaching/MSBDesign/projects/Sten.Vercammen
|
|
||||||
|
|
||||||
The main goal of this code is to give an overview, and an understandable
|
|
||||||
implementation, of known techniques for pattern matching and solving the
|
|
||||||
sub-graph homomorphism problem. The presented techniques do not include
|
|
||||||
performance adaptations/optimizations. It is not optimized to be efficient
|
|
||||||
but rather for the ease of understanding the workings of the algorithms.
|
|
||||||
The paper does list some possible extensions/optimizations.
|
|
||||||
|
|
||||||
It is intended as a guideline, even for novices, and provides an in-depth look
|
|
||||||
at the workings behind various techniques for efficient pattern matching.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import graph
|
|
||||||
# import numpy as np
|
|
||||||
import math
|
|
||||||
import collections
|
|
||||||
import random
|
|
||||||
|
|
||||||
class GraphGenerator(object):
|
|
||||||
"""
|
|
||||||
Generates a random Graph with dv an array containing all vertices (there type),
|
|
||||||
de an array containing all edges (their type) and dc_inc an array representing
|
|
||||||
the incoming edges (analogue for dc_out)
|
|
||||||
"""
|
|
||||||
def __init__(self, dv, de, dc_inc, dc_out, debug=False):
|
|
||||||
if len(de) != len(dc_inc):
|
|
||||||
raise ValueError('de and dc_inc should be the same length.')
|
|
||||||
if len(de) != len(dc_out):
|
|
||||||
raise ValueError('de and dc_out should be the same length.')
|
|
||||||
|
|
||||||
self.dv = dv
|
|
||||||
self.de = de
|
|
||||||
self.dc_inc = dc_inc
|
|
||||||
self.dc_out = dc_out
|
|
||||||
|
|
||||||
# print for debugging, so you know the used values
|
|
||||||
if debug:
|
|
||||||
print('dv')
|
|
||||||
print('[',','.join(map(str,dv)),']')
|
|
||||||
print('_____')
|
|
||||||
print('de')
|
|
||||||
print('[',','.join(map(str,de)),']')
|
|
||||||
print('_____')
|
|
||||||
print('dc_inc')
|
|
||||||
print('[',','.join(map(str,dc_inc)),']')
|
|
||||||
print('_____')
|
|
||||||
print('dc_out')
|
|
||||||
print('[',','.join(map(str,dc_out)),']')
|
|
||||||
print('_____')
|
|
||||||
|
|
||||||
self.graph = graph.Graph()
|
|
||||||
self.vertices = []
|
|
||||||
# create all the vertices:
|
|
||||||
for v_type in self.dv:
|
|
||||||
# v_type represents the type of the vertex
|
|
||||||
self.vertices.append(self.graph.addCreateVertex('v' + str(v_type)))
|
|
||||||
|
|
||||||
index = 0
|
|
||||||
# create all edges
|
|
||||||
for e_type in self.de:
|
|
||||||
# e_type represents the type of the edge
|
|
||||||
src = self.vertices[self.dc_out[index]] # get src vertex
|
|
||||||
tgt = self.vertices[self.dc_inc[index]] # get tgt vertex
|
|
||||||
self.graph.addCreateEdge(src, tgt, 'e' + str(e_type)) # create edge
|
|
||||||
index += 1
|
|
||||||
|
|
||||||
def getRandomGraph(self):
|
|
||||||
return self.graph
|
|
||||||
|
|
||||||
def getRandomPattern(self, max_nr_of_v, max_nr_of_e, start=0, debug=False):
|
|
||||||
# create pattern
|
|
||||||
pattern = graph.Graph()
|
|
||||||
|
|
||||||
# map from graph to new pattern
|
|
||||||
graph_to_pattern = {}
|
|
||||||
|
|
||||||
# map of possible edges
|
|
||||||
# we don't need a dict, but python v2.7 does not have an OrderedSet
|
|
||||||
possible_edges = collections.OrderedDict()
|
|
||||||
|
|
||||||
# set of chosen edges
|
|
||||||
chosen_edges = set()
|
|
||||||
|
|
||||||
# start node from graph
|
|
||||||
g_node = self.vertices[start]
|
|
||||||
p_node = pattern.addCreateVertex(g_node.type)
|
|
||||||
# for debuging, print the order in which the pattern gets created and
|
|
||||||
# connects it edges
|
|
||||||
if debug:
|
|
||||||
print('v'+str(id(p_node))+'=pattern.addCreateVertex('+"'"+str(g_node.type)+"'"+')')
|
|
||||||
# save corrolation
|
|
||||||
graph_to_pattern[g_node] = p_node
|
|
||||||
|
|
||||||
def insertAllEdges(edges, possible_edges, chosen_edges):
|
|
||||||
for edge in edges:
|
|
||||||
# if we did not chose the edge
|
|
||||||
if edge not in chosen_edges:
|
|
||||||
# if inc_edge not in possible edges, add it with value 1
|
|
||||||
possible_edges[edge] = None
|
|
||||||
|
|
||||||
def insertEdges(g_vertex, possible_edges, chosen_edges):
|
|
||||||
insertAllEdges(g_vertex.incoming_edges, possible_edges, chosen_edges)
|
|
||||||
insertAllEdges(g_vertex.outgoing_edges, possible_edges, chosen_edges)
|
|
||||||
|
|
||||||
insertEdges(g_node, possible_edges, chosen_edges)
|
|
||||||
|
|
||||||
while max_nr_of_v > len(graph_to_pattern) and max_nr_of_e > len(chosen_edges):
|
|
||||||
candidate = None
|
|
||||||
if len(possible_edges) == 0:
|
|
||||||
break
|
|
||||||
# get a random number between 0 and len(possible_edges)
|
|
||||||
# We us a triangular distribution to approximate the fact that
|
|
||||||
# the first element is the longest in the possible_edges and
|
|
||||||
# already had the post chance of beeing choosen.
|
|
||||||
# (The approximation is because the first few ellements where
|
|
||||||
# added in the same itteration, but doing this exact is
|
|
||||||
# computationally expensive.)
|
|
||||||
if len(possible_edges) == 1:
|
|
||||||
randie = 0
|
|
||||||
else:
|
|
||||||
randie = int(round(random.triangular(1, len(possible_edges), len(possible_edges)))) - 1
|
|
||||||
candidate = list(possible_edges.keys())[randie]
|
|
||||||
del possible_edges[candidate]
|
|
||||||
chosen_edges.add(candidate)
|
|
||||||
|
|
||||||
src = graph_to_pattern.get(candidate.src)
|
|
||||||
tgt = graph_to_pattern.get(candidate.tgt)
|
|
||||||
src_is_new = True
|
|
||||||
if src != None and tgt != None:
|
|
||||||
# create edge between source and target
|
|
||||||
pattern.addCreateEdge(src, tgt, candidate.type)
|
|
||||||
if debug:
|
|
||||||
print('pattern.addCreateEdge('+'v'+str(id(src))+', '+'v'+str(id(tgt))+', '+"'"+str(candidate.type)+"'"+')')
|
|
||||||
# skip adding new edges
|
|
||||||
continue
|
|
||||||
elif src == None:
|
|
||||||
# create pattern vertex
|
|
||||||
src = pattern.addCreateVertex(candidate.src.type)
|
|
||||||
if debug:
|
|
||||||
print('v'+str(id(src))+'=pattern.addCreateVertex('+"'"+str(candidate.src.type)+"'"+')')
|
|
||||||
# map newly created pattern vertex
|
|
||||||
graph_to_pattern[candidate.src] = src
|
|
||||||
# create edge between source and target
|
|
||||||
pattern.addCreateEdge(src, tgt, candidate.type)
|
|
||||||
if debug:
|
|
||||||
print('pattern.addCreateEdge('+'v'+str(id(src))+', '+'v'+str(id(tgt))+', '+"'"+str(candidate.type)+"'"+')')
|
|
||||||
elif tgt == None:
|
|
||||||
src_is_new = False
|
|
||||||
# create pattern vertex
|
|
||||||
tgt = pattern.addCreateVertex(candidate.tgt.type)
|
|
||||||
if debug:
|
|
||||||
print('v'+str(id(tgt))+'=pattern.addCreateVertex('+"'"+str(candidate.tgt.type)+"'"+')')
|
|
||||||
# map newly created pattern vertex
|
|
||||||
graph_to_pattern[candidate.tgt] = tgt
|
|
||||||
# create edge between source and target
|
|
||||||
pattern.addCreateEdge(src, tgt, candidate.type)
|
|
||||||
if debug:
|
|
||||||
print('pattern.addCreateEdge('+'v'+str(id(src))+', '+'v'+str(id(tgt))+', '+"'"+str(candidate.type)+"'"+')')
|
|
||||||
else:
|
|
||||||
raise RuntimeError('Bug: src or tgt of edge should be in out pattern')
|
|
||||||
|
|
||||||
# select the vertex from the chosen edge that was not yet part of the pattern
|
|
||||||
if src_is_new:
|
|
||||||
new_vertex = candidate.src
|
|
||||||
else:
|
|
||||||
new_vertex = candidate.tgt
|
|
||||||
# insert all edges from the new vertex
|
|
||||||
insertEdges(new_vertex, possible_edges, chosen_edges)
|
|
||||||
|
|
||||||
return pattern
|
|
||||||
|
|
||||||
def createConstantPattern():
|
|
||||||
"""
|
|
||||||
Use this to create the same pattern over and over again.
|
|
||||||
"""
|
|
||||||
# create pattern
|
|
||||||
pattern = graph.Graph()
|
|
||||||
|
|
||||||
|
|
||||||
# copy and paste printed pattern from debug output or create a pattern
|
|
||||||
# below the following line:
|
|
||||||
# ----------------------------------------------------------------------
|
|
||||||
v4447242448=pattern.addCreateVertex('v4')
|
|
||||||
v4457323088=pattern.addCreateVertex('v6')
|
|
||||||
pattern.addCreateEdge(v4447242448, v4457323088, 'e4')
|
|
||||||
v4457323216=pattern.addCreateVertex('v8')
|
|
||||||
pattern.addCreateEdge(v4457323216, v4447242448, 'e4')
|
|
||||||
v4457323344=pattern.addCreateVertex('v7')
|
|
||||||
pattern.addCreateEdge(v4457323216, v4457323344, 'e3')
|
|
||||||
v4457323472=pattern.addCreateVertex('v7')
|
|
||||||
pattern.addCreateEdge(v4457323344, v4457323472, 'e1')
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------
|
|
||||||
return pattern
|
|
||||||
|
|
||||||
def get_random_host_and_guest(nr_vtxs, nr_vtx_types, nr_edges, nr_edge_types, pattern_nr_vtxs=3, pattern_nr_edges=15):
|
|
||||||
dv = [random.randint(0, nr_vtx_types) for _ in range(nr_vtxs)]
|
|
||||||
de = [random.randint(0, nr_edge_types) for _ in range(nr_edges)]
|
|
||||||
dc_inc = [random.randint(0, nr_vtxs-1) for _ in range(nr_edges)]
|
|
||||||
dc_out = [random.randint(0, nr_vtxs-1) for _ in range(nr_edges)]
|
|
||||||
|
|
||||||
return get_host_and_guest(dv, de, dc_inc, dc_out, pattern_nr_vtxs, pattern_nr_edges)
|
|
||||||
|
|
||||||
def get_host_and_guest(dv, de, dc_inc, dc_out, pattern_nr_vtxs=3, pattern_nr_edges=15):
|
|
||||||
gg = GraphGenerator(dv, de, dc_inc, dc_out)
|
|
||||||
graph = gg.getRandomGraph()
|
|
||||||
pattern = gg.getRandomPattern(pattern_nr_vtxs, pattern_nr_edges, debug=False)
|
|
||||||
return (graph, pattern)
|
|
||||||
|
|
||||||
|
|
||||||
def get_large_host_and_guest():
|
|
||||||
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 ]
|
|
||||||
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 ]
|
|
||||||
return get_host_and_guest(dv, de, dc_inc, dc_out)
|
|
||||||
|
|
||||||
def get_small_host_and_guest():
|
|
||||||
dv = [0, 1, 0, 1, 0]
|
|
||||||
de = [0, 0, 0]
|
|
||||||
dc_inc = [0, 2, 4]
|
|
||||||
dc_out = [1, 3, 3]
|
|
||||||
return get_host_and_guest(dv, de, dc_inc, dc_out)
|
|
||||||
|
|
||||||
|
|
@ -1,160 +0,0 @@
|
||||||
# coding: utf-8
|
|
||||||
|
|
||||||
"""
|
|
||||||
Author: Sten Vercamman
|
|
||||||
Univeristy of Antwerp
|
|
||||||
|
|
||||||
Example code for paper: Efficient model transformations for novices
|
|
||||||
url: http://msdl.cs.mcgill.ca/people/hv/teaching/MSBDesign/projects/Sten.Vercammen
|
|
||||||
|
|
||||||
The main goal of this code is to give an overview, and an understandable
|
|
||||||
implementation, of known techniques for pattern matching and solving the
|
|
||||||
sub-graph homomorphism problem. The presented techniques do not include
|
|
||||||
performance adaptations/optimizations. It is not optimized to be efficient
|
|
||||||
but rather for the ease of understanding the workings of the algorithms.
|
|
||||||
The paper does list some possible extensions/optimizations.
|
|
||||||
|
|
||||||
It is intended as a guideline, even for novices, and provides an in-depth look
|
|
||||||
at the workings behind various techniques for efficient pattern matching.
|
|
||||||
"""
|
|
||||||
|
|
||||||
class Properties(object):
|
|
||||||
"""
|
|
||||||
Holds all Properties.
|
|
||||||
"""
|
|
||||||
def __init__(self):
|
|
||||||
# member variables:
|
|
||||||
self.properties = {}
|
|
||||||
|
|
||||||
def addProperty(self, name, value):
|
|
||||||
"""
|
|
||||||
Adds property (overrides if name already exists).
|
|
||||||
"""
|
|
||||||
self.properties[name] = value
|
|
||||||
|
|
||||||
def getProperty(self, name):
|
|
||||||
"""
|
|
||||||
Returns property with given name or None if not found.
|
|
||||||
"""
|
|
||||||
return self.properties.get(name)
|
|
||||||
|
|
||||||
class Edge(Properties):
|
|
||||||
"""
|
|
||||||
Describes an Edge with source and target Node.
|
|
||||||
The Edge can have several properties, like a name, a weight, etc...
|
|
||||||
"""
|
|
||||||
def __init__(self, src, tgt, str_type=None):
|
|
||||||
# Call parent class constructor
|
|
||||||
Properties.__init__(self)
|
|
||||||
# member variables:
|
|
||||||
self.src = src
|
|
||||||
self.tgt = tgt
|
|
||||||
self.type = str_type
|
|
||||||
|
|
||||||
class Vertex(Properties):
|
|
||||||
"""
|
|
||||||
Describes a Vertex with incoming, outgoing and undirected (both ways) edges.
|
|
||||||
The vertex can have several properties, like a name, a weight, etc...
|
|
||||||
"""
|
|
||||||
def __init__(self, str_type):
|
|
||||||
# Call parent class constructor
|
|
||||||
Properties.__init__(self)
|
|
||||||
# member variables:
|
|
||||||
self.incoming_edges = set() # undirected edges should be stored both in
|
|
||||||
self.outgoing_edges = set() # incoming and outgoing edges
|
|
||||||
self.type = str_type
|
|
||||||
|
|
||||||
def addIncomingEdge(self, edge):
|
|
||||||
"""
|
|
||||||
Adds an incoming Edge.
|
|
||||||
"""
|
|
||||||
if not isinstance(edge, Edge):
|
|
||||||
raise TypeError('addIncomingEdge without it being an edge')
|
|
||||||
self.incoming_edges.add(edge)
|
|
||||||
|
|
||||||
def addOutgoingEdge(self, edge):
|
|
||||||
"""
|
|
||||||
Adds an outgoing Edge.
|
|
||||||
"""
|
|
||||||
if not isinstance(edge, Edge):
|
|
||||||
raise TypeError('addOutgoingEdge without it being an edge')
|
|
||||||
self.outgoing_edges.add(edge)
|
|
||||||
|
|
||||||
def addUndirectedEdge(self, edge):
|
|
||||||
"""
|
|
||||||
Adds an undirected (or bi-directed) Edge.
|
|
||||||
"""
|
|
||||||
self.addIncomingEdge(edge)
|
|
||||||
self.addOutgoingEdge(edge)
|
|
||||||
|
|
||||||
class Graph(object):
|
|
||||||
"""
|
|
||||||
Holds a Graph.
|
|
||||||
"""
|
|
||||||
def __init__(self):
|
|
||||||
# member variables:
|
|
||||||
# redundant type keeping, "needed" for fast iterating over specific type
|
|
||||||
self.vertices = {} # {type, set(v1, v2, ...)}
|
|
||||||
self.edges = {} # {type, set(e1, e2, ...)}
|
|
||||||
self.num_vertices = 0
|
|
||||||
|
|
||||||
def addCreateVertex(self, str_type):
|
|
||||||
"""
|
|
||||||
Creates a Vertex of str_type, stores it and returs it
|
|
||||||
(so that properties can be added to it).
|
|
||||||
"""
|
|
||||||
vertex = Vertex(str_type)
|
|
||||||
self.addVertex(vertex)
|
|
||||||
return vertex
|
|
||||||
|
|
||||||
def addVertex(self, vertex):
|
|
||||||
"""
|
|
||||||
Stores a Vertex into the Graph.
|
|
||||||
"""
|
|
||||||
if not isinstance(vertex, Vertex):
|
|
||||||
raise TypeError('addVertex expects a Vertex')
|
|
||||||
# add vertex, but it first creates a new set for the vertex type
|
|
||||||
# if the type does not exist in the dictionary
|
|
||||||
if vertex not in self.vertices.get(vertex.type, set()):
|
|
||||||
self.num_vertices += 1
|
|
||||||
self.vertices.setdefault(vertex.type, set()).add(vertex)
|
|
||||||
|
|
||||||
def getVerticesOfType(self, str_type):
|
|
||||||
"""
|
|
||||||
Returns all vertices of a specific type,
|
|
||||||
Return [] if there are no vertices with the given type
|
|
||||||
"""
|
|
||||||
return self.vertices.get(str_type, [])
|
|
||||||
|
|
||||||
def getEdgesOfType(self, str_type):
|
|
||||||
"""
|
|
||||||
Returns all edges of a specific type,
|
|
||||||
Return [] if there are no edges with the given type
|
|
||||||
"""
|
|
||||||
return self.edges.get(str_type, [])
|
|
||||||
|
|
||||||
def addCreateEdge(self, src, tgt, str_type):
|
|
||||||
"""
|
|
||||||
Creates edge of str_type from src to tgt, and returns it,
|
|
||||||
so that properties can be added to the edge.
|
|
||||||
"""
|
|
||||||
if not isinstance(src, Vertex):
|
|
||||||
raise TypeError('addCreateEdge: src is not a Vertex')
|
|
||||||
if not isinstance(tgt, Vertex):
|
|
||||||
raise TypeError('addCreateEdge: tgt is not a Vertex')
|
|
||||||
edge = Edge(src, tgt, str_type)
|
|
||||||
# link vertices connected to this edge
|
|
||||||
edge.src.addOutgoingEdge(edge)
|
|
||||||
edge.tgt.addIncomingEdge(edge)
|
|
||||||
self.addEdge(edge)
|
|
||||||
return edge
|
|
||||||
|
|
||||||
def addEdge(self, edge):
|
|
||||||
"""
|
|
||||||
Stores an Edge into the Graph.
|
|
||||||
"""
|
|
||||||
if not isinstance(edge, Edge):
|
|
||||||
raise TypeError('addEdge expects an Edge')
|
|
||||||
# add edge, but it first creates a new set for the edge type
|
|
||||||
# if the type does not exist in the dictionary
|
|
||||||
self.edges.setdefault(edge.type, set()).add(edge)
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
# coding: utf-8
|
|
||||||
|
|
||||||
"""
|
|
||||||
Author: Sten Vercamman
|
|
||||||
Univeristy of Antwerp
|
|
||||||
|
|
||||||
Example code for paper: Efficient model transformations for novices
|
|
||||||
url: http://msdl.cs.mcgill.ca/people/hv/teaching/MSBDesign/projects/Sten.Vercammen
|
|
||||||
|
|
||||||
The main goal of this code is to give an overview, and an understandable
|
|
||||||
implementation, of known techniques for pattern matching and solving the
|
|
||||||
sub-graph homomorphism problem. The presented techniques do not include
|
|
||||||
performance adaptations/optimizations. It is not optimized to be efficient
|
|
||||||
but rather for the ease of understanding the workings of the algorithms.
|
|
||||||
The paper does list some possible extensions/optimizations.
|
|
||||||
|
|
||||||
It is intended as a guideline, even for novices, and provides an in-depth look
|
|
||||||
at the workings behind various techniques for efficient pattern matching.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import graph as mg
|
|
||||||
|
|
||||||
def printGraph(fileName, graph, matched_v={}, matched_e={}):
|
|
||||||
if not isinstance(graph, mg.Graph):
|
|
||||||
raise TypeError('Can only print Graph Graphs')
|
|
||||||
|
|
||||||
with open(fileName, 'w') as f:
|
|
||||||
f.write('digraph randomGraph {\n\n')
|
|
||||||
for str_type, plan_vertices in graph.vertices.items():
|
|
||||||
for plan_vertex in plan_vertices:
|
|
||||||
vertex_str = str(id(plan_vertex)) + ' [label="'+str(str_type)+'"'
|
|
||||||
if plan_vertex in list(matched_v.values()):
|
|
||||||
vertex_str += ', style=dashed, style=filled]\n'
|
|
||||||
else:
|
|
||||||
vertex_str += ']\n'
|
|
||||||
f.write(vertex_str)
|
|
||||||
for out_edge in plan_vertex.outgoing_edges:
|
|
||||||
edge_str = str(id(plan_vertex)) + ' -> ' + str(id(out_edge.tgt)) + ' [label="'+str(out_edge.type)+'"'
|
|
||||||
if out_edge in list(matched_e.values()):
|
|
||||||
edge_str += ', style=dashed, penwidth = 4]\n'
|
|
||||||
else:
|
|
||||||
edge_str += ']\n'
|
|
||||||
f.write(edge_str)
|
|
||||||
f.write('\n}')
|
|
||||||
|
|
@ -1,76 +0,0 @@
|
||||||
# coding: utf-8
|
|
||||||
|
|
||||||
"""
|
|
||||||
Author: Sten Vercamman
|
|
||||||
Univeristy of Antwerp
|
|
||||||
|
|
||||||
Example code for paper: Efficient model transformations for novices
|
|
||||||
url: http://msdl.cs.mcgill.ca/people/hv/teaching/MSBDesign/projects/Sten.Vercammen
|
|
||||||
|
|
||||||
The main goal of this code is to give an overview, and an understandable
|
|
||||||
implementation, of known techniques for pattern matching and solving the
|
|
||||||
sub-graph homomorphism problem. The presented techniques do not include
|
|
||||||
performance adaptations/optimizations. It is not optimized to be efficient
|
|
||||||
but rather for the ease of understanding the workings of the algorithms.
|
|
||||||
The paper does list some possible extensions/optimizations.
|
|
||||||
|
|
||||||
It is intended as a guideline, even for novices, and provides an in-depth look
|
|
||||||
at the workings behind various techniques for efficient pattern matching.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from generator import *
|
|
||||||
from patternMatching import *
|
|
||||||
|
|
||||||
import graphToDot
|
|
||||||
|
|
||||||
import random
|
|
||||||
|
|
||||||
debug = False
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
"""
|
|
||||||
The main function called when running from the command line.
|
|
||||||
"""
|
|
||||||
random.seed(0)
|
|
||||||
|
|
||||||
graph, pattern = get_random_host_and_guest(
|
|
||||||
nr_vtxs = 10,
|
|
||||||
nr_vtx_types = 0,
|
|
||||||
nr_edges = 20,
|
|
||||||
nr_edge_types = 0,
|
|
||||||
)
|
|
||||||
|
|
||||||
# graph, pattern = get_large_host_and_guest()
|
|
||||||
# graph, pattern = get_small_host_and_guest()
|
|
||||||
|
|
||||||
# override random pattern by copy pasting output from terminal to create
|
|
||||||
# pattern, paste it in the createConstantPattern function in the generator.py
|
|
||||||
# pattern = gg.createConstantPattern()
|
|
||||||
|
|
||||||
# generate here to know pattern and graph before searching it
|
|
||||||
graphToDot.printGraph('randomPattern.dot', pattern)
|
|
||||||
graphToDot.printGraph('randomGraph.dot', graph)
|
|
||||||
|
|
||||||
|
|
||||||
#PM = PatternMatching('naive')
|
|
||||||
#PM = PatternMatching('SP')
|
|
||||||
# PM = PatternMatching('Ullmann')
|
|
||||||
PM = PatternMatching('VF2')
|
|
||||||
matches = [m for m in PM.matchVF2(pattern, graph)]
|
|
||||||
print("found", len(matches), "matches:", matches)
|
|
||||||
|
|
||||||
# regenerate graph, to show matched pattern
|
|
||||||
for i, (v,e) in enumerate(matches):
|
|
||||||
graphToDot.printGraph(f'randomGraph-{i}.dot', graph, v, e)
|
|
||||||
|
|
||||||
if debug:
|
|
||||||
print(len(v))
|
|
||||||
print('___')
|
|
||||||
print(v)
|
|
||||||
for key, value in v.items():
|
|
||||||
print(value.type)
|
|
||||||
print(len(e))
|
|
||||||
print(e)
|
|
||||||
print('___')
|
|
||||||
for key, value in e.items():
|
|
||||||
print(value.type)
|
|
||||||
|
|
@ -1,603 +0,0 @@
|
||||||
# coding: utf-8
|
|
||||||
|
|
||||||
"""
|
|
||||||
Author: Sten Vercamman
|
|
||||||
Univeristy of Antwerp
|
|
||||||
|
|
||||||
Example code for paper: Efficient model transformations for novices
|
|
||||||
url: http://msdl.cs.mcgill.ca/people/hv/teaching/MSBDesign/projects/Sten.Vercammen
|
|
||||||
|
|
||||||
The main goal of this code is to give an overview, and an understandable
|
|
||||||
implementation, of known techniques for pattern matching and solving the
|
|
||||||
sub-graph homomorphism problem. The presented techniques do not include
|
|
||||||
performance adaptations/optimizations. It is not optimized to be efficient
|
|
||||||
but rather for the ease of understanding the workings of the algorithms.
|
|
||||||
The paper does list some possible extensions/optimizations.
|
|
||||||
|
|
||||||
It is intended as a guideline, even for novices, and provides an in-depth look
|
|
||||||
at the workings behind various techniques for efficient pattern matching.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import collections
|
|
||||||
import itertools
|
|
||||||
|
|
||||||
class PatternMatching(object):
|
|
||||||
"""
|
|
||||||
Returns an occurrence of a given pattern from the given Graph
|
|
||||||
"""
|
|
||||||
def __init__(self, optimize=True):
|
|
||||||
self.optimize = optimize
|
|
||||||
|
|
||||||
def matchNaive(self, pattern, vertices, edges, pattern_vertices=None):
|
|
||||||
"""
|
|
||||||
Try to find an occurrence of the pattern in the Graph naively.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# print('matchNaive...')
|
|
||||||
# print('pattern.vertices:', pattern.vertices)
|
|
||||||
# print('pattern.edges:', pattern.edges)
|
|
||||||
# print('vertices:', vertices)
|
|
||||||
# print('edges:', edges)
|
|
||||||
# print('pattern_vertices:', pattern_vertices)
|
|
||||||
|
|
||||||
# allow call with specific arguments
|
|
||||||
if pattern_vertices == None:
|
|
||||||
pattern_vertices = pattern.vertices
|
|
||||||
|
|
||||||
def visitEdge(pattern_vertices, p_edge, inc, g_edges, visited_p_vertices, visited_p_edges, visited_g_vertices, visited_g_edges, vertices, edges):
|
|
||||||
# print('visitEdge')
|
|
||||||
"""
|
|
||||||
Visit a pattern edge, and try to bind it to a graph edge.
|
|
||||||
(If the first fails, try the second, and so on...)
|
|
||||||
"""
|
|
||||||
for g_edge in g_edges:
|
|
||||||
# only reckon the edge if its in edges and not visited
|
|
||||||
# (as the graph might be a subgraph of a more complex graph)
|
|
||||||
if g_edge not in edges.get(g_edge.type, []) or g_edge in visited_g_edges:
|
|
||||||
continue
|
|
||||||
if g_edge.type == p_edge.type and g_edge not in visited_g_edges:
|
|
||||||
visited_p_edges[p_edge] = g_edge
|
|
||||||
visited_g_edges.add(g_edge)
|
|
||||||
if inc:
|
|
||||||
p_vertex = p_edge.src
|
|
||||||
else:
|
|
||||||
p_vertex = p_edge.tgt
|
|
||||||
if visitVertices(pattern_vertices, p_vertex, visited_p_vertices, visited_p_edges, visited_g_vertices, visited_g_edges, vertices, edges):
|
|
||||||
return True
|
|
||||||
# remove added edges if they lead to no match, retry with others
|
|
||||||
del visited_p_edges[p_edge]
|
|
||||||
visited_g_edges.remove(g_edge)
|
|
||||||
# no edge leads to a possitive match
|
|
||||||
return False
|
|
||||||
|
|
||||||
def visitEdges(pattern_vertices, p_edges, inc, g_edges, visited_p_vertices, visited_p_edges, visited_g_vertices, visited_g_edges, vertices, edges):
|
|
||||||
# print('visitEdges')
|
|
||||||
"""
|
|
||||||
Visit all edges of the pattern vertex (edges given as argument).
|
|
||||||
We need to try visiting them for all its permutations, as matching
|
|
||||||
v -e1-> first and v -e2-> second and v -e3-> third, might not result
|
|
||||||
in a matching an occurrence of the pattern, but matching v -e2->
|
|
||||||
first and v -e3-> second and v -e1-> third might.
|
|
||||||
"""
|
|
||||||
def removePrevEdge(visitedEdges, visited_p_edges, visited_g_edges):
|
|
||||||
"""
|
|
||||||
Undo the binding of the brevious edge, (the current bindinds do
|
|
||||||
not lead to an occurrence of the pattern in the graph).
|
|
||||||
"""
|
|
||||||
for wrong_edge in visitedEdges:
|
|
||||||
# remove binding (pattern edge to graph edge)
|
|
||||||
wrong_g_edge = visited_p_edges.get(wrong_edge)
|
|
||||||
del visited_p_edges[wrong_edge]
|
|
||||||
# remove visited graph edge
|
|
||||||
visited_g_edges.remove(wrong_g_edge)
|
|
||||||
|
|
||||||
for it in itertools.permutations(p_edges):
|
|
||||||
visitedEdges = []
|
|
||||||
foundallEdges = True
|
|
||||||
for edge in it:
|
|
||||||
if visited_p_edges.get(edge) == None:
|
|
||||||
if not visitEdge(pattern_vertices, edge, inc, g_edges, visited_p_vertices, visited_p_edges, visited_g_vertices, visited_g_edges, vertices, edges):
|
|
||||||
# this did not work, so we have to undo all added edges
|
|
||||||
# (the current edge is not added, as it failed)
|
|
||||||
# we then can try a different permutation
|
|
||||||
removePrevEdge(visitedEdges, visited_p_edges, visited_g_edges)
|
|
||||||
foundallEdges = False
|
|
||||||
break # try other order
|
|
||||||
# add good visited (we know it succeeded)
|
|
||||||
visitedEdges.append(edge)
|
|
||||||
else:
|
|
||||||
# we visited this pattern edge, and have the coressponding graph edge
|
|
||||||
# if it is an incoming pattern edge, we need to make sure that
|
|
||||||
# the graph target that is map from the pattern target
|
|
||||||
# (of this incoming pattern edge, which has to be bound at this point)
|
|
||||||
# has the graph adge as an incoming edge,
|
|
||||||
# otherwise the graph is not properly connected
|
|
||||||
if inc:
|
|
||||||
if not visited_p_edges[edge] in visited_p_vertices[edge.tgt].incoming_edges:
|
|
||||||
# did not work
|
|
||||||
removePrevEdge(visitedEdges, visited_p_edges, visited_g_edges)
|
|
||||||
foundallEdges = False
|
|
||||||
break # try other order
|
|
||||||
else:
|
|
||||||
# analog for an outgoing edge
|
|
||||||
if not visited_p_edges[edge] in visited_p_vertices[edge.src].outgoing_edges:
|
|
||||||
# did not work
|
|
||||||
removePrevEdge(visitedEdges, visited_p_edges, visited_g_edges)
|
|
||||||
foundallEdges = False
|
|
||||||
break # try other order
|
|
||||||
|
|
||||||
# all edges are good, look no further
|
|
||||||
if foundallEdges:
|
|
||||||
break
|
|
||||||
return foundallEdges
|
|
||||||
|
|
||||||
def visitVertex(pattern_vertices, p_vertex, g_vertex, visited_p_vertices, visited_p_edges, visited_g_vertices, visited_g_edges, vertices, edges):
|
|
||||||
# print('visitVertex')
|
|
||||||
"""
|
|
||||||
Visit a pattern vertex, and try to bind it to the graph vertex
|
|
||||||
(both are given as argument). A binding is successful if all the
|
|
||||||
pattern vertex his incoming and outgoing edges can be bound
|
|
||||||
(to the graph vertex).
|
|
||||||
"""
|
|
||||||
if g_vertex in visited_g_vertices:
|
|
||||||
return False
|
|
||||||
# save visited graph vertex
|
|
||||||
visited_g_vertices.add(g_vertex)
|
|
||||||
# map pattern vertex to visited graph vertex
|
|
||||||
visited_p_vertices[p_vertex] = g_vertex
|
|
||||||
|
|
||||||
if visitEdges(pattern_vertices, p_vertex.incoming_edges, True, g_vertex.incoming_edges, visited_p_vertices, visited_p_edges, visited_g_vertices, visited_g_edges, vertices, edges):
|
|
||||||
if visitEdges(pattern_vertices, p_vertex.outgoing_edges, False, g_vertex.outgoing_edges, visited_p_vertices, visited_p_edges, visited_g_vertices, visited_g_edges, vertices, edges):
|
|
||||||
return True
|
|
||||||
# cleanup, remove from visited as this does not lead to
|
|
||||||
# an occurrence of the pttern in the graph
|
|
||||||
visited_g_vertices.remove(g_vertex)
|
|
||||||
del visited_p_vertices[p_vertex]
|
|
||||||
return False
|
|
||||||
|
|
||||||
def visitVertices(pattern_vertices, p_vertex, visited_p_vertices, visited_p_edges, visited_g_vertices, visited_g_edges, vertices, edges):
|
|
||||||
# print('visitVertices')
|
|
||||||
"""
|
|
||||||
Visit a pattern vertex and try to bind a graph vertex to it.
|
|
||||||
"""
|
|
||||||
# if already matched or if it is a vertex not in the pattern_vertices
|
|
||||||
# (second is for when you want to match the pattern partionally)
|
|
||||||
if visited_p_vertices.get(p_vertex) != None or p_vertex not in pattern_vertices.get(p_vertex.type, set()):
|
|
||||||
return True
|
|
||||||
|
|
||||||
# try visiting graph vertices of same type as pattern vertex
|
|
||||||
for g_vertex in vertices.get(p_vertex.type, []):
|
|
||||||
if g_vertex not in visited_g_vertices:
|
|
||||||
if visitVertex(pattern_vertices, p_vertex, g_vertex, visited_p_vertices, visited_p_edges, visited_g_vertices, visited_g_edges, vertices, edges):
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
visited_p_vertices = {}
|
|
||||||
visited_p_edges = {}
|
|
||||||
visited_g_vertices = set()
|
|
||||||
visited_g_edges = set()
|
|
||||||
|
|
||||||
# for loop is need for when pattern consists of multiple not connected structures
|
|
||||||
allVertices = []
|
|
||||||
for _, p_vertices in pattern_vertices.items():
|
|
||||||
allVertices.extend(p_vertices)
|
|
||||||
foundIt = False
|
|
||||||
for it_p_vertices in itertools.permutations(allVertices):
|
|
||||||
foundIt = True
|
|
||||||
for p_vertex in it_p_vertices:
|
|
||||||
if not visitVertices(pattern_vertices, p_vertex, visited_p_vertices, visited_p_edges, visited_g_vertices, visited_g_edges, vertices, edges):
|
|
||||||
foundIt = False
|
|
||||||
# reset visited
|
|
||||||
visited_p_vertices = {}
|
|
||||||
visited_p_edges = {}
|
|
||||||
visited_g_vertices = set()
|
|
||||||
visited_g_edges = set()
|
|
||||||
break
|
|
||||||
if foundIt:
|
|
||||||
break
|
|
||||||
if foundIt:
|
|
||||||
return (visited_p_vertices, visited_p_edges)
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def createAdjacencyMatrixMap(self, graph, pattern):
|
|
||||||
"""
|
|
||||||
Return adjacency matrix and the order of the vertices.
|
|
||||||
"""
|
|
||||||
# print('createAdjacencyMatrixMap...')
|
|
||||||
# print('graph:', graph)
|
|
||||||
# print('pattern:', pattern)
|
|
||||||
|
|
||||||
matrix = collections.OrderedDict() # { vertex, (index, [has edge from index to pos?]) }
|
|
||||||
|
|
||||||
# contains all vertices we'll use for the AdjacencyMatrix
|
|
||||||
allVertices = []
|
|
||||||
|
|
||||||
if self.optimize:
|
|
||||||
# insert only the vertices from the graph which have a type
|
|
||||||
# that is present in the pattern
|
|
||||||
for vertex_type, _ in pattern.vertices.items():
|
|
||||||
graph_vertices = graph.vertices.get(vertex_type)
|
|
||||||
if graph_vertices != None:
|
|
||||||
allVertices.extend(graph_vertices)
|
|
||||||
else:
|
|
||||||
# we will not be able to find the pattern
|
|
||||||
# as the pattern contains a vertex of a certain type
|
|
||||||
# that is not present in the host graph
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
# insert all vertices from the graph
|
|
||||||
for _, vertices in graph.vertices.items():
|
|
||||||
allVertices.extend(vertices)
|
|
||||||
|
|
||||||
# create squared zero matrix
|
|
||||||
index = 0
|
|
||||||
for vertex in allVertices:
|
|
||||||
matrix[vertex] = (index, [False] * len(allVertices))
|
|
||||||
index += 1
|
|
||||||
|
|
||||||
for _, edges in graph.edges.items():
|
|
||||||
for edge in edges:
|
|
||||||
if self.optimize:
|
|
||||||
if edge.tgt not in matrix or edge.src not in matrix:
|
|
||||||
# skip adding edge if the target or source type
|
|
||||||
# is not present in the pattern
|
|
||||||
# (and therefor not added to the matrix)
|
|
||||||
continue
|
|
||||||
index = matrix[edge.tgt][0]
|
|
||||||
matrix[edge.src][1][index] = True
|
|
||||||
|
|
||||||
AM = []
|
|
||||||
vertices_order = []
|
|
||||||
for vertex, row in matrix.items():
|
|
||||||
AM.append(row[1])
|
|
||||||
vertices_order.append(vertex)
|
|
||||||
|
|
||||||
return AM, vertices_order
|
|
||||||
|
|
||||||
def matchVF2(self, pattern, graph):
|
|
||||||
# print('matchVF2...')
|
|
||||||
# print('pattern:', pattern)
|
|
||||||
# print('graph:', graph)
|
|
||||||
|
|
||||||
class VF2_Obj(object):
|
|
||||||
"""
|
|
||||||
Structor for keeping the VF2 data.
|
|
||||||
"""
|
|
||||||
def __init__(self, len_graph_vertices, len_pattern_vertices):
|
|
||||||
# represents if n-the element (h[n] or p[n]) matched
|
|
||||||
self.host_vtx_is_matched = [False]*len_graph_vertices
|
|
||||||
self.pattern_vtx_is_matched = [False]*len_pattern_vertices
|
|
||||||
|
|
||||||
# save mapping from pattern to graph
|
|
||||||
self.mapping = {}
|
|
||||||
self.edge_mapping = {}
|
|
||||||
|
|
||||||
# preference lvl 1
|
|
||||||
# ordered set of vertices adjecent to M_graph connected via an outgoing edge
|
|
||||||
self.N_out_graph = [-1]*len_graph_vertices
|
|
||||||
# ordered set of vertices adjecent to M_pattern connected via an outgoing edge
|
|
||||||
self.N_out_pattern = [-1]*len_pattern_vertices
|
|
||||||
|
|
||||||
# preference lvl 2
|
|
||||||
# ordered set of vertices adjecent to M_graph connected via an incoming edge
|
|
||||||
self.N_inc_graph = [-1]*len_graph_vertices
|
|
||||||
# ordered set of vertices adjecent to M_pattern connected via an incoming edge
|
|
||||||
self.N_inc_pattern = [-1]*len_pattern_vertices
|
|
||||||
|
|
||||||
# preference lvl 3
|
|
||||||
# not in the above
|
|
||||||
|
|
||||||
def findM(H, P, h, p, VF2_obj, index_M=0):
|
|
||||||
"""
|
|
||||||
Find an isomorphic mapping for the vertices of P to H.
|
|
||||||
This mapping is represented by a matrix M if,
|
|
||||||
and only if M(MH)^T = P^T.
|
|
||||||
|
|
||||||
This operates in a simular way as Ullmann. Ullmann has a predefind
|
|
||||||
order for matching (sorted on most edges first). VF2's order is to
|
|
||||||
first try to match the adjacency vertices connected via outgoing
|
|
||||||
edges, then thos connected via incoming edges and then those that
|
|
||||||
not connected to the currently mathed vertices.
|
|
||||||
"""
|
|
||||||
def addOutNeighbours(neighbours, N, index_M):
|
|
||||||
"""
|
|
||||||
Given outgoing neighbours (a row from an adjacency matrix),
|
|
||||||
label them as added by saving when they got added (index_M
|
|
||||||
represents this, otherwise it is -1)
|
|
||||||
"""
|
|
||||||
for neighbour_index in range(0, len(neighbours)):
|
|
||||||
if neighbours[neighbour_index]:
|
|
||||||
if N[neighbour_index] == -1:
|
|
||||||
N[neighbour_index] = index_M
|
|
||||||
|
|
||||||
def addIncNeighbours(G, j, N, index_M):
|
|
||||||
"""
|
|
||||||
Given the adjacency matrix, and the colum j, representing that
|
|
||||||
we want to add the incoming edges to vertex j,
|
|
||||||
label them as added by saving when they got added (index_M
|
|
||||||
represents this, otherwise it is -1)
|
|
||||||
"""
|
|
||||||
for i in range(0, len(G)):
|
|
||||||
if G[i][j]:
|
|
||||||
if N[i] == -1:
|
|
||||||
N[i] = index_M
|
|
||||||
|
|
||||||
def delNeighbours(N, index_M):
|
|
||||||
"""
|
|
||||||
Remove neighbours that where added at index_M.
|
|
||||||
If we call this function, we are backtracking and we want to
|
|
||||||
remove the added neighbours from the just tried matching (n, m)
|
|
||||||
pair (whiched failed).
|
|
||||||
"""
|
|
||||||
for n in range(0, len(N)):
|
|
||||||
if N[n] == index_M:
|
|
||||||
N[n] = -1
|
|
||||||
|
|
||||||
def feasibilityTest(H, P, h, p, VF2_obj, n, m):
|
|
||||||
"""
|
|
||||||
Examine all the nodes connected to n and m; if such nodes are
|
|
||||||
in the current partial mapping, check if each branch from or to
|
|
||||||
n has a corresponding branch from or to m and vice versa.
|
|
||||||
|
|
||||||
If the nodes and the branches of the graphs being matched also
|
|
||||||
carry semantic attributes, another condition must also hold for
|
|
||||||
F(s, n, m) to be true; namely the attributes of the nodes and of
|
|
||||||
the branches being paired must be compatible.
|
|
||||||
|
|
||||||
Another pruning step is to check if the nr of ext_edges between
|
|
||||||
the matched_vertices from the pattern and its adjecent vertices
|
|
||||||
are less than or equal to the nr of ext_edges between
|
|
||||||
matched_vertices from the graph and its adjecent vertices.
|
|
||||||
|
|
||||||
And if the nr of ext_edges between those adjecent vertices from
|
|
||||||
the pattern and the not connected vertices are less than or
|
|
||||||
equal to the nr of ext_edges between those adjecent vertices from
|
|
||||||
the graph and its adjecent vertices.
|
|
||||||
"""
|
|
||||||
# Get all neighbours from graph node n and pattern node m
|
|
||||||
# (including n and m)
|
|
||||||
neighbours_graph = {}
|
|
||||||
neighbours_graph[h[n].type] = set([h[n]])
|
|
||||||
|
|
||||||
neighbours_pattern = {}
|
|
||||||
neighbours_pattern[p[m].type] = set([p[m]])
|
|
||||||
|
|
||||||
# add all neihgbours of pattern vertex m
|
|
||||||
for i in range(0, len(P)): # P is a nxn-matrix
|
|
||||||
if (P[m][i] or P[i][m]) and VF2_obj.pattern_vtx_is_matched[i]:
|
|
||||||
neighbours_pattern.setdefault(p[i].type, set()).add(p[i])
|
|
||||||
|
|
||||||
# add all neihgbours of graph vertex n
|
|
||||||
for i in range(0, len(H)): # P is a nxn-matrix
|
|
||||||
if (H[n][i] or H[i][n]) and VF2_obj.host_vtx_is_matched[i]:
|
|
||||||
neighbours_graph.setdefault(h[i].type, set()).add(h[i])
|
|
||||||
|
|
||||||
# take a coding shortcut,
|
|
||||||
# use self.matchNaive function to see if it is feasable.
|
|
||||||
# this way, we immidiatly test the semantic attributes
|
|
||||||
# print('pattern.vertices', pattern.vertices)
|
|
||||||
matched = self.matchNaive(pattern, pattern_vertices=neighbours_pattern, vertices=neighbours_graph, edges=graph.edges)
|
|
||||||
if matched == None:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# count ext_edges from host_vtx_is_matched to a adjecent vertices and
|
|
||||||
# cuotn ext_edges for adjecent vertices and not matched vertices
|
|
||||||
# connected via the ext_edges
|
|
||||||
ext_edges_graph_ca = 0
|
|
||||||
ext_edges_graph_an = 0
|
|
||||||
# for all core vertices
|
|
||||||
for x in range(0, len(VF2_obj.host_vtx_is_matched)):
|
|
||||||
# for all its neighbours
|
|
||||||
for y in range(0, len(H)):
|
|
||||||
if H[x][y]:
|
|
||||||
# if it is a neighbor and not yet matched
|
|
||||||
if (VF2_obj.N_out_graph[y] != -1 or VF2_obj.N_inc_graph[y] != -1) and VF2_obj.host_vtx_is_matched[y]:
|
|
||||||
# if we matched it
|
|
||||||
if VF2_obj.host_vtx_is_matched[x] != -1:
|
|
||||||
ext_edges_graph_ca += 1
|
|
||||||
else:
|
|
||||||
ext_edges_graph_an += 1
|
|
||||||
|
|
||||||
# count ext_edges from pattern_vtx_is_matched to a adjecent vertices
|
|
||||||
# connected via the ext_edges
|
|
||||||
ext_edges_pattern_ca = 0
|
|
||||||
ext_edges_pattern_an = 0
|
|
||||||
# for all core vertices
|
|
||||||
for x in range(0, len(VF2_obj.pattern_vtx_is_matched)):
|
|
||||||
# for all its neighbours
|
|
||||||
for y in range(0, len(P)):
|
|
||||||
if P[x][y]:
|
|
||||||
# if it is a neighbor and not yet matched
|
|
||||||
if (VF2_obj.N_out_pattern[y] != -1 or VF2_obj.N_inc_pattern[y] != -1) and VF2_obj.pattern_vtx_is_matched[y]:
|
|
||||||
# if we matched it
|
|
||||||
if VF2_obj.pattern_vtx_is_matched[x] != -1:
|
|
||||||
ext_edges_pattern_ca += 1
|
|
||||||
else:
|
|
||||||
ext_edges_pattern_an += 1
|
|
||||||
|
|
||||||
# The nr of ext_edges between matched_vertices from the pattern
|
|
||||||
# and its adjecent vertices must be less than or equal to the nr
|
|
||||||
# of ext_edges between matched_vertices from the graph and its
|
|
||||||
# adjecent vertices, otherwise we wont find an occurrence
|
|
||||||
if ext_edges_pattern_ca > ext_edges_graph_ca:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# The nr of ext_edges between those adjancent vertices from the
|
|
||||||
# pattern and its not connected vertices must be less than or
|
|
||||||
# equal to the nr of ext_edges between those adjacent vertices
|
|
||||||
# from the graph and its not connected vertices,
|
|
||||||
# otherwise we wont find an occurrence
|
|
||||||
if ext_edges_pattern_an > ext_edges_graph_an:
|
|
||||||
return False
|
|
||||||
|
|
||||||
return matched
|
|
||||||
|
|
||||||
def matchPhase(index_M, VF2_obj, n, m):
|
|
||||||
"""
|
|
||||||
The matching fase of the VF2 algorithm. If the chosen n, m pair
|
|
||||||
passes the feasibilityTest, the pair gets added and we start
|
|
||||||
to search for the next matching pair.
|
|
||||||
"""
|
|
||||||
# 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
|
|
||||||
|
|
||||||
|
|
||||||
matched = feasibilityTest(H, P, h, p, VF2_obj, n, m)
|
|
||||||
|
|
||||||
if matched != False:
|
|
||||||
# print(self.indent*" ","adding to match:", n, "->", m)
|
|
||||||
# adapt VF2_obj
|
|
||||||
VF2_obj.host_vtx_is_matched[n] = True
|
|
||||||
VF2_obj.pattern_vtx_is_matched[m] = True
|
|
||||||
VF2_obj.mapping[h[n]] = p[m]
|
|
||||||
# VF2_obj.edge_mapping
|
|
||||||
addOutNeighbours(H[n], VF2_obj.N_out_graph, index_M)
|
|
||||||
addIncNeighbours(H, n, VF2_obj.N_inc_graph, index_M)
|
|
||||||
addOutNeighbours(P[m], VF2_obj.N_out_pattern, index_M)
|
|
||||||
addIncNeighbours(P, m, VF2_obj.N_inc_pattern, index_M)
|
|
||||||
|
|
||||||
if index_M > 0:
|
|
||||||
# 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)
|
|
||||||
|
|
||||||
self.indent += 1
|
|
||||||
matched = yield from findM(H, P, h, p, VF2_obj, index_M + 1)
|
|
||||||
if matched:
|
|
||||||
# return True
|
|
||||||
# print(self.indent*" ","found match", len(self.results), ", continuing...")
|
|
||||||
pass
|
|
||||||
self.indent -= 1
|
|
||||||
|
|
||||||
if True:
|
|
||||||
# else:
|
|
||||||
# print(self.indent*" ","backtracking... remove", n, "->", m)
|
|
||||||
|
|
||||||
# else, backtrack, adapt VF2_obj
|
|
||||||
VF2_obj.host_vtx_is_matched[n] = False
|
|
||||||
VF2_obj.pattern_vtx_is_matched[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
|
|
||||||
|
|
||||||
def preferred(index_M, VF2_obj, N_graph, N_pattern):
|
|
||||||
"""
|
|
||||||
Try to match the adjacency vertices connected via outgoing
|
|
||||||
or incoming edges. (Depending on what is given for N_graph and
|
|
||||||
N_pattern.)
|
|
||||||
"""
|
|
||||||
for n in range(0, len(N_graph)):
|
|
||||||
# skip graph vertices that are not in VF2_obj.N_out_graph
|
|
||||||
# (or already matched)
|
|
||||||
if N_graph[n] == -1 or VF2_obj.host_vtx_is_matched[n]:
|
|
||||||
# print(self.indent*" "," skipping")
|
|
||||||
continue
|
|
||||||
# print(self.indent*" "," n:", n)
|
|
||||||
for m in range(0, len(N_pattern)):
|
|
||||||
# skip graph vertices that are not in VF2_obj.N_out_pattern
|
|
||||||
# (or already matched)
|
|
||||||
if N_pattern[m] == -1 or VF2_obj.pattern_vtx_is_matched[m]:
|
|
||||||
continue
|
|
||||||
# print(self.indent*" "," m:", m)
|
|
||||||
matched = yield from matchPhase(index_M, VF2_obj, n, m)
|
|
||||||
if matched:
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
def leastPreferred(index_M, VF2_obj):
|
|
||||||
"""
|
|
||||||
Try to match the vertices that are not connected to the curretly
|
|
||||||
matched vertices.
|
|
||||||
"""
|
|
||||||
for n in range(0, len(VF2_obj.N_out_graph)):
|
|
||||||
# skip vertices that are connected to the graph
|
|
||||||
# (or already matched)
|
|
||||||
if not (VF2_obj.N_out_graph[n] == -1 and VF2_obj.N_inc_graph[n] == -1) or VF2_obj.host_vtx_is_matched[n]:
|
|
||||||
# print(self.indent*" "," skipping")
|
|
||||||
continue
|
|
||||||
# print(" n:", n)
|
|
||||||
for m in range(0, len(VF2_obj.N_out_pattern)):
|
|
||||||
# skip vertices that are connected to the graph
|
|
||||||
# (or already matched)
|
|
||||||
if not (VF2_obj.N_out_pattern[m] == -1 and VF2_obj.N_inc_pattern[m] == -1) or VF2_obj.pattern_vtx_is_matched[m]:
|
|
||||||
# print(self.indent*" "," skipping")
|
|
||||||
continue
|
|
||||||
# print(self.indent*" "," m:", m)
|
|
||||||
matched = yield from matchPhase(index_M, VF2_obj, n, m)
|
|
||||||
if matched:
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
# print(self.indent*" ","index_M:", index_M)
|
|
||||||
|
|
||||||
# We are at the end, we found an candidate.
|
|
||||||
if index_M == len(p):
|
|
||||||
# print(self.indent*" ","end...")
|
|
||||||
bound_graph_vertices = {}
|
|
||||||
for vertex_bound, _ in VF2_obj.mapping.items():
|
|
||||||
bound_graph_vertices.setdefault(vertex_bound.type, set()).add(vertex_bound)
|
|
||||||
|
|
||||||
result = self.matchNaive(pattern, vertices=bound_graph_vertices, edges=graph.edges)
|
|
||||||
if result != None:
|
|
||||||
yield result
|
|
||||||
return result != None
|
|
||||||
|
|
||||||
if index_M > 0:
|
|
||||||
# try the candidates is the preffered order
|
|
||||||
# first try the adjacent vertices connected via the outgoing edges.
|
|
||||||
# print(self.indent*" ","preferred L1")
|
|
||||||
matched = yield from preferred(index_M, VF2_obj, VF2_obj.N_out_graph, VF2_obj.N_out_pattern)
|
|
||||||
if matched:
|
|
||||||
return True
|
|
||||||
|
|
||||||
# print(self.indent*" ","preferred L2")
|
|
||||||
# then try the adjacent vertices connected via the incoming edges.
|
|
||||||
matched = yield from preferred(index_M, VF2_obj, VF2_obj.N_inc_graph, VF2_obj.N_inc_pattern)
|
|
||||||
if matched:
|
|
||||||
return True
|
|
||||||
|
|
||||||
# print(self.indent*" ","leastPreferred")
|
|
||||||
# and lastly, try the vertices not connected to the currently matched vertices
|
|
||||||
matched = yield from leastPreferred(index_M, VF2_obj)
|
|
||||||
if matched:
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
# create adjacency matrix of the graph
|
|
||||||
H, h = self.createAdjacencyMatrixMap(graph, pattern)
|
|
||||||
# create adjacency matrix of the pattern
|
|
||||||
P, p = self.createAdjacencyMatrixMap(pattern, pattern)
|
|
||||||
|
|
||||||
VF2_obj = VF2_Obj(len(h), len(p))
|
|
||||||
|
|
||||||
# Only for debugging:
|
|
||||||
self.indent = 0
|
|
||||||
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()
|
|
||||||
|
|
||||||
yield from findM(H, P, h, p, VF2_obj)
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
rm *.svg
|
|
||||||
rm *.dot
|
|
||||||
python main.py
|
|
||||||
# dot randomGraph.dot -Tsvg > randomGraph.svg
|
|
||||||
for filename in randomGraph-*.dot; do
|
|
||||||
dot $filename -Tsvg > $filename.svg
|
|
||||||
done
|
|
||||||
|
|
||||||
firefox *.svg
|
|
||||||
|
|
@ -3,7 +3,7 @@ from uuid import UUID
|
||||||
from services.bottom.V0 import Bottom
|
from services.bottom.V0 import Bottom
|
||||||
from services.scd import SCD
|
from services.scd import SCD
|
||||||
from services.od import OD
|
from services.od import OD
|
||||||
from pattern_matching.matcher import Graph, Edge, Vertex, MatcherVF2
|
from transformation.matcher.matcher import Graph, Edge, Vertex, MatcherVF2
|
||||||
from transformation import ramify
|
from transformation import ramify
|
||||||
import itertools
|
import itertools
|
||||||
import re
|
import re
|
||||||
Loading…
Add table
Add a link
Reference in a new issue