RecedingHorizonControl.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  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.Airport import Airport
  7. from aman.config.AirportSequencing import AirportSequencing
  8. from aman.config.RHC import RHC
  9. from aman.sys.aco.Node import Node
  10. from aman.sys.RecedingHorizonWindow import RecedingHorizonWindow
  11. from aman.types.Inbound import Inbound
  12. class RecedingHorizonControl:
  13. def __init__(self, config : RHC):
  14. self.Windows = []
  15. # contains the current index and the missed update counter
  16. self.AssignedWindow = {}
  17. self.Configuration = config
  18. self.FreezedIndex = int(self.Configuration.FixedBeforeArrival.seconds / self.Configuration.WindowSize)
  19. def insertInWindow(self, inbound : Inbound, usePTA : bool):
  20. if False == usePTA:
  21. referenceTime = inbound.EnrouteArrivalTime
  22. else:
  23. referenceTime = inbound.PlannedArrivalTime
  24. inserted = False
  25. for i in range(0, len(self.Windows)):
  26. window = self.Windows[i]
  27. # find the correct window
  28. if window.StartTime <= referenceTime and window.EndTime > referenceTime:
  29. self.AssignedWindow[inbound.Callsign] = [ i, 0 ]
  30. inbound.FixedSequence = i < self.FreezedIndex
  31. if True == inbound.FixedSequence and None == inbound.PlannedArrivalTime:
  32. inbound.PlannedArrivalTime = inbound.EnrouteArrivalTime
  33. window.insert(inbound)
  34. inserted = True
  35. break
  36. # create the new window
  37. if False == inserted:
  38. if 0 != len(self.Windows):
  39. lastWindowTime = self.Windows[-1].EndTime
  40. else:
  41. lastWindowTime = dt.utcfromtimestamp(int(time.time())).replace(tzinfo = pytz.UTC)
  42. timestep = timedelta(seconds = self.Configuration.WindowSize)
  43. while True:
  44. self.Windows.append(RecedingHorizonWindow(lastWindowTime, lastWindowTime + timestep))
  45. if self.Windows[-1].EndTime > referenceTime:
  46. window = self.Windows[-1]
  47. window.insert(inbound)
  48. self.AssignedWindow[inbound.Callsign] = [ len(self.Windows) - 1, 0 ]
  49. inbound.FixedSequence = len(self.Windows) < self.FreezedIndex
  50. if True == inbound.FixedSequence and None == inbound.PlannedArrivalTime:
  51. inbound.PlannedArrivalTime = inbound.EnrouteArrivalTime
  52. break
  53. lastWindowTime = self.Windows[-1].EndTime
  54. window.Inbounds.sort(key = lambda x: x.PlannedArrivalTime if None != x.PlannedArrivalTime else x.EnrouteArrivalTime)
  55. def updateReport(self, inbound : Inbound):
  56. # check if we need to update
  57. if inbound.Callsign in self.AssignedWindow:
  58. index = self.AssignedWindow[inbound.Callsign][0]
  59. self.AssignedWindow[inbound.Callsign][1] = 0
  60. plannedInbound = self.Windows[index].inbound(inbound.Callsign)
  61. plannedInbound.Report = inbound.Report
  62. plannedInbound.ReportTime = inbound.ReportTime
  63. plannedInbound.CurrentPosition = inbound.CurrentPosition
  64. plannedInbound.RequestedRunway = inbound.RequestedRunway
  65. # ingore fixed updates
  66. if True == plannedInbound.FixedSequence or index <= self.FreezedIndex:
  67. plannedInbound.FixedSequence = True
  68. return
  69. plannedInbound.WTC = inbound.WTC
  70. # check if we need to update the inbound
  71. if None == plannedInbound.PlannedStar:
  72. reference = inbound.EnrouteArrivalTime
  73. if plannedInbound.EnrouteArrivalTime > reference:
  74. reference = plannedInbound.EnrouteArrivalTime
  75. if reference < self.Windows[index].StartTime or reference >= self.Windows[index].EndTime:
  76. self.Windows[index].remove(inbound.Callsign)
  77. self.AssignedWindow.pop(inbound.Callsign)
  78. inbound.EnrouteArrivalTime = reference
  79. self.updateReport(inbound)
  80. else:
  81. plannedInbound.EnrouteArrivalTime = reference
  82. self.Windows[index].Inbounds.sort(key = lambda x: x.PlannedArrivalTime if None != x.PlannedArrivalTime else x.EnrouteArrivalTime)
  83. else:
  84. self.insertInWindow(inbound, False)
  85. def resequenceInbound(self, inbound : Inbound):
  86. index = self.AssignedWindow[inbound.Callsign][0]
  87. if inbound.PlannedArrivalTime < self.Windows[index].StartTime or inbound.PlannedArrivalTime >= self.Windows[index].EndTime:
  88. self.Windows[index].remove(inbound.Callsign)
  89. self.AssignedWindow.pop(inbound.Callsign)
  90. self.insertInWindow(inbound, True)
  91. else:
  92. inbound.FixedSequence = index < self.FreezedIndex
  93. self.Windows[index].Inbounds.sort(key = lambda x: x.PlannedArrivalTime if None != x.PlannedArrivalTime else x.EnrouteArrivalTime)
  94. def latestFixedInbounds(self, configuration : Airport, sequenceConfiguration : AirportSequencing):
  95. if 0 == len(self.Windows):
  96. return None, None
  97. # create the runway tree
  98. runwayInbounds = {}
  99. for runway in sequenceConfiguration.ActiveArrivalRunways:
  100. runwayInbounds[runway.Runway.Name] = None
  101. # create the IAF tree
  102. iafInbounds = {}
  103. for star in configuration.ArrivalRouteConstraints:
  104. altitude = configuration.ArrivalRouteConstraints[star][0].Altitude
  105. iaf = configuration.ArrivalRouteConstraints[star][0].Name
  106. if iaf not in iafInbounds:
  107. iafInbounds[iaf] = { altitude : None }
  108. elif altitude not in iafInbounds[iaf]:
  109. iafInbounds[iaf][altitude] = None
  110. # associate the inbounds to the runways and the IAFs
  111. for i in range(min(self.FreezedIndex, len(self.Windows)), -1, -1):
  112. for inbound in self.Windows[i].Inbounds:
  113. if None == inbound.PlannedRunway or None == inbound.PlannedArrivalRoute:
  114. continue
  115. node = Node(inbound, None, None, None, None)
  116. node.EstimatedTouchdownTime = inbound.PlannedArrivalTime
  117. node.EstimatedIafAltitude = inbound.PlannedArrivalRoute[0].Altitude
  118. node.EstimatedIafTime = inbound.PlannedArrivalRoute[0].PTA
  119. if inbound.PlannedRunway.Name in runwayInbounds:
  120. if None == runwayInbounds[inbound.PlannedRunway.Name] or runwayInbounds[inbound.PlannedRunway.Name].EstimatedTouchdownTime < node.EstimatedTouchdownTime:
  121. runwayInbounds[inbound.PlannedRunway.Name] = node
  122. if inbound.PlannedArrivalRoute[0].Name in iafInbounds:
  123. delta = 100000.0
  124. targetLevel = None
  125. for level in iafInbounds[inbound.PlannedArrivalRoute[0].Name]:
  126. difference = abs(level - inbound.PlannedArrivalRoute[0].Altitude)
  127. if difference < delta:
  128. delta = difference
  129. targetLevel = level
  130. if None == iafInbounds[inbound.PlannedArrivalRoute[0].Name][targetLevel]:
  131. iafInbounds[inbound.PlannedArrivalRoute[0].Name][targetLevel] = Node
  132. elif iafInbounds[inbound.PlannedArrivalRoute[0].Name][targetLevel].EstimatedIafTime < node.EstimatedIafTime:
  133. iafInbounds[inbound.PlannedArrivalRoute[0].Name][targetLevel] = Node
  134. return runwayInbounds, iafInbounds
  135. def optimizationRelevantInbounds(self):
  136. # no new inbounds
  137. if len(self.Windows) <= self.FreezedIndex + 1:
  138. return None, None
  139. inbounds = []
  140. earliestArrivalTime = self.Windows[self.FreezedIndex + 1].StartTime
  141. # check the overlapping windows
  142. for i in range(self.FreezedIndex + 1, len(self.Windows)):
  143. for inbound in self.Windows[i].Inbounds:
  144. inbounds.append(inbound)
  145. if 20 <= len(inbounds):
  146. break
  147. # check if we found relevant inbounds
  148. if 0 != len(inbounds):
  149. inbounds.sort(key = lambda x: x.PlannedArrivalTime if None != x.PlannedArrivalTime else x.EnrouteArrivalTime)
  150. return inbounds, earliestArrivalTime
  151. else:
  152. return None, None
  153. def sequence(self):
  154. inbounds = []
  155. for i in range(0, len(self.Windows)):
  156. for inbound in self.Windows[i].Inbounds:
  157. inbounds.append(inbound)
  158. return inbounds
  159. def cleanupWindows(self):
  160. currentUtc = dt.utcfromtimestamp(int(time.time())).replace(tzinfo = pytz.UTC)
  161. offsetCorrection = 0
  162. # delete the non-required windows
  163. while 0 != len(self.Windows) and currentUtc > self.Windows[0].EndTime:
  164. # cleanup the association table
  165. for inbound in self.Windows[0].Inbounds:
  166. self.AssignedWindow.pop(inbound.Callsign)
  167. offsetCorrection += 1
  168. self.Windows.pop(0)
  169. # correct the association table
  170. if 0 != offsetCorrection:
  171. for callsign in self.AssignedWindow:
  172. self.AssignedWindow[callsign][0] -= offsetCorrection
  173. if self.AssignedWindow[callsign][0] <= self.FreezedIndex:
  174. self.Windows[self.AssignedWindow[callsign][0]].inbound(callsign).FixedSequence = True
  175. # delete the non-updated aircrafts and increase the missed-counter for later runs
  176. callsigns = []
  177. for callsign in self.AssignedWindow:
  178. if 2 < self.AssignedWindow[callsign][1]:
  179. self.Windows[self.AssignedWindow[callsign][0]].remove(callsign)
  180. callsigns.append(callsign)
  181. self.AssignedWindow[callsign][1] += 1
  182. for callsign in callsigns:
  183. self.AssignedWindow.pop(callsign)