verifier.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. import wrappers.modelverse as mv
  2. import commons
  3. class Edge(object):
  4. """
  5. Small helper class for association validation.
  6. Represents an edge as a connection between two nodes (n1, n2).
  7. Does not assume any direction. As such, the eq and hash implementations
  8. do not care about the ordering and an Edge("a", "b") object is equal to Edge("b", "a").
  9. """
  10. def __init__(self, n1, n2):
  11. self.n1 = n1
  12. self.n2 = n2
  13. def __eq__(self, other):
  14. # type: (Edge) -> bool
  15. if other.n1 == self.n1 or other.n1 == self.n2 and other.n2 == self.n1 or other.n2 == self.n2:
  16. return True
  17. return False
  18. def __hash__(self):
  19. return hash(self.n1) ^ hash(self.n2)
  20. def __repr__(self):
  21. return "{}-{}".format(self.n1, self.n2)
  22. class Verifier(object):
  23. def __init__(self, instance_model):
  24. self._instance_model = instance_model
  25. self._example_models = None
  26. self._available_types = None
  27. def set_instance_model(self, model):
  28. self._instance_model = model
  29. def get_instance_model(self):
  30. return self._instance_model
  31. def init(self):
  32. """
  33. Inits the verifier. Loads required data from the modelverse
  34. to avoid reloading it for every step.
  35. """
  36. self._example_models = commons.all_example_models()
  37. self._available_types = commons.get_available_types()
  38. def verify_node_typing(self):
  39. """
  40. Checks if every node in instance model is typed by a node in any example model.
  41. Should actually not be neccessary since instance modeling only allows types from
  42. example models.
  43. """
  44. # Check for every node in instance model if it has a valid type
  45. all_nodes = mv.all_instances(self._instance_model, "Node")
  46. for node in all_nodes:
  47. node_typ = commons.get_node_type(self._instance_model, node)
  48. if node_typ not in self._available_types:
  49. return {"OK": False, "error": "Type {} from instance model not in example models".format(node_typ)}
  50. return {"OK":True, "error":None, "affected":[]}
  51. def verify_node_multiplicity(self):
  52. """
  53. A node is mandatory in the instance model if it occurs in every example model.
  54. More specifically, if a node type appears occ = [n0, n1, ... n_i] times in example model i,
  55. it needs to occur min(occ) in the instance model.
  56. """
  57. total_occurences = {k: [] for k in self._available_types}
  58. for node, occ_list in total_occurences.iteritems():
  59. for exm in self._example_models:
  60. num_occ = commons.count_occurences(node, exm)
  61. occ_list.append(num_occ)
  62. elem_cardinality = {k: min(v) for k, v in total_occurences.iteritems()}
  63. for el, min_occ in elem_cardinality.iteritems():
  64. if not commons.count_occurences(el, self._instance_model) >= min_occ:
  65. return {"OK": False, "error": "Node with type {} needs to occur at least {} times".format(el, min_occ)}
  66. return {"OK": True, "error": None, "affected":[]}
  67. def _get_attributes_of_all_types(self):
  68. """
  69. Helper for attribute check that returns a dictionary of the form {type:[attr_key_1, attr_key_2, ...], ...},
  70. listing all attributes of every type from example models
  71. """
  72. attrs_of_types = {node_type:[] for node_type in self._available_types}
  73. for exm in self._example_models:
  74. for node_type, attrs in attrs_of_types.iteritems():
  75. attrs_of_type_in_exm = [x.key for x in commons.get_all_attributes_of_type(exm, node_type)]
  76. for attr in attrs_of_type_in_exm:
  77. if not attr in attrs:
  78. attrs.append(attr)
  79. return attrs_of_types
  80. def _has_attribute_key(self, model, node_id, attr_key):
  81. """
  82. True if node with node_id has an attribute with key attr_key, False otherwise.
  83. """
  84. attrs = commons.get_attributes_of_node(model, node_id)
  85. for attr in attrs:
  86. if attr.key == attr_key:
  87. return True
  88. return False
  89. def _is_attribute_mandatory(self, node_type, attr_key):
  90. """
  91. Helper for attribute check that returns True if the attribute attr_key of type node_type is mandatory by
  92. looking at all example models.
  93. """
  94. # iterate through all example models: If every node with type node_type has the attribute with attr_key,
  95. # it is mandatory
  96. for exm in self._example_models:
  97. nodes_of_type = commons.all_nodes_with_type(exm, node_type)
  98. for node in nodes_of_type:
  99. if not self._has_attribute_key(exm, node, attr_key):
  100. return False
  101. return True
  102. def verify_attributes(self):
  103. """
  104. 1. For every attribute key of a typed node in the instance model, there must be a corresponding
  105. attribute key in some example model.
  106. 2. An attribute for a type is mandatory if it occurs in every example model.
  107. TODO: Would make more sense to change to: An attribute is mandatory if it occurs in every example model where the
  108. associated typed node occurs (as explained in the thesis text).
  109. """
  110. # Check attribute keys for every node in instance model
  111. all_nodes = mv.all_instances(self._instance_model, "Node")
  112. for node in all_nodes:
  113. attrs = commons.get_attributes_of_node(self._instance_model, node)
  114. if not attrs:
  115. continue
  116. node_typ = commons.get_node_type(self._instance_model, node)
  117. for attr in attrs:
  118. # check every example model if such an attribute key is present for the specific type
  119. for exm in self._example_models:
  120. exm_attrs = commons.get_all_attributes_of_type(exm, node_typ)
  121. if attr.key in [x.key for x in exm_attrs]:
  122. break
  123. else:
  124. # loop completed without break, so we did not find any key in example models
  125. return {"OK": False, "error": "No key {} for type {} in example models".format(attr.key, node_typ),
  126. "affected":[node]}
  127. # Check if mandatory attributes are present in instance model
  128. attr_mandatory = {node_type:{} for node_type in self._available_types}
  129. all_attrs = self._get_attributes_of_all_types()
  130. for typ_i, attr_list in all_attrs.iteritems():
  131. for typ_j, dic in attr_mandatory.iteritems():
  132. if typ_j == typ_i:
  133. for attr in attr_list:
  134. dic[attr] = False
  135. # have dict like {"PC": {"owner":False}, "Router": {"IP":False}, ...}
  136. # now need to check which one is mandatory and set the boolean accordingly
  137. for node_type, attr_dict in attr_mandatory.iteritems():
  138. for attr, _ in attr_dict.iteritems():
  139. if self._is_attribute_mandatory(node_type, attr):
  140. attr_dict[attr] = True
  141. # for every node in instance model, check if it has the mandatory attributes
  142. for node in mv.all_instances(self._instance_model, "Node"):
  143. node_type = commons.get_node_type(self._instance_model, node)
  144. node_attrs = commons.get_attributes_of_node(self._instance_model, node)
  145. attr_mand = attr_mandatory[node_type]
  146. for attr, mand in attr_mand.iteritems():
  147. if mand:
  148. if not attr in [x.key for x in node_attrs]:
  149. return {"OK": False, "error":"Attribute {} for type {} mandatory".format(attr, node_type),
  150. "affected":[node]}
  151. return {"OK": True, "error": None, "affected":[]}
  152. def verify_associations(self):
  153. """
  154. 1. An edge between two types is mandatory if in every example model that contains nodes of both types, every
  155. node is connected with an edge to the other type.
  156. 2. For every association in the instance model, the types of the source and target must correspond
  157. to the types of an association in some example model. This check should not be necessary since this
  158. is already enforced while instance modeling (see im_scene.py, draw_edge() which checks this when trying
  159. to connect two nodes).
  160. """
  161. # construct the set of mandatory edges
  162. all_edges = set()
  163. for exm in self._example_models:
  164. all_links = mv.all_instances(exm, "Edge")
  165. for link in all_links:
  166. src_id = mv.read_association_source(exm, link)[0]
  167. dest_id = mv.read_association_destination(exm, link)[0]
  168. src_type = commons.get_node_type(exm, src_id)
  169. dest_type = commons.get_node_type(exm, dest_id)
  170. all_edges.add(Edge(src_type, dest_type))
  171. edge_mandatory = {e:True for e in all_edges}
  172. for cand_edge, _ in edge_mandatory.iteritems():
  173. # check every example model if it contains the required types
  174. # and if there are two noes of the type which are not connected -> not mandatory
  175. found = False
  176. for exm in self._example_models:
  177. if found:
  178. break
  179. typed_nodes_a = commons.all_nodes_with_type(exm, cand_edge.n1)
  180. typed_nodes_b = commons.all_nodes_with_type(exm, cand_edge.n2)
  181. if not typed_nodes_a or not typed_nodes_b:
  182. # example model does not contain the two types
  183. continue
  184. for src_node in typed_nodes_a:
  185. # if this node is not connected to a node typed by the required type, the edge is not mandatory
  186. if not commons.has_edge_to_type(exm, src_node, cand_edge.n2):
  187. edge_mandatory[cand_edge] = False
  188. found = True
  189. mandatory_edges = [edge for edge,mandatory in edge_mandatory.iteritems() if mandatory]
  190. # check if instance model contains the types and the edge
  191. for edge in mandatory_edges:
  192. if not commons.model_contains_type(self._instance_model, edge.n1):
  193. continue
  194. if not commons.model_contains_type(self._instance_model, edge.n2):
  195. continue
  196. # instance model contains both types -> are they connected?
  197. for node in commons.all_nodes_with_type(self._instance_model, edge.n1):
  198. if not commons.has_edge_to_type(self._instance_model, node, edge.n2):
  199. return {"OK":False, "error": "Edge between {} and {} mandatory".format(edge.n1, edge.n2),
  200. "affected":[]}
  201. # other way round
  202. for node in commons.all_nodes_with_type(self._instance_model, edge.n2):
  203. if not commons.has_edge_to_type(self._instance_model, node, edge.n1):
  204. return {"OK":False, "error": "Edge between {} and {} mandatory".format(edge.n2, edge.n1),
  205. "affected":[]}
  206. # lastly, check if all edges in the instance model are actually valid
  207. all_edges = mv.all_instances(self._instance_model, "Edge")
  208. for edge in all_edges:
  209. src_id = mv.read_association_source(self._instance_model, edge)[0]
  210. dest_id = mv.read_association_destination(self._instance_model, edge)[0]
  211. src_type = commons.get_node_type(self._instance_model, src_id)
  212. dest_type = commons.get_node_type(self._instance_model, dest_id)
  213. if not commons.is_edge_supported(src_type, dest_type):
  214. return {"OK":False, "error": "Edge between {} and {} not valid".format(edge.n1, edge.n2),
  215. "affected":[src_id, dest_id]}
  216. return {"OK": True, "error": None, "affected":[]}