From 3ae35a87d09d57e93d73b1f0df93a773ad146ac9 Mon Sep 17 00:00:00 2001 From: Joeri Exelmans Date: Wed, 4 Sep 2024 09:34:26 +0200 Subject: [PATCH] pattern matching: convert tabs to spaces --- pattern_matching/generator.py | 314 ++++---- pattern_matching/graph.py | 244 +++--- pattern_matching/graphToDot.py | 44 +- pattern_matching/main.py | 114 +-- pattern_matching/patternMatching.py | 1128 ++++++++++++++------------- 5 files changed, 929 insertions(+), 915 deletions(-) diff --git a/pattern_matching/generator.py b/pattern_matching/generator.py index ba60b18..53e4b78 100644 --- a/pattern_matching/generator.py +++ b/pattern_matching/generator.py @@ -1,8 +1,8 @@ # coding: utf-8 """ -Author: Sten Vercamman - Univeristy of Antwerp +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 @@ -25,178 +25,178 @@ 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.') + """ + 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 + 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('_____') + # 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 + 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 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() + 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 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() + # 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() + # 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 + # 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 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) + 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) + 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) + 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') + 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) + # 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 + return pattern - def createConstantPattern(): - """ - Use this to create the same pattern over and over again. - """ - # create pattern - pattern = graph.Graph() + 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') + # 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 + # ---------------------------------------------------------------------- + return pattern diff --git a/pattern_matching/graph.py b/pattern_matching/graph.py index c9f62cc..de98fe7 100644 --- a/pattern_matching/graph.py +++ b/pattern_matching/graph.py @@ -1,8 +1,8 @@ # coding: utf-8 """ -Author: Sten Vercamman - Univeristy of Antwerp +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 @@ -19,139 +19,139 @@ at the workings behind various techniques for efficient pattern matching. """ class Properties(object): - """ - Holds all Properties. - """ - def __init__(self): - # member variables: - self.properties = {} + """ + 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 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) + 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 + """ + 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 + """ + 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 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 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) + 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, ...)} + """ + 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, ...)} + + 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 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 + self.vertices.setdefault(vertex.type, set()).add(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 - 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 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 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 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) \ No newline at end of file + 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) diff --git a/pattern_matching/graphToDot.py b/pattern_matching/graphToDot.py index 506a18f..a9aceb4 100644 --- a/pattern_matching/graphToDot.py +++ b/pattern_matching/graphToDot.py @@ -1,8 +1,8 @@ # coding: utf-8 """ -Author: Sten Vercamman - Univeristy of Antwerp +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 @@ -21,24 +21,24 @@ 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') + 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}') \ No newline at end of file + 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}') \ No newline at end of file diff --git a/pattern_matching/main.py b/pattern_matching/main.py index e0d734d..534ef3f 100644 --- a/pattern_matching/main.py +++ b/pattern_matching/main.py @@ -1,8 +1,8 @@ # coding: utf-8 """ -Author: Sten Vercamman - Univeristy of Antwerp +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 @@ -18,8 +18,8 @@ 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 * +from generator import * +from patternMatching import * import graphToDot @@ -28,66 +28,66 @@ import random debug = False if __name__ == '__main__': - """ - The main function called when running from the command line. - """ - nr_of_vertices = 50 - nr_of_diff_types_v = 2 - nr_of_edges = 150 - nr_of_diff_types_e = 2 + """ + The main function called when running from the command line. + """ + nr_of_vertices = 50 + nr_of_diff_types_v = 2 + nr_of_edges = 150 + nr_of_diff_types_e = 2 - dv = [random.randint(0, nr_of_diff_types_v) for _ in range(nr_of_vertices)] - de = [random.randint(0, nr_of_diff_types_e) for _ in range(nr_of_edges)] - dc_inc = [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)] + dv = [random.randint(0, nr_of_diff_types_v) for _ in range(nr_of_vertices)] + de = [random.randint(0, nr_of_diff_types_e) for _ in range(nr_of_edges)] + dc_inc = [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)] - # 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 ] - # 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 ] + # 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 ] + # 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 ] - 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) + 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) - graph = gg.getRandomGraph() + graph = gg.getRandomGraph() - print(graph.vertices) - pattern = gg.getRandomPattern(3, 15, debug=debug) - print(pattern.vertices) + print(graph.vertices) + pattern = gg.getRandomPattern(3, 15, debug=debug) + print(pattern.vertices) - # 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() + # 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) + # 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) + + #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) + # 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) \ No newline at end of file + 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) \ No newline at end of file diff --git a/pattern_matching/patternMatching.py b/pattern_matching/patternMatching.py index 6f181c9..3106a01 100644 --- a/pattern_matching/patternMatching.py +++ b/pattern_matching/patternMatching.py @@ -1,8 +1,8 @@ # coding: utf-8 """ -Author: Sten Vercamman - Univeristy of Antwerp +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 @@ -22,558 +22,572 @@ import collections import itertools class PatternMatching(object): - """ - Returns an occurrence of a given pattern from the given Graph - """ - def __init__(self, optimize=True): - # store the type of matching we want to use - self.optimize = optimize - - def matchNaive(self, pattern, vertices, edges, pattern_vertices=None): - """ - Try to find an occurrence of the pattern in the Graph naively. - """ - # 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): - """ - 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): - """ - 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): - """ - 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): - """ - 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. - """ - 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): - - 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.core_graph = [False]*len_graph_vertices - self.core_pattern = [False]*len_pattern_vertices - - # save mapping from pattern to graph - self.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.core_pattern[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.core_graph[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 - if not self.matchNaive(pattern, pattern_vertices=neighbours_pattern, vertices=neighbours_graph, edges=graph.edges): - return False - - # count ext_edges from core_graph 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.core_graph)): - # 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.core_graph[y]: - # if we matched it - if VF2_obj.core_graph[x] != -1: - ext_edges_graph_ca += 1 - else: - ext_edges_graph_an += 1 - - # count ext_edges from core_pattern 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.core_pattern)): - # 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.core_pattern[y]: - # if we matched it - if VF2_obj.core_pattern[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 True - - def matchPhase(H, P, h, p, 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 - - - if feasibilityTest(H, P, h, p, VF2_obj, n, m): - print(self.indent*" ","adding to match:", n, "->", m) - # adapt VF2_obj - VF2_obj.core_graph[n] = True - VF2_obj.core_pattern[m] = True - VF2_obj.mapping[h[n]] = p[m] - 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.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 - - def preferred(H, P, h, p, 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.core_graph[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.core_pattern[m]: - continue - print(self.indent*" "," m:", m) - matched = yield from matchPhase(H, P, h, p, index_M, VF2_obj, n, m) - if matched: - return True - - return False - - def leastPreferred(H, P, h, p, 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.core_graph[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.core_pattern[m]: - # print(self.indent*" "," skipping") - continue - print(self.indent*" "," m:", m) - matched = yield from matchPhase(H, P, h, p, 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(H, P, h, p, 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(H, P, h, p, 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(H, P, h, p, index_M, VF2_obj) - if matched: - return True - - return False - - # create adjecency matrix of the graph - H, h = self.createAdjacencyMatrixMap(graph, pattern) - # create adjecency 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) + """ + Returns an occurrence of a given pattern from the given Graph + """ + def __init__(self, optimize=True): + # store the type of matching we want to use + 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:', pattern) + 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): + """ + 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): + """ + 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): + """ + 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): + """ + 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.core_graph = [False]*len_graph_vertices + self.core_pattern = [False]*len_pattern_vertices + + # save mapping from pattern to graph + self.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.core_pattern[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.core_graph[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 + if not self.matchNaive(pattern, pattern_vertices=neighbours_pattern, vertices=neighbours_graph, edges=graph.edges): + return False + + # count ext_edges from core_graph 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.core_graph)): + # 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.core_graph[y]: + # if we matched it + if VF2_obj.core_graph[x] != -1: + ext_edges_graph_ca += 1 + else: + ext_edges_graph_an += 1 + + # count ext_edges from core_pattern 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.core_pattern)): + # 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.core_pattern[y]: + # if we matched it + if VF2_obj.core_pattern[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 True + + def matchPhase(H, P, h, p, 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 + + + if feasibilityTest(H, P, h, p, VF2_obj, n, m): + print(self.indent*" ","adding to match:", n, "->", m) + # adapt VF2_obj + VF2_obj.core_graph[n] = True + VF2_obj.core_pattern[m] = True + VF2_obj.mapping[h[n]] = p[m] + 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.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 + + def preferred(H, P, h, p, 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.core_graph[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.core_pattern[m]: + continue + print(self.indent*" "," m:", m) + matched = yield from matchPhase(H, P, h, p, index_M, VF2_obj, n, m) + if matched: + return True + + return False + + def leastPreferred(H, P, h, p, 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.core_graph[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.core_pattern[m]: + # print(self.indent*" "," skipping") + continue + print(self.indent*" "," m:", m) + matched = yield from matchPhase(H, P, h, p, 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(H, P, h, p, 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(H, P, h, p, 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(H, P, h, p, index_M, VF2_obj) + if matched: + return True + + return False + + # create adjecency matrix of the graph + H, h = self.createAdjacencyMatrixMap(graph, pattern) + # create adjecency 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)