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) _optional_edge = add_edge_element(f"{attribute_element_name}.optional_link", attribute_element, _optional_node)
return _name_model, _optional_model return _name_model, _optional_model
##### SCD META-MODEL #####
# # CLASSES, i.e. elements typed by Class # # CLASSES, i.e. elements typed by Class
# # Element # # Element
element_node = add_node_element("Element") element_node = add_node_element("Element")
@ -64,6 +66,7 @@ def bootstrap_scd(state: State) -> UUID:
model_ref_node = add_node_element("ModelRef") model_ref_node = add_node_element("ModelRef")
# # Global Constraint # # Global Constraint
glob_constr_node = add_node_element("GlobalConstraint") glob_constr_node = add_node_element("GlobalConstraint")
# # ASSOCIATIONS, i.e. elements typed by Association # # ASSOCIATIONS, i.e. elements typed by Association
# # Association # # Association
assoc_edge = add_edge_element("Association", class_node, class_node) 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) inh_edge = add_edge_element("Inheritance", element_node, element_node)
# # Attribute Link # # Attribute Link
attr_link_edge = add_edge_element("AttributeLink", element_node, attr_node) attr_link_edge = add_edge_element("AttributeLink", element_node, attr_node)
# # INHERITANCES, i.e. elements typed by Inheritance # # INHERITANCES, i.e. elements typed by Inheritance
# # Class inherits from Element # # Class inherits from Element
add_edge_element("class_inh_element", class_node, element_node) 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) add_edge_element("attr_link_inh_element", attr_link_edge, element_node)
# # ModelRef inherits from Attribute # # ModelRef inherits from Attribute
add_edge_element("model_ref_inh_attr", model_ref_node, attr_node) add_edge_element("model_ref_inh_attr", model_ref_node, attr_node)
# # ATTRIBUTES, i.e. elements typed by Attribute # # ATTRIBUTES, i.e. elements typed by Attribute
# # Action Code # TODO: Update to ModelRef when action code is explicitly modelled # # Action Code # TODO: Update to ModelRef when action code is explicitly modelled
action_code_node = add_node_element("ActionCode") action_code_node = add_node_element("ActionCode")
# # MODELREFS, i.e. elements typed by ModelRef # # MODELREFS, i.e. elements typed by ModelRef
# # Integer # # Integer
integer_node = add_node_element("Integer", str(integer_type_root)) 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)) string_node = add_node_element("String", str(string_type_root))
# # Boolean # # Boolean
boolean_node = add_node_element("Boolean", str(boolean_type_root)) boolean_node = add_node_element("Boolean", str(boolean_type_root))
# # ATTRIBUTE LINKS, i.e. elements typed by AttributeLink # # ATTRIBUTE LINKS, i.e. elements typed by AttributeLink
# # name attribute of AttributeLink # # name attribute of AttributeLink
attr_name_edge = add_edge_element("AttributeLink_name", attr_link_edge, string_node) 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_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_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) assoc_t_u_c_edge = add_edge_element("Association_target_upper_cardinality", assoc_edge, integer_node)
# # bootstrap primitive types # # bootstrap primitive types
# # order is important, integer must be first # # order is important, integer must be first
bootstrap_integer_type(mcl_root, integer_type_root, state) 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_float_type(mcl_root, float_type_root, state)
bootstrap_string_type(mcl_root, string_type_root, state) bootstrap_string_type(mcl_root, string_type_root, state)
bootstrap_type_type(mcl_root, type_type_root, state) bootstrap_type_type(mcl_root, type_type_root, state)
# # ATTRIBUTE ATTRIBUTES, assign 'name' and 'optional' attributes to all AttributeLinks # # ATTRIBUTE ATTRIBUTES, assign 'name' and 'optional' attributes to all AttributeLinks
# # AttributeLink_name # # AttributeLink_name
m_name, m_opt = add_attribute_attributes("AttributeLink_name", attr_name_edge) 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 framework.conformance import Conformance
from services.od import OD from services.od import OD
from transformation.ramify import ramify from transformation.ramify import ramify
from services.bottom.V0 import Bottom
from services.primitives.integer_type import Integer from services.primitives.integer_type import Integer
from pattern_matching import mvs_adapter
from pattern_matching.matcher import MatcherVF2
import sys import sys
@ -62,6 +65,9 @@ def main():
int_type_id = state.read_dict(state.read_root(), "Integer") int_type_id = state.read_dict(state.read_root(), "Integer")
int_type = UUID(state.read_value(int_type_id)) 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) # scd2 = SCD(scd_node, state)
# for el in scd2.list_elements(): # for el in scd2.list_elements():
# print(el) # print(el)
@ -70,7 +76,7 @@ def main():
model_id = state.create_node() model_id = state.create_node()
scd = SCD(model_id, state) scd = SCD(model_id, state)
scd.create_class("Abstract", abstract=True) 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_inheritance("A", "Abstract")
scd.create_model_ref("Integer", int_type) scd.create_model_ref("Integer", int_type)
scd.create_attribute_link("A", "Integer", "size", False) scd.create_attribute_link("A", "Integer", "size", False)
@ -97,16 +103,46 @@ def main():
od = OD(model_id, inst_id, state) od = OD(model_id, inst_id, state)
od.create_object("a", "A") od.create_object("a", "A")
od.create_object("a2", "A")
od.create_object("b", "B") od.create_object("b", "B")
od.create_link("A2B", "a", "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....") print("checking conformance....")
conf2 = Conformance(state, inst_id, model_id) conf2 = Conformance(state, inst_id, model_id)
print("conforms?", conf2.check_nominal(log=True)) 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__": if __name__ == "__main__":
main() main()

View file

@ -200,3 +200,33 @@ class GraphGenerator(object):
# ---------------------------------------------------------------------- # ----------------------------------------------------------------------
return pattern 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

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

View file

@ -31,34 +31,17 @@ if __name__ == '__main__':
""" """
The main function called when running from the command line. The main function called when running from the command line.
""" """
nr_of_vertices = 50 random.seed(0)
nr_of_diff_types_v = 2
nr_of_edges = 150
nr_of_diff_types_e = 2
dv = [random.randint(0, nr_of_diff_types_v) for _ in range(nr_of_vertices)] graph, pattern = get_random_host_and_guest(
de = [random.randint(0, nr_of_diff_types_e) for _ in range(nr_of_edges)] nr_vtxs = 10,
dc_inc = [random.randint(0, nr_of_vertices-1) for _ in range(nr_of_edges)] nr_vtx_types = 0,
dc_out = [random.randint(0, nr_of_vertices-1) for _ in range(nr_of_edges)] nr_edges = 20,
nr_edge_types = 0,
)
# override random graph by copy pasting output from terminal # graph, pattern = 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 ] # graph, pattern = get_small_host_and_guest()
# 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)
# override random pattern by copy pasting output from terminal to create # override random pattern by copy pasting output from terminal to create
# pattern, paste it in the createConstantPattern function in the generator.py # pattern, paste it in the createConstantPattern function in the generator.py

View file

@ -3,6 +3,34 @@
import itertools 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: class Graph:
def __init__(self): def __init__(self):
self.vtxs = [] self.vtxs = []
@ -18,14 +46,20 @@ class Vertex:
return f"V({self.value})" return f"V({self.value})"
class Edge: class Edge:
def __init__(self, src: Vertex, tgt: Vertex): def __init__(self, src: Vertex, tgt: Vertex, label=None):
self.src = src self.src = src
self.tgt = tgt self.tgt = tgt
self.label = label
# Add ourselves to src/tgt vertices
self.src.outgoing.append(self) self.src.outgoing.append(self)
self.tgt.incoming.append(self) self.tgt.incoming.append(self)
def __repr__(self): def __repr__(self):
return f"E({self.src}->{self.tgt})" if self.label != None:
return f"E({self.src}--{self.label}->{self.tgt})"
else:
return f"E({self.src}->{self.tgt})"
class MatcherState: class MatcherState:
def __init__(self): def __init__(self):
@ -38,8 +72,7 @@ class MatcherState:
self.h_unmatched_vtxs = [] self.h_unmatched_vtxs = []
self.g_unmatched_vtxs = [] self.g_unmatched_vtxs = []
# the most recently added pair of (guest,host) vertices # boundary is the most recently added (to the mapping) pair of (guest -> host) vertices
# will always try to grow mapping via outgoing/incoming edges of this pair before attempting other non-connected vertices
self.boundary = None self.boundary = None
@staticmethod @staticmethod
@ -89,6 +122,9 @@ class MatcherState:
((ge,he) for ge,he in self.mapping_edges.items()), ((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: class MatcherVF2:
# Guest is the pattern # Guest is the pattern
@ -97,6 +133,14 @@ class MatcherVF2:
self.guest = guest self.guest = guest
self.compare_fn = compare_fn 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): def match(self):
yield from self._match( yield from self._match(
state=MatcherState.make_initial(self.host, self.guest), state=MatcherState.make_initial(self.host, self.guest),
@ -104,26 +148,30 @@ class MatcherVF2:
def _match(self, state, already_visited, indent=0): def _match(self, state, already_visited, indent=0):
# input()
def print_debug(*args): def print_debug(*args):
pass 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() # Keep track of the states in the search space that we already visited
if hashable_state in already_visited: hashable = state.make_hashable()
print_debug(" "*indent, " SKIP - ALREADY VISITED") if hashable in already_visited:
print_debug(" "*indent, " ", hashable_state) print_debug(" SKIP - ALREADY VISITED")
# print_debug(" ", hashable)
return return
print_debug(" "*indent, " ADD STATE") # print_debug(" ", [hash(a) for a in already_visited])
print_debug(" "*indent, " ", hashable_state) # print_debug(" ADD STATE")
already_visited.add(hashable_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): 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("GOT MATCH:")
print_debug(" "*indent, " ", state.mapping_vtxs) print_debug(" ", state.mapping_vtxs)
print_debug(" "*indent, " ", state.mapping_edges) print_debug(" ", state.mapping_edges)
yield state yield state
return return
@ -136,63 +184,84 @@ class MatcherVF2:
raise Exception("wtf!") raise Exception("wtf!")
def attempt_grow(direction, indent): def attempt_grow(direction, indent):
print_debug(" "*indent, 'attempt_grow', direction) for g_matched_vtx, h_matched_vtx in state.mapping_vtxs.items():
if state.boundary != None: print_debug('attempt_grow', direction)
g_vtx, h_vtx = state.boundary for g_candidate_edge in getattr(g_matched_vtx, direction):
for g_candidate_edge in getattr(g_vtx, direction): print_debug('g_candidate_edge:', g_candidate_edge)
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
g_candidate_vtx = read_edge(g_candidate_edge, direction) g_candidate_vtx = read_edge(g_candidate_edge, direction)
for h_candidate_edge in getattr(h_vtx, direction): # g_to_skip_vtxs.add(g_candidate_vtx)
print_debug(" "*indent, 'h_candidate_edge:', h_candidate_edge) 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: 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 continue # skip already matched host edge
h_candidate_vtx = read_edge(h_candidate_edge, direction) 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) new_state = state.grow_edge(h_candidate_edge, g_candidate_edge)
yield from attempt_match_vtxs( yield from attempt_match_vtxs(
new_state, new_state,
g_candidate_vtx, g_candidate_vtx,
h_candidate_vtx, h_candidate_vtx,
indent+1) 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): 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 g_candidate_vtx in state.mapping_vtxs:
if state.mapping_vtxs[g_candidate_vtx] != h_candidate_vtx: 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 return # guest vtx is already mapped but doesn't match host vtx
if h_candidate_vtx in state.r_mapping_vtxs: if h_candidate_vtx in state.r_mapping_vtxs:
if state.r_mapping_vtxs[h_candidate_vtx] != g_candidate_vtx: 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 return # host vtx is already mapped but doesn't match guest vtx
g_outdegree = len(g_candidate_vtx.outgoing) g_outdegree = len(g_candidate_vtx.outgoing)
h_outdegree = len(h_candidate_vtx.outgoing) h_outdegree = len(h_candidate_vtx.outgoing)
if g_outdegree > h_outdegree: if g_outdegree > h_outdegree:
print_debug(" nope, outdegree")
return return
g_indegree = len(g_candidate_vtx.incoming) g_indegree = len(g_candidate_vtx.incoming)
h_indegree = len(h_candidate_vtx.incoming) h_indegree = len(h_candidate_vtx.incoming)
if g_indegree > h_indegree: if g_indegree > h_indegree:
print_debug(" nope, indegree")
return return
if not self.compare_fn(g_candidate_vtx.value, h_candidate_vtx.value): if not self.compare_fn(g_candidate_vtx.value, h_candidate_vtx.value):
print_debug(" nope, bad compare")
return return
new_state = state.grow_vtx( new_state = state.grow_vtx(
h_candidate_vtx, h_candidate_vtx,
g_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) 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('outgoing', indent+1)
yield from attempt_grow('incoming', indent+1) yield from attempt_grow('incoming', indent+1)
print_debug(" "*indent, 'least preferred...') print_debug('least preferred...')
for g_candidate_vtx in state.g_unmatched_vtxs: if state.boundary != None:
for h_candidate_vtx in state.h_unmatched_vtxs: g_boundary_vtx, _ = state.boundary
yield from attempt_match_vtxs(state, g_candidate_vtx, h_candidate_vtx, indent+1) 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)
if indent == 0: if indent == 0:
print_debug('visited', len(already_visited), 'states total') print_debug('visited', len(already_visited), 'states total')

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) result = self.state.read_edge(edge)
return result[1] if result != None else result 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]: def read_incoming_edges(self, target: UUID, label=None) -> List[UUID]:
""" """
Reads incoming edges of an element. Optionally, filter them based on their label 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: # An attribute-link is indistinguishable from an ordinary link:
return self.create_link(attr_link_name, object_name, target_name) 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 from services.primitives.integer_type import Integer
int_node = self.bottom.create_node() int_node = self.bottom.create_node()
integer_t = Integer(int_node, self.bottom.state) integer_t = Integer(int_node, self.bottom.state)
integer_t.create(value) 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" # By convention, the type model must have a ModelRef named "Integer"
self.create_model_ref(name, "Integer", int_node) self.create_model_ref(name, "Integer", int_node)
return name 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: # Identical to the same SCD method:
def create_model_ref(self, name: str, type_name: str, model: UUID): def create_model_ref(self, name: str, type_name: str, model: UUID):
# create element + morphism links # 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): 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) 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) 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: if max_c != None:
set_cardinality("upper", max_c) set_cardinality("upper", max_c)
return class_node
def create_association(self, name: str, source: str, target: str, def create_association(self, name: str, source: str, target: str,
src_min_c: int = None, src_max_c: int = None, src_min_c: int = None, src_max_c: int = None,
tgt_min_c: int = None, tgt_max_c: int = None): tgt_min_c: int = None, tgt_max_c: int = None):

View file

@ -105,6 +105,9 @@ class PyState(State):
else: else:
return None, None 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]: def read_dict(self, elem: Element, value: Any) -> Optional[Element]:
e = self.read_dict_edge(elem, value) e = self.read_dict_edge(elem, value)
if e == None: if e == None:

View file

@ -117,7 +117,10 @@ def ramify(state: State, model: UUID) -> UUID:
# - max-card: same as original # - max-card: same as original
upper_card = find_cardinality(class_node, class_upper_card_node) upper_card = find_cardinality(class_node, class_upper_card_node)
print('creating class', class_name, "with card 0 ..", upper_card) 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): for (attr_name, attr_type) in get_attributes(class_node):
print(' creating attribute', attr_name, "with type String") 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")