RecedingHorizonControl.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  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. sequenced.HasValidSequence = True
  101. # resort the inbound
  102. if sequenced.PlannedArrivalTime < self.Windows[index].StartTime or sequenced.PlannedArrivalTime >= self.Windows[index].EndTime:
  103. self.Windows[index].remove(sequenced.Callsign)
  104. self.AssignedWindow.pop(sequenced.Callsign)
  105. self.insertInWindow(sequenced, True)
  106. else:
  107. sequenced.FixedSequence = index < self.FreezedIndex
  108. self.Windows[index].Inbounds.sort(key = lambda x: x.PlannedArrivalTime if None != x.PlannedArrivalTime else x.EnrouteArrivalTime)
  109. def latestFixedInbounds(self, configuration : Airport, sequenceConfiguration : AirportSequencing):
  110. if 0 == len(self.Windows):
  111. return None, None
  112. # create the runway tree
  113. runwayInbounds = {}
  114. for runway in sequenceConfiguration.ActiveArrivalRunways:
  115. runwayInbounds[runway.Runway.Name] = None
  116. # create the IAF tree
  117. iafInbounds = {}
  118. for star in configuration.ArrivalRouteConstraints:
  119. altitude = configuration.ArrivalRouteConstraints[star][0].Altitude
  120. iaf = configuration.ArrivalRouteConstraints[star][0].Name
  121. if iaf not in iafInbounds:
  122. iafInbounds[iaf] = { altitude : None }
  123. elif altitude not in iafInbounds[iaf]:
  124. iafInbounds[iaf][altitude] = None
  125. # associate the inbounds to the runways and the IAFs
  126. for i in range(min(self.FreezedIndex, len(self.Windows)), -1, -1):
  127. for inbound in self.Windows[i].Inbounds:
  128. if None == inbound.PlannedRunway or None == inbound.PlannedArrivalRoute:
  129. continue
  130. node = Node(inbound, None, None, None, None)
  131. if inbound.PlannedRunway.Name in runwayInbounds:
  132. if None == runwayInbounds[inbound.PlannedRunway.Name] or runwayInbounds[inbound.PlannedRunway.Name].Inbound.PlannedArrivalTime < node.Inbound.PlannedArrivalTime:
  133. runwayInbounds[inbound.PlannedRunway.Name] = node
  134. if inbound.PlannedArrivalRoute[0].Waypoint.Name in iafInbounds:
  135. delta = 100000.0
  136. targetLevel = None
  137. for level in iafInbounds[inbound.PlannedArrivalRoute[0].Waypoint.Name]:
  138. difference = abs(level - inbound.PlannedArrivalRoute[0].Altitude)
  139. if difference < delta:
  140. delta = difference
  141. targetLevel = level
  142. if None == iafInbounds[inbound.PlannedArrivalRoute[0].Waypoint.Name][targetLevel]:
  143. iafInbounds[inbound.PlannedArrivalRoute[0].Waypoint.Name][targetLevel] = node
  144. elif iafInbounds[inbound.PlannedArrivalRoute[0].Waypoint.Name][targetLevel].Inbound.PlannedArrivalTime < node.Inbound.PlannedArrivalTime:
  145. iafInbounds[inbound.PlannedArrivalRoute[0].Waypoint.Name][targetLevel] = node
  146. return runwayInbounds, iafInbounds
  147. def optimizationRelevantInbounds(self):
  148. inbounds = []
  149. earliestArrivalTime = self.Windows[self.FreezedIndex + 1].StartTime
  150. # check if we have a reconnect in the freezed blocks (VATSIM specific behavior)
  151. for i in range(0, self.FreezedIndex + 1):
  152. for inbound in self.Windows[i].Inbounds:
  153. if False == inbound.HasValidSequence:
  154. inbounds.sort(key = lambda x: x.PlannedArrivalTime if None != x.PlannedArrivalTime else x.EnrouteArrivalTime)
  155. inbounds.append(copy.deepcopy(inbound))
  156. # no new inbounds
  157. if len(self.Windows) <= self.FreezedIndex + 1:
  158. if 0 == len(inbounds):
  159. return None, None
  160. else:
  161. return inbounds, earliestArrivalTime
  162. # check the overlapping windows
  163. for i in range(self.FreezedIndex + 1, len(self.Windows)):
  164. for inbound in self.Windows[i].Inbounds:
  165. inbounds.append(copy.deepcopy(inbound))
  166. if 20 <= len(inbounds):
  167. break
  168. # check if we found relevant inbounds
  169. if 0 != len(inbounds):
  170. inbounds.sort(key = lambda x: x.PlannedArrivalTime if None != x.PlannedArrivalTime else x.EnrouteArrivalTime)
  171. return inbounds, earliestArrivalTime
  172. else:
  173. return None, None
  174. def sequence(self):
  175. inbounds = []
  176. for i in range(0, len(self.Windows)):
  177. for inbound in self.Windows[i].Inbounds:
  178. if True == inbound.HasValidSequence:
  179. inbounds.append(inbound)
  180. return inbounds
  181. def cleanupWindows(self):
  182. currentUtc = dt.utcfromtimestamp(int(time.time())).replace(tzinfo = pytz.UTC)
  183. offsetCorrection = 0
  184. # delete the non-required windows
  185. while 0 != len(self.Windows) and currentUtc > self.Windows[0].EndTime:
  186. # cleanup the association table
  187. for inbound in self.Windows[0].Inbounds:
  188. self.AssignedWindow.pop(inbound.Callsign)
  189. offsetCorrection += 1
  190. self.Windows.pop(0)
  191. # correct the association table
  192. if 0 != offsetCorrection:
  193. for callsign in self.AssignedWindow:
  194. self.AssignedWindow[callsign][0] -= offsetCorrection
  195. if self.AssignedWindow[callsign][0] <= self.FreezedIndex:
  196. self.Windows[self.AssignedWindow[callsign][0]].inbound(callsign).FixedSequence = True
  197. # delete the non-updated aircrafts and increase the missed-counter for later runs
  198. callsigns = []
  199. for callsign in self.AssignedWindow:
  200. if 2 < self.AssignedWindow[callsign][1]:
  201. self.Windows[self.AssignedWindow[callsign][0]].remove(callsign)
  202. callsigns.append(callsign)
  203. self.AssignedWindow[callsign][1] += 1
  204. for callsign in callsigns:
  205. self.AssignedWindow.pop(callsign)