#!/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 plannedInbound.MaximumTimeToGain = inbound.MaximumTimeToGain # get the reference time and update some times in case no replacement is needed if plannedInbound.InitialArrivalTime == plannedInbound.EarliestArrivalTime + plannedInbound.MaximumTimeToGain: plannedInbound.InitialArrivalTime = inbound.InitialArrivalTime plannedInbound.EarliestArrivalTime = inbound.EarliestArrivalTime plannedInbound.EstimatedArrivalTime = inbound.EstimatedArrivalTime reference = inbound.EarliestArrivalTime else: plannedInbound.InitialArrivalTime = inbound.InitialArrivalTime if plannedInbound.EarliestArrivalTime < inbound.EarliestArrivalTime: plannedInbound.EarliestArrivalTime = inbound.EarliestArrivalTime if plannedInbound.EstimatedArrivalTime < inbound.EstimatedArrivalTime: plannedInbound.EstimatedArrivalTime = inbound.EstimatedArrivalTime reference = plannedInbound.EarliestArrivalTime # update the windows if reference < self.Windows[index].StartTime or reference >= self.Windows[index].EndTime: 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.EarliestArrivalTime and window.EndTime > inbound.EarliestArrivalTime: 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.EarliestArrivalTime: 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.EarliestArrivalTime) 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)