RecedingHorizonControl.py 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. #!/usr/bin/env python
  2. import time
  3. from datetime import datetime as dt
  4. from datetime import timedelta
  5. import pytz
  6. from aman.config.RHC import RHC
  7. from aman.sys.RecedingHorizonWindow import RecedingHorizonWindow
  8. from aman.types.Inbound import Inbound
  9. class RecedingHorizonControl:
  10. def __init__(self, config : RHC):
  11. self.Windows = []
  12. # contains the current index and the missed update counter
  13. self.AssignedWindow = {}
  14. self.Configuration = config
  15. self.FreezedIndex = int(self.Configuration.FixedBeforeArrival.seconds / self.Configuration.WindowSize)
  16. def insertInWindow(self, inbound : Inbound, usePTA : bool):
  17. if False == usePTA:
  18. referenceTime = inbound.EarliestArrivalTime
  19. else:
  20. referenceTime = inbound.PlannedArrivalTime
  21. inserted = False
  22. for i in range(0, len(self.Windows)):
  23. window = self.Windows[i]
  24. # find the correct window
  25. if window.StartTime <= referenceTime and window.EndTime > referenceTime:
  26. if i > self.FreezedIndex:
  27. self.AssignedWindow[inbound.Callsign] = [ i, 0 ]
  28. if False == usePTA:
  29. inbound.PlannedArrivalTime = inbound.EarliestArrivalTime
  30. window.insert(inbound)
  31. inserted = True
  32. break
  33. # create the new window
  34. if False == inserted:
  35. if 0 != len(self.Windows):
  36. lastWindowTime = self.Windows[-1].EndTime
  37. else:
  38. lastWindowTime = dt.utcfromtimestamp(int(time.time())).replace(tzinfo = pytz.UTC)
  39. timestep = timedelta(seconds = self.Configuration.WindowSize)
  40. while True:
  41. self.Windows.append(RecedingHorizonWindow(lastWindowTime, lastWindowTime + timestep))
  42. if self.Windows[-1].EndTime > referenceTime:
  43. window = self.Windows[-1]
  44. self.AssignedWindow[inbound.Callsign] = [ len(self.Windows) - 1, 0 ]
  45. window.insert(inbound)
  46. if False == usePTA:
  47. inbound.PlannedArrivalTime = max(window.StartTime, inbound.EarliestArrivalTime)
  48. break
  49. lastWindowTime = self.Windows[-1].EndTime
  50. window.Inbounds.sort(key = lambda x: x.PlannedArrivalTime)
  51. def updateReport(self, inbound : Inbound):
  52. # check if we need to update
  53. if inbound.Callsign in self.AssignedWindow:
  54. index = self.AssignedWindow[inbound.Callsign][0]
  55. self.AssignedWindow[inbound.Callsign][1] = 0
  56. # check if we assume the scheduling as fixed
  57. if index < self.FreezedIndex:
  58. return
  59. plannedInbound = self.Windows[index].inbound(inbound.Callsign)
  60. plannedInbound.CurrentPosition = inbound.CurrentPosition
  61. plannedInbound.ArrivalCandidates = inbound.ArrivalCandidates
  62. plannedInbound.WTC = inbound.WTC
  63. # check if we need to update the inbound
  64. if plannedInbound.PlannedArrivalTime < inbound.EarliestArrivalTime:
  65. if inbound.EarliestArrivalTime < self.Windows[index].StartTime or inbound.EarliestArrivalTime >= self.Windows[index].EndTime:
  66. self.Windows[index].remove(inbound.Callsign)
  67. self.AssignedWindow.pop(inbound.Callsign)
  68. self.updateReport(inbound)
  69. else:
  70. plannedInbound.PlannedStar = inbound.PlannedStar
  71. plannedInbound.PlannedRunway = inbound.PlannedRunway
  72. plannedInbound.InitialArrivalTime = inbound.InitialArrivalTime
  73. if plannedInbound.PlannedArrivalTime == plannedInbound.EarliestArrivalTime:
  74. plannedInbound.PlannedArrivalTime = inbound.EarliestArrivalTime
  75. plannedInbound.EarliestArrivalTime = inbound.EarliestArrivalTime
  76. else:
  77. self.insertInWindow(inbound, False)
  78. def resequenceInbound(self, inbound : Inbound):
  79. index = self.AssignedWindow[inbound.Callsign][0]
  80. if inbound.PlannedArrivalTime < self.Windows[index].StartTime or inbound.PlannedArrivalTime >= self.Windows[index].EndTime:
  81. self.Windows[index].remove(inbound.Callsign)
  82. self.AssignedWindow.pop(inbound.Callsign)
  83. self.insertInWindow(inbound, True)
  84. else:
  85. self.Windows[index].Inbounds.sort(key = lambda x: x.PlannedArrivalTime)
  86. def lastFixedInboundOnRunway(self, runway : str):
  87. # no inbounds available
  88. if 0 == len(self.Windows):
  89. return None
  90. # search from the back to the front to find the last inbound
  91. for i in range(min(self.FreezedIndex, len(self.Windows)), -1, -1):
  92. for inbound in self.Windows[i].Inbounds:
  93. if runway == inbound.PlannedRunway.Runway.name:
  94. return inbound
  95. # no inbound found
  96. return None
  97. def optimizationRelevantInbounds(self):
  98. # no new inbounds
  99. if len(self.Windows) <= self.FreezedIndex:
  100. return None, None
  101. inbounds = []
  102. earliestArrivalTime = None
  103. # check the overlapping windows
  104. for i in range(0, len(self.Windows)):
  105. #for i in range(self.FreezedIndex + 1, min(len(self.Windows), self.FreezedIndex + 1 + self.Configuration.WindowOverlap)):
  106. if None == earliestArrivalTime:
  107. earliestArrivalTime = self.Windows[i].StartTime
  108. for inbound in self.Windows[i].Inbounds:
  109. inbounds.append(inbound)
  110. # check if we found relevant inbounds
  111. if 0 != len(inbounds):
  112. inbounds.sort(key = lambda x: x.InitialArrivalTime)
  113. return inbounds, earliestArrivalTime
  114. else:
  115. return None, None
  116. def sequence(self):
  117. inbounds = []
  118. for i in range(0, len(self.Windows)):
  119. for inbound in self.Windows[i].Inbounds:
  120. inbounds.append(inbound)
  121. return inbounds
  122. def cleanupWindows(self):
  123. currentUtc = dt.utcfromtimestamp(int(time.time())).replace(tzinfo = pytz.UTC)
  124. offsetCorrection = 0
  125. # delete the non-required windows
  126. while 0 != len(self.Windows) and currentUtc > self.Windows[0].EndTime:
  127. # cleanup the association table
  128. for inbound in self.Windows[0].Inbounds:
  129. self.AssignedWindow.pop(inbound.Callsign)
  130. offsetCorrection += 1
  131. self.Windows.pop(0)
  132. # correct the association table
  133. if 0 != offsetCorrection:
  134. for callsign in self.AssignedWindow:
  135. self.AssignedWindow[callsign][0] -= offsetCorrection
  136. # delete the non-updated aircrafts and increase the missed-counter for later runs
  137. callsigns = []
  138. for callsign in self.AssignedWindow:
  139. if 2 < self.AssignedWindow[callsign][1]:
  140. self.Windows[self.AssignedWindow[callsign][0]].remove(callsign)
  141. callsigns.append(callsign)
  142. self.AssignedWindow[callsign][1] += 1
  143. for callsign in callsigns:
  144. self.AssignedWindow.pop(callsign)