RecedingHorizonControl.py 7.8 KB

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