Przeglądaj źródła

Object Diagrams: slots work

Joeri Exelmans 11 miesięcy temu
rodzic
commit
2b22be01ec

+ 30 - 6
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()

+ 0 - 31
pattern_matching/enum.py

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

+ 4 - 4
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)
 

+ 0 - 6
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

+ 0 - 528
pattern_matching/planGraph.py

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

+ 0 - 115
pattern_matching/searchGraph.py

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

+ 30 - 17
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
-
-        # 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
-
-        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
-
-        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)
+        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
+
+    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)
+
+    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
+
+    # 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 - 0
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)