himesis.py 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749
  1. """
  2. Core logic for Himesis. Based on the code in AToMPM and earlier work by Marc Provost.
  3. Himesis is a hierarchical, directed and labelled graph kernel, built on top of the ModelVerse State.
  4. The name Himesis has etymological roots from genesis for "origin", mimesis for "representation" and
  5. the syllable "Hi" stands for "Hierarchical".
  6. .. See Also:
  7. http://modelverse.uantwerpen.be/people/mprovost/projects/himesis/index.html
  8. """
  9. import uuid, copy
  10. from state.base import State
  11. class HConstants:
  12. """
  13. Himesis constants must start with '$' to ensure there are no name clashes
  14. with user-attributes (which are prohibited from starting with '$')
  15. """
  16. GUID = '$GUID__'
  17. METAMODELS = '$mms__'
  18. MISSING_METAMODELS = '$missing_mms__'
  19. FULLTYPE = '$ft__'
  20. CONNECTOR_TYPE = '$ct__'
  21. ATTRIBUTES = "$attr__"
  22. MT_LABEL = '$MT_label__'
  23. MT_CONSTRAINT = '$MT_constraint__'
  24. MT_ACTION = '$MT_action__'
  25. MT_SUBTYPE_MATCH = '$MT_subtypeMatching__'
  26. MT_SUBTYPES = '$MT_subtypes__'
  27. MT_DIRTY = '$MT_dirty__'
  28. MT_PIVOT_IN = '$MT_pivotIn__'
  29. MT_PIVOT_OUT = '$MT_pivotOut__'
  30. class Himesis:
  31. """
  32. Creates a typed, attributed, directed, hierarchical, multi-graph.
  33. Args:
  34. name (str): The name of this graph.
  35. mvs (State): The backend to use for the graph representations.
  36. """
  37. Constants = HConstants
  38. EDGE_LIST_THRESHOLD = 10**3
  39. def __init__(self, name, mvs):
  40. if not name:
  41. name = self.__class__.__name__
  42. self.name = name
  43. self.state = mvs
  44. self.attrib = {}
  45. self.vcount = 0
  46. self.edges = []
  47. # mmTypeData: enables add_node() to properly initialize to-be nodes s.t. they reflect the default values specified by their metamodels
  48. # _index2guid: a fast lookup of the node's guid by its index
  49. # session: area which provides a clean and efficient way to remember information across rules
  50. self.mmTypeData = {}
  51. self._index2guid = {}
  52. self.session = {}
  53. @staticmethod
  54. def is_RAM_attribute(attr_name):
  55. return not attr_name.startswith('$')
  56. def copy(self):
  57. mvs = copy.deepcopy(self.state)
  58. cpy = Himesis(self.name, mvs)
  59. cpy._index2guid = copy.deepcopy(self._index2guid)
  60. ''' hergin :: motif-integration FIX for mmTypeData bug '''
  61. cpy.mmTypeData = copy.deepcopy(self.mmTypeData)
  62. cpy.session = copy.deepcopy(self.session)
  63. cpy.name = copy.deepcopy(self.name)
  64. return cpy
  65. def __copy__(self):
  66. return self.copy()
  67. def __deepcopy__(self, memo):
  68. return self.__copy__()
  69. def __getitem__(self, item):
  70. return self.attrib[item]
  71. def __setitem__(self, key, value):
  72. self.attrib[key] = value
  73. def __str__(self):
  74. s = super(Himesis, self).__str__()
  75. return self.name + ' ' + s[s.index('('):] + ' ' + str(self[Himesis.Constants.GUID])
  76. def get_id(self):
  77. """
  78. Returns the unique identifier of the graph
  79. """
  80. return self[Himesis.Constants.GUID]
  81. def vertex_count(self):
  82. return len(self._index2guid)
  83. def edge_count(self):
  84. return len(self.edges)
  85. def node_iter(self):
  86. """
  87. Iterates over the nodes in the graph, by index
  88. """
  89. return range(self.vertex_count())
  90. def node_guid_iter(self):
  91. """
  92. Iterates over the nodes in the graph, by guid index
  93. """
  94. return (self.get_node(ix) for ix in self.node_iter())
  95. def edge_iter(self):
  96. """
  97. Iterates over the edges in the graph, by index
  98. """
  99. return iter(self.edges)
  100. def follow_edge(self, node, label):
  101. return self.state.read_dict(node, label)
  102. def node_set_attribute(self, node, key, value):
  103. if Himesis.Constants.ATTRIBUTES not in self.state.read_dict_keys(node):
  104. attrs = self.state.create_node()
  105. self.state.create_dict(node, Himesis.Constants.ATTRIBUTES, attrs)
  106. else:
  107. attrs = self.follow_edge(node, Himesis.Constants.ATTRIBUTES)
  108. if key in self.state.read_dict_keys(attrs):
  109. target = self.follow_edge(attrs, key)
  110. self.state.delete_node(target)
  111. target = self.state.create_nodevalue(value)
  112. self.state.create_dict(attrs, key, target)
  113. def node_get_attribute(self, node, key):
  114. if Himesis.Constants.ATTRIBUTES not in self.state.read_dict_keys(node):
  115. return None
  116. attrs = self.follow_edge(node, Himesis.Constants.ATTRIBUTES)
  117. if key in self.state.read_dict_keys(attrs):
  118. target = self.follow_edge(attrs, key)
  119. return self.state.read_value(target)
  120. return None
  121. def node_get_attributes(self, node):
  122. if Himesis.Constants.ATTRIBUTES not in self.state.read_dict_keys(node):
  123. return []
  124. attrs = self.follow_edge(node, Himesis.Constants.ATTRIBUTES)
  125. res = {}
  126. for key in self.state.read_dict_keys(attrs):
  127. target = self.follow_edge(attrs, key)
  128. res[key] = self.state.read_value(target)
  129. return res
  130. def node_has_attribute(self, node, key):
  131. attr = self.node_get_attribute(node, key)
  132. return attr is not None
  133. def get_first_node_with_attribute_equals(self, key, value):
  134. for node in self.node_guid_iter():
  135. attr = self.node_get_attribute(node, key)
  136. if attr == value:
  137. return node
  138. return None
  139. def add_node(self, fulltype=None, isConnector=None):
  140. # ALL NODES ARE NOW IDENTIFIED WITH A UUID!
  141. # newNodeIndex = self.vertex_count()
  142. # if newNodeGuid is None:
  143. # newNodeGuid = uuid.uuid4()
  144. nid = self.state.create_node()
  145. # self.node_set_attribute(nid, Himesis.Constants.GUID, newNodeGuid)
  146. self.node_set_attribute(nid, Himesis.Constants.FULLTYPE, fulltype)
  147. self.node_set_attribute(nid, Himesis.Constants.CONNECTOR_TYPE, isConnector)
  148. # self.add_vertices(1)
  149. # self.vs[newNodeIndex][Himesis.Constants.GUID] = newNodeGuid
  150. # self.vs[newNodeIndex][Himesis.Constants.FULLTYPE] = fulltype
  151. # self.vs[newNodeIndex][Himesis.Constants.CONNECTOR_TYPE] = isConnector
  152. if fulltype in self.mmTypeData :
  153. for attr, val in self.mmTypeData[fulltype].items():
  154. self.node_set_attribute(nid, str(attr), val)
  155. # self.vs[newNodeIndex][str(attr)] = val
  156. self._index2guid[self.vcount] = nid
  157. self.vcount += 1
  158. return nid
  159. def delete_nodes(self, nodes):
  160. td = []
  161. for n in nodes:
  162. self.state.delete_node(n)
  163. td += [k for k, v in self._index2guid.items() if v == n]
  164. # self.delete_vertices(nodes)
  165. # Regenerate the lookup because node indices have changed
  166. for t in td:
  167. del self._index2guid[t]
  168. # self._guid2index = dict((self.vs[node][Himesis.Constants.GUID], node) for node in self.node_iter())
  169. def get_node(self, index):
  170. if index in self._index2guid:
  171. return self._index2guid[index]
  172. raise KeyError('Node not found with specified id. Make sure to only create nodes via Himesis.add_node().')
  173. def edge_get_source(self, edge):
  174. return self.state.read_edge(edge)[0]
  175. def edge_get_target(self, edge):
  176. return self.state.read_edge(edge)[1]
  177. def link_nodes(self, A, B, label=None):
  178. if label is None:
  179. self.state.create_edge(A, B)
  180. else:
  181. self.state.create_dict(A, label, B)
  182. def unlink_nodes(self, A, B, label=""):
  183. if self.state.read_reverse_dict(B, label) != A:
  184. raise ValueError("No connection with label '%s' found between nodes %s and %s" % (label, A, B))
  185. edge = self.state.read_dict_edge(A, label)
  186. self.state.delete_edge(edge)
  187. # def get_node(self, guid):
  188. # """
  189. # Retrieves the node instance with the specified guid
  190. # @param guid: The guid of the node.
  191. # """
  192. # if guid in self._guid2index:
  193. # if self._guid2index[guid] >= self.vcount() or \
  194. # self.vs[self._guid2index[guid]][Himesis.Constants.GUID] != guid :
  195. # self._guid2index = dict((self.vs[node][Himesis.Constants.GUID], node) for node in self.node_iter())
  196. # try:
  197. # return self._guid2index[guid]
  198. # except KeyError:
  199. # #TODO: This should be a TransformationLanguageSpecificException
  200. # raise KeyError('Invalid node id. Make sure to only delete nodes via Himesis.delete_nodes(): ' + str(guid))
  201. # else :
  202. # #TODO: This should be a TransformationLanguageSpecificException
  203. # raise KeyError('Node not found with specified id. Make sure to only create nodes via Himesis.add_node(): ' + str(guid))
  204. # def draw(self, visual_style={}, label=None, show_guid=False, show_id=False, debug=False, width=600, height=900):
  205. # """
  206. # Visual graphic rendering of the graph.
  207. #
  208. # Args:
  209. # @param label: The attribute to use as node label in the figure.
  210. # If not provided, the index of the node is used.
  211. # @param visual_style: More drawing options
  212. # (see http://igraph.sourceforge.net/doc/python/igraph.Graph-class.html#__plot__ for more details).
  213. # """
  214. # if 'layout' not in visual_style:
  215. # visual_style["layout"] = 'fr'
  216. # if 'margin' not in visual_style:
  217. # visual_style["margin"] = 10
  218. #
  219. # # Set the labels
  220. # if not label:
  221. # if show_guid:
  222. # visual_style["vertex_label"] = [str(self.vs[i][Himesis.Constants.GUID])[:4] for i in self.node_iter()]
  223. # elif show_id:
  224. # visual_style["vertex_label"] = [str(i) for i in self.node_iter()]
  225. # else:
  226. # visual_style["vertex_label"] = [''] * self.vcount()
  227. # else:
  228. # try:
  229. # visual_style["vertex_label"] = self.vs[label]
  230. # for n in self.node_iter():
  231. # if not visual_style["vertex_label"][n]:
  232. # visual_style["vertex_label"][n] = self.vs[n][Himesis.Constants.FULLTYPE]
  233. # if debug:
  234. # visual_style["vertex_label"][n] = str(n) + ':' + visual_style["vertex_label"][n]
  235. # elif debug:
  236. # visual_style["vertex_label"][n] = str(n) + ':' + visual_style["vertex_label"][n]
  237. # except:
  238. # raise Exception('%s is not a valid attribute' % label)
  239. #
  240. # return ig.plot(self, bbox=(0, 0, width, height), **visual_style)
  241. def execute(self, *args):
  242. raise AttributeError('This method is not implemented')
  243. class HimesisPattern(Himesis):
  244. def __init__(self, name, mvs):
  245. super(HimesisPattern, self).__init__(name, mvs)
  246. # Caches
  247. self.nodes_label = {}
  248. self.nodes_pivot_out = {}
  249. def get_node_with_label(self, label):
  250. """
  251. Retrieves the index of the node with the specified label.
  252. Args:
  253. label: The label of the node.
  254. """
  255. if not self.nodes_label:
  256. self.nodes_label = {}
  257. self.nodes_label = { self.node_get_attribute(n, Himesis.Constants.MT_LABEL): n \
  258. for n in self.node_guid_iter() \
  259. if self.node_has_attribute(n, Himesis.Constants.MT_LABEL) }
  260. # self.nodes_label = dict([(self.vs[i][Himesis.Constants.MT_LABEL], i) for i in self.node_iter()])
  261. if label in self.nodes_label:
  262. return self.nodes_label[label]
  263. def get_pivot_out(self, pivot):
  264. """
  265. Retrieves the index of the pivot node.
  266. Args:
  267. pivot: The label of the pivot.
  268. """
  269. if not self.nodes_pivot_out:
  270. self.nodes_pivot_out = { self.node_get_attribute(n, Himesis.Constants.MT_PIVOT_OUT): n \
  271. for n in self.node_guid_iter() \
  272. if self.node_has_attribute(n, Himesis.Constants.MT_PIVOT_OUT) }
  273. # self.nodes_pivot_out = dict([(i, self.vs[i][Himesis.Constants.MT_PIVOT_OUT]) for i in self.node_iter()])
  274. if pivot in self.nodes_pivot_out:
  275. return self.nodes_pivot_out[pivot]
  276. class HimesisPreConditionPattern(HimesisPattern):
  277. def __init__(self, name, mvs):
  278. super(HimesisPreConditionPattern, self).__init__(name, mvs)
  279. self.nodes_pivot_in = {}
  280. def get_pivot_in(self, pivot):
  281. """
  282. Retrieves the index of the pivot node.
  283. Args:
  284. pivot: The label of the pivot.
  285. """
  286. if not self.nodes_pivot_in:
  287. self.nodes_pivot_in = { self.node_get_attribute(n, Himesis.Constants.MT_PIVOT_IN): n \
  288. for n in self.node_guid_iter() \
  289. if self.node_has_attribute(n, Himesis.Constants.MT_PIVOT_IN) }
  290. # self.nodes_pivot_in = dict([(self.vs[i][Himesis.Constants.MT_PIVOT_IN], i) for i in self.node_iter()])
  291. if pivot in self.nodes_pivot_in:
  292. return self.nodes_pivot_in[pivot]
  293. def constraint(self, mtLabel2graphIndexMap, graph): # TODO??
  294. """
  295. If a constraint shall be specified, the corresponding Himesis graph must override this method.
  296. The condition must be specified in the pattern graph and not the input graph.
  297. By default, the constraint evaluates to True.
  298. @param PreMatch: The current match, before the rewriting.
  299. @param graph: The whole input graph.
  300. """
  301. raise NotImplementedError('Use graph[Himesis.Constants.MT_CONSTRAINT]() instead')
  302. class HimesisPreConditionPatternLHS(HimesisPreConditionPattern):
  303. def __init__(self, name, mvs):
  304. super(HimesisPreConditionPatternLHS, self).__init__(name, mvs)
  305. self.NACs = []
  306. self.bound_start_index = 0 # index of first bound NAC in NACs list
  307. def addNAC(self, nac):
  308. """
  309. Appends the NAC to this LHS pattern
  310. """
  311. if nac.LHS != self:
  312. nac.LHS = self
  313. if nac.bridge is None:
  314. nac.bridge = nac.compute_bridge()
  315. self.NACs.append(nac)
  316. def addNACs(self, NACs):
  317. """
  318. Stores the list of NACs in decreasing order of their size
  319. @param nacs: list of NACs
  320. @postcondition: the NACs will be stored in decreasing order of their bridge sizes
  321. """
  322. bound = []
  323. unbound = []
  324. for nac in NACs:
  325. nac.LHS = self
  326. nac.bridge_size = nac.compute_bridge().vertex_count()
  327. if nac.bridge_size > 0:
  328. bound.append(nac)
  329. else:
  330. unbound.append(nac)
  331. bound.sort(key=lambda nac: (nac.bridge_size, nac.vertex_count()), reverse=True)
  332. unbound.sort(key=lambda nac: nac.vertex_count(), reverse=True)
  333. self.NACs = unbound + bound
  334. self.bound_start_index = len(unbound)
  335. def getUnboundNACs(self):
  336. return self.NACs[:self.bound_start_index]
  337. def getBoundNACs(self):
  338. return self.NACs[self.bound_start_index:]
  339. def hasBoundNACs(self):
  340. return self.bound_start_index < len(self.NACs)
  341. class HimesisPreConditionPatternNAC(HimesisPreConditionPattern):
  342. def __init__(self, LHS, name, mvs):
  343. super(HimesisPreConditionPatternNAC, self).__init__(name, mvs)
  344. self.LHS = LHS
  345. self.bridge_size = 0
  346. def set_bridge_size(self):
  347. """
  348. Computes the bridge and stores the number of its nodes.
  349. """
  350. if self.LHS is None:
  351. raise Exception('Missing LHS to compute bridge')
  352. self.bridge_size = self.compute_bridge().vertex_count()
  353. def compute_bridge(self):
  354. """
  355. Creates a HimesisPreConditionPattern defined as the intersection of graph with this instance.
  356. This is called the 'bridge'.
  357. From a topological point of view, this method computes the largest common subgraph of these two graphs.
  358. However, the similarity of nodes of the bridge relies on the meta-model type of the nodes.
  359. Furthermore, every attribute value is the conjunction of the constraints defined in each graph.
  360. """
  361. # G1 is the smallest graph and G2 is the bigger graph
  362. G1 = self
  363. G2 = self.LHS
  364. if G1.vertex_count() > G2.vertex_count():
  365. # Swap
  366. G1, G2 = G2, G1
  367. # The bridge
  368. G = HimesisPreConditionPattern('', self.state)
  369. G[Himesis.Constants.GUID] = uuid.uuid4()
  370. # We don't need to actually solve the largest common subgraph (LCS) problem
  371. # because we assume that the nodes are labelled uniquely in each graph
  372. # and that if a label is in G1 and in G2, then it will be in G
  373. if G1.vertex_count() == 0:
  374. return G
  375. for v1 in G1.node_guid_iter():
  376. label = G1.node_get_attribute(v1, Himesis.Constants.MT_LABEL)
  377. v2 = G2.get_first_node_with_attribute_equals(Himesis.Constants.MT_LABEL, label)
  378. if v2 is None: continue
  379. newNodeIndex = G.add_node()
  380. # Now do a conjunction of the attributes
  381. for attr, val in G1.node_get_attributes(v1).items():
  382. G.node_set_attribute(newNodeIndex, attr, val)
  383. for attr, val in G2.node_get_attributes(v2).items():
  384. if not G.node_has_attribute(newNodeIndex, attr):
  385. G.node_set_attribute(newNodeIndex, attr, val)
  386. # Ignore non-RAM attributes ('special' and HConstants attributes)
  387. elif not Himesis.is_RAM_attribute(attr): continue
  388. # Handle normal attribute
  389. else:
  390. if not val:
  391. # There is no constraint for this attribute
  392. continue
  393. # The attribute constraint code is the conjunction of the LHS constraint
  394. # with the NAC constraint for this attribute
  395. def get_evalAttrConditions(_attr, _v1, _v2):
  396. def evalAttrConditions(mtLabel2graphIndexMap, graph):
  397. return G1.node_get_attribute(_v1, _attr)(mtLabel2graphIndexMap, graph) and \
  398. G2.node_get_attribute(_v2, _attr)(mtLabel2graphIndexMap, graph)
  399. return evalAttrConditions
  400. G.node_set_attribute(newNodeIndex, attr, get_evalAttrConditions(attr, v1, v2))
  401. # G.vs[newNodeIndex][attr] = get_evalAttrConditions(attr, v1.index, v2.index)
  402. # Now add the edges
  403. # We only need to go through the edges of the smaller graph
  404. for e in G1.edge_iter():
  405. src, tgt = self.state.read_edge(e)
  406. src_label = self.node_get_attribute(src, Himesis.Constants.MT_LABEL)
  407. tgt_label = self.node_get_attribute(tgt, Himesis.Constants.MT_LABEL)
  408. src = G.get_first_node_with_attribute_equals(Himesis.Constants.MT_LABEL, src_label)
  409. tgt = G.get_first_node_with_attribute_equals(Himesis.Constants.MT_LABEL, tgt_label)
  410. G.link_nodes(src, tgt)
  411. # Labels2 = G2.vs[Himesis.Constants.MT_LABEL]
  412. # for label in G1.vs[Himesis.Constants.MT_LABEL]:
  413. # if label in Labels2:
  414. # # Get the corresponding node from G1
  415. # v1 = G1.vs.select(lambda v : v[Himesis.Constants.MT_LABEL] == label)
  416. # if len(v1) == 1:
  417. # v1 = v1[0]
  418. # elif len(v1) == 0:
  419. # #unreachable line...
  420. # raise Exception('Label does not exist :: ' + str(label))
  421. # else:
  422. # raise Exception('Label is not unique :: ' + str(label))
  423. # # Get the corresponding node from G2
  424. # v2 = G2.vs.select(lambda v : v[Himesis.Constants.MT_LABEL] == label)
  425. # if len(v2) == 1:
  426. # v2 = v2[0]
  427. # elif len(v2) == 0:
  428. # # Unreachable line...
  429. # raise Exception('Label does not exist :: ' + str(label))
  430. # else:
  431. # raise Exception('Label is not unique :: ' + str(label))
  432. # newNodeIndex = G.add_node()
  433. # # Now do a conjunction of the attributes
  434. # for attr in v1.attribute_names():
  435. # G.vs[newNodeIndex][attr] = v1[attr]
  436. # for attr in v2.attribute_names():
  437. # # The attribute is not in v1
  438. # if attr not in G.vs[newNodeIndex].attribute_names():
  439. # G.vs[newNodeIndex][attr] = v2[attr]
  440. # # Give this node its own GUID attribute
  441. # elif attr == Himesis.Constants.GUID:
  442. # G.vs[newNodeIndex][Himesis.Constants.GUID] = uuid.uuid4()
  443. # continue
  444. # # Ignore non-RAM attributes ('special' and HConstants attributes)
  445. # elif not Himesis.is_RAM_attribute(attr):
  446. # continue
  447. # # Handle normal attribute
  448. # else :
  449. # if not v2[attr]:
  450. # # There is no constraint for this attribute
  451. # continue
  452. #
  453. # # The attribute constraint code is the conjunction of the LHS constraint
  454. # # with the NAC constraint for this attribute
  455. # def get_evalAttrConditions(_attr,_v1,_v2) :
  456. # def evalAttrConditions(mtLabel2graphIndexMap,graph):
  457. # return G1.vs[_v1][_attr](mtLabel2graphIndexMap, graph) and \
  458. # G2.vs[_v2][_attr](mtLabel2graphIndexMap, graph)
  459. # return evalAttrConditions
  460. # G.vs[newNodeIndex][attr] = get_evalAttrConditions(attr,v1.index,v2.index)
  461. # #else: v1[attr] == v2[attr], so we don't need to do anything more
  462. # # Now add the edges
  463. # # We only need to go through the edges of the smaller graph
  464. # for e in G1.edge_iter():
  465. # src_label = G1.vs[G1.es[e].source][Himesis.Constants.MT_LABEL]
  466. # tgt_label = G1.vs[G1.es[e].target][Himesis.Constants.MT_LABEL]
  467. # src = G.vs.select(lambda v : v[Himesis.Constants.MT_LABEL] == src_label)
  468. # tgt = G.vs.select(lambda v : v[Himesis.Constants.MT_LABEL] == tgt_label)
  469. # if len(src) == len(tgt) == 1:
  470. # src = src[0]
  471. # tgt = tgt[0]
  472. # G.add_edges([(src.index, tgt.index)])
  473. # elif len(src) == 0 :
  474. # # raise Exception('Label does not exist :: '+str(src_label))
  475. # pass
  476. # elif len(tgt) == 0 :
  477. # # raise Exception('Label does not exist :: '+str(tgt_label))
  478. # pass
  479. # elif len(src) > 1 :
  480. # raise Exception('Label is not unique :: ' + str(src_label))
  481. # elif len(tgt) > 1 :
  482. # raise Exception('Label is not unique :: ' + str(tgt_label))
  483. return G
  484. class HimesisPostConditionPattern(HimesisPattern):
  485. def __init__(self, name, mvs):
  486. super(HimesisPostConditionPattern, self).__init__(name, mvs)
  487. self.pre = None
  488. def action(self, mtLabel2graphIndexMap, graph):
  489. """
  490. If an action shall be specified, the corresponding Himesis graph must override this method.
  491. The action must be specified in the pattern graph and not the input graph.
  492. """
  493. raise NotImplementedError('Use graph[Himesis.Constants.MT_ACTION]() instead')
  494. # This method implements the rewriting part of the rule.
  495. '''
  496. NOTE
  497. certain rule applications may have side-effects that aren't caused by
  498. the rewriting per se... at present, the only instance of this is when a
  499. rule produces entities of a formalism not loaded on the asworker... in
  500. this case, we prepend appropriate {'op':'LOADMM','name':...} entries to
  501. packet.deltas
  502. NOTE
  503. when creating new nodes, information about the match is bundled so that
  504. the said new nodes' icons get created near the icons of nodes matched
  505. in the LHS
  506. NOTE
  507. deletes must be performed last because they alter igraph indices and
  508. which we use to map __pLabels to source graph nodes... however, to
  509. avoid violating maximum association multiplicities, deletes in the
  510. source model must be performed first... thus, RM* operations, if any,
  511. are placed at the start of packet.deltas
  512. '''
  513. def execute(self, packet, match):
  514. graph = packet.graph
  515. # Changes to packet.graph are logged in packet.deltas
  516. packet.deltas = []
  517. # Init deltas with rule side-effects (see NOTE)
  518. for mm in self[Himesis.Constants.MISSING_METAMODELS]() :
  519. packet.deltas.append({'op':'LOADMM','name':mm})
  520. # Set the attributes of graph.vs[graphNodeIndex] to match those of self.vs[rhsNodeIndex]
  521. def set_attributes(rhsNodeIndex, graphNodeIndex, newNode, pLabel2graphIndexMap) :
  522. changedSomething = False
  523. for attrName, attrVal in self.node_get_attributes(rhsNodeIndex).items():
  524. # for attrName in self.vs[rhsNodeIndex].attribute_names() :
  525. if Himesis.is_RAM_attribute(attrName) :
  526. # attrVal = self.vs[rhsNodeIndex][attrName]
  527. if attrVal is None :
  528. # Not 'really' an attribute
  529. continue
  530. oldVal = None
  531. try :
  532. if not newNode:
  533. # oldVal = graph.vs[graphNodeIndex][attrName]
  534. oldVal = graph.node_get_attribute(graphNodeIndex, attrName)
  535. # newVal = self.vs[rhsNodeIndex][attrName](pLabel2graphIndexMap, graph)
  536. newVal = self.node_get_attribute(rhsNodeIndex, attrName)(pLabel2graphIndexMap, graph)
  537. if oldVal != newVal:
  538. graph.node_set_attribute(graphNodeIndex, attrName, newVal)
  539. # graph.vs[graphNodeIndex][attrName] = newVal
  540. packet.deltas.append(
  541. {'op':'CHATTR',
  542. 'guid':graphNodeIndex,
  543. 'attr':attrName,
  544. 'old_val':oldVal,
  545. 'new_val':newVal})
  546. changedSomething = True
  547. except Exception as e :
  548. raise Exception("An error has occurred while computing the value of the attribute '%s' :: %s" % (attrName, e))
  549. return changedSomething
  550. # Build a dictionary {label: node index} mapping each label of the pattern to a node in the graph to rewrite.
  551. # Because of the uniqueness property of labels in a rule, we can store all LHS labels
  552. # and subsequently add the labels corresponding to the nodes to be created.
  553. labels = match.copy()
  554. # Update attribute values
  555. LHS_labels = self.pre_labels
  556. for label in LHS_labels:
  557. rhsNodeIndex = self.get_node_with_label(label)
  558. if rhsNodeIndex is None:
  559. continue # not in the interface graph (LHS n RHS)
  560. if set_attributes(rhsNodeIndex, labels[label], False, labels):
  561. graph.node_set_attribute(labels[label], Himesis.Constants.MT_DIRTY, True)
  562. # graph.vs[labels[label]][Himesis.Constants.MT_DIRTY] = True
  563. # Create new nodes (non-connectors first)
  564. if self.vertex_count() == 0 :
  565. RHS_labels = []
  566. else:
  567. RHS_labels = []
  568. for node in self.node_guid_iter():
  569. RHS_labels.append(self.node_get_attribute(node, Himesis.Constants.MT_LABEL))
  570. # RHS_labels = self.vs[Himesis.Constants.MT_LABEL]
  571. # sort non-connectors first
  572. RHS_labels.sort(key=lambda x: self.node_get_attribute(self.get_node_with_label(x), Himesis.Constants.CONNECTOR_TYPE) or False)
  573. # RHS_labels.sort(key=lambda x: self.vs[ self.get_node_with_label(x) ][Himesis.Constants.CONNECTOR_TYPE] or False)
  574. neighborhood = [graph.node_get_attributes(labels[l]) for l in LHS_labels]
  575. new_labels = []
  576. for label in RHS_labels:
  577. rhsNodeIndex = self.get_node_with_label(label)
  578. if label not in LHS_labels:
  579. new_labels += [label]
  580. newNodeIndex = graph.add_node(
  581. self.node_get_attribute(rhsNodeIndex, Himesis.Constants.FULLTYPE),
  582. self.node_get_attribute(rhsNodeIndex, Himesis.Constants.CONNECTOR_TYPE))
  583. packet.deltas.append(
  584. {'op':'MKNODE',
  585. 'neighborhood':neighborhood,
  586. 'guid':graph.node_get_attribute(newNodeIndex, Himesis.Constants.GUID)})
  587. labels[label] = newNodeIndex
  588. set_attributes(rhsNodeIndex, newNodeIndex, True, labels)
  589. # Link new nodes (Create new edges)
  590. visited_edges = []
  591. for label in sorted(new_labels):
  592. edges = [e for e in self.edge_iter() if (e not in visited_edges and
  593. (label == self.node_get_attribute(self.edge_get_source(e), Himesis.Constants.MT_LABEL) or
  594. label == self.node_get_attribute(self.edge_get_target(e), Himesis.Constants.MT_LABEL)))]
  595. for edge in edges:
  596. # src_label = self.node_get_attribute(self.edge_get_source(edge), Himesis.Constants.MT_LABEL)
  597. # tgt_label = self.node_get_attribute(self.edge_get_target(edge), Himesis.Constants.MT_LABEL)
  598. graph.link_nodes(self.edge_get_source(edge), self.edge_get_target(edge))
  599. # graph.add_edges([(labels[src_label], labels[tgt_label])])
  600. packet.deltas.append(
  601. {'op':'MKEDGE',
  602. 'guid1':self.edge_get_source(edge),
  603. 'guid2':self.edge_get_target(edge)})
  604. visited_edges.append(edge.index)
  605. # Set the output pivots
  606. for node in self.node_guid_iter():
  607. pivot_out = self.node_get_attribute(node, Himesis.Constants.MT_PIVOT_OUT)
  608. if pivot_out:
  609. label = self.node_get_attribute(node, Himesis.Constants.MT_LABEL)
  610. packet.global_pivots[pivot_out] = labels[label]
  611. # Perform the post-action
  612. try:
  613. packet.deltas.extend(self[Himesis.Constants.MT_ACTION](labels, graph))
  614. except Exception as e:
  615. raise Exception('An error has occurred while applying the post-action', e)
  616. # Delete nodes (automatically deletes adjacent edges)
  617. labels_to_delete = []
  618. rmnodes = []
  619. rmedges = []
  620. for label in LHS_labels:
  621. if label not in RHS_labels:
  622. labels_to_delete.append(labels[label])
  623. rmnodes.append({'op':'RMNODE','attrs': graph.node_get_attributes(labels[label]),'guid': labels[label]})
  624. edges = [e for e in graph.edge_iter() if labels[label] == graph.edge_get_source(e) or labels[label] == graph.edge_get_target(e)]
  625. for edge in edges:
  626. # for edge in graph.es.select(lambda e: (labels[label] == e.source or labels[label] == e.target)) :
  627. # found = False
  628. for rmedge in rmedges :
  629. if rmedge['guid1'] == graph.edge_get_source(edge) and \
  630. rmedge['guid2'] == graph.edge_get_target(edge):
  631. # found = True
  632. break
  633. else:
  634. rmedges.append({'op':'RMEDGE',
  635. 'guid1':graph.edge_get_source(edge),
  636. 'guid2':graph.edge_get_target(edge)})
  637. if len(labels_to_delete) > 0 :
  638. packet.deltas = rmedges + rmnodes + packet.deltas
  639. graph.delete_nodes(labels_to_delete)
  640. ''' hergin :: motif-integration start :: remove the deleted nodes from pivots list '''
  641. for uuid in packet.global_pivots:
  642. deleted=False
  643. for toBeDeleted in rmnodes:
  644. if toBeDeleted['guid'] == packet.global_pivots[uuid]:
  645. del packet.global_pivots[uuid]
  646. deleted=True
  647. continue
  648. if deleted:
  649. continue
  650. ''' hergin :: motif-integration end '''