RecedingHorizonControl.py 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  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. if inbound.PlannedRunway.Name in runwayInbounds:
  117. if None == runwayInbounds[inbound.PlannedRunway.Name] or runwayInbounds[inbound.PlannedRunway.Name].Inbound.PlannedArrivalTime < node.Inbound.PlannedArrivalTime:
  118. runwayInbounds[inbound.PlannedRunway.Name] = node
  119. if inbound.PlannedArrivalRoute[0].Waypoint.Name in iafInbounds:
  120. delta = 100000.0
  121. targetLevel = None
  122. for level in iafInbounds[inbound.PlannedArrivalRoute[0].Waypoint.Name]:
  123. difference = abs(level - inbound.PlannedArrivalRoute[0].Altitude)
  124. if difference < delta:
  125. delta = difference
  126. targetLevel = level
  127. if None == iafInbounds[inbound.PlannedArrivalRoute[0].Waypoint.Name][targetLevel]:
  128. iafInbounds[inbound.PlannedArrivalRoute[0].Waypoint.Name][targetLevel] = node
  129. elif iafInbounds[inbound.PlannedArrivalRoute[0].Waypoint.Name][targetLevel].Inbound.PlannedArrivalTime < node.Inbound.PlannedArrivalTime:
  130. iafInbounds[inbound.PlannedArrivalRoute[0].Waypoint.Name][targetLevel] = node
  131. return runwayInbounds, iafInbounds
  132. def optimizationRelevantInbounds(self):
  133. # no new inbounds
  134. if len(self.Windows) <= self.FreezedIndex + 1:
  135. return None, None
  136. inbounds = []
  137. earliestArrivalTime = self.Windows[self.FreezedIndex + 1].StartTime
  138. # check the overlapping windows
  139. for i in range(self.FreezedIndex + 1, len(self.Windows)):
  140. for inbound in self.Windows[i].Inbounds:
  141. inbounds.append(inbound)
  142. if 20 <= len(inbounds):
  143. break
  144. # check if we found relevant inbounds
  145. if 0 != len(inbounds):
  146. inbounds.sort(key = lambda x: x.PlannedArrivalTime if None != x.PlannedArrivalTime else x.EnrouteArrivalTime)
  147. return inbounds, earliestArrivalTime
  148. else:
  149. return None, None
  150. def sequence(self):
  151. inbounds = []
  152. for i in range(0, len(self.Windows)):
  153. for inbound in self.Windows[i].Inbounds:
  154. inbounds.append(inbound)
  155. return inbounds
  156. def cleanupWindows(self):
  157. currentUtc = dt.utcfromtimestamp(int(time.time())).replace(tzinfo = pytz.UTC)
  158. offsetCorrection = 0
  159. # delete the non-required windows
  160. while 0 != len(self.Windows) and currentUtc > self.Windows[0].EndTime:
  161. # cleanup the association table
  162. for inbound in self.Windows[0].Inbounds:
  163. self.AssignedWindow.pop(inbound.Callsign)
  164. offsetCorrection += 1
  165. self.Windows.pop(0)
  166. # correct the association table
  167. if 0 != offsetCorrection:
  168. for callsign in self.AssignedWindow:
  169. self.AssignedWindow[callsign][0] -= offsetCorrection
  170. if self.AssignedWindow[callsign][0] <= self.FreezedIndex:
  171. self.Windows[self.AssignedWindow[callsign][0]].inbound(callsign).FixedSequence = True
  172. # delete the non-updated aircrafts and increase the missed-counter for later runs
  173. callsigns = []
  174. for callsign in self.AssignedWindow:
  175. if 2 < self.AssignedWindow[callsign][1]:
  176. self.Windows[self.AssignedWindow[callsign][0]].remove(callsign)
  177. callsigns.append(callsign)
  178. self.AssignedWindow[callsign][1] += 1
  179. for callsign in callsigns:
  180. self.AssignedWindow.pop(callsign)