diff --git a/experiments/exp_scd.py b/experiments/exp_scd.py index 2e206be..8f2d75e 100644 --- a/experiments/exp_scd.py +++ b/experiments/exp_scd.py @@ -6,9 +6,17 @@ from uuid import UUID from services.scd import SCD from framework.conformance import Conformance from services.od import OD +from transformation.ramify import ramify +from services.primitives.integer_type import Integer import sys +def create_integer_node(state, i: int): + node = state.create_node() + integer_t = Integer(node, state) + integer_t.create(i) + return node + def main(): state = DevState() root = state.read_root() # id: 0 @@ -42,11 +50,21 @@ def main(): print_tree(tgt, max_depth, depth+1) print("explore...") - print_tree(root, 1) + print_tree(root, 2) + + int_type_id = state.read_dict(state.read_root(), "Integer") + int_type = UUID(state.read_value(int_type_id)) + + scd2 = SCD(scd_node, state) + for el in scd2.list_elements(): + print(el) + model_id = state.create_node() scd = SCD(model_id, state) scd.create_class("A") + scd.create_model_ref("Integer", int_type) + scd.create_attribute_link("A", "Integer", "size", False) scd.create_class("B") scd.create_association("A2B", "A", "B", src_min_c=1, @@ -55,15 +73,16 @@ def main(): tgt_max_c=2, ) - print_tree(model_id, 1) + print_tree(model_id, 2) + conf = Conformance(state, model_id, scd_node) print("Check nominal conformance...") print(conf.check_nominal(log=True)) - print("Check structural conformance...") - print(conf.check_structural(log=True)) - print("Check nominal conformance (again)...") - print(conf.check_nominal(log=True)) + # print("Check structural conformance...") + # print(conf.check_structural(log=True)) + # print("Check nominal conformance (again)...") + # print(conf.check_nominal(log=True)) inst_id = state.create_node() od = OD(model_id, inst_id, state) @@ -72,8 +91,13 @@ def main(): od.create_object("b", "B") od.create_link("A2B", "a", "b") + od.create_slot("size", "a", od.create_integer_value(42)) + + print("checking conformance....") conf2 = Conformance(state, inst_id, model_id) print(conf2.check_nominal(log=True)) + # ramify(state, model_id) + if __name__ == "__main__": main() diff --git a/pattern_matching/enum.py b/pattern_matching/enum.py deleted file mode 100644 index afde1bb..0000000 --- a/pattern_matching/enum.py +++ /dev/null @@ -1,31 +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 Enum(object): - """ - Custom Enum object for compatibility (enum is introduced in python 3.4) - Usage create : a = Enum(['e0', 'e1', ...]) - Usage call : a.e0 - """ - def __init__(self, args): - next = 0 - for arg in args: - self.__dict__[arg] = next - next += 1 \ No newline at end of file diff --git a/pattern_matching/main.py b/pattern_matching/main.py index 8bd5e5e..e0d734d 100644 --- a/pattern_matching/main.py +++ b/pattern_matching/main.py @@ -47,10 +47,10 @@ if __name__ == '__main__': # 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] + 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) diff --git a/pattern_matching/patternMatching.py b/pattern_matching/patternMatching.py index 914b60c..6f181c9 100644 --- a/pattern_matching/patternMatching.py +++ b/pattern_matching/patternMatching.py @@ -18,11 +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 planGraph import * - import collections import itertools -# import numpy as np class PatternMatching(object): """ @@ -563,9 +560,6 @@ class PatternMatching(object): return False - - print("graph:", graph) - # create adjecency matrix of the graph H, h = self.createAdjacencyMatrixMap(graph, pattern) # create adjecency matrix of the pattern diff --git a/pattern_matching/planGraph.py b/pattern_matching/planGraph.py deleted file mode 100644 index 2742ac2..0000000 --- a/pattern_matching/planGraph.py +++ /dev/null @@ -1,528 +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 searchGraph import * -from enum import * - -# Enum for all primitive operation types -# note: inc represent primitive operation in (as in is a reserved keyword in python) -PRIM_OP = Enum(['lkp', 'inc', 'out', 'src', 'tgt']) - -class PlanGraph(object): - """ - Holds the PlanGraph for a pattern. - Can create the search plan of the pattern for a given SearchGraph. - """ - def __init__(self, pattern): - if not isinstance(pattern, Graph): - raise TypeError('PlanGraph expects the pattern to be a Graph') - # member variables: - self.vertices = [] # will not be searched in - self.edges = [] # will not be searched in - - # representation map, maps vertex from pattern to element from PlanGraph - # (no need for edges) - repr_map = {} - - # 1.1: for every vertex in the pattern graph, - # create a vertex representing the pattern element - for str_type, vertices in pattern.vertices.items(): - for vertex in vertices: - # we only need to know the type of the vertex - plan_vertex = Vertex(str_type) - # and we need to know that is was a vertex - plan_vertex.is_vertex = True - # for re-linking the edges, we'll need to map the - # vertex of the pattern to the plan_vertex - repr_map[vertex] = plan_vertex - # save created plan_vertex - self.vertices.append(plan_vertex) - # 1.2: for every edge in the pattern graph, - # create a vertex representing the pattern elemen - for str_type, edges in pattern.edges.items(): - for edge in edges: - # we only need to know the type of the edge - plan_vertex = Vertex(edge.type) - # and we need to know that is was an edge - plan_vertex.is_vertex = False - # save created plan_vertex - self.vertices.append(plan_vertex) - # 4: for every element x from the PlanGraph - # that represents an edge e in the pattern: - # 4.1: create an edge labelled tgt from x to the vertex in the PlanGraph - # representing the target vertex of e in the pattern graph, - # and a reverted edge labelled in - # 4.1.1: tgt: - plan_edge = Edge(plan_vertex, repr_map[edge.tgt]) - # backup src and tgt (Edmonds might override it) - plan_edge.orig_src = plan_edge.src - plan_edge.orig_tgt = plan_edge.tgt - plan_edge.label = PRIM_OP.tgt - # link vertices connected to this plan_edge - plan_edge.src.addOutgoingEdge(plan_edge) - plan_edge.tgt.addIncomingEdge(plan_edge) - # tgt and src cost are always 1, we use logaritmic cost, - # (=> cost = ln(1) = 0.0) so that we do not need to minimaze - # a product, but can minimize a sum - # (as ln(c1...ck) = ln(c1) + ... + ln (ck)) - plan_edge.cost = 0.0 - # backup orig cost, as Edmonds changes cost - plan_edge.orig_cost = plan_edge.cost - # save created edge - self.edges.append(plan_edge) - # 4.1.2: in: - plan_edge = Edge(repr_map[edge.tgt], plan_vertex) - # backup src and tgt (Edmonds might override it) - plan_edge.orig_src = plan_edge.src - plan_edge.orig_tgt = plan_edge.tgt - plan_edge.label = PRIM_OP.inc - # link vertices connected to this plan_edge - plan_edge.src.addOutgoingEdge(plan_edge) - plan_edge.tgt.addIncomingEdge(plan_edge) - # save created edge - self.edges.append(plan_edge) - - # 4.2: create an edge labelled src from x to the vertex in the PlanGraph - # representing the source vertex of e in the pattern graph - # and a reverted edge labelled out - # 4.2.1: src - plan_edge = Edge(plan_vertex, repr_map[edge.src]) - # backup src and tgt (Edmonds might override it) - plan_edge.orig_src = plan_edge.src - plan_edge.orig_tgt = plan_edge.tgt - plan_edge.label = PRIM_OP.src - # link vertices connected to this plan_edge - plan_edge.src.addOutgoingEdge(plan_edge) - plan_edge.tgt.addIncomingEdge(plan_edge) - # tgt and src cost are always 1, we use logaritmic cost, - # (=> cost = ln(1) = 0.0) so that we do not need to minimaze - # a product, but can minimize a sum - # (as ln(c1...ck) = ln(c1) + ... + ln (ck)) - plan_edge.cost = 0.0 - # backup orig cost, as Edmonds changes cost - plan_edge.orig_cost = plan_edge.cost - # save created edge - self.edges.append(plan_edge) - # 4.2.2: out - plan_edge = Edge(repr_map[edge.src], plan_vertex) - # backup src and tgt (Edmonds might override it) - plan_edge.orig_src = plan_edge.src - plan_edge.orig_tgt = plan_edge.tgt - plan_edge.label = PRIM_OP.out - # link vertices connected to this plan_edge - plan_edge.src.addOutgoingEdge(plan_edge) - plan_edge.tgt.addIncomingEdge(plan_edge) - # save created edge - self.edges.append(plan_edge) - # 2: create a root vertex - self.root = Vertex('root') - # don't add it to the vertices - - # 3: for each element in the PlanGraph (that is not the root vertex), - # create an edge from the root to it, and label it lkp - for vertex in self.vertices: - plan_edge = Edge(self.root, vertex) - # backup src and tgt (Edmonds might override it) - plan_edge.orig_src = plan_edge.src - plan_edge.orig_tgt = plan_edge.tgt - plan_edge.label = PRIM_OP.lkp - # link vertices connected to this plan_edge - plan_edge.src.addOutgoingEdge(plan_edge) - plan_edge.tgt.addIncomingEdge(plan_edge) - # save created edge - self.edges.append(plan_edge) - - def updatePlanCost(self, graph): - """ - returns True if sucessfully updated cost, - returns False if a type in the pattern is not in the graph. - """ - if not isinstance(graph, SearchGraph): - raise TypeError('updatePlanCost expects a SearchGraph') - # update, lkp, in and out (not src and tgt as they are constant) - - for edge in self.edges: - if edge.label == PRIM_OP.lkp: - edge.cost = graph.getCostLkp(edge.tgt.type, edge.tgt.is_vertex) - if edge.cost == None: - print('failed lkp') - return False - elif edge.label == PRIM_OP.inc: - # in(v, e), binds an incoming edge e from an already bound vertex v, - # depends on the number of incoming edges of type e for the vertex type - edge.cost = graph.getCostInc(edge.src.type, edge.tgt.type) - if edge.cost == None: - print('failed in') - return False - elif edge.label == PRIM_OP.out: - # (analogue for out(v, e)) - edge.cost = graph.getCostOut(edge.src.type, edge.tgt.type) - if edge.cost == None: - print('failed out') - return False - # else: ignore src and tgt - # backup orig cost, as Edmonds changes cost - edge.orig_cost = edge.cost - return True - - def Edmonds(self, searchGraph): - """ - Returns the minimum directed spanning tree (MDST) - for the pattern and the provided graph. - Returns None if it is impossible to find the pattern in the Graph - (vertex type of edge type from pattern not in Graph). - """ - # update the cost for the PlanGraph - if not self.updatePlanCost(searchGraph): - print('type in pattern not found in Graph (in Edmonds)') - # (returns False if a type in the pattern can not be found in the graph) - return None - # Complete Edmonds algorithm has optimization steps: - # a: remove edges entering the root - # b: merge parallel edges from same src to same tgt with mim weight - # we can ignore this as: - # a: the root does not have incoming edges - # b: the PlanGraph does not have such paralllel edges - - # 1: for each node v (other than root), find incoming edge with lowest weight - # insert those - pi_v = {} - for plan_vertex in self.vertices: - min_weight = float('infinity') - min_edge = None - for plan_edge in plan_vertex.incoming_edges: - if plan_edge.cost < min_weight: - min_weight = plan_edge.cost - min_edge = plan_edge - # save plan_vertex and it's minimum incoming edge - pi_v[plan_vertex] = min_edge - if min_edge == None: - raise RuntimeError('baka: no min_edge found') - - def getCycle(vertex, reverse_graph, visited): - """ - Walk from vertex to root, we walk in a reverse order, as each vertex - only has one incoming edge, so we walk to the source of that incoming - edge. We stop when we already visited a vertex we walked on. - In both cases we return None. - When we visit a vertex from our current path, we return that cycle, - by first removing its tail. - """ - def addToVisited(walked, visited): - for vertex in walked: - visited.add(vertex) - - walked = [] # we could only save it once, but we need order - current_path = set() # and lookup in an array is slower than in set - # we asume root is in visited (it must be in it) - while vertex not in visited: - if vertex in current_path: - # we found a cycle, the cycle however might look like a: O--, - # g f e where we first visited a, then b, c, d,... - # h d c b a k points back to d, completing a cycle, - # i j k but c b a is the tail that does not belong - # in the cycle, removing this is "easy" as we know that - # we first visited the tail, so they are the first elements - # in our walked path - for tail_part in walked: - if tail_part != vertex: - current_path.remove(tail_part) - else: - break - - addToVisited(walked, visited) - return current_path - current_path.add(vertex) - walked.append(vertex) - # by definition, an MDST only has one incoming edge per vertex - # so we follow it upwards - # vertex <--(minimal edge)-- src - vertex = reverse_graph[vertex].src - - # no cycle found (the current path let to a visited vertex) - addToVisited(walked, visited) # add walked to visited - return None - - class VertexGraph(Vertex): - """ - Acts as a super vertex, holds a subgraph (that is/was once a cyle). - Uses for Edmonds contractions step. - The incoming edges are the edges leading to the vertices in the - VertexGraph (they exclude edges from a vertex in the cycle to - another vertex in the cycle). - Analogue for outgoing edges. - """ - def __init__(self, cycle, reverseGraph): - # Call parent class constructor - str_type = '' - for vertex in cycle: - str_type += str(vertex.type) - Vertex.__init__(self, str_type) - # member variables: - self.internalMDST = {} - - minIntWeight = self.findMinIntWeight(cycle, reverseGraph) - self.updateMinExtEdge(minIntWeight, reverseGraph) - - - def findMinIntWeight(self, cycle, reverseGraph): - """ - Find the the smallest cost of the cycle his internal incoming edges. - (Also save its internalMDST (currently a cycle).) - (The VertexGraph formed by the cycle will be added to the - reverseGraph by calling findMinExtEdge.) - """ - minIntWeight = float('infinity') - - cycleEdges = [] - origTgts = [] - for cyclePart in cycle: - cycleEdges.append(reverseGraph[cyclePart]) - origTgts.append(reverseGraph[cyclePart].orig_tgt) - - for vertex in cycle: - # add incoming edges to this VertexGraph - for inc_edge in vertex.incoming_edges: - # edge from within the cycle - if inc_edge.src in cycle: - minIntWeight = min(minIntWeight, inc_edge.cost) - else: - # edge from outside the cycle - self.addIncomingEdge(inc_edge) - # add outgoing edges to this VertexGraph - for out_edge in vertex.outgoing_edges: - if out_edge.tgt not in cycle: - # edge leaves the cycle - self.addOutgoingEdge(out_edge) - # update src to this VertexGraph - out_edge.src = self - # save internal MDST - min_edge = reverseGraph[vertex] - if min_edge.src in cycle: - self.internalMDST[vertex] = min_edge - else: - raise TypeError('how is this a cycle') - - return minIntWeight - - def updateMinExtEdge(self, minIntWeight, reverseGraph): - """ - Modifies all external incoming edges their cost and finds the - minimum external incoming edge with this modified weight. - This found edge will break the cycle, update the internalMDST - from a cycle to an MDST, updates the reverseGraph to include - the vertexGraph. - """ - minExt = None - minModWeight = -float('infinity') - - # Find incoming edge from outside of the circle with minimal - # modified cost. This edge will break the cycle. - for inc_edge in self.incoming_edges: - # An incoming edge (with src from within the cycle), can be - # from a contracted part of the graph. Assume bc is a - # contracted part (VertexGraph) a, bc is a newly formed - # cycle (due to the breaking of the previous cycle bc). bc - # has at least lkp incoming edges to b and c, but we should - # not consider the lkp of c to break the cycle. - # If we want to break a, bc, select plausable edges, - # /<--\ - # a bc bc's MDST b <-- c - # \-->/ - # by looking at their original targets. - # (if cycle inc_edge.orig_tgt == external inc_edge.orig_tgt) - if reverseGraph[inc_edge.tgt].orig_tgt == inc_edge.orig_tgt: - # modify costL cost of inc_edge - - # (cost of previously choosen minimum edge to cycle vertex - minIntWeight) - inc_edge.cost -= (reverseGraph[inc_edge.tgt].cost - minIntWeight) - if minExt is None or minModWeight > inc_edge.cost: - # save better edge from outside of the cycle - minExt = inc_edge - minModWeight = inc_edge.cost - - # Example: a, b is a cycle (we know that there are no other - # incoming edges to a and/or b, as there is on;y exactly one - # incoming edge per vertex), and the arow from c to b represents - # the minExt edge. We will remove the bottem arrow (from a to b) - # /<--\ and save the minExt edge in the reverseGraph. - # a b <-- c This breaks the cycle. As the internalMDST - # \-->/ saves the intenal MDST, and currently still - # holds a cycle, we have to remove it from the internalMDST. - # We have to remove all vertex bindings of the cycle from the - # reverseGraph (as it is contracted into a single VertexGraph), - # and store the minExt edge to this VertexGraph in it. - for int_vertex, _ in self.internalMDST.items(): - del reverseGraph[int_vertex] # remove cycle from reverseGraph - - del self.internalMDST[minExt.tgt] # remove/break cycle - - for inc_edge in self.incoming_edges: - # update inc_edge's target to this VertexGraph - inc_edge.tgt = self - - # save minExt edge to this VertexGraph in the reverseGraph - reverseGraph[self] = minExt - - while True: - # 2: find all cycles: - cycles = [] - visited = set([self.root]) # root does not have incoming edges, - for vertex in list(pi_v.keys()): # it can not be part of a cycle - if vertex not in visited: # getCycle depends on root being in visited - cycle = getCycle(vertex, pi_v, visited) - if cycle != None: - cycles.append(cycle) - - # 2: if the set of edges {pi(v), v} does not contain any cycles, - # Then we found our minimum directed spanning tree - # otherwise, we'll have to resolve the cycles - if len(cycles) == 0: - break - - # 3: For each formed cycle: - # 3a: find internal incoming edge with the smallest cost - # 3b: modify the cost of each arc which enters the cycle - # 3c: replace smallert internal edge with the modified edge which has the smallest cost - for cycle in cycles: - # Breaks a cycle by: - # - contracting cycle into VertexGraph - # - finding the internal incoming edge with the smallest cost - # - modify the cost of each arc which enters the cycle - # - replacing the smallest internal edge with the modified edge which has the smallest cost - # - changing reverseGraph accordingly (removes elements from cycle, ads vertexGraph) - # (This will find a solution as the graph keeps shrinking with every cycle, - # in the worst case the same amount as there are vertices, until - # onlty the root and one vertexGraph remains) - vertexGraph = VertexGraph(cycle, pi_v) - - class SortedContainer(object): - """ - A container that keeps elemets sorted based on a given sortValue. - Elements with the same value, will be returned in the order they got inserted. - """ - def __init__(self): - # member variables: - self.keys = [] # stores key in sorted order (sorted when pop gets called) - self.sorted = {} # {key, [elems with same key]} - - def add(self, sortValue, element): - """ - Adds element with sortValue to the SortedContainer. - """ - elems = self.sorted.get(sortValue) - if elems == None: - self.sorted[sortValue] = [element] - self.keys.append(sortValue) - else: - elems.append(element) - - def pop(self): - """ - Sorts the SortedContainer, returns element with smallest sortValue. - """ - self.keys.sort() - elems = self.sorted[self.keys[0]] - elem = elems.pop() - if len(elems) == 0: - del self.sorted[self.keys[0]] - del self.keys[0] - return elem - - def empty(self): - """ - Returns whether or not the sorted container is empty. - """ - return (len(self.keys) == 0) - - def createPRIM_OP(edge, inc_cost=True): - """ - Helper function to keep argument list short, - return contracted data for a PRIM_OP. - """ - if edge.label == PRIM_OP.inc or edge.label == PRIM_OP.out: - if inc_cost: # op # vertex type # actual edge type - return (edge.label, edge.orig_src.type, edge.orig_tgt.type, edge.cost) - else: - return (edge.label, edge.orig_src.type, edge.orig_tgt.type) - elif edge.label == PRIM_OP.lkp: - if inc_cost: # op # vertex/edge type # is vertex or edge - return (edge.label, edge.orig_tgt.type, edge.orig_tgt.is_vertex, edge.cost) - else: - return (edge.label, edge.orig_tgt.type, edge.orig_tgt.is_vertex) - else: # src, tgt operation - if inc_cost: # op # actual edge type - return (edge.label, edge.orig_src.type, edge.cost) - else: - return (edge.label, edge.orig_src.type) - - def flattenReverseGraph(vertex, inc_edge, reverseGraph): - """ - Flattens the reverseGraph, so that the vertexGraph node can get - processed to create a forwardGraph. - """ - if not isinstance(vertex, VertexGraph): - reverseGraph[vertex] = inc_edge - else: - reverseGraph[inc_edge.orig_tgt] = inc_edge - for vg, eg in inc_edge.tgt.internalMDST.items(): - flattenReverseGraph(vg, eg, reverseGraph) - if isinstance(inc_edge.src, VertexGraph): - for vg, eg in inc_edge.src.internalMDST.items(): - flattenReverseGraph(vg, eg, reverseGraph) - - def createForwardGraph(vertex, inc_edge, forwardGraph): - """ - Create a forwardGraph, keeping in mind that their can be vertexGraph - in the reverseGraph. - """ - if not isinstance(vertex, VertexGraph): - forwardGraph.setdefault(inc_edge.orig_src, []).append(inc_edge) - else: - forwardGraph.setdefault(inc_edge.orig_src, []).append(inc_edge) - for vg, eg in vertex.internalMDST.items(): - createForwardGraph(vg, eg, forwardGraph) - - MDST = [] - # pi_v contains {vertex, incoming_edge} - # we want to start from root and follow the outgoing edges - # so we have to build the forwardGraph graph for pi_v - # (Except for the root (has 0), each vertex has exactly one incoming edge, - # but might have multiple outgoing edges) - forwardGraph = {} # {vertex, [outgoing edge 1, ... ] } - reverseGraph = {} - - # flatten reverseGraph (for the vertexGraph elements) - for v, e in pi_v.items(): - flattenReverseGraph(v, e, reverseGraph) - - # create the forwardGraph - for vertex, edge in reverseGraph.items(): - createForwardGraph(vertex, edge, forwardGraph) - - # create the MDST in a best first manner (lowest value first) - current = SortedContainer() # allows easy walking true tree - for edge in forwardGraph[self.root]: - current.add(edge.orig_cost, edge) # use orig cost, not modified - while current.empty() != True: - p_op = current.pop() # p_op contains an outgoing edge - MDST.append(createPRIM_OP(p_op)) - for edge in forwardGraph.get(p_op.orig_tgt, []): - current.add(edge.orig_cost, edge) - return MDST \ No newline at end of file diff --git a/pattern_matching/searchGraph.py b/pattern_matching/searchGraph.py deleted file mode 100644 index cb3c359..0000000 --- a/pattern_matching/searchGraph.py +++ /dev/null @@ -1,115 +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 graph import * - -import math - -class SearchGraph(Graph): - """ - A SearchGraph is an extended Graph, it keeps traks of statistics - for creating the cost model when generating a search plan. - It stire the amount of edges for each edge.type per vertex.type. - """ - def __init__(self, orig=None, deepCopy=False): - Graph.__init__(self) - # member variables: - self.nr_of_inc_edges = {} # {vertex_type, {edge_type, nr of incoming edges of edge_type for vertex_type } } - self.nr_of_out_edges = {} # {vertex_type, {edge_type, nr of outgoing edges of edge_type for vertex_type } } - - if orig != None: - if not (isinstance(orig, Graph) or isinstance(orig, SearchGraph)): - raise TypeError('Can only create SearchGraph from Graph and SearchGraph types') - if not deepCopy: - # copy all memeber elements: - self.vertices = orig.vertices # this is a reference - self.edges = orig.edges # this is a reference - # udpate the edge counters for each edge - for _, edges in self.edges.items(): - for edge in edges: - self.addToEdgeCounters(edge) - else: # TODO: deepcopy (not really needed) - pass - - 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. - This also add the Edge to the Edge counters - """ - # call parent fucntion, this function is an extention - edge = Graph.addCreateEdge(self, src, tgt, str_type) - self.updateEdgeCounters(edge) - return edge - - def addToEdgeCounters(self, edge): - """ - Add the Edge to the Edge counters. - """ - # get {edge.type, counter} for tgt vertex of edge (or create it) - edge_counters = self.nr_of_inc_edges.setdefault(edge.tgt.type, {}) - # increase counter of edge.type by 1 - edge_counters[edge.type] = edge_counters.get(edge.type, 0) + 1 - # get {edge.type, counter} for src vertex of edge (or create it) - edge_counters = self.nr_of_out_edges.setdefault(edge.src.type, {}) - # increase counter of edge.type by 1 - edge_counters[edge.type] = edge_counters.get(edge.type, 0) + 1 - - def getCostLkp(self, type, is_vertex): - """ - Returns the cost of a lkp primitive operation (of a vertex or edge). - Returns None if vertex type or edge type not present in Host Graph - """ - if is_vertex: - cost = len(self.getVerticesOfType(type)) - else: - cost = len(self.getEdgesOfType(type)) - if cost == 0: - return None - # we use a logaritmic cost - return math.log(cost) - - def getCostInc(self, vertex_type, edge_type): - """ - Returns the cost of an in primitive operation. - Returns None if vertex_type or edge_type not present in Host Graph - """ - cost = float(self.nr_of_inc_edges.get(vertex_type, {}).get(edge_type)) - if cost != None: - nr_of_vertices_with_type = len(self.getVerticesOfType(vertex_type)) - if nr_of_vertices_with_type != 0: - cost /= len(self.getVerticesOfType(vertex_type)) - # we use a logaritmic cost - cost = math.log(cost) - return cost - - def getCostOut(self, vertex_type, edge_type): - """ - Returns the cost of an out primitive operation. - Returns None if vertex_type or edge_type not present in Host Graph - """ - cost = float(self.nr_of_out_edges.get(vertex_type, {}).get(edge_type)) - if cost != None: - nr_of_vertices_with_type = len(self.getVerticesOfType(vertex_type)) - if nr_of_vertices_with_type != 0: - cost /= len(self.getVerticesOfType(vertex_type)) - # we use a logaritmic cost - cost = math.log(cost) - return cost \ No newline at end of file diff --git a/services/od.py b/services/od.py index 4df282b..520a120 100644 --- a/services/od.py +++ b/services/od.py @@ -45,24 +45,37 @@ class OD: return object_node - - def create_slot(self, object_name: str, attr_name: str, value: UUID): - attr_node = self.bottom.read_outgoing_elements(self.type_model, attr_name) # get the attribute + def get_class_of_object(self, object_name: str): object_node, = self.bottom.read_outgoing_elements(self.model, object_name) # get the object - slot_node = value + type_el, = self.bottom.read_outgoing_elements(object_node, "Morphism") + for key in self.bottom.read_keys(self.type_model): + type_el2, = self.bottom.read_outgoing_elements(self.type_model, key) + if type_el == type_el2: + return key - # generate a unique name for the slot - i = 0; - while len(self.bottom.read_outgoing_elements(self.model, f"{object_name}.{attr_name}{i}")) != 0: - i += 1 + def create_slot(self, attr_name: str, object_name: str, target_name: str): + class_name = self.get_class_of_object(object_name) + attr_link_name = f"{class_name}_{attr_name}" + # An attribute-link is indistinguishable from an ordinary link: + return self.create_link(attr_link_name, object_name, target_name) - self.bottom.create_edge(self.model, slot_node, f"{object_name}.{attr_name}{i}") # attach to model root - slot_link = self.bottom.create_edge(object_node, slot_node) # attach to object - self.bottom.create_edge(self.model, slot_link, f"{object_name}.{attr_name}{i}_link") # attach attr-link to model + def create_integer_value(self, value: int): + from services.primitives.integer_type import Integer + int_node = self.bottom.create_node() + integer_t = Integer(int_node, self.bottom.state) + integer_t.create(value) + name = 'int'+str(value) # name of the ref to the created integer + # By convention, the type model must have a ModelRef named "Integer" + self.create_model_ref(name, "Integer", int_node) + return name - self.bottom.create_edge(slot_node, attr_node, "Morphism") # slot typed-by attribute - slot_link_type, = self.bottom.read_outgoing_elements(self.type_model, "AttributeLink") - self.bottom.create_edge(slot_link, slot_link_type) + # Identical to the same SCD method: + def create_model_ref(self, name: str, type_name: str, model: UUID): + # create element + morphism links + element_node = self.bottom.create_node(str(model)) # create element node + self.bottom.create_edge(self.model, element_node, name) # attach to model + scd_node, = self.bottom.read_outgoing_elements(self.type_model, type_name) # retrieve type + self.bottom.create_edge(element_node, scd_node, "Morphism") # create morphism link def create_link(self, assoc_name: str, src_obj_name: str, tgt_obj_name: str): diff --git a/transformation/ramify.py b/transformation/ramify.py new file mode 100644 index 0000000..2d00a07 --- /dev/null +++ b/transformation/ramify.py @@ -0,0 +1,18 @@ +from state.base import State +from uuid import UUID +from services.bottom.V0 import Bottom +from services.scd import SCD + +def ramify(state: State, model: UUID) -> UUID: + """ + Parameters: + bottom: Bottom-service, wrapping MVS + model: An SCD-conforming meta-model to ramify + """ + scd = SCD(model, state) + + classes = scd.get_classes() + print(classes) + + attrs = scd.get_attributes('A') + print(attrs)