RecedingHorizonControl.py 11 KB

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