himesis.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571
  1. '''This file is part of AToMPM - A Tool for Multi-Paradigm Modelling
  2. Copyright 2011 by the AToMPM team and licensed under the LGPL
  3. See COPYING.lesser and README.md in the root of this project for full details'''
  4. import uuid, copy, igraph as ig
  5. class HConstants:
  6. '''
  7. Himesis constants must start with '$' to ensure there are no name clashes
  8. with user-attributes (which are prohibited from starting with '$')
  9. '''
  10. GUID = '$GUID__'
  11. METAMODELS = '$mms__'
  12. MISSING_METAMODELS = '$missing_mms__'
  13. FULLTYPE = '$ft__'
  14. CONNECTOR_TYPE = '$ct__'
  15. MT_LABEL = '$MT_label__'
  16. MT_CONSTRAINT = '$MT_constraint__'
  17. MT_ACTION = '$MT_action__'
  18. MT_SUBTYPE_MATCH = '$MT_subtypeMatching__'
  19. MT_SUBTYPES = '$MT_subtypes__'
  20. MT_DIRTY = '$MT_dirty__'
  21. MT_PIVOT_IN = '$MT_pivotIn__'
  22. MT_PIVOT_OUT = '$MT_pivotOut__'
  23. class Himesis(ig.Graph):
  24. """
  25. Creates a typed, attributed, directed, multi-graph.
  26. @param num_nodes: the total number of nodes. If not known, you can add more vertices later
  27. @param edges: the list of edges where each edge is a tuple representing the ids of the source and target nodes
  28. """
  29. Constants = HConstants
  30. EDGE_LIST_THRESHOLD = 10**3
  31. @staticmethod
  32. def is_RAM_attribute(attr_name):
  33. return not attr_name.startswith('$')
  34. def __init__(self, name='', num_nodes=0, edges=[]):
  35. """
  36. Creates a typed, attributed, directed, multi-graph.
  37. @param name: the name of this graph
  38. @param num_nodes: the total number of nodes. If not known, you can add more vertices later
  39. @param edges: the list of edges where each edge is a tuple representing the ids of the source and target nodes
  40. """
  41. ig.Graph.__init__(self, directed=True, n=num_nodes, edges=edges)
  42. if not name:
  43. name = self.__class__.__name__
  44. self.name = name
  45. # mmTypeData: enables add_node() to properly initialize to-be nodes s.t. they reflect the default values specified by their metamodels
  46. # _guid2index: a fast lookup of the node's index by its guid
  47. # session: area which provides a clean and efficient way to remember information across rules
  48. self.mmTypeData = {}
  49. self._guid2index = {}
  50. self.session = {}
  51. def copy(self):
  52. cpy = ig.Graph.copy(self)
  53. cpy._guid2index = copy.deepcopy(self._guid2index)
  54. ''' hergin :: motif-integration FIX for mmTypeData bug '''
  55. cpy.mmTypeData = copy.deepcopy(self.mmTypeData)
  56. cpy.session = copy.deepcopy(self.session)
  57. cpy.name = copy.deepcopy(self.name)
  58. return cpy
  59. def __copy__(self):
  60. return self.copy()
  61. def __deepcopy__(self, memo):
  62. return self.__copy__()
  63. def __str__(self):
  64. s = super(Himesis, self).__str__()
  65. return self.name + ' ' + s[s.index('('):] + ' ' + str(self[Himesis.Constants.GUID])
  66. def get_id(self):
  67. """
  68. Returns the unique identifier of the graph
  69. """
  70. return self[Himesis.Constants.GUID]
  71. def node_iter(self):
  72. """
  73. Iterates over the nodes in the graph, by index
  74. """
  75. return xrange(self.vcount())
  76. def edge_iter(self):
  77. """
  78. Iterates over the edges in the graph, by index
  79. """
  80. return xrange(self.ecount())
  81. def add_node(self, fulltype=None, isConnector=None, newNodeGuid=None):
  82. newNodeIndex = self.vcount()
  83. if newNodeGuid == None :
  84. newNodeGuid = uuid.uuid4()
  85. self.add_vertices(1)
  86. self.vs[newNodeIndex][Himesis.Constants.GUID] = newNodeGuid
  87. self.vs[newNodeIndex][Himesis.Constants.FULLTYPE] = fulltype
  88. self.vs[newNodeIndex][Himesis.Constants.CONNECTOR_TYPE] = isConnector
  89. if fulltype in self.mmTypeData :
  90. for attr,val in self.mmTypeData[fulltype].iteritems():
  91. self.vs[newNodeIndex][str(attr)] = val
  92. self._guid2index[newNodeGuid] = newNodeIndex
  93. return newNodeIndex
  94. def delete_nodes(self, nodes):
  95. self.delete_vertices(nodes)
  96. # Regenerate the lookup because node indices have changed
  97. self._guid2index = dict((self.vs[node][Himesis.Constants.GUID], node) for node in self.node_iter())
  98. def get_node(self,guid):
  99. """
  100. Retrieves the node instance with the specified guid
  101. @param guid: The guid of the node.
  102. """
  103. if guid in self._guid2index:
  104. if self._guid2index[guid] >= self.vcount() or \
  105. self.vs[self._guid2index[guid]][Himesis.Constants.GUID] != guid :
  106. self._guid2index = dict((self.vs[node][Himesis.Constants.GUID], node) for node in self.node_iter())
  107. try:
  108. return self._guid2index[guid]
  109. except KeyError:
  110. #TODO: This should be a TransformationLanguageSpecificException
  111. raise KeyError('Invalid node id. Make sure to only delete nodes via Himesis.delete_nodes(): ' + str(guid))
  112. else :
  113. #TODO: This should be a TransformationLanguageSpecificException
  114. raise KeyError('Node not found with specified id. Make sure to only create nodes via Himesis.add_node(): ' + str(guid))
  115. def draw(self, visual_style={}, label=None, show_guid=False, show_id=False, debug=False, width=600, height=900):
  116. """
  117. Visual graphic rendering of the graph.
  118. @param label: The attribute to use as node label in the figure.
  119. If not provided, the index of the node is used.
  120. @param visual_style: More drawing options
  121. (see http://igraph.sourceforge.net/doc/python/igraph.Graph-class.html#__plot__ for more details).
  122. """
  123. if 'layout' not in visual_style:
  124. visual_style["layout"] = 'fr'
  125. if 'margin' not in visual_style:
  126. visual_style["margin"] = 10
  127. # Set the labels
  128. if not label:
  129. if show_guid:
  130. visual_style["vertex_label"] = [str(self.vs[i][Himesis.Constants.GUID])[:4] for i in self.node_iter()]
  131. elif show_id:
  132. visual_style["vertex_label"] = [str(i) for i in self.node_iter()]
  133. else:
  134. visual_style["vertex_label"] = [''] * self.vcount()
  135. else:
  136. try:
  137. visual_style["vertex_label"] = self.vs[label]
  138. for n in self.node_iter():
  139. if not visual_style["vertex_label"][n]:
  140. visual_style["vertex_label"][n] = self.vs[n][Himesis.Constants.FULLTYPE]
  141. if debug:
  142. visual_style["vertex_label"][n] = str(n) + ':' + visual_style["vertex_label"][n]
  143. elif debug:
  144. visual_style["vertex_label"][n] = str(n) + ':' + visual_style["vertex_label"][n]
  145. except:
  146. raise Exception('%s is not a valid attribute' % label)
  147. return ig.plot(self, bbox=(0, 0, width, height), **visual_style)
  148. def execute(self, *args):
  149. raise AttributeError('This method is not implemented')
  150. class HimesisPattern(Himesis):
  151. def __init__(self, name='', num_nodes=0, edges=[]):
  152. super(HimesisPattern, self).__init__(name, num_nodes, edges)
  153. self.nodes_label = {}
  154. self.nodes_pivot_out = {}
  155. def get_node_with_label(self, label):
  156. """
  157. Retrieves the index of the node with the specified label.
  158. @param label: The label of the node.
  159. """
  160. if not self.nodes_label:
  161. self.nodes_label = dict([(self.vs[i][Himesis.Constants.MT_LABEL], i) for i in self.node_iter()])
  162. if label in self.nodes_label:
  163. return self.nodes_label[label]
  164. def get_pivot_out(self, pivot):
  165. """
  166. Retrieves the index of the pivot node
  167. @param pivot: The label of the pivot.
  168. """
  169. if not self.nodes_pivot_out and Himesis.Constants.MT_PIVOT_OUT in self.vs.attribute_names():
  170. self.nodes_pivot_out = dict([(i, self.vs[i][Himesis.Constants.MT_PIVOT_OUT]) for i in self.node_iter()])
  171. if pivot in self.nodes_pivot_out:
  172. return self.nodes_pivot_out[pivot]
  173. class HimesisPreConditionPattern(HimesisPattern):
  174. def __init__(self, name='', num_nodes=0, edges=[]):
  175. super(HimesisPreConditionPattern, self).__init__(name, num_nodes, edges)
  176. self.nodes_pivot_in = {}
  177. def get_pivot_in(self, pivot):
  178. """
  179. Retrieves the index of the pivot node
  180. @param pivot: The label of the pivot.
  181. """
  182. if not self.nodes_pivot_in and Himesis.Constants.MT_PIVOT_IN in self.vs.attribute_names():
  183. self.nodes_pivot_in = dict([(self.vs[i][Himesis.Constants.MT_PIVOT_IN], i) for i in self.node_iter()])
  184. if pivot in self.nodes_pivot_in:
  185. return self.nodes_pivot_in[pivot]
  186. def constraint(self, mtLabel2graphIndexMap, graph):
  187. """
  188. If a constraint shall be specified, the corresponding Himesis graph must override this method.
  189. The condition must be specified in the pattern graph and not the input graph.
  190. By default, the constraint evaluates to True.
  191. @param PreMatch: The current match, before the rewriting.
  192. @param graph: The whole input graph.
  193. """
  194. raise NotImplementedError('Use graph[Himesis.Constants.MT_CONSTRAINT]() instead')
  195. class HimesisPreConditionPatternLHS(HimesisPreConditionPattern):
  196. def __init__(self, name='', num_nodes=0, edges=[]):
  197. super(HimesisPreConditionPatternLHS, self).__init__(name, num_nodes, edges)
  198. self.NACs = []
  199. self.bound_start_index = 0 # index of first bound NAC in NACs list
  200. def addNAC(self, nac):
  201. """
  202. Appends the NAC to this LHS pattern
  203. """
  204. if nac.LHS != self:
  205. nac.LHS = self
  206. if nac.bridge is None:
  207. nac.bridge = nac.compute_bridge()
  208. self.NACs.append(nac)
  209. def addNACs(self, NACs):
  210. """
  211. Stores the list of NACs in decreasing order of their size
  212. @param nacs: list of NACs
  213. @postcondition: the NACs will be stored in decreasing order of their bridge sizes
  214. """
  215. bound = []
  216. unbound = []
  217. for nac in NACs:
  218. nac.LHS = self
  219. nac.bridge_size = nac.compute_bridge()
  220. if nac.bridge_size > 0:
  221. bound.append(nac)
  222. else:
  223. unbound.append(nac)
  224. bound.sort(key=lambda nac: (nac.bridge_size, nac.vcount()), reverse=True)
  225. unbound.sort(key=lambda nac: nac.vcount(), reverse=True)
  226. self.NACs = unbound + bound
  227. self.bound_start_index = len(unbound)
  228. def getUnboundNACs(self):
  229. return self.NACs[:self.bound_start_index]
  230. def getBoundNACs(self):
  231. return self.NACs[self.bound_start_index:]
  232. def hasBoundNACs(self):
  233. return self.bound_start_index < len(self.NACs)
  234. class HimesisPreConditionPatternNAC(HimesisPreConditionPattern):
  235. def __init__(self, LHS=None, name='', num_nodes=0, edges=[]):
  236. super(HimesisPreConditionPatternNAC, self).__init__(name, num_nodes, edges)
  237. self.LHS = LHS
  238. self.bridge_size = 0
  239. def set_bridge_size(self):
  240. """
  241. Computes the bridge and stores the number of its nodes.
  242. """
  243. if self.LHS is None:
  244. raise Exception('Missing LHS to compute bridge')
  245. self.bridge_size = self.compute_bridge().vcount()
  246. def compute_bridge(self):
  247. """
  248. Creates a HimesisPreConditionPattern defined as the intersection of graph with this instance.
  249. This is called the 'bridge'.
  250. From a topological point of view, this method computes the largest common subgraph of these two graphs.
  251. However, the similarity of nodes of the bridge relies on the meta-model type of the nodes.
  252. Furthermore, every attribute value is the conjunction of the constraints defined in each graph.
  253. """
  254. # G1 is the smallest graph and G2 is the bigger graph
  255. G1 = self
  256. G2 = self.LHS
  257. if G1.vcount() > G2.vcount():
  258. # Swap
  259. G1, G2 = G2, G1
  260. # The bridge
  261. G = HimesisPreConditionPattern()
  262. G[Himesis.Constants.GUID] = uuid.uuid4()
  263. # We don't need to actually solve the largest common subgraph (LCS) problem
  264. # because we assume that the nodes are labelled uniquely in each graph
  265. # and that if a label is in G1 and in G2, then it will be in G
  266. if len(G1.vs) == 0:
  267. return G
  268. Labels2 = G2.vs[Himesis.Constants.MT_LABEL]
  269. for label in G1.vs[Himesis.Constants.MT_LABEL]:
  270. if label in Labels2:
  271. # Get the corresponding node from G1
  272. v1 = G1.vs.select(lambda v : v[Himesis.Constants.MT_LABEL] == label)
  273. if len(v1) == 1:
  274. v1 = v1[0]
  275. elif len(v1) == 0:
  276. #unreachable line...
  277. raise Exception('Label does not exist :: ' + str(label))
  278. else:
  279. raise Exception('Label is not unique :: ' + str(label))
  280. # Get the corresponding node from G2
  281. v2 = G2.vs.select(lambda v : v[Himesis.Constants.MT_LABEL] == label)
  282. if len(v2) == 1:
  283. v2 = v2[0]
  284. elif len(v2) == 0:
  285. # Unreachable line...
  286. raise Exception('Label does not exist :: ' + str(label))
  287. else:
  288. raise Exception('Label is not unique :: ' + str(label))
  289. newNodeIndex = G.add_node()
  290. # Now do a conjunction of the attributes
  291. for attr in v1.attribute_names():
  292. G.vs[newNodeIndex][attr] = v1[attr]
  293. for attr in v2.attribute_names():
  294. # The attribute is not in v1
  295. if attr not in G.vs[newNodeIndex].attribute_names():
  296. G.vs[newNodeIndex][attr] = v2[attr]
  297. # Give this node its own GUID attribute
  298. elif attr == Himesis.Constants.GUID:
  299. G.vs[newNodeIndex][Himesis.Constants.GUID] = uuid.uuid4()
  300. continue
  301. # Ignore non-RAM attributes ('special' and HConstants attributes)
  302. elif not Himesis.is_RAM_attribute(attr):
  303. continue
  304. # Handle normal attribute
  305. else :
  306. if not v2[attr]:
  307. # There is no constraint for this attribute
  308. continue
  309. # The attribute constraint code is the conjunction of the LHS constraint
  310. # with the NAC constraint for this attribute
  311. def get_evalAttrConditions(_attr,_v1,_v2) :
  312. def evalAttrConditions(mtLabel2graphIndexMap,graph):
  313. return G1.vs[_v1][_attr](mtLabel2graphIndexMap, graph) and \
  314. G2.vs[_v2][_attr](mtLabel2graphIndexMap, graph)
  315. return evalAttrConditions
  316. G.vs[newNodeIndex][attr] = get_evalAttrConditions(attr,v1.index,v2.index)
  317. #else: v1[attr] == v2[attr], so we don't need to do anything more
  318. # Now add the edges
  319. # We only need to go through the edges of the smaller graph
  320. for e in G1.edge_iter():
  321. src_label = G1.vs[G1.es[e].source][Himesis.Constants.MT_LABEL]
  322. tgt_label = G1.vs[G1.es[e].target][Himesis.Constants.MT_LABEL]
  323. src = G.vs.select(lambda v : v[Himesis.Constants.MT_LABEL] == src_label)
  324. tgt = G.vs.select(lambda v : v[Himesis.Constants.MT_LABEL] == tgt_label)
  325. if len(src) == len(tgt) == 1:
  326. src = src[0]
  327. tgt = tgt[0]
  328. G.add_edges([(src.index, tgt.index)])
  329. elif len(src) == 0 :
  330. # raise Exception('Label does not exist :: '+str(src_label))
  331. pass
  332. elif len(tgt) == 0 :
  333. # raise Exception('Label does not exist :: '+str(tgt_label))
  334. pass
  335. elif len(src) > 1 :
  336. raise Exception('Label is not unique :: ' + str(src_label))
  337. elif len(tgt) > 1 :
  338. raise Exception('Label is not unique :: ' + str(tgt_label))
  339. return G
  340. class HimesisPostConditionPattern(HimesisPattern):
  341. def __init__(self, name='', num_nodes=0, edges=[]):
  342. super(HimesisPostConditionPattern, self).__init__(name, num_nodes, edges)
  343. self.pre = None
  344. def action(self, mtLabel2graphIndexMap, graph):
  345. """
  346. If an action shall be specified, the corresponding Himesis graph must override this method.
  347. The action must be specified in the pattern graph and not the input graph.
  348. """
  349. raise NotImplementedError('Use graph[Himesis.Constants.MT_ACTION]() instead')
  350. # This method implements the rewriting part of the rule.
  351. '''
  352. NOTE
  353. certain rule applications may have side-effects that aren't caused by
  354. the rewriting per se... at present, the only instance of this is when a
  355. rule produces entities of a formalism not loaded on the asworker... in
  356. this case, we prepend appropriate {'op':'LOADMM','name':...} entries to
  357. packet.deltas
  358. NOTE
  359. when creating new nodes, information about the match is bundled so that
  360. the said new nodes' icons get created near the icons of nodes matched
  361. in the LHS
  362. NOTE
  363. deletes must be performed last because they alter igraph indices and
  364. which we use to map __pLabels to source graph nodes... however, to
  365. avoid violating maximum association multiplicities, deletes in the
  366. source model must be performed first... thus, RM* operations, if any,
  367. are placed at the start of packet.deltas
  368. '''
  369. def execute(self, packet, match):
  370. graph = packet.graph
  371. # Changes to packet.graph are logged in packet.deltas
  372. packet.deltas = []
  373. # Init deltas with rule side-effects (see NOTE)
  374. for mm in self[Himesis.Constants.MISSING_METAMODELS]() :
  375. packet.deltas.append({'op':'LOADMM','name':mm})
  376. # Set the attributes of graph.vs[graphNodeIndex] to match those of self.vs[rhsNodeIndex]
  377. def set_attributes(rhsNodeIndex, graphNodeIndex, newNode, pLabel2graphIndexMap) :
  378. changedSomething = False
  379. for attrName in self.vs[rhsNodeIndex].attribute_names() :
  380. if Himesis.is_RAM_attribute(attrName) :
  381. attrVal = self.vs[rhsNodeIndex][attrName]
  382. if attrVal == None :
  383. # Not 'really' an attribute
  384. continue
  385. oldVal = None
  386. if not newNode :
  387. oldVal = graph.vs[graphNodeIndex][attrName]
  388. try :
  389. newVal = self.vs[rhsNodeIndex][attrName](pLabel2graphIndexMap, graph)
  390. if oldVal != newVal :
  391. graph.vs[graphNodeIndex][attrName] = newVal
  392. packet.deltas.append(
  393. {'op':'CHATTR',
  394. 'guid':graph.vs[graphNodeIndex][Himesis.Constants.GUID],
  395. 'attr':attrName,
  396. 'old_val':oldVal,
  397. 'new_val':newVal})
  398. changedSomething = True
  399. except Exception, e :
  400. raise Exception("An error has occurred while computing the value of the attribute '%s' :: %s" % (attrName, e))
  401. return changedSomething
  402. # Build a dictionary {label: node index} mapping each label of the pattern to a node in the graph to rewrite.
  403. # Because of the uniqueness property of labels in a rule, we can store all LHS labels
  404. # and subsequently add the labels corresponding to the nodes to be created.
  405. labels = match.copy()
  406. # Update attribute values
  407. LHS_labels = self.pre_labels
  408. for label in LHS_labels:
  409. rhsNodeIndex = self.get_node_with_label(label)
  410. if rhsNodeIndex is None:
  411. continue # not in the interface graph (LHS n RHS)
  412. if set_attributes(rhsNodeIndex, labels[label], False, labels) :
  413. graph.vs[labels[label]][Himesis.Constants.MT_DIRTY] = True
  414. # Create new nodes (non-connectors first)
  415. if self.vcount() == 0 :
  416. RHS_labels = []
  417. else :
  418. RHS_labels = self.vs[Himesis.Constants.MT_LABEL]
  419. def nonConnectorsFirst(l1,l2) :
  420. c1 = self.vs[ self.get_node_with_label(l1) ][Himesis.Constants.CONNECTOR_TYPE]
  421. c2 = self.vs[ self.get_node_with_label(l2) ][Himesis.Constants.CONNECTOR_TYPE]
  422. if c1 and c2 :
  423. return 0
  424. elif c1 :
  425. return 1
  426. return -1
  427. RHS_labels.sort(nonConnectorsFirst)
  428. neighborhood = map(lambda l: graph.vs[labels[l]].attributes(), LHS_labels)
  429. new_labels = []
  430. for label in RHS_labels:
  431. rhsNodeIndex = self.get_node_with_label(label)
  432. if label not in LHS_labels:
  433. new_labels += [label]
  434. newNodeIndex = graph.add_node(
  435. self.vs[rhsNodeIndex][Himesis.Constants.FULLTYPE],
  436. self.vs[rhsNodeIndex][Himesis.Constants.CONNECTOR_TYPE])
  437. packet.deltas.append(
  438. {'op':'MKNODE',
  439. 'neighborhood':neighborhood,
  440. 'guid':graph.vs[newNodeIndex][Himesis.Constants.GUID]})
  441. labels[label] = newNodeIndex
  442. set_attributes(rhsNodeIndex, newNodeIndex, True, labels)
  443. # Link new nodes (Create new edges)
  444. visited_edges = []
  445. for label in sorted(new_labels):
  446. for edge in self.es.select(lambda e: (e.index not in visited_edges and
  447. (label == self.vs[e.source][Himesis.Constants.MT_LABEL] or
  448. label == self.vs[e.target][Himesis.Constants.MT_LABEL]))):
  449. src_label = self.vs[edge.source][Himesis.Constants.MT_LABEL]
  450. tgt_label = self.vs[edge.target][Himesis.Constants.MT_LABEL]
  451. graph.add_edges([(labels[src_label], labels[tgt_label])])
  452. packet.deltas.append(
  453. {'op':'MKEDGE',
  454. 'guid1':graph.vs[labels[src_label]][Himesis.Constants.GUID],
  455. 'guid2':graph.vs[labels[tgt_label]][Himesis.Constants.GUID]})
  456. visited_edges.append(edge.index)
  457. # Set the output pivots
  458. if Himesis.Constants.MT_PIVOT_OUT in self.vs.attribute_names():
  459. for node in self.vs.select(lambda v: v[Himesis.Constants.MT_PIVOT_OUT]):
  460. node = node.index
  461. label = self.vs[node][Himesis.Constants.MT_LABEL]
  462. pivot_out = self.vs[node][Himesis.Constants.MT_PIVOT_OUT]
  463. packet.global_pivots[pivot_out] = graph.vs[labels[label]][Himesis.Constants.GUID]
  464. # Perform the post-action
  465. try:
  466. packet.deltas.extend(self[Himesis.Constants.MT_ACTION](labels, graph))
  467. except Exception, e:
  468. raise Exception('An error has occurred while applying the post-action', e)
  469. # Delete nodes (automatically deletes adjacent edges)
  470. labels_to_delete = []
  471. rmnodes = []
  472. rmedges = []
  473. for label in LHS_labels:
  474. if label not in RHS_labels:
  475. labels_to_delete.append(labels[label])
  476. rmnodes.append({'op':'RMNODE','attrs':graph.vs[labels[label]].attributes()})
  477. for edge in graph.es.select(lambda e: (labels[label] == e.source or labels[label] == e.target)) :
  478. found = False
  479. for rmedge in rmedges :
  480. if rmedge['guid1'] == graph.vs[edge.source][Himesis.Constants.GUID] and \
  481. rmedge['guid2'] == graph.vs[edge.target][Himesis.Constants.GUID] :
  482. found = True
  483. break
  484. if not found :
  485. rmedges.append({'op':'RMEDGE',
  486. 'guid1':graph.vs[edge.source][Himesis.Constants.GUID],
  487. 'guid2':graph.vs[edge.target][Himesis.Constants.GUID]})
  488. if len(labels_to_delete) > 0 :
  489. packet.deltas = rmedges + rmnodes + packet.deltas
  490. graph.delete_nodes(labels_to_delete)
  491. ''' hergin :: motif-integration start :: remove the deleted nodes from pivots list '''
  492. for uuid in packet.global_pivots:
  493. deleted=False
  494. for toBeDeleted in rmnodes:
  495. if toBeDeleted['attrs']['$GUID__'] == packet.global_pivots[uuid]:
  496. del packet.global_pivots[uuid]
  497. deleted=True
  498. continue
  499. if deleted:
  500. continue
  501. ''' hergin :: motif-integration end '''