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