Files
aman-sys/aman/sys/RecedingHorizonControl.py
2021-10-12 22:28:50 +02:00

154 lines
6.5 KiB
Python

#!/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)