matcher.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  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 sys
  5. from copy import deepcopy
  6. from ..util.infinity import INFINITY
  7. from ..core.match_algo import HimesisMatcher
  8. from ..core.himesis import HConstants as HC
  9. from .rule_primitive import RulePrimitive
  10. from .messages import MatchSet, Match, TransformationException
  11. if sys.version_info[0] >= 3:
  12. from functools import reduce
  13. class Matcher(RulePrimitive):
  14. '''
  15. Binds the source graph according to the pre-condition pattern.
  16. '''
  17. def __init__(self, condition, max=INFINITY):
  18. '''
  19. Binds the source graph according to the pre-condition pattern.
  20. @param condition: The pre-condition pattern.
  21. @param max: The maximum number of matches.
  22. '''
  23. super(Matcher, self).__init__()
  24. self.max = max
  25. self.condition = condition
  26. def __str__(self):
  27. s = super(Matcher, self).__str__()
  28. s = s.split(' ')
  29. s.insert(1, '[%s]' % self.condition.name)
  30. return reduce(lambda x, y: '%s %s' % (x,y), s)
  31. def packet_in(self, packet):
  32. self.exception = None
  33. self.is_success = False
  34. if self.condition[HC.GUID] in packet.match_sets:
  35. matchSet = packet.match_sets[self.condition[HC.GUID]]
  36. else:
  37. matchSet = MatchSet()
  38. # Find the matches
  39. try:
  40. i = 1
  41. if i <= self.max:
  42. for mapping in self._match(packet.graph, packet.global_pivots):
  43. # Convert the mapping to a Match object
  44. match = Match()
  45. match.from_mapping(mapping, packet.graph, self.condition)
  46. matchSet.matches.append(match)
  47. i += 1
  48. if i > self.max:
  49. # We don't need any more matches
  50. break
  51. except Exception as e:
  52. self.is_success = False
  53. self.exception = TransformationException(e)
  54. self.exception.packet = packet
  55. self.exception.transformation_unit = self
  56. return packet
  57. # Don't forget to add the match set to the packet, even if no matches were found
  58. if len(matchSet.matches) > 0:
  59. packet.match_sets[self.condition[HC.GUID]] = matchSet
  60. # Identify that this is the condition we are currently processing
  61. packet.current = self.condition[HC.GUID]
  62. # Success only if matches were found
  63. self.is_success = len(matchSet.matches) > 0
  64. return packet
  65. def _match(self, graph, pivots) :
  66. '''
  67. Matcher with pivots and (possibly) multiple NACs
  68. 1. Verify that no unbound NAC has a match
  69. 2. Let the "bridge" denote the biggest graph that is the intersection of the LHS and a NAC, among all NACs
  70. 3. Match the common part between the LHS & the NAC, i.e., the "bridge"
  71. 3.1 Continue the matching ensuring no occurrence of the NAC
  72. 3.2. If a NAC is found, ignore the current bridge mapping
  73. 3.3. Continue to find complete matches of the LHS,
  74. given each partial match found in 3.1.
  75. 3.4. For each valid match, verify that no occurrence of any remaining bound NAC is found,
  76. given the mapping found in 3.3.
  77. '''
  78. pred1 = {} # To optimize the matcher, since otherwise matcher will compute the predecessors of the source graph many times
  79. succ1 = {} # To optimize the matcher, since otherwise matcher will compute the successors of the source graph many times
  80. # Cache the pivot nodes of the source graph
  81. pivots = deepcopy(pivots)
  82. pivots.to_source_node_indices(graph)
  83. #===================================================================
  84. # First process the NACs that are not bound to the LHS
  85. #===================================================================
  86. for NAC in self.condition.getUnboundNACs():
  87. # Look for a NAC match
  88. nacMatcher = HimesisMatcher(source_graph=graph, pattern_graph=NAC)
  89. # Convert the pivots
  90. nac_pivots = pivots.to_mapping(graph, NAC)
  91. try:
  92. for mapping in nacMatcher.match_iter(context=nac_pivots):
  93. # Make the mapping into {...,NAClabel:graphIndex,...}
  94. match = Match()
  95. match.from_mapping(mapping, graph, self.condition)
  96. if NAC[HC.MT_CONSTRAINT](match.to_label_mapping(graph), graph):
  97. # An unbound NAC has been found: this pattern can never match
  98. return
  99. except: raise
  100. finally: nacMatcher.reset_recursion_limit()
  101. # For further matching optimizations
  102. pred1 = nacMatcher.pred1
  103. succ1 = nacMatcher.succ1
  104. # Either there are no NACs, or there were only unbound NACs that do not match, so match the LHS now
  105. if not self.condition.hasBoundNACs():
  106. lhsMatcher = HimesisMatcher(source_graph=graph, pattern_graph=self.condition, pred1=pred1, succ1=succ1)
  107. # Convert the pivots
  108. lhs_pivots = pivots.to_mapping(graph, self.condition)
  109. try:
  110. for mapping in lhsMatcher.match_iter(context=lhs_pivots):
  111. # Make the mapping into {...,LHSlabel:graphIndex,...}
  112. match = Match()
  113. match.from_mapping(mapping, graph, self.condition)
  114. if self.condition[HC.MT_CONSTRAINT](match.to_label_mapping(graph), graph):
  115. yield mapping
  116. except: raise
  117. finally: lhsMatcher.reset_recursion_limit()
  118. # The matching is complete
  119. return
  120. #===================================================================
  121. # Now process the NACs that have some nodes bound to the LHS
  122. #===================================================================
  123. # Continue the matching looking for the LHS now
  124. lhsMatcher = HimesisMatcher(source_graph=graph, pattern_graph=self.condition, pred1=pred1, succ1=succ1)
  125. # Augment the bridge mapping with the pivot mappings
  126. lhs_pivots = pivots.to_mapping(graph, self.condition)
  127. try:
  128. for mapping in lhsMatcher.match_iter(context=lhs_pivots):
  129. # Make the mapping into {...,LHSlabel:graphIndex,...}
  130. match = Match()
  131. match.from_mapping(mapping, graph, self.condition)
  132. if self.condition[HC.MT_CONSTRAINT](match.to_label_mapping(graph), graph):
  133. # A match of the LHS is found: ensure that no remaining NAC do match
  134. invalid = False
  135. for NAC in self.condition.getBoundNACs():
  136. # This mapping represents the mapping of the bridge of this NAC with the LHS
  137. bridgeMapping = match.to_mapping(graph, NAC)
  138. # Now continue the matching looking for a match of the corresponding NAC
  139. nacMatcher = HimesisMatcher(source_graph=graph, pattern_graph=NAC, pred1=pred1, succ1=succ1)
  140. for nac_mapping in nacMatcher.match_iter(context=bridgeMapping):
  141. # Make the mapping into {...,NAClabel:graphIndex,...}
  142. match = Match()
  143. match.from_mapping(nac_mapping, graph, NAC)
  144. if NAC[HC.MT_CONSTRAINT](match.to_label_mapping(graph), graph):
  145. # An occurrence of the NAC is found: current mapping is not valid
  146. invalid = True
  147. break
  148. if invalid:
  149. # An occurrence of the NAC was found: current mapping is not valid
  150. break
  151. else:
  152. # Either there are no bound NACs or no occurrence of any bound NAC was found: current mapping is valid
  153. yield mapping
  154. except: raise
  155. finally: lhsMatcher.reset_recursion_limit()