round.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. from typing import *
  2. from sccd.statechart.dynamic.candidate_generator import *
  3. from sccd.util.debug import *
  4. from sccd.common.exceptions import *
  5. from sccd.util import timer
  6. # 1st bitmap: arenas covered by transitions fired
  7. # 2nd bitmap: arenas covered by transitions that had a stable target state
  8. RoundResult = Tuple[Bitmap, Bitmap]
  9. class Round(ABC):
  10. __slots__ = ["name", "parent", "callbacks", "remainder_events", "next_events"]
  11. def __init__(self, name):
  12. self.name = name
  13. self.parent = None
  14. self.callbacks: List[Callable] = []
  15. self.remainder_events = [] # events enabled for the remainder of the current round
  16. self.next_events = [] # events enabled for the entirety of the next round
  17. def when_done(self, callback):
  18. self.callbacks.append(callback)
  19. def run_and_cycle_events(self, forbidden_arenas: Bitmap = Bitmap()) -> RoundResult:
  20. # with timer.Context("round: %s" % self.name):
  21. changed, stable = self._run(forbidden_arenas)
  22. if changed:
  23. # notify round observers
  24. for callback in self.callbacks:
  25. callback()
  26. # rotate enabled events
  27. self.remainder_events = self.next_events
  28. self.next_events = []
  29. print_debug("completed "+self.name)
  30. return (changed, stable)
  31. def reset(self):
  32. self.remainder_events = []
  33. self.next_events = []
  34. @abstractmethod
  35. def _run(self, forbidden_arenas: Bitmap) -> RoundResult:
  36. pass
  37. def add_remainder_event(self, event: InternalEvent):
  38. self.remainder_events.append(event)
  39. def add_next_event(self, event: InternalEvent):
  40. self.next_events.append(event)
  41. def enabled_events(self) -> List[InternalEvent]:
  42. if self.parent:
  43. return self.remainder_events + self.parent.enabled_events()
  44. else:
  45. return self.remainder_events
  46. def __str__(self):
  47. return self.name
  48. class SuperRoundMaximality(ABC):
  49. __slots__ = []
  50. @staticmethod
  51. @abstractmethod
  52. def forbidden_arenas(arenas_changed: Bitmap, arenas_stabilized: Bitmap) -> Bitmap:
  53. pass
  54. class TakeOne(SuperRoundMaximality):
  55. __slots__ = []
  56. @staticmethod
  57. def forbidden_arenas(arenas_changed: Bitmap, arenas_stabilized: Bitmap) -> Bitmap:
  58. return arenas_changed
  59. class TakeMany(SuperRoundMaximality):
  60. __slots__ = []
  61. @staticmethod
  62. def forbidden_arenas(arenas_changed: Bitmap, arenas_stabilized: Bitmap) -> Bitmap:
  63. return Bitmap()
  64. class Syntactic(SuperRoundMaximality):
  65. __slots__ = []
  66. @staticmethod
  67. def forbidden_arenas(arenas_changed: Bitmap, arenas_stabilized: Bitmap) -> Bitmap:
  68. return arenas_stabilized
  69. # Examples: Big step, combo step
  70. class SuperRound(Round):
  71. __slots__ = ["subround", "maximality", "limit"]
  72. def __init__(self, name, subround: Round, maximality: SuperRoundMaximality, limit: Optional[int] = None):
  73. super().__init__(name)
  74. self.subround = subround
  75. subround.parent = self
  76. self.maximality = maximality
  77. self.limit = limit
  78. def __str__(self):
  79. return self.name + " > " + str(self.subround)
  80. def _run(self, forbidden_arenas: Bitmap) -> RoundResult:
  81. arenas_changed = Bitmap()
  82. arenas_stabilized = Bitmap()
  83. subrounds = 0
  84. while True:
  85. changed, stabilized = self.subround.run_and_cycle_events(forbidden_arenas) # no forbidden arenas in subround
  86. if not changed:
  87. break # no more transitions could be executed, done!
  88. if self.limit is not None:
  89. subrounds += 1
  90. if subrounds >= self.limit:
  91. raise ModelRuntimeError("%s: Limit reached! (%d×%s) Possibly a never-ending big step." % (self.name, subrounds, self.subround.name))
  92. arenas_changed |= changed
  93. arenas_stabilized |= stabilized
  94. forbidden_arenas |= self.maximality.forbidden_arenas(arenas_changed, arenas_stabilized)
  95. return (arenas_changed, arenas_stabilized)
  96. class SmallStep(Round):
  97. __slots__ = ["execution", "generator"]
  98. def __init__(self, name, execution, generator: CandidateGenerator):
  99. super().__init__(name)
  100. self.execution = execution
  101. self.generator = generator
  102. def _run(self, forbidden_arenas: Bitmap) -> RoundResult:
  103. enabled_events = self.enabled_events()
  104. # The cost of sorting our enabled events is smaller than the benefit gained by having to loop less often over it in our transition execution code:
  105. # enabled_events.sort(key=lambda e: e.id)
  106. transitions = self.generator.generate(self.execution, enabled_events, forbidden_arenas)
  107. dirty_arenas = Bitmap()
  108. stable_arenas = Bitmap()
  109. for t in transitions:
  110. arena = t.arena_bitmap
  111. self.execution.fire_transition(enabled_events, t)
  112. dirty_arenas |= arena
  113. if t.target_stable:
  114. stable_arenas |= arena
  115. enabled_events = self.enabled_events()
  116. # enabled_events.sort(key=lambda e: e.id)
  117. return (dirty_arenas, stable_arenas)