RecedingHorizonControl.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  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. self.AssignedWindow[inbound.Callsign] = [ i, 0 ]
  27. if False == usePTA:
  28. inbound.PlannedArrivalTime = inbound.EarliestArrivalTime
  29. inbound.FixedSequence = i < self.FreezedIndex
  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. inbound.FixedSequence = len(self.Windows) < self.FreezedIndex
  49. break
  50. lastWindowTime = self.Windows[-1].EndTime
  51. window.Inbounds.sort(key = lambda x: x.PlannedArrivalTime)
  52. def updateReport(self, inbound : Inbound):
  53. # check if we need to update
  54. if inbound.Callsign in self.AssignedWindow:
  55. index = self.AssignedWindow[inbound.Callsign][0]
  56. self.AssignedWindow[inbound.Callsign][1] = 0
  57. plannedInbound = self.Windows[index].inbound(inbound.Callsign)
  58. plannedInbound.CurrentPosition = inbound.CurrentPosition
  59. # ingore fixed updates
  60. if True == plannedInbound.FixedSequence or index < self.FreezedIndex:
  61. return
  62. plannedInbound.ArrivalCandidates = inbound.ArrivalCandidates
  63. plannedInbound.WTC = inbound.WTC
  64. # check if we need to update the inbound
  65. if plannedInbound.PlannedArrivalTime < inbound.EarliestArrivalTime:
  66. if inbound.EarliestArrivalTime < self.Windows[index].StartTime or inbound.EarliestArrivalTime >= self.Windows[index].EndTime:
  67. self.Windows[index].remove(inbound.Callsign)
  68. self.AssignedWindow.pop(inbound.Callsign)
  69. self.updateReport(inbound)
  70. else:
  71. plannedInbound.PlannedStar = inbound.PlannedStar
  72. plannedInbound.PlannedRunway = inbound.PlannedRunway
  73. plannedInbound.InitialArrivalTime = inbound.InitialArrivalTime
  74. if plannedInbound.PlannedArrivalTime == plannedInbound.EarliestArrivalTime:
  75. plannedInbound.PlannedArrivalTime = inbound.EarliestArrivalTime
  76. plannedInbound.EarliestArrivalTime = inbound.EarliestArrivalTime
  77. else:
  78. self.insertInWindow(inbound, False)
  79. def resequenceInbound(self, inbound : Inbound):
  80. index = self.AssignedWindow[inbound.Callsign][0]
  81. if inbound.PlannedArrivalTime < self.Windows[index].StartTime or inbound.PlannedArrivalTime >= self.Windows[index].EndTime:
  82. self.Windows[index].remove(inbound.Callsign)
  83. self.AssignedWindow.pop(inbound.Callsign)
  84. self.insertInWindow(inbound, True)
  85. else:
  86. inbound.FixedSequence = index < self.FreezedIndex
  87. self.Windows[index].Inbounds.sort(key = lambda x: x.PlannedArrivalTime)
  88. def lastFixedInboundOnRunway(self, runway : str):
  89. # no inbounds available
  90. if 0 == len(self.Windows):
  91. return None
  92. # search from the back to the front to find the last inbound
  93. for i in range(min(self.FreezedIndex, len(self.Windows)), -1, -1):
  94. for inbound in self.Windows[i].Inbounds:
  95. if runway == inbound.PlannedRunway.Name:
  96. return inbound
  97. # no inbound found
  98. return None
  99. def optimizationRelevantInbounds(self):
  100. # no new inbounds
  101. if len(self.Windows) <= self.FreezedIndex:
  102. return None, None
  103. inbounds = []
  104. earliestArrivalTime = None
  105. # check the overlapping windows
  106. for i in range(0, len(self.Windows)):
  107. #for i in range(self.FreezedIndex + 1, min(len(self.Windows), self.FreezedIndex + 1 + self.Configuration.WindowOverlap)):
  108. if None == earliestArrivalTime:
  109. earliestArrivalTime = self.Windows[i].StartTime
  110. for inbound in self.Windows[i].Inbounds:
  111. inbounds.append(inbound)
  112. # check if we found relevant inbounds
  113. if 0 != len(inbounds):
  114. inbounds.sort(key = lambda x: x.InitialArrivalTime)
  115. return inbounds, earliestArrivalTime
  116. else:
  117. return None, None
  118. def sequence(self):
  119. inbounds = []
  120. for i in range(0, len(self.Windows)):
  121. for inbound in self.Windows[i].Inbounds:
  122. inbounds.append(inbound)
  123. return inbounds
  124. def cleanupWindows(self):
  125. currentUtc = dt.utcfromtimestamp(int(time.time())).replace(tzinfo = pytz.UTC)
  126. offsetCorrection = 0
  127. # delete the non-required windows
  128. while 0 != len(self.Windows) and currentUtc > self.Windows[0].EndTime:
  129. # cleanup the association table
  130. for inbound in self.Windows[0].Inbounds:
  131. self.AssignedWindow.pop(inbound.Callsign)
  132. offsetCorrection += 1
  133. self.Windows.pop(0)
  134. # correct the association table
  135. if 0 != offsetCorrection:
  136. for callsign in self.AssignedWindow:
  137. self.AssignedWindow[callsign][0] -= offsetCorrection
  138. if self.AssignedWindow[callsign][0] < self.FreezedIndex:
  139. self.Windows[self.AssignedWindow[callsign][0]].inbound(callsign).FixedSequence = True
  140. # delete the non-updated aircrafts and increase the missed-counter for later runs
  141. callsigns = []
  142. for callsign in self.AssignedWindow:
  143. if 2 < self.AssignedWindow[callsign][1]:
  144. self.Windows[self.AssignedWindow[callsign][0]].remove(callsign)
  145. callsigns.append(callsign)
  146. self.AssignedWindow[callsign][1] += 1
  147. for callsign in callsigns:
  148. self.AssignedWindow.pop(callsign)