Tweak matcher (compute connected components in advance). Simple pattern matching with RAMification (incl. Python expressions) seems to work.

This commit is contained in:
Joeri Exelmans 2024-09-06 21:10:23 +02:00
parent bed3529676
commit 4160a8953e
13 changed files with 388 additions and 70 deletions

View file

@ -53,6 +53,8 @@ def bootstrap_scd(state: State) -> UUID:
_optional_edge = add_edge_element(f"{attribute_element_name}.optional_link", attribute_element, _optional_node)
return _name_model, _optional_model
##### SCD META-MODEL #####
# # CLASSES, i.e. elements typed by Class
# # Element
element_node = add_node_element("Element")
@ -64,6 +66,7 @@ def bootstrap_scd(state: State) -> UUID:
model_ref_node = add_node_element("ModelRef")
# # Global Constraint
glob_constr_node = add_node_element("GlobalConstraint")
# # ASSOCIATIONS, i.e. elements typed by Association
# # Association
assoc_edge = add_edge_element("Association", class_node, class_node)
@ -71,6 +74,7 @@ def bootstrap_scd(state: State) -> UUID:
inh_edge = add_edge_element("Inheritance", element_node, element_node)
# # Attribute Link
attr_link_edge = add_edge_element("AttributeLink", element_node, attr_node)
# # INHERITANCES, i.e. elements typed by Inheritance
# # Class inherits from Element
add_edge_element("class_inh_element", class_node, element_node)
@ -84,9 +88,11 @@ def bootstrap_scd(state: State) -> UUID:
add_edge_element("attr_link_inh_element", attr_link_edge, element_node)
# # ModelRef inherits from Attribute
add_edge_element("model_ref_inh_attr", model_ref_node, attr_node)
# # ATTRIBUTES, i.e. elements typed by Attribute
# # Action Code # TODO: Update to ModelRef when action code is explicitly modelled
action_code_node = add_node_element("ActionCode")
# # MODELREFS, i.e. elements typed by ModelRef
# # Integer
integer_node = add_node_element("Integer", str(integer_type_root))
@ -94,6 +100,7 @@ def bootstrap_scd(state: State) -> UUID:
string_node = add_node_element("String", str(string_type_root))
# # Boolean
boolean_node = add_node_element("Boolean", str(boolean_type_root))
# # ATTRIBUTE LINKS, i.e. elements typed by AttributeLink
# # name attribute of AttributeLink
attr_name_edge = add_edge_element("AttributeLink_name", attr_link_edge, string_node)
@ -111,6 +118,7 @@ def bootstrap_scd(state: State) -> UUID:
assoc_s_u_c_edge = add_edge_element("Association_source_upper_cardinality", assoc_edge, integer_node)
assoc_t_l_c_edge = add_edge_element("Association_target_lower_cardinality", assoc_edge, integer_node)
assoc_t_u_c_edge = add_edge_element("Association_target_upper_cardinality", assoc_edge, integer_node)
# # bootstrap primitive types
# # order is important, integer must be first
bootstrap_integer_type(mcl_root, integer_type_root, state)
@ -118,6 +126,7 @@ def bootstrap_scd(state: State) -> UUID:
bootstrap_float_type(mcl_root, float_type_root, state)
bootstrap_string_type(mcl_root, string_type_root, state)
bootstrap_type_type(mcl_root, type_type_root, state)
# # ATTRIBUTE ATTRIBUTES, assign 'name' and 'optional' attributes to all AttributeLinks
# # AttributeLink_name
m_name, m_opt = add_attribute_attributes("AttributeLink_name", attr_name_edge)

View file

@ -7,7 +7,10 @@ from services.scd import SCD
from framework.conformance import Conformance
from services.od import OD
from transformation.ramify import ramify
from services.bottom.V0 import Bottom
from services.primitives.integer_type import Integer
from pattern_matching import mvs_adapter
from pattern_matching.matcher import MatcherVF2
import sys
@ -62,6 +65,9 @@ def main():
int_type_id = state.read_dict(state.read_root(), "Integer")
int_type = UUID(state.read_value(int_type_id))
string_type_id = state.read_dict(state.read_root(), "String")
string_type = UUID(state.read_value(string_type_id))
# scd2 = SCD(scd_node, state)
# for el in scd2.list_elements():
# print(el)
@ -70,7 +76,7 @@ def main():
model_id = state.create_node()
scd = SCD(model_id, state)
scd.create_class("Abstract", abstract=True)
scd.create_class("A", min_c=1, max_c=10)
scd.create_class("A", min_c=1, max_c=2)
scd.create_inheritance("A", "Abstract")
scd.create_model_ref("Integer", int_type)
scd.create_attribute_link("A", "Integer", "size", False)
@ -97,16 +103,46 @@ def main():
od = OD(model_id, inst_id, state)
od.create_object("a", "A")
od.create_object("a2", "A")
od.create_object("b", "B")
od.create_link("A2B", "a", "b")
od.create_link("A2B", "a2", "b")
od.create_slot("size", "a", od.create_integer_value(42))
od.create_slot("size", "a", od.create_integer_value("a.size", 42))
print("checking conformance....")
conf2 = Conformance(state, inst_id, model_id)
print("conforms?", conf2.check_nominal(log=True))
ramify(state, model_id)
ramified_MM_id = ramify(state, model_id)
pattern_id = state.create_node()
pattern = OD(ramified_MM_id, pattern_id, state)
pattern.create_object("a1", "A")
pattern.create_slot("size", "a1", pattern.create_string_value("a1.size", 'v < 100'))
# pattern.create_object("a2", "A")
# pattern.create_slot("size", "a2", pattern.create_string_value("a2.size", '99'))
pattern.create_object("b1", "B")
# pattern.create_link("A2B", "a1", "b1")
conf3 = Conformance(state, pattern_id, ramified_MM_id)
print("conforms?", conf3.check_nominal(log=True))
host = mvs_adapter.model_to_graph(state, inst_id)
guest = mvs_adapter.model_to_graph(state, pattern_id)
print(host.vtxs)
print(host.edges)
print("matching...")
matcher = MatcherVF2(host, guest, mvs_adapter.RAMCompare(Bottom(state)))
prev = None
for m in matcher.match():
print("\nMATCH:\n", m)
input()
print("DONE")
if __name__ == "__main__":
main()

View file

@ -200,3 +200,33 @@ class GraphGenerator(object):
# ----------------------------------------------------------------------
return pattern
def get_random_host_and_guest(nr_vtxs, nr_vtx_types, nr_edges, nr_edge_types, pattern_nr_vtxs=3, pattern_nr_edges=15):
dv = [random.randint(0, nr_vtx_types) for _ in range(nr_vtxs)]
de = [random.randint(0, nr_edge_types) for _ in range(nr_edges)]
dc_inc = [random.randint(0, nr_vtxs-1) for _ in range(nr_edges)]
dc_out = [random.randint(0, nr_vtxs-1) for _ in range(nr_edges)]
return get_host_and_guest(dv, de, dc_inc, dc_out, pattern_nr_vtxs, pattern_nr_edges)
def get_host_and_guest(dv, de, dc_inc, dc_out, pattern_nr_vtxs=3, pattern_nr_edges=15):
gg = GraphGenerator(dv, de, dc_inc, dc_out)
graph = gg.getRandomGraph()
pattern = gg.getRandomPattern(pattern_nr_vtxs, pattern_nr_edges, debug=False)
return (graph, pattern)
def get_large_host_and_guest():
dv = [ 10,5,4,0,8,6,8,0,4,8,5,5,7,0,10,0,5,6,10,4,0,3,0,8,2,7,5,8,1,0,2,10,0,0,1,6,8,4,7,6,4,2,10,10,6,4,6,0,2,7 ]
de = [ 8,10,8,1,6,7,4,3,5,2,0,0,9,6,0,3,8,3,2,7,2,3,10,8,10,8,10,2,5,5,10,6,7,5,1,2,1,2,2,3,7,7,2,1,7,2,9,10,8,1,9,4,1,3,1,1,8,2,2,9,10,9,1,9,4,10,10,10,9,3,5,3,6,6,9,1,2,6,3,2,4,10,9,6,5,6,2,4,3,2,4,10,6,2,8,8,0,5,1,7,3,4,3,8,7,3,0,8,3,3,8,5,10,5,9,3,1,10,3,2,6,3,10,0,5,10,9,10,0,1,4,7,10,3,1,9,1,2,3,7,4,3,7,8,8,4,5,10,1,4 ]
dc_inc = [ 0,25,18,47,22,25,16,45,38,25,5,45,15,44,17,46,6,17,35,8,16,29,48,47,25,34,4,20,24,1,47,44,8,25,32,3,16,6,33,21,6,13,41,10,17,25,21,33,31,30,5,4,45,26,16,42,12,25,29,3,32,30,14,26,11,13,7,13,3,43,43,22,48,37,20,28,15,40,19,33,43,16,49,36,11,25,9,42,3,22,16,40,42,44,27,30,1,18,10,35,19,6,9,43,37,38,45,19,41,14,37,45,0,31,29,31,24,20,44,46,8,45,43,3,38,38,35,12,19,45,7,34,20,28,12,17,45,17,35,49,20,21,49,1,35,38,38,36,33,30 ]
dc_out = [ 9,2,49,49,37,33,16,21,5,46,4,15,9,6,14,22,16,33,23,21,15,31,37,23,47,3,30,26,35,9,29,21,39,32,22,43,5,9,41,30,31,30,37,33,31,34,23,22,34,26,44,36,38,33,48,5,9,34,13,7,48,41,43,26,26,7,12,6,12,28,22,8,29,22,24,27,16,4,31,41,32,15,19,20,38,0,26,18,43,46,40,17,29,14,34,14,32,17,32,47,16,45,7,4,35,22,42,11,38,2,0,29,4,38,17,44,9,23,5,10,31,17,1,11,16,5,37,27,35,32,45,16,18,1,14,4,42,24,43,31,21,38,6,34,39,46,20,1,38,47 ]
return get_host_and_guest(dv, de, dc_inc, dc_out)
def get_small_host_and_guest():
dv = [0, 1, 0, 1, 0]
de = [0, 0, 0]
dc_inc = [0, 2, 4]
dc_out = [1, 3, 3]
return get_host_and_guest(dv, de, dc_inc, dc_out)

View file

@ -96,6 +96,7 @@ class Graph(object):
# redundant type keeping, "needed" for fast iterating over specific type
self.vertices = {} # {type, set(v1, v2, ...)}
self.edges = {} # {type, set(e1, e2, ...)}
self.num_vertices = 0
def addCreateVertex(self, str_type):
"""
@ -114,6 +115,8 @@ class Graph(object):
raise TypeError('addVertex expects a Vertex')
# add vertex, but it first creates a new set for the vertex type
# if the type does not exist in the dictionary
if vertex not in self.vertices.get(vertex.type, set()):
self.num_vertices += 1
self.vertices.setdefault(vertex.type, set()).add(vertex)
def getVerticesOfType(self, str_type):

View file

@ -31,34 +31,17 @@ if __name__ == '__main__':
"""
The main function called when running from the command line.
"""
nr_of_vertices = 50
nr_of_diff_types_v = 2
nr_of_edges = 150
nr_of_diff_types_e = 2
random.seed(0)
dv = [random.randint(0, nr_of_diff_types_v) for _ in range(nr_of_vertices)]
de = [random.randint(0, nr_of_diff_types_e) for _ in range(nr_of_edges)]
dc_inc = [random.randint(0, nr_of_vertices-1) for _ in range(nr_of_edges)]
dc_out = [random.randint(0, nr_of_vertices-1) for _ in range(nr_of_edges)]
graph, pattern = get_random_host_and_guest(
nr_vtxs = 10,
nr_vtx_types = 0,
nr_edges = 20,
nr_edge_types = 0,
)
# override random graph by copy pasting output from terminal
# dv = [ 10,5,4,0,8,6,8,0,4,8,5,5,7,0,10,0,5,6,10,4,0,3,0,8,2,7,5,8,1,0,2,10,0,0,1,6,8,4,7,6,4,2,10,10,6,4,6,0,2,7 ]
# de = [ 8,10,8,1,6,7,4,3,5,2,0,0,9,6,0,3,8,3,2,7,2,3,10,8,10,8,10,2,5,5,10,6,7,5,1,2,1,2,2,3,7,7,2,1,7,2,9,10,8,1,9,4,1,3,1,1,8,2,2,9,10,9,1,9,4,10,10,10,9,3,5,3,6,6,9,1,2,6,3,2,4,10,9,6,5,6,2,4,3,2,4,10,6,2,8,8,0,5,1,7,3,4,3,8,7,3,0,8,3,3,8,5,10,5,9,3,1,10,3,2,6,3,10,0,5,10,9,10,0,1,4,7,10,3,1,9,1,2,3,7,4,3,7,8,8,4,5,10,1,4 ]
# dc_inc = [ 0,25,18,47,22,25,16,45,38,25,5,45,15,44,17,46,6,17,35,8,16,29,48,47,25,34,4,20,24,1,47,44,8,25,32,3,16,6,33,21,6,13,41,10,17,25,21,33,31,30,5,4,45,26,16,42,12,25,29,3,32,30,14,26,11,13,7,13,3,43,43,22,48,37,20,28,15,40,19,33,43,16,49,36,11,25,9,42,3,22,16,40,42,44,27,30,1,18,10,35,19,6,9,43,37,38,45,19,41,14,37,45,0,31,29,31,24,20,44,46,8,45,43,3,38,38,35,12,19,45,7,34,20,28,12,17,45,17,35,49,20,21,49,1,35,38,38,36,33,30 ]
# dc_out = [ 9,2,49,49,37,33,16,21,5,46,4,15,9,6,14,22,16,33,23,21,15,31,37,23,47,3,30,26,35,9,29,21,39,32,22,43,5,9,41,30,31,30,37,33,31,34,23,22,34,26,44,36,38,33,48,5,9,34,13,7,48,41,43,26,26,7,12,6,12,28,22,8,29,22,24,27,16,4,31,41,32,15,19,20,38,0,26,18,43,46,40,17,29,14,34,14,32,17,32,47,16,45,7,4,35,22,42,11,38,2,0,29,4,38,17,44,9,23,5,10,31,17,1,11,16,5,37,27,35,32,45,16,18,1,14,4,42,24,43,31,21,38,6,34,39,46,20,1,38,47 ]
dv = [0, 1, 0, 1, 0]
de = [0, 0, 0]
dc_inc = [0, 2, 4]
dc_out = [1, 3, 3]
gg = GraphGenerator(dv, de, dc_inc, dc_out, debug)
graph = gg.getRandomGraph()
print(graph.vertices)
pattern = gg.getRandomPattern(3, 15, debug=debug)
print(pattern.vertices)
# graph, pattern = get_large_host_and_guest()
# graph, pattern = get_small_host_and_guest()
# override random pattern by copy pasting output from terminal to create
# pattern, paste it in the createConstantPattern function in the generator.py

View file

@ -3,6 +3,34 @@
import itertools
from util.timer import Timer
# like finding the 'strongly connected componenets', but edges are navigable in any direction
def find_connected_components(graph):
next_component = 0
vtx_to_component = {}
component_to_vtxs = []
for vtx in graph.vtxs:
if vtx in vtx_to_component:
continue
vtx_to_component[vtx] = next_component
vtxs = []
component_to_vtxs.append(vtxs)
add_recursively(vtx, vtxs, vtx_to_component, next_component)
next_component += 1
return (vtx_to_component, component_to_vtxs)
def add_recursively(vtx, vtxs: list, d: dict, component: int, already_visited: set = set()):
if vtx in already_visited:
return
already_visited.add(vtx)
vtxs.append(vtx)
d[vtx] = component
for edge in vtx.outgoing:
add_recursively(edge.tgt, vtxs, d, component, already_visited)
for edge in vtx.incoming:
add_recursively(edge.src, vtxs, d, component, already_visited)
class Graph:
def __init__(self):
self.vtxs = []
@ -18,13 +46,19 @@ class Vertex:
return f"V({self.value})"
class Edge:
def __init__(self, src: Vertex, tgt: Vertex):
def __init__(self, src: Vertex, tgt: Vertex, label=None):
self.src = src
self.tgt = tgt
self.label = label
# Add ourselves to src/tgt vertices
self.src.outgoing.append(self)
self.tgt.incoming.append(self)
def __repr__(self):
if self.label != None:
return f"E({self.src}--{self.label}->{self.tgt})"
else:
return f"E({self.src}->{self.tgt})"
class MatcherState:
@ -38,8 +72,7 @@ class MatcherState:
self.h_unmatched_vtxs = []
self.g_unmatched_vtxs = []
# the most recently added pair of (guest,host) vertices
# will always try to grow mapping via outgoing/incoming edges of this pair before attempting other non-connected vertices
# boundary is the most recently added (to the mapping) pair of (guest -> host) vertices
self.boundary = None
@staticmethod
@ -89,6 +122,9 @@ class MatcherState:
((ge,he) for ge,he in self.mapping_edges.items()),
))
def __repr__(self):
# return self.make_hashable().__repr__()
return "VTXS: "+self.mapping_vtxs.__repr__()+"\nEDGES: "+self.mapping_edges.__repr__()
class MatcherVF2:
# Guest is the pattern
@ -97,6 +133,14 @@ class MatcherVF2:
self.guest = guest
self.compare_fn = compare_fn
with Timer("find_connected_components - host"):
self.host_vtx_to_component, self.host_component_to_vtxs = find_connected_components(host)
with Timer("find_connected_components - guest"):
self.guest_vtx_to_component, self.guest_component_to_vtxs = find_connected_components(guest)
print("number of host connected components:", len(self.host_component_to_vtxs))
print("number of guest connected components:", len(self.guest_component_to_vtxs))
def match(self):
yield from self._match(
state=MatcherState.make_initial(self.host, self.guest),
@ -104,26 +148,30 @@ class MatcherVF2:
def _match(self, state, already_visited, indent=0):
# input()
def print_debug(*args):
pass
# print(*args) # uncomment to see a trace of the matching process
# print(" "*indent, *args) # uncomment to see a trace of the matching process
print_debug(" "*indent, "match")
print_debug("match")
hashable_state = state.make_hashable()
if hashable_state in already_visited:
print_debug(" "*indent, " SKIP - ALREADY VISITED")
print_debug(" "*indent, " ", hashable_state)
# Keep track of the states in the search space that we already visited
hashable = state.make_hashable()
if hashable in already_visited:
print_debug(" SKIP - ALREADY VISITED")
# print_debug(" ", hashable)
return
print_debug(" "*indent, " ADD STATE")
print_debug(" "*indent, " ", hashable_state)
already_visited.add(hashable_state)
# print_debug(" ", [hash(a) for a in already_visited])
# print_debug(" ADD STATE")
# print_debug(" ", hash(hashable))
already_visited.add(hashable)
if len(state.mapping_vtxs) == len(self.guest.vtxs) and len(state.mapping_edges) == len(self.guest.edges):
print_debug(" "*indent, "GOT MATCH:")
print_debug(" "*indent, " ", state.mapping_vtxs)
print_debug(" "*indent, " ", state.mapping_edges)
print_debug("GOT MATCH:")
print_debug(" ", state.mapping_vtxs)
print_debug(" ", state.mapping_edges)
yield state
return
@ -136,61 +184,82 @@ class MatcherVF2:
raise Exception("wtf!")
def attempt_grow(direction, indent):
print_debug(" "*indent, 'attempt_grow', direction)
if state.boundary != None:
g_vtx, h_vtx = state.boundary
for g_candidate_edge in getattr(g_vtx, direction):
print_debug(" "*indent, 'g_candidate_edge:', g_candidate_edge)
if g_candidate_edge in state.mapping_edges:
print_debug(" "*indent, " skip, guest edge already matched")
continue # skip already matched guest edge
for g_matched_vtx, h_matched_vtx in state.mapping_vtxs.items():
print_debug('attempt_grow', direction)
for g_candidate_edge in getattr(g_matched_vtx, direction):
print_debug('g_candidate_edge:', g_candidate_edge)
g_candidate_vtx = read_edge(g_candidate_edge, direction)
for h_candidate_edge in getattr(h_vtx, direction):
print_debug(" "*indent, 'h_candidate_edge:', h_candidate_edge)
# g_to_skip_vtxs.add(g_candidate_vtx)
if g_candidate_edge in state.mapping_edges:
print_debug(" skip, guest edge already matched")
continue # skip already matched guest edge
for h_candidate_edge in getattr(h_matched_vtx, direction):
if g_candidate_edge.label != h_candidate_edge.label:
print_debug(" labels differ")
continue
print_debug('h_candidate_edge:', h_candidate_edge)
if h_candidate_edge in state.r_mapping_edges:
print_debug(" "*indent, " skip, host edge already matched")
print_debug(" skip, host edge already matched")
continue # skip already matched host edge
h_candidate_vtx = read_edge(h_candidate_edge, direction)
print_debug(" "*indent, 'grow edge', g_candidate_edge, ':', h_candidate_edge)
print_debug('grow edge', g_candidate_edge, ':', h_candidate_edge, id(g_candidate_edge), id(h_candidate_edge))
new_state = state.grow_edge(h_candidate_edge, g_candidate_edge)
yield from attempt_match_vtxs(
new_state,
g_candidate_vtx,
h_candidate_vtx,
indent+1)
print_debug('backtrack edge', g_candidate_edge, ':', h_candidate_edge, id(g_candidate_edge), id(h_candidate_edge))
def attempt_match_vtxs(state, g_candidate_vtx, h_candidate_vtx, indent):
print_debug(" "*indent, 'attempt_match_vtxs')
print_debug('attempt_match_vtxs')
if g_candidate_vtx in state.mapping_vtxs:
if state.mapping_vtxs[g_candidate_vtx] != h_candidate_vtx:
print_debug(" "*indent, " nope, guest already mapped (mismatch)")
print_debug(" nope, guest already mapped (mismatch)")
return # guest vtx is already mapped but doesn't match host vtx
if h_candidate_vtx in state.r_mapping_vtxs:
if state.r_mapping_vtxs[h_candidate_vtx] != g_candidate_vtx:
print_debug(" "*indent, " nope, host already mapped (mismatch)")
print_debug(" nope, host already mapped (mismatch)")
return # host vtx is already mapped but doesn't match guest vtx
g_outdegree = len(g_candidate_vtx.outgoing)
h_outdegree = len(h_candidate_vtx.outgoing)
if g_outdegree > h_outdegree:
print_debug(" nope, outdegree")
return
g_indegree = len(g_candidate_vtx.incoming)
h_indegree = len(h_candidate_vtx.incoming)
if g_indegree > h_indegree:
print_debug(" nope, indegree")
return
if not self.compare_fn(g_candidate_vtx.value, h_candidate_vtx.value):
print_debug(" nope, bad compare")
return
new_state = state.grow_vtx(
h_candidate_vtx,
g_candidate_vtx)
print_debug(" "*indent, 'grow vtx', g_candidate_vtx, ':', h_candidate_vtx)
print_debug('grow vtx', g_candidate_vtx, ':', h_candidate_vtx, id(g_candidate_vtx), id(h_candidate_vtx))
yield from self._match(new_state, already_visited, indent+1)
print_debug('backtrack vtx', g_candidate_vtx, ':', h_candidate_vtx, id(g_candidate_vtx), id(h_candidate_vtx))
print_debug(" "*indent, 'preferred...')
print_debug('preferred...')
yield from attempt_grow('outgoing', indent+1)
yield from attempt_grow('incoming', indent+1)
print_debug(" "*indent, 'least preferred...')
for g_candidate_vtx in state.g_unmatched_vtxs:
print_debug('least preferred...')
if state.boundary != None:
g_boundary_vtx, _ = state.boundary
guest_boundary_component = self.guest_vtx_to_component[g_boundary_vtx]
# only try guest vertices that are in a different component (all vertices in the same component are already discovered via 'attempt_grow')
guest_components_to_try = (c for i,c in enumerate(self.guest_component_to_vtxs) if i != guest_boundary_component)
# for the host vertices however, we have to try them from all components, because different connected components of our pattern (=guest) could be mapped onto the same connected component in the host
else:
guest_components_to_try = self.guest_component_to_vtxs
for g_candidate_vtxs in guest_components_to_try:
for g_candidate_vtx in g_candidate_vtxs:
if g_candidate_vtx in state.mapping_vtxs:
print_debug("skip (already matched)", g_candidate_vtx)
continue
for h_candidate_vtx in state.h_unmatched_vtxs:
yield from attempt_match_vtxs(state, g_candidate_vtx, h_candidate_vtx, indent+1)

View file

@ -0,0 +1,156 @@
from state.base import State
from uuid import UUID
from services.bottom.V0 import Bottom
from pattern_matching.matcher import Graph, Edge, Vertex
import itertools
import re
from util.timer import Timer
class _is_edge:
def __repr__(self):
return "EDGE"
# just a unique symbol that is only equal to itself
IS_EDGE = _is_edge()
class IS_TYPE:
def __init__(self, type):
# mvs-node of the type
self.type = type
def __repr__(self):
return f"TYPE({str(self.type)[-4:]})"
# def __eq__(self, other):
# if not isinstance(other, IS_TYPE):
# return False
# return other.type == self.type
# def __hash__(self):
# return self.type.__hash__()
UUID_REGEX = re.compile(r"[0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z]-[0-9a-z][0-9a-z][0-9a-z][0-9a-z]-[0-9a-z][0-9a-z][0-9a-z][0-9a-z]-[0-9a-z][0-9a-z][0-9a-z][0-9a-z]-[0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z]")
# Converts an object/class diagram in MVS state to the pattern matcher graph type
# ModelRefs are flattened
def model_to_graph(state: State, model: UUID):
with Timer("model_to_graph"):
bottom = Bottom(state)
graph = Graph()
mvs_edges = []
modelrefs = {}
def extract_modelref(el):
value = bottom.read_value(el)
# If the value of the el is a ModelRef (only way to detect this is to match a regex - not very clean), then extract it. We'll create a link to the referred model later.
if bottom.is_edge(el):
mvs_edges.append(el)
return IS_EDGE
if isinstance(value, str):
if UUID_REGEX.match(value) != None:
# side-effect
modelrefs[el] = UUID(value)
return None
return value
# MVS-Nodes become vertices
uuid_to_vtx = { node: Vertex(value=extract_modelref(node)) for node in bottom.read_outgoing_elements(model) }
graph.vtxs = [ vtx for vtx in uuid_to_vtx.values() ]
# For every MSV-Edge, two edges are created (for src and tgt)
for mvs_edge in mvs_edges:
mvs_src = bottom.read_edge_source(mvs_edge)
if mvs_src in uuid_to_vtx:
graph.edges.append(Edge(
src=uuid_to_vtx[mvs_src],
tgt=uuid_to_vtx[mvs_edge],
label="outgoing"))
mvs_tgt = bottom.read_edge_target(mvs_edge)
if mvs_tgt in uuid_to_vtx:
graph.edges.append(Edge(
src=uuid_to_vtx[mvs_tgt],
tgt=uuid_to_vtx[mvs_edge],
label="tgt"))
for node, ref in modelrefs.items():
# Recursively convert ref'ed model to graph
ref_model = model_to_graph(state, ref)
# Flatten and create link to ref'ed model
graph.vtxs += ref_model.vtxs
graph.edges += ref_model.edges
graph.edges.append(Edge(
src=uuid_to_vtx[node],
tgt=ref_model.vtxs[0], # which node to link to?? dirty
label="modelref"))
# # Add typing information
# for i,node in enumerate(bottom.read_outgoing_elements(model)):
# type_node, = bottom.read_outgoing_elements(node, "Morphism")
# print('node', node, 'has type', type_node)
# # We create a Vertex storing the type
# type_vertex = Vertex(value=IS_TYPE(type_node))
# graph.vtxs.append(type_vertex)
# type_edge = Edge(
# src=uuid_to_vtx[node],
# tgt=type_vertex,
# label="type")
# print(type_edge)
# graph.edges.append(type_edge)
return graph
# Function object for pattern matching. Decides whether to match host and guest vertices, where guest is a RAMified instance (e.g., the attributes are all strings with Python expressions), and the host is an instance (=object diagram) of the original model (=class diagram)
class RAMCompare:
def __init__(self, bottom):
self.bottom = bottom
type_model_id = bottom.state.read_dict(bottom.state.read_root(), "SCD")
self.scd_model = UUID(bottom.state.read_value(type_model_id))
def is_subtype_of(self, supposed_subtype: UUID, supposed_supertype: UUID):
inheritance_node, = self.bottom.read_outgoing_elements(self.scd_model, "Inheritance")
if supposed_subtype == supposed_supertype:
# reflexive:
return True
for outgoing in self.bottom.read_outgoing_edges(supposed_subtype):
if inheritance_node in self.bottom.read_outgoing_elements(outgoing, "Morphism"):
# 'outgoing' is an inheritance link
supertype = self.bottom.read_edge_target(outgoing)
if supertype != supposed_subtype:
if self.is_subtype_of(supertype, supposed_supertype):
return True
return False
def __call__(self, g_val, h_val):
if g_val == None:
return h_val == None
# mvs-edges (which are converted to vertices) only match with mvs-edges
if g_val == IS_EDGE:
return h_val == IS_EDGE
if h_val == IS_EDGE:
return False
# types only match with their supertypes
if isinstance(g_val, IS_TYPE):
if not isinstance(h_val, IS_TYPE):
return False
g_val_original_type = self.bottom.read_outgoing_elements(g_val.type, "RAMifies")
result = self.is_subtype_of(h_val.type, g_val_original_type)
print("RESULT", result)
return result
if isinstance(h_val, IS_TYPE):
return False
# print(g_val, h_val)
return eval(g_val, {}, {'v': h_val})

View file

@ -81,6 +81,9 @@ class Bottom:
result = self.state.read_edge(edge)
return result[1] if result != None else result
def is_edge(self, elem: UUID) -> bool:
return self.state.is_edge(elem)
def read_incoming_edges(self, target: UUID, label=None) -> List[UUID]:
"""
Reads incoming edges of an element. Optionally, filter them based on their label

View file

@ -59,16 +59,26 @@ class OD:
# 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):
def create_integer_value(self, name: str, 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
# 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
def create_string_value(self, name: str, value: str):
from services.primitives.string_type import String
string_node = self.bottom.create_node()
string_t = String(string_node, self.bottom.state)
string_t.create(value)
# name = '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, "String", string_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
@ -79,6 +89,7 @@ class OD:
def create_link(self, assoc_name: str, src_obj_name: str, tgt_obj_name: str):
print(tgt_obj_name)
src_obj_node, = self.bottom.read_outgoing_elements(self.model, src_obj_name)
tgt_obj_node, = self.bottom.read_outgoing_elements(self.model, tgt_obj_name)

View file

@ -72,6 +72,8 @@ class SCD:
if max_c != None:
set_cardinality("upper", max_c)
return class_node
def create_association(self, name: str, source: str, target: str,
src_min_c: int = None, src_max_c: int = None,
tgt_min_c: int = None, tgt_max_c: int = None):

View file

@ -105,6 +105,9 @@ class PyState(State):
else:
return None, None
def is_edge(self, elem: Element) -> bool:
return elem in self.edges
def read_dict(self, elem: Element, value: Any) -> Optional[Element]:
e = self.read_dict_edge(elem, value)
if e == None:

View file

@ -117,7 +117,10 @@ def ramify(state: State, model: UUID) -> UUID:
# - max-card: same as original
upper_card = find_cardinality(class_node, class_upper_card_node)
print('creating class', class_name, "with card 0 ..", upper_card)
ramified_scd.create_class(class_name, abstract=None, max_c=upper_card)
ramified_class = ramified_scd.create_class(class_name, abstract=None, max_c=upper_card)
# traceability link
bottom.create_edge(ramified_class, class_node, "RAMifies")
for (attr_name, attr_type) in get_attributes(class_node):
print(' creating attribute', attr_name, "with type String")

10
util/timer.py Normal file
View file

@ -0,0 +1,10 @@
import time
class Timer:
def __init__(self, text):
self.text = text
def __enter__(self):
self.start_time = time.perf_counter_ns()
def __exit__(self, exc_type, exc_value, traceback):
self.end_time = time.perf_counter_ns()
print(self.text, (self.end_time - self.start_time)/1000000, "ms")