RecedingHorizonControl.py 6.5 KB

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