浏览代码

Abstract away Object Diagram pattern matching in a function that does all the magic

Joeri Exelmans 11 月之前
父节点
当前提交
f45872d3f7
共有 3 个文件被更改,包括 96 次插入108 次删除
  1. 5 33
      experiments/exp_scd.py
  2. 90 74
      pattern_matching/mvs_adapter.py
  3. 1 1
      transformation/rewriter.py

+ 5 - 33
experiments/exp_scd.py

@@ -137,18 +137,6 @@ def main():
     conf4 = Conformance(state, rhs_id, ramified_mm_id)
     print("RHS conforms?", conf4.check_nominal(log=True))
 
-    # Convert to format understood by matching algorithm
-    host = mvs_adapter.model_to_graph(state, dsl_m_id, dsl_mm_id)
-    guest = mvs_adapter.model_to_graph(state, lhs_id, ramified_mm_id)
-
-    print("HOST:")
-    print(host.vtxs)
-    print(host.edges)
-
-    print("GUEST:")
-    print(guest.vtxs)
-    print(guest.edges)
-
     def render_ramification():
         uml = (""
             # Render original and RAMified meta-models
@@ -177,16 +165,9 @@ def main():
         uml += plantuml.render_trace_conformance(state, dsl_m_id, dsl_mm_id)
 
         print("matching...")
-        matcher = MatcherVF2(host, guest, mvs_adapter.RAMCompare(Bottom(state), dsl_m_od))
-        for m, color in zip(matcher.match(), ["red", "orange"]):
-            print("\nMATCH:\n", m)
-            name_mapping = {}
-            # id_mapping = {}
-            for guest_vtx, host_vtx in m.mapping_vtxs.items():
-                if isinstance(guest_vtx, mvs_adapter.NamedNode) and isinstance(host_vtx, mvs_adapter.NamedNode):
-                    # id_mapping[guest_vtx.node_id] = host_vtx.node_id
-                    name_mapping[guest_vtx.name] = host_vtx.name
-            print(name_mapping)
+        generator = mvs_adapter.match_od(state, dsl_m_id, dsl_mm_id, lhs_id, ramified_mm_id)
+        for name_mapping, color in zip(generator, ["red", "orange"]):
+            print("\nMATCH:\n", name_mapping)
 
             # Render every match
             uml += plantuml.render_trace_match(state, name_mapping, lhs_id, dsl_m_id, color)
@@ -197,17 +178,8 @@ def main():
     def render_rewrite():
         uml = render_ramification()
 
-        matcher = MatcherVF2(host, guest, mvs_adapter.RAMCompare(Bottom(state), dsl_m_od))
-        for m in matcher.match():
-            name_mapping = {}
-            # id_mapping = {}
-            for guest_vtx, host_vtx in m.mapping_vtxs.items():
-                if isinstance(guest_vtx, mvs_adapter.NamedNode) and isinstance(host_vtx, mvs_adapter.NamedNode):
-                    # id_mapping[guest_vtx.node_id] = host_vtx.node_id
-                    name_mapping[guest_vtx.name] = host_vtx.name
-            print(name_mapping)
-
-
+        generator = mvs_adapter.match_od(state, dsl_m_id, dsl_mm_id, lhs_id, ramified_mm_id)
+        for name_mapping in generator:
             rewriter.rewrite(state, lhs_id, rhs_id, ramified_mm_id, name_mapping, dsl_m_id, dsl_mm_id)
 
             # Render match

+ 90 - 74
pattern_matching/mvs_adapter.py

@@ -3,7 +3,7 @@ from uuid import UUID
 from services.bottom.V0 import Bottom
 from services.scd import SCD
 from services.od import OD
-from pattern_matching.matcher import Graph, Edge, Vertex
+from pattern_matching.matcher import Graph, Edge, Vertex, MatcherVF2
 from transformation import ramify
 import itertools
 import re
@@ -188,87 +188,103 @@ def model_to_graph(state: State, model: UUID, metamodel: UUID, prefix=""):
 
         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, host_od):
-        self.bottom = bottom
-        self.host_od = host_od
-
-        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):
-        if supposed_subtype == supposed_supertype:
-            # reflexive:
-            return True
-
-        inheritance_node, = self.bottom.read_outgoing_elements(self.scd_model, "Inheritance")
-
-        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 match_types(self, g_vtx_type, h_vtx_type):
-        # types only match with their supertypes
-        # we assume that 'RAMifies'-traceability links have been created between guest and host types
-        try:
-            g_vtx_original_type = ramify.get_original_type(self.bottom, g_vtx_type)
-        except:
-            return False
 
-        return self.is_subtype_of(h_vtx_type, g_vtx_original_type)
+def match_od(state, host_m, host_mm, pattern_m, pattern_mm):
+    # 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, host_od):
+            self.bottom = bottom
+            self.host_od = host_od
+
+            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):
+            if supposed_subtype == supposed_supertype:
+                # reflexive:
+                return True
+
+            inheritance_node, = self.bottom.read_outgoing_elements(self.scd_model, "Inheritance")
 
+            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
 
-    # Memoizing the result of comparison gives a huge performance boost!
-    # Especially `is_subtype_of` is very slow, and will be performed many times over on the same pair of nodes during the matching process.
-    # Assuming the model is not altered *during* matching, this is safe.
-    @functools.cache
-    def __call__(self, g_vtx, h_vtx):
-        # First check if the types match (if we have type-information)
-        if hasattr(g_vtx, 'typ'):
-            if not hasattr(h_vtx, 'typ'):
-                # if guest has a type, host must have a type
+            return False
+
+        def match_types(self, g_vtx_type, h_vtx_type):
+            # types only match with their supertypes
+            # we assume that 'RAMifies'-traceability links have been created between guest and host types
+            try:
+                g_vtx_original_type = ramify.get_original_type(self.bottom, g_vtx_type)
+            except:
                 return False
-            return self.match_types(g_vtx.typ, h_vtx.typ)
 
-        # Then, match by value
+            return self.is_subtype_of(h_vtx_type, g_vtx_original_type)
 
-        if g_vtx.value == None:
-            return h_vtx.value == None
 
-        # mvs-edges (which are converted to vertices) only match with mvs-edges
-        if g_vtx.value == IS_EDGE:
-            return h_vtx.value == IS_EDGE
+        # Memoizing the result of comparison gives a huge performance boost!
+        # Especially `is_subtype_of` is very slow, and will be performed many times over on the same pair of nodes during the matching process.
+        # Assuming the model is not altered *during* matching, this is safe.
+        @functools.cache
+        def __call__(self, g_vtx, h_vtx):
+            # First check if the types match (if we have type-information)
+            if hasattr(g_vtx, 'typ'):
+                if not hasattr(h_vtx, 'typ'):
+                    # if guest has a type, host must have a type
+                    return False
+                return self.match_types(g_vtx.typ, h_vtx.typ)
 
-        if h_vtx.value == IS_EDGE:
-            return False
+            # Then, match by value
 
-        if g_vtx.value == IS_MODELREF:
-            return h_vtx.value == IS_MODELREF
+            if g_vtx.value == None:
+                return h_vtx.value == None
 
-        if h_vtx.value == IS_MODELREF:
-            return False
+            # mvs-edges (which are converted to vertices) only match with mvs-edges
+            if g_vtx.value == IS_EDGE:
+                return h_vtx.value == IS_EDGE
 
-        # print(g_vtx.value, h_vtx.value)
-        def get_slot(h_vtx, slot_name: str):
-            slot_node = self.host_od.get_slot(h_vtx.node_id, slot_name)
-            return slot_node
-
-        def read_int(slot: UUID):
-            i = Integer(slot, self.bottom.state)
-            return i.read()
-
-        try:
-            return eval(g_vtx.value, {}, {
-                'v': h_vtx.value,
-                'get_slot': functools.partial(get_slot, h_vtx),
-                'read_int': read_int,
-            })
-        except Exception as e:
-            return False
+            if h_vtx.value == IS_EDGE:
+                return False
+
+            if g_vtx.value == IS_MODELREF:
+                return h_vtx.value == IS_MODELREF
+
+            if h_vtx.value == IS_MODELREF:
+                return False
+
+            # print(g_vtx.value, h_vtx.value)
+            def get_slot(h_vtx, slot_name: str):
+                slot_node = self.host_od.get_slot(h_vtx.node_id, slot_name)
+                return slot_node
+
+            def read_int(slot: UUID):
+                i = Integer(slot, self.bottom.state)
+                return i.read()
+
+            try:
+                return eval(g_vtx.value, {}, {
+                    'v': h_vtx.value,
+                    'get_slot': functools.partial(get_slot, h_vtx),
+                    'read_int': read_int,
+                })
+            except Exception as e:
+                return False
+
+    # Convert to format understood by matching algorithm
+    host = model_to_graph(state, host_m, host_mm)
+    guest = model_to_graph(state, pattern_m, pattern_mm)
+
+    matcher = MatcherVF2(host, guest, RAMCompare(Bottom(state), OD(host_mm, host_m, state)))
+    for m in matcher.match():
+        # print("\nMATCH:\n", m)
+        # Convert mapping
+        name_mapping = {}
+        for guest_vtx, host_vtx in m.mapping_vtxs.items():
+            if isinstance(guest_vtx, NamedNode) and isinstance(host_vtx, NamedNode):
+                name_mapping[guest_vtx.name] = host_vtx.name
+        yield name_mapping

+ 1 - 1
transformation/rewriter.py

@@ -25,7 +25,7 @@ def process_rule(state, lhs: UUID, rhs: UUID):
 
 # Rewrite is performed in-place
 # Also updates the mapping in-place, to become RHS -> host
-def rewrite(state, lhs: UUID, rhs: UUID, rhs_mm: UUID, match_mapping: dict, m_to_transform: UUID, mm: UUID) -> dict:
+def rewrite(state, lhs: UUID, rhs: UUID, rhs_mm: UUID, match_mapping: dict, m_to_transform: UUID, mm: UUID):
     bottom = Bottom(state)
 
     scd_metamodel_id = state.read_dict(state.read_root(), "SCD")