Object Diagrams: slots work
This commit is contained in:
parent
d2c996f4f7
commit
2b22be01ec
8 changed files with 79 additions and 704 deletions
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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):
|
||||
|
|
|
|||
18
transformation/ramify.py
Normal file
18
transformation/ramify.py
Normal 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)
|
||||
Loading…
Add table
Add a link
Reference in a new issue