himesis.py 26 KB

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