Object Diagrams: slots work

This commit is contained in:
Joeri Exelmans 2024-09-03 14:00:55 +02:00
parent d2c996f4f7
commit 2b22be01ec
8 changed files with 79 additions and 704 deletions

View file

@ -6,9 +6,17 @@ from uuid import UUID
from services.scd import SCD from services.scd import SCD
from framework.conformance import Conformance from framework.conformance import Conformance
from services.od import OD from services.od import OD
from transformation.ramify import ramify
from services.primitives.integer_type import Integer
import sys 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(): def main():
state = DevState() state = DevState()
root = state.read_root() # id: 0 root = state.read_root() # id: 0
@ -42,11 +50,21 @@ def main():
print_tree(tgt, max_depth, depth+1) print_tree(tgt, max_depth, depth+1)
print("explore...") 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() model_id = state.create_node()
scd = SCD(model_id, state) scd = SCD(model_id, state)
scd.create_class("A") 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_class("B")
scd.create_association("A2B", "A", "B", scd.create_association("A2B", "A", "B",
src_min_c=1, src_min_c=1,
@ -55,15 +73,16 @@ def main():
tgt_max_c=2, tgt_max_c=2,
) )
print_tree(model_id, 1) print_tree(model_id, 2)
conf = Conformance(state, model_id, scd_node) conf = Conformance(state, model_id, scd_node)
print("Check nominal conformance...") print("Check nominal conformance...")
print(conf.check_nominal(log=True)) print(conf.check_nominal(log=True))
print("Check structural conformance...") # print("Check structural conformance...")
print(conf.check_structural(log=True)) # print(conf.check_structural(log=True))
print("Check nominal conformance (again)...") # print("Check nominal conformance (again)...")
print(conf.check_nominal(log=True)) # print(conf.check_nominal(log=True))
inst_id = state.create_node() inst_id = state.create_node()
od = OD(model_id, inst_id, state) od = OD(model_id, inst_id, state)
@ -72,8 +91,13 @@ def main():
od.create_object("b", "B") od.create_object("b", "B")
od.create_link("A2B", "a", "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) conf2 = Conformance(state, inst_id, model_id)
print(conf2.check_nominal(log=True)) print(conf2.check_nominal(log=True))
# ramify(state, model_id)
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View file

@ -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

View file

@ -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_inc = [ 0,25,18,47,22,25,16,45,38,25,5,45,15,44,17,46,6,17,35,8,16,29,48,47,25,34,4,20,24,1,47,44,8,25,32,3,16,6,33,21,6,13,41,10,17,25,21,33,31,30,5,4,45,26,16,42,12,25,29,3,32,30,14,26,11,13,7,13,3,43,43,22,48,37,20,28,15,40,19,33,43,16,49,36,11,25,9,42,3,22,16,40,42,44,27,30,1,18,10,35,19,6,9,43,37,38,45,19,41,14,37,45,0,31,29,31,24,20,44,46,8,45,43,3,38,38,35,12,19,45,7,34,20,28,12,17,45,17,35,49,20,21,49,1,35,38,38,36,33,30 ]
# dc_out = [ 9,2,49,49,37,33,16,21,5,46,4,15,9,6,14,22,16,33,23,21,15,31,37,23,47,3,30,26,35,9,29,21,39,32,22,43,5,9,41,30,31,30,37,33,31,34,23,22,34,26,44,36,38,33,48,5,9,34,13,7,48,41,43,26,26,7,12,6,12,28,22,8,29,22,24,27,16,4,31,41,32,15,19,20,38,0,26,18,43,46,40,17,29,14,34,14,32,17,32,47,16,45,7,4,35,22,42,11,38,2,0,29,4,38,17,44,9,23,5,10,31,17,1,11,16,5,37,27,35,32,45,16,18,1,14,4,42,24,43,31,21,38,6,34,39,46,20,1,38,47 ] # dc_out = [ 9,2,49,49,37,33,16,21,5,46,4,15,9,6,14,22,16,33,23,21,15,31,37,23,47,3,30,26,35,9,29,21,39,32,22,43,5,9,41,30,31,30,37,33,31,34,23,22,34,26,44,36,38,33,48,5,9,34,13,7,48,41,43,26,26,7,12,6,12,28,22,8,29,22,24,27,16,4,31,41,32,15,19,20,38,0,26,18,43,46,40,17,29,14,34,14,32,17,32,47,16,45,7,4,35,22,42,11,38,2,0,29,4,38,17,44,9,23,5,10,31,17,1,11,16,5,37,27,35,32,45,16,18,1,14,4,42,24,43,31,21,38,6,34,39,46,20,1,38,47 ]
# dv = [0, 1, 0, 1, 0] dv = [0, 1, 0, 1, 0]
# de = [0, 0, 0] de = [0, 0, 0]
# dc_inc = [0, 2, 4] dc_inc = [0, 2, 4]
# dc_out = [1, 3, 3] dc_out = [1, 3, 3]
gg = GraphGenerator(dv, de, dc_inc, dc_out, debug) gg = GraphGenerator(dv, de, dc_inc, dc_out, debug)

View file

@ -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. at the workings behind various techniques for efficient pattern matching.
""" """
from planGraph import *
import collections import collections
import itertools import itertools
# import numpy as np
class PatternMatching(object): class PatternMatching(object):
""" """
@ -563,9 +560,6 @@ class PatternMatching(object):
return False return False
print("graph:", graph)
# create adjecency matrix of the graph # create adjecency matrix of the graph
H, h = self.createAdjacencyMatrixMap(graph, pattern) H, h = self.createAdjacencyMatrixMap(graph, pattern)
# create adjecency matrix of the pattern # create adjecency matrix of the pattern

View file

@ -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

View file

@ -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

View file

@ -45,24 +45,37 @@ class OD:
return object_node return object_node
def get_class_of_object(self, object_name: str):
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
object_node, = self.bottom.read_outgoing_elements(self.model, object_name) # get the object 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 def create_slot(self, attr_name: str, object_name: str, target_name: str):
i = 0; class_name = self.get_class_of_object(object_name)
while len(self.bottom.read_outgoing_elements(self.model, f"{object_name}.{attr_name}{i}")) != 0: attr_link_name = f"{class_name}_{attr_name}"
i += 1 # 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 def create_integer_value(self, value: int):
slot_link = self.bottom.create_edge(object_node, slot_node) # attach to object from services.primitives.integer_type import Integer
self.bottom.create_edge(self.model, slot_link, f"{object_name}.{attr_name}{i}_link") # attach attr-link to model 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 # Identical to the same SCD method:
slot_link_type, = self.bottom.read_outgoing_elements(self.type_model, "AttributeLink") def create_model_ref(self, name: str, type_name: str, model: UUID):
self.bottom.create_edge(slot_link, slot_link_type) # 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): def create_link(self, assoc_name: str, src_obj_name: str, tgt_obj_name: str):

18
transformation/ramify.py Normal file
View file

@ -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)