scheduler.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. # -*- coding: Latin-1 -*-
  2. """
  3. The Activity Heap is based on a heap, though allows for reschedules.
  4. To allow reschedules to happen, a model is accompagnied by a flag to
  5. indicate whether or not it is still valid.
  6. As soon as a model is rescheduled, the flag of the previously scheduled
  7. time is set and another entry is added. This causes the heap to become *dirty*,
  8. requiring a check for the flag as soon as the first element is requested.
  9. Due to the possibility for a dirty heap, the heap will be cleaned up as
  10. soon as the number of invalid elements becomes too high.
  11. This cleanup method has O(n) complexity and is therefore only
  12. ran when the heap becomes way too dirty.
  13. Another problem is that it might consume more memory than other schedulers,
  14. due to invalid elements being kept in memory.
  15. However, the actual model and states are not duplicated as they are references.
  16. The additional memory requirement should not be a problem in most situations.
  17. The 'activity' part from the name stems from the fact that only models where
  18. the *time_next* attribute is smaller than infinity will be scheduled.
  19. Since these elements are not added to the heap, they aren't taken into account
  20. in the complexity. This allows for severe optimisations in situations where
  21. a lot of models can be scheduled for infinity.
  22. Of all provided schedulers, this one is the most mature due to it being the
  23. oldest and also the default scheduler. It is also applicable in every situation
  24. and it offers sufficient performance in most cases.
  25. This scheduler is ideal in situations where (nearly) no reschedules happen
  26. and where most models transition at a different time.
  27. It results in slow behaviour in situations requiring lots of rescheduling,
  28. and thus lots of dirty elements.
  29. This method is also applied in the VLE simulator and is the common approach
  30. to heap schedulers that require invalidation. It varies from the scheduler in
  31. ADEVS due to the heap from the heapq library being used, which doesn't offer
  32. functions to restructure the heap.
  33. Reimplementing these methods in pure Python would be unnecessarily slow.
  34. """
  35. from heapq import heappush, heappop, heapify
  36. class Scheduler(object):
  37. """
  38. Scheduler class itself
  39. """
  40. def __init__(self, models, epsilon, totalModels):
  41. """
  42. Constructor
  43. :param models: all models in the simulation
  44. """
  45. self.heap = []
  46. self.id_fetch = [None] * totalModels
  47. for model in models:
  48. if model.time_next[0] != float('inf'):
  49. self.id_fetch[model.model_id] = [model.time_next, model.model_id, True, model]
  50. heappush(self.heap, self.id_fetch[model.model_id])
  51. else:
  52. self.id_fetch[model.model_id] = [model.time_next, model.model_id, False, model]
  53. self.invalids = 0
  54. self.maxInvalids = len(models)*2
  55. self.epsilon = epsilon
  56. def schedule(self, model):
  57. """
  58. Schedule a model
  59. :param model: the model to schedule
  60. """
  61. # Create the entry, as we have accepted the model
  62. elem = [model.time_next, model.model_id, False, model]
  63. try:
  64. self.id_fetch[model.model_id] = elem
  65. except IndexError:
  66. # A completely new model
  67. self.id_fetch.append(elem)
  68. self.maxInvalids += 2
  69. # Check if it requires to be scheduled
  70. if model.time_next[0] != float('inf'):
  71. self.id_fetch[model.model_id][2] = True
  72. heappush(self.heap, self.id_fetch[model.model_id])
  73. def unschedule(self, model):
  74. """
  75. Unschedule a model
  76. :param model: model to unschedule
  77. """
  78. if model.time_next != float('inf'):
  79. self.invalids += 1
  80. # Update the referece still in the heap
  81. self.id_fetch[model.model_id][2] = False
  82. # Remove the reference in our id_fetch
  83. self.id_fetch[model.model_id] = None
  84. self.maxInvalids -= 2
  85. def massReschedule(self, reschedule_set):
  86. """
  87. Reschedule all models provided.
  88. Equivalent to calling unschedule(model); schedule(model) on every element in the iterable.
  89. :param reschedule_set: iterable containing all models to reschedule
  90. """
  91. #NOTE rather dirty, though a lot faster for huge models
  92. inf = float('inf')
  93. for model in reschedule_set:
  94. if model.model_id is None:
  95. continue
  96. event = self.id_fetch[model.model_id]
  97. if event[2]:
  98. if model.time_next == event[0]:
  99. continue
  100. elif event[0][0] != inf:
  101. self.invalids += 1
  102. event[2] = False
  103. if model.time_next[0] != inf:
  104. self.id_fetch[model.model_id] = [model.time_next, model.model_id, True, model]
  105. heappush(self.heap, self.id_fetch[model.model_id])
  106. if self.invalids >= self.maxInvalids:
  107. self.heap = [i for i in self.heap if i[2] and (i[0][0] != inf)]
  108. heapify(self.heap)
  109. self.invalids = 0
  110. def readFirst(self):
  111. """
  112. Returns the time of the first model that has to transition
  113. :returns: timestamp of the first model
  114. """
  115. self.cleanFirst()
  116. return self.heap[0][0]
  117. def cleanFirst(self):
  118. """
  119. Clean up the invalid elements in front of the list
  120. """
  121. try:
  122. while not self.heap[0][2]:
  123. heappop(self.heap)
  124. self.invalids -= 1
  125. except IndexError:
  126. # Nothing left, so it as clean as can be
  127. pass
  128. def getImminent(self, time):
  129. """
  130. Returns a list of all models that transition at the provided time, with a specified epsilon deviation allowed.
  131. :param time: timestamp to check for models
  132. .. warning:: For efficiency, this method only checks the **first** elements, so trying to invoke this function with a timestamp higher than the value provided with the *readFirst* method, will **always** return an empty set.
  133. """
  134. immChildren = []
  135. t, age = time
  136. try:
  137. # Age must be exactly the same
  138. first = self.heap[0]
  139. while (first[0][0] <= t) and (first[0][1] == age):
  140. # Check if the found event is actually still active
  141. if(first[2]):
  142. # Active, so event is imminent
  143. immChildren.append(first[3])
  144. first[2] = False
  145. else:
  146. # Wasn't active, but we will have to pop this to get the next
  147. # So we can lower the number of invalids
  148. self.invalids -= 1
  149. # Advance the while loop
  150. heappop(self.heap)
  151. first = self.heap[0]
  152. except IndexError:
  153. pass
  154. return immChildren