candidate_generator.py 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. from typing import *
  2. from dataclasses import *
  3. from abc import *
  4. from sccd.util import timer
  5. from sccd.util.bitmap import *
  6. from sccd.statechart.static.tree import *
  7. from sccd.statechart.dynamic.event import *
  8. @dataclass
  9. class CacheCounter:
  10. __slots__ = ["cache_hits", "cache_misses"]
  11. cache_hits: int
  12. cache_misses: int
  13. ctr = CacheCounter(0, 0)
  14. if timer.TIMINGS:
  15. import atexit
  16. def print_stats():
  17. print("\ncache hits: %s, cache misses: %s" %(ctr.cache_hits, ctr.cache_misses))
  18. atexit.register(print_stats)
  19. class GeneratorStrategy(ABC):
  20. __slots__ = ["priority_ordered_transitions"]
  21. def __init__(self, priority_ordered_transitions):
  22. self.priority_ordered_transitions = priority_ordered_transitions
  23. @abstractmethod
  24. def cache_init(self):
  25. pass
  26. @abstractmethod
  27. def key(self, execution, events, forbidden_arenas):
  28. pass
  29. @abstractmethod
  30. def generate(self, execution, events, forbidden_arenas):
  31. pass
  32. @abstractmethod
  33. def filter_f(self, execution, events):
  34. pass
  35. # First filter on current state, then filter on current events
  36. class CurrentConfigStrategy(GeneratorStrategy):
  37. __slots__ = []
  38. def cache_init(self):
  39. return {}
  40. def key(self, execution, events, forbidden_arenas):
  41. return (execution.configuration, forbidden_arenas)
  42. def generate(self, execution, events, forbidden_arenas):
  43. return [ t for t in self.priority_ordered_transitions
  44. if (t.source.state_id_bitmap & execution.configuration)
  45. and (not forbidden_arenas & t.arena_bitmap) ]
  46. def filter_f(self, execution, events):
  47. return lambda t: (not t.trigger or t.trigger.check(events)) and execution.check_guard(t, events)
  48. # # First filter based on current events, then filter on current state
  49. # class EnabledEventsStrategy(GeneratorStrategy):
  50. # __slots__ = ["statechart"]
  51. # def __init__(self, priority_ordered_transitions, statechart):
  52. # super().__init__(priority_ordered_transitions)
  53. # self.statechart = statechart
  54. # def cache_init(self):
  55. # cache = {}
  56. # cache[(0, 0)] = self.generate(None, 0, 0)
  57. # for event_id in bm_items(self.statechart.internal_events):
  58. # events_bitmap = bit(event_id)
  59. # cache[(events_bitmap, 0)] = self.generate(None, events_bitmap, 0)
  60. # return cache
  61. # def key(self, execution, events, forbidden_arenas):
  62. # return (events_bitmap, forbidden_arenas)
  63. # def generate(self, execution, events, forbidden_arenas):
  64. # return [ t for t in self.priority_ordered_transitions
  65. # if (not t.trigger or t.trigger.check(events))
  66. # and (not forbidden_arenas & t.arena_bitmap) ]
  67. # def filter_f(self, execution, events):
  68. # return lambda t: (execution.configuration & t.source.state_id_bitmap) and execution.check_guard(t, enabled_events)
  69. # class CurrentConfigAndEnabledEventsStrategy(GeneratorStrategy):
  70. # __slots__ = ["statechart"]
  71. # def __init__(self, priority_ordered_transitions, statechart):
  72. # super().__init__(priority_ordered_transitions)
  73. # self.statechart = statechart
  74. # def cache_init(self):
  75. # return {}
  76. # def key(self, execution, events, forbidden_arenas):
  77. # return (execution.configuration, events_bitmap, forbidden_arenas)
  78. # def generate(self, execution, events, forbidden_arenas):
  79. # return [ t for t in self.priority_ordered_transitions
  80. # if (not t.trigger or t.trigger.check(events))
  81. # and (t.source.state_id_bitmap & execution.configuration)
  82. # and (not forbidden_arenas & t.arena_bitmap) ]
  83. # def filter_f(self, execution, events):
  84. # return lambda t: execution.check_guard(t, enabled_events)
  85. class CandidateGenerator:
  86. __slots__ = ["strategy", "cache"]
  87. def __init__(self, strategy):
  88. self.strategy = strategy
  89. self.cache = strategy.cache_init()
  90. def generate(self, execution, enabled_events: List[InternalEvent], forbidden_arenas: Bitmap) -> List[Transition]:
  91. with timer.Context("generate candidates"):
  92. # events_bitmap = bm_from_list(e.id for e in enabled_events)
  93. key = self.strategy.key(execution, enabled_events, forbidden_arenas)
  94. try:
  95. candidates = self.cache[key]
  96. ctr.cache_hits += 1
  97. except KeyError:
  98. candidates = self.cache[key] = self.strategy.generate(execution, enabled_events, forbidden_arenas)
  99. ctr.cache_misses += 1
  100. candidates = filter(self.strategy.filter_f(execution, enabled_events), candidates)
  101. if DEBUG:
  102. candidates = list(candidates) # convert generator to list (gotta do this, otherwise the generator will be all used up by our debug printing
  103. if candidates:
  104. print()
  105. if enabled_events:
  106. print("events: " + str(enabled_events))
  107. print("candidates: " + ", ".join(str(t) for t in candidates))
  108. candidates = iter(candidates)
  109. t = next(candidates, None)
  110. if t:
  111. return [t]
  112. else:
  113. return []
  114. class ConcurrentCandidateGenerator:
  115. __slots__ = ["strategy", "cache", "synchronous"]
  116. def __init__(self, strategy, synchronous):
  117. self.strategy = strategy
  118. self.cache = strategy.cache_init()
  119. self.synchronous = synchronous
  120. def generate(self, execution, enabled_events: List[InternalEvent], forbidden_arenas: Bitmap) -> List[Transition]:
  121. with timer.Context("generate candidates"):
  122. events = list(enabled_events)
  123. transitions = []
  124. while True:
  125. key = self.strategy.key(execution, events, forbidden_arenas)
  126. try:
  127. candidates = self.cache[key]
  128. ctr.cache_hits += 1
  129. except KeyError:
  130. candidates = self.cache[key] = self.strategy.generate(execution, events, forbidden_arenas)
  131. ctr.cache_misses += 1
  132. candidates = filter(self.strategy.filter_f(execution, events), candidates)
  133. t = next(candidates, None)
  134. if t:
  135. transitions.append(t)
  136. else:
  137. break
  138. events.extend(InternalEvent(name=event_name, params=[]) for event_name in t.raised_events)
  139. forbidden_arenas |= t.arena_bitmap
  140. return transitions