RecedingHorizonControl.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  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 update(self, inbound : Inbound):
  17. # check if we need to update
  18. if inbound.Report.aircraft.callsign in self.AssignedWindow:
  19. index = self.AssignedWindow[inbound.Report.aircraft.callsign][0]
  20. self.AssignedWindow[inbound.Report.aircraft.callsign][1] = 0
  21. # check if we assume the scheduling as fixed
  22. if index < self.FreezedIndex:
  23. return
  24. plannedInbound = self.Windows[index].inbound(inbound.Report.aircraft.callsign)
  25. plannedInbound.CurrentPosition = inbound.CurrentPosition
  26. plannedInbound.MaximumTimeToGain = inbound.MaximumTimeToGain
  27. plannedInbound.ArrivalCandidates = inbound.ArrivalCandidates
  28. # check if we need to update the inbound
  29. if plannedInbound.PlannedArrivalTime < inbound.EarliestArrivalTime:
  30. if inbound.EarliestArrivalTime < self.Windows[index].StartTime or inbound.EarliestArrivalTime >= self.Windows[index].EndTime:
  31. self.Windows[index].remove(inbound.Report.aircraft.callsign)
  32. self.AssignedWindow.pop(inbound.Report.aircraft.callsign)
  33. self.update(inbound)
  34. else:
  35. plannedInbound.PlannedStar = inbound.PlannedStar
  36. plannedInbound.PlannedRunway = inbound.PlannedRunway
  37. plannedInbound.InitialArrivalTime = inbound.InitialArrivalTime
  38. plannedInbound.EarliestArrivalTime = inbound.EarliestArrivalTime
  39. plannedInbound.PlannedArrivalTime = inbound.EarliestArrivalTime
  40. else:
  41. inserted = False
  42. for i in range(0, len(self.Windows)):
  43. window = self.Windows[i]
  44. # find the correct window
  45. if window.StartTime <= inbound.EarliestArrivalTime and window.EndTime > inbound.EarliestArrivalTime:
  46. if i > self.FreezedIndex:
  47. self.AssignedWindow[inbound.Report.aircraft.callsign] = [ i, 0 ]
  48. inbound.PlannedArrivalTime = inbound.EarliestArrivalTime
  49. window.insert(inbound)
  50. inserted = True
  51. break
  52. # create the new window
  53. if False == inserted:
  54. if 0 != len(self.Windows):
  55. lastWindowTime = self.Windows[-1].EndTime
  56. else:
  57. lastWindowTime = dt.utcfromtimestamp(int(time.time())).replace(tzinfo = pytz.UTC)
  58. timestep = timedelta(seconds = self.Configuration.WindowSize)
  59. while True:
  60. self.Windows.append(RecedingHorizonWindow(lastWindowTime, lastWindowTime + timestep))
  61. if self.Windows[-1].EndTime > inbound.EarliestArrivalTime:
  62. self.AssignedWindow[inbound.Report.aircraft.callsign] = [ len(self.Windows) - 1, 0 ]
  63. self.Windows[-1].insert(inbound)
  64. inbound.PlannedArrivalTime = max(self.Windows[-1].StartTime, inbound.EarliestArrivalTime)
  65. self.Windows[-1].Inbounds.sort(key = lambda x: x.PlannedArrivalTime)
  66. break
  67. lastWindowTime = self.Windows[-1].EndTime
  68. def lastFixedInboundOnRunway(self, runway : str):
  69. # no inbounds available
  70. if 0 == len(self.Windows):
  71. return None
  72. # search from the back to the front to find the last inbound
  73. for i in range(min(self.FreezedIndex, len(self.Windows)), -1, -1):
  74. for inbound in self.Windows[i].Inbounds:
  75. if runway == inbound.PlannedRunway.Runway.name:
  76. return inbound
  77. # no inbound found
  78. return None
  79. def optimizationRelevantInbounds(self):
  80. # no new inbounds
  81. if len(self.Windows) <= self.FreezedIndex:
  82. return None, None
  83. inbounds = []
  84. earliestArrivalTime = None
  85. # check the overlapping windows
  86. #for i in range(self.FreezedIndex + 1, min(len(self.Windows), self.FreezedIndex + 1 + self.Configuration.WindowOverlap)):
  87. for i in range(0, len(self.Windows)):
  88. if None == earliestArrivalTime:
  89. earliestArrivalTime = self.Windows[i].StartTime
  90. for inbound in self.Windows[i].Inbounds:
  91. inbounds.append(inbound)
  92. # check if we found relevant inbounds
  93. if 0 != len(inbounds):
  94. return inbounds, earliestArrivalTime
  95. else:
  96. return None, None
  97. def cleanupWindows(self):
  98. currentUtc = dt.utcfromtimestamp(int(time.time())).replace(tzinfo = pytz.UTC)
  99. offsetCorrection = 0
  100. # delete the non-required windows
  101. while 0 != len(self.Windows) and currentUtc > self.Windows[0].EndTime:
  102. # cleanup the association table
  103. for inbound in self.Windows[0].Inbounds:
  104. self.AssignedWindow.pop(inbound.Report.aircraft.callsign)
  105. offsetCorrection += 1
  106. self.Windows.pop(0)
  107. # correct the association table
  108. if 0 != offsetCorrection:
  109. for callsign in self.AssignedWindow:
  110. self.AssignedWindow[callsign][0] -= offsetCorrection
  111. # delete the non-updated aircrafts and increase the missed-counter for later runs
  112. callsigns = []
  113. for callsign in self.AssignedWindow:
  114. if 2 < self.AssignedWindow[callsign][1]:
  115. self.Windows[self.AssignedWindow[callsign][0]].remove(callsign)
  116. callsigns.append(callsign)
  117. self.AssignedWindow[callsign][1] += 1
  118. for callsign in callsigns:
  119. self.AssignedWindow.pop(callsign)