Browse Source

implement the RHC mananger

Sven Czarnian 3 years ago
parent
commit
a0b00f7c42
2 changed files with 186 additions and 0 deletions
  1. 153 0
      aman/sys/RecedingHorizonControl.py
  2. 33 0
      aman/sys/RecedingHorizonWindow.py

+ 153 - 0
aman/sys/RecedingHorizonControl.py

@@ -0,0 +1,153 @@
+#!/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)

+ 33 - 0
aman/sys/RecedingHorizonWindow.py

@@ -0,0 +1,33 @@
+#!/usr/bin/env python
+
+from aman.types.Inbound import Inbound
+
+class RecedingHorizonWindow:
+    def __init__(self, startTime, endTime):
+        self.StartTime = startTime
+        self.EndTime = endTime
+        self.Inbounds = []
+
+    def isInWindow(self, inbound : Inbound):
+        for report in self.Inbounds:
+            if report.Report.aircraft.callsign == inbound.Report.aircraft.callsign:
+                return True
+        return False
+
+    def inbound(self, callsign : str):
+        for report in self.Inbounds:
+            if report.Report.aircraft.callsign == callsign:
+                return report
+        return None
+
+    def insert(self, inbound : Inbound):
+        for i in range(0, len(self.Inbounds)):
+            if self.Inbounds[i].Report.aircraft.callsign == inbound.Report.aircraft.callsign:
+                return
+        self.Inbounds.append(inbound)
+
+    def remove(self, callsign : str):
+        for i in range(0, len(self.Inbounds)):
+            if self.Inbounds[i].Report.aircraft.callsign == callsign:
+                self.Inbounds.pop(i)
+                return