Преглед на файлове

reworked edge checking in verify code

Lucas Heer преди 7 години
родител
ревизия
aff95fc175
променени са 6 файла, в които са добавени 90 реда и са изтрити 158 реда
  1. 43 2
      commons.py
  2. 0 2
      sketchUI/im_mainwindow.py
  3. 1 1
      sketchUI/im_scene.py
  4. 0 16
      sketchUI/mvops.py
  5. 45 136
      verifier.py
  6. 1 1
      wrappers/modelverse_SCCD.py

+ 43 - 2
commons.py

@@ -58,9 +58,9 @@ def all_nodes_with_type(model, typ):
     ret = [node for node in all_nodes if mv.read_attrs(model, node)["typeID"] == typ]
     return ret
 
-def get_node_type(model, node):
+def get_node_type(model, node_id):
     """ Returns the type attribute of node in model as string"""
-    return mv.read_attrs(model, node)["typeID"]
+    return mv.read_attrs(model, node_id)["typeID"]
 
 def get_available_types():
     """ Returns a list of all types of nodes in all example models """
@@ -120,6 +120,47 @@ def get_attributes_of_node(model, node_id):
         ret.append(attr)
     return ret
 
+def model_contains_type(model, node_type):
+    """
+    True if some node in model is typed by node_type, False otherwise.
+    """
+    all_types = all_nodes_with_type(model, node_type)
+    if not all_types:
+        return False
+    return True
+
+def has_edge_to_type(model, node_id, type_id):
+    """
+    True if the node identified by node_id is connected to a node of type type_id, False otherwise.
+    """
+    outgoings = mv.read_outgoing(model, node_id, "Edge")
+    dest_ids = []
+    for edge in outgoings:
+        dest_node = mv.read_association_destination(model, edge)[0]
+        dest_ids.append(dest_node)
+    dest_types = []
+    for node_id in dest_ids:
+        node_type = get_node_type(model, node_id)
+        dest_types.append(node_type)
+
+    return type_id in dest_types
+
+def is_edge_supported(from_type, to_type):
+    """
+    True if an association between from_type and to_type exists in any example model
+    False otherwise
+    """
+    for m in all_example_models():
+        nodes_from = all_nodes_with_type(m, from_type)
+        nodes_to = all_nodes_with_type(m, to_type)
+        for nf in nodes_from:
+            for nt in nodes_to:
+                assocs = get_associations_between(m, nf, nt)
+                if assocs:
+                    # somewhere in an example model, there is such an association
+                    return True
+    return False
+
 def new_instance_model():
     """
     Adds a new, empty instance model to the Modelverse.

+ 0 - 2
sketchUI/im_mainwindow.py

@@ -177,7 +177,6 @@ class IMMainWindow(QMainWindow, Ui_MainWindow):
         verify = Verifier(self._cur_model)
         verify.init()
 
-        """
         self.plainTextEdit.appendPlainText("Verify: Checking node typing ...")
         self.plainTextEdit.repaint()
         ret = verify.verify_node_typing()
@@ -199,7 +198,6 @@ class IMMainWindow(QMainWindow, Ui_MainWindow):
         else:
             self.plainTextEdit.appendPlainText("OK")
             self.plainTextEdit.repaint()
-        """
 
         self.plainTextEdit.appendPlainText("Verify: Checking attributes ...")
         self.plainTextEdit.repaint()

+ 1 - 1
sketchUI/im_scene.py

@@ -98,7 +98,7 @@ class CustomScene(QGraphicsScene):
         from_type = from_item.get_type()
         to_type = to_item.get_type()
         if is_new:
-            if not mvops.is_edge_supported(from_type, to_type):
+            if not commons.is_edge_supported(from_type, to_type):
                 self._parent.plainTextEdit.appendPlainText("Error: Edge not supported between types {} and {}".format(from_type, to_type))
                 return
 

+ 0 - 16
sketchUI/mvops.py

@@ -9,22 +9,6 @@ import commons
 from sketchUI.graphics_node_item import IconType
 
 
-def is_edge_supported(from_type, to_type):
-    """
-    True if an association between from_type and to_type exists in any example model
-    False otherwise
-    """
-    for m in commons.all_example_models():
-        nodes_from = commons.all_nodes_with_type(m, from_type)
-        nodes_to = commons.all_nodes_with_type(m, to_type)
-        for nf in nodes_from:
-            for nt in nodes_to:
-                assocs = commons.get_associations_between(m, nf, nt)
-                if assocs:
-                    # somewhere in an example model, there is such an association
-                    return True
-    return False
-
 def add_node(model, node_type):
     """ Adds new node to model "model" with type attribute "node_type" """
     node_id = mv.instantiate(model, "Node")

+ 45 - 136
verifier.py

@@ -5,19 +5,25 @@ import commons
 class Edge(object):
     """
     Small helper class for association validation.
-    Stores an edge as a tuple of (source, dest)
+    Represents an edge as a connection between two nodes (n1, n2).
+    Does not assume any direction. As such, the eq and hash implementations
+    do not care about the ordering and an Edge("a", "b") object is equal to Edge("b", "a").
     """
-    def __init__(self, src, dest):
-        self.src = src
-        self.dest = dest
+    def __init__(self, n1, n2):
+        self.n1 = n1
+        self.n2 = n2
 
     def __eq__(self, other):
-        if other.src == self.src and other.dest == self.dest:
+        # type: (Edge) -> bool
+        if other.n1 == self.n1 or other.n1 == self.n2 and other.n2 == self.n1 or other.n2 == self.n2:
             return True
         return False
 
+    def __hash__(self):
+        return hash(self.n1) ^ hash(self.n2)
+
     def __repr__(self):
-        return "{}->{}".format(self.src, self.dest)
+        return "{}-{}".format(self.n1, self.n2)
 
 
 class Verifier(object):
@@ -118,6 +124,7 @@ class Verifier(object):
         attribute key in some example model.
         2. An attribute for a type is mandatory if it occurs in every example model.
         """
+
         # Check attribute keys for every node in instance model
         all_nodes = mv.all_instances(self._instance_model, "Node")
         for node in all_nodes:
@@ -172,135 +179,37 @@ class Verifier(object):
         to connect two nodes).
         """
 
+        # get a set of mandatory edges (= edges that occur in every example model)
+        all_edges = {model: set() for model in self._example_models}
+        for exm in self._example_models:
+            all_links = mv.all_instances(exm, "Edge")
+            for link in all_links:
+                src_id = mv.read_association_source(exm, link)[0]
+                dest_id = mv.read_association_destination(exm, link)[0]
+                src_type = commons.get_node_type(exm, src_id)
+                dest_type = commons.get_node_type(exm, dest_id)
+                all_edges[exm].add(Edge(src_type, dest_type))
+        mandatory_edges = set.intersection(*all_edges.values())
+
+        # check if instance model contains the types and the edge
+        for edge in mandatory_edges:
+            if not commons.model_contains_type(self._instance_model, edge.n1):
+                continue
+            if not commons.model_contains_type(self._instance_model, edge.n2):
+                continue
+            # instance model contains both types
+            for node in commons.all_nodes_with_type(self._instance_model, edge.n1):
+                if not commons.has_edge_to_type(self._instance_model, node, edge.n2):
+                    return {"OK":False, "error": "Edge between {} and {} mandatory".format(edge.n1, edge.n2)}
+
+        # lastly, check if all edges in the instance model are actually supported
+        all_edges = mv.all_instances(self._instance_model, "Edge")
+        for edge in all_edges:
+            src_id = mv.read_association_source(self._instance_model, edge)[0]
+            dest_id = mv.read_association_destination(self._instance_model, edge)[0]
+            src_type = commons.get_node_type(self._instance_model, src_id)
+            dest_type = commons.get_node_type(self._instance_model, dest_id)
+            if not commons.is_edge_supported(src_type, dest_type):
+                return {"OK":False, "error": "Edge between {} and {} not supported".format(edge.n1, edge.n2)}
 
         return {"OK": True, "error": None}
-
-def verify(instance_model_path):
-    """
-    Verify an instance model against all example models (mv folder currently hard-coded)
-    Performs checks similar to the linguistic conformance check as seen in the MDE class:
-    1. Node typing
-    2. Node cardinality
-    3. Attribute validity
-    4. Associations
-    """
-    example_models = commons.all_example_models()
-
-    # 1. Check if all nodes in instance model are typed by a node in the example models
-    # For that, first get all available types from example models
-    print("Verify: checking node typing ...")
-    all_types = []
-    for exm in example_models:
-        all_nodes = mv.all_instances(exm, "Node")
-        for node in all_nodes:
-            typ = mv.read_attrs(exm, node)["typeID"]
-            if not typ in all_types:
-                all_types.append(typ)
-    #print("Verify: types found in example models: {}".format(all_types))
-
-    # Now, check for every element in instance model if it is a valid type
-    all_nodes = mv.all_instances(instance_model_path, "Node")
-    for node in all_nodes:
-        node_typ = mv.read_attrs(instance_model_path, node)["typeID"]
-        if node_typ not in all_types:
-            return {"OK":False, "error":"Type {} from instance model not in example models".format(node_typ)}
-
-    # 2. Check node cardinality: a node is mandatory in the instance model if it occurs in every
-    # example model. Additionally, it needs to occur at least n times if it occurs at least n times in every example model.
-    print("Verify: checking node multiplicity ...")
-    total_occurences = {k:[] for k in all_types}
-    for node, occ_list in total_occurences.iteritems():
-        for exm in example_models:
-            num_occ = _count_occurences(node, exm)
-            occ_list.append(num_occ)
-
-    elem_cardinality = {k:min(v) for k, v in total_occurences.iteritems()}
-    for el, min_occ in elem_cardinality.iteritems():
-        if not _count_occurences(el, instance_model_path) >= min_occ:
-            return {"OK":False, "error":"Node with type {} needs to occur at least {} times".format(el, min_occ)}
-
-    # 3. Check attribute types: Is every attribute in instance model present in some example model?
-    print("Verify: checking attributes ...")
-    all_nodes = mv.all_instances(instance_model_path, "Node")
-    for node in all_nodes:
-        node_typ = mv.read_attrs(instance_model_path, node)["typeID"]
-        attrs = commons.get_all_attributes_of_type(instance_model_path, node_typ)
-        if len(attrs) == 0:
-            continue
-        #print("Attributes of node {} of type {} are: {}".format(node, node_typ, attrs))
-        # check example models for such an attribute key
-        for im_attribute in attrs:
-            found = False
-            for exm in example_models:
-                if found:
-                    break
-                exm_attrs = commons.get_all_attributes_of_type(exm, node_typ)
-                for exm_attr in exm_attrs:
-                    if exm_attr.key == im_attribute.key:
-                        #print("Found attribute {} in model {}".format(im_attribute, exm))
-                        found = True
-                        break
-            if not found:
-                return {"OK":False, "error":"Attribute {} not found in example models".format(im_attribute)}
-
-    # TODO: Attribute mandatory if present in all example model nodes
-
-
-    # 4. Associations
-    # Check multiplicity: an association needs to occur n>0 times if it occurs n>0 times in every example model
-    # Check assocation type validity as well
-    print("Verify: checking associations ...")
-    all_edges = {k:[] for k in example_models}
-    for exm in example_models:
-        all_nodes = mv.all_instances(exm, "Node")
-        for node in all_nodes:
-            node_typ = mv.read_attrs(exm, node)["typeID"]
-            out_associations = mv.read_outgoing(exm, node, "Edge")
-            for assoc in out_associations:
-                if assoc.startswith("__"):
-                    continue
-                target = mv.read_association_destination(exm, assoc)[0]
-                target_typ = mv.read_attrs(exm, target)["typeID"]
-                all_edges[exm].append(Edge(node_typ, target_typ))
-
-    unique_edges = []
-    for edge_lst in all_edges.values():
-        for edge in edge_lst:
-            if not edge in unique_edges:
-                unique_edges.append(edge)
-
-    edge_ctr = {k:[] for k in unique_edges}
-    for edge, ctr_lst in edge_ctr.iteritems():
-        for exm, edges in all_edges.iteritems():
-            ctr_lst.append(edges.count(edge))
-
-    for k, v in edge_ctr.iteritems():
-        edge_ctr[k] = min(v)
-
-    print(edge_ctr)  # {Edge("AP", "Tablet"): 0, Edge("Router", "PC"): 1, ...}
-    return {"OK": True, "error": None}
-
-    # check if mandatory edges are present in instance model
-    # first, get all edges in instance model
-    all_edges = mv.all_instances(instance_model_path, "Edge")
-    im_edges = []
-    for edge in all_edges:
-        if edge.startswith("__"):
-            continue
-        edge_src = mv.read_association_source(instance_model_path, edge)[0]
-        edge_dest = mv.read_association_destination(instance_model_path, edge)[0]
-        edge_src_typ = mv.read_attrs(instance_model_path, edge_src)["typeID"]
-        edge_dest_typ = mv.read_attrs(instance_model_path, edge_dest)["typeID"]
-        im_edges.append(Edge(edge_src_typ, edge_dest_typ))
-
-    # Check if all edges in instance models are valid (connecting types correspond to example models)
-    for edge in im_edges:
-        if edge not in edge_ctr.keys():
-            return {"OK":False, "error":"Edge {} not valid according to example models".format(edge)}
-
-    # now, check if for every mandatory edge, it occurs sufficiently enough
-    for edge, cnt in edge_ctr.iteritems():
-        if im_edges.count(edge) < cnt:
-            return {"OK":False, "error":"Edge {} needs to appear {} times in instance model".format(edge, cnt)}
-
-    return {"OK":True, "error":None}

+ 1 - 1
wrappers/modelverse_SCCD.py

@@ -1,7 +1,7 @@
 """
 Generated by Statechart compiler by Glenn De Jonghe, Joeri Exelmans, Simon Van Mierlo, and Yentl Van Tendeloo (for the inspiration)
 
-Date:   Thu Apr 26 14:15:31 2018
+Date:   Thu Apr 26 17:38:33 2018
 
 Model author: Yentl Van Tendeloo
 Model name:   MvK Server