123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153 |
- #!/usr/bin/env python
- import time
- from datetime import datetime as dt
- from datetime import timedelta
- import pytz
- from aman.config.RHC import RHC
- from aman.sys.RecedingHorizonWindow import RecedingHorizonWindow
- from aman.types.Inbound import Inbound
- class RecedingHorizonControl:
- def __init__(self, config : RHC):
- self.Windows = []
- # contains the current index and the missed update counter
- self.AssignedWindow = {}
- self.Configuration = config
- self.FreezedIndex = int(self.Configuration.FixedBeforeArrival.seconds / self.Configuration.WindowSize)
- def update(self, inbound : Inbound):
- # check if we need to update
- if inbound.Report.aircraft.callsign in self.AssignedWindow:
- index = self.AssignedWindow[inbound.Report.aircraft.callsign][0]
- self.AssignedWindow[inbound.Report.aircraft.callsign][1] = 0
- # check if we assume the scheduling as fixed
- if index < self.FreezedIndex:
- return
- plannedInbound = self.Windows[index].inbound(inbound.Report.aircraft.callsign)
- plannedInbound.CurrentPosition = inbound.CurrentPosition
- # check if not accessed by the optimizer
- if plannedInbound.InitialArrivalTime == plannedInbound.EstimatedArrivalTime:
- notPlanned = True
- update = True
- # check if the ITA is greater or the ETA
- elif plannedInbound.EstimatedArrivalTime != inbound.InitialArrivalTime:
- notPlanned = False
- update = True
- # update the windows
- if True == update:
- if inbound.InitialArrivalTime >= self.Windows[index].StartTime and inbound.InitialArrivalTime < self.Windows[index].EndTime:
- keepInWindow = True
- else:
- keepInWindow = False
- # check if planning can be updated or needs to be due to a delay
- if True == notPlanned or plannedInbound.Inbound.EstimatedArrivalTime < inbound.InitialArrivalTime:
- # keep it in this bucket
- if True == keepInWindow:
- plannedInbound.EstimatedArrivalTime = inbound.InitialArrivalTime
- plannedInbound.InitialArrivalTime = inbound.InitialArrivalTime
- self.Windows[index].Inbounds.sort(key = lambda x: x.EstimatedArrivalTime)
- # put it in the new bucket
- else:
- self.Windows[index].remove(inbound.Report.aircraft.callsign)
- self.AssignedWindow.pop(inbound.Report.aircraft.callsign)
- self.update(inbound)
- else:
- inserted = False
- for i in range(0, len(self.Windows)):
- window = self.Windows[i]
- # find the correct window
- if window.StartTime <= inbound.EstimatedArrivalTime and window.EndTime > inbound.EstimatedArrivalTime:
- if i > self.FreezedIndex:
- self.AssignedWindow[inbound.Report.aircraft.callsign] = [ i, 0 ]
- window.insert(inbound)
- inserted = True
- break
- # create the new window
- if False == inserted:
- if 0 != len(self.Windows):
- lastWindowTime = self.Windows[-1].EndTime
- else:
- lastWindowTime = dt.utcfromtimestamp(int(time.time())).replace(tzinfo = pytz.UTC)
- timestep = timedelta(seconds = self.Configuration.WindowSize)
- while True:
- self.Windows.append(RecedingHorizonWindow(lastWindowTime, lastWindowTime + timestep))
- if self.Windows[-1].EndTime > inbound.EstimatedArrivalTime:
- self.AssignedWindow[inbound.Report.aircraft.callsign] = [ len(self.Windows) - 1, 0 ]
- self.Windows[-1].insert(inbound)
- self.Windows[-1].Inbounds.sort(key = lambda x: x.EstimatedArrivalTime)
- break
- lastWindowTime = self.Windows[-1].EndTime
- def lastFixedInboundOnRunway(self, runway : str):
- # no inbounds available
- if 0 == len(self.Windows):
- return None
- # search from the back to the front to find the last inbound
- for i in range(min(self.FreezedIndex, len(self.Windows)), -1, -1):
- for inbound in self.Windows[i].Inbounds:
- if runway == inbound.PlannedRunway.Runway.name:
- return inbound
- # no inbound found
- return None
- def optimizationRelevantInbounds(self):
- # no new inbounds
- if len(self.Windows) <= self.FreezedIndex:
- return None
- inbounds = []
- # check the overlapping windows
- for i in range(self.FreezedIndex + 1, min(len(self.Windows), self.FreezedIndex + 1 + self.Configuration.WindowOverlap)):
- for inbound in self.Windows[i].Inbounds:
- inbounds.append(inbound)
- # check if we found relevant inbounds
- if 0 != len(inbounds):
- return inbounds
- else:
- return None
- def cleanupWindows(self):
- currentUtc = dt.utcfromtimestamp(int(time.time())).replace(tzinfo = pytz.UTC)
- offsetCorrection = 0
- # delete the non-required windows
- while 0 != len(self.Windows) and currentUtc > self.Windows[0].EndTime:
- # cleanup the association table
- for inbound in self.Windows[0].Inbounds:
- self.AssignedWindow.pop(inbound.Report.aircraft.callsign)
- offsetCorrection += 1
- self.Windows.pop(0)
- # correct the association table
- if 0 != offsetCorrection:
- for callsign in self.AssignedWindow:
- self.AssignedWindow[callsign][0] -= offsetCorrection
- # delete the non-updated aircrafts and increase the missed-counter for later runs
- callsigns = []
- for callsign in self.AssignedWindow:
- if 2 < self.AssignedWindow[callsign][1]:
- self.Windows[self.AssignedWindow[callsign][0]].remove(callsign)
- callsigns.append(callsign)
- self.AssignedWindow[callsign][1] += 1
- for callsign in callsigns:
- self.AssignedWindow.pop(callsign)
|