adapt the code to split up predictions form the inbounds
This commit is contained in:
		| @@ -9,26 +9,27 @@ import itertools | ||||
|  | ||||
| from aman.sys.aco.Configuration import Configuration | ||||
| from aman.sys.aco.RunwayManager import RunwayManager | ||||
| from aman.types.Inbound import Inbound | ||||
| from aman.sys.aco.Node import Node | ||||
|  | ||||
| # This class implements a single ant of the following paper: | ||||
| # https://sci-hub.mksa.top/10.1109/cec.2019.8790135 | ||||
| class Ant: | ||||
|     def __init__(self, pheromoneTable : np.array, configuration : Configuration): | ||||
|     def __init__(self, pheromoneTable : np.array, configuration : Configuration, nodes): | ||||
|         self.Configuration = configuration | ||||
|         self.Nodes = nodes | ||||
|         self.RunwayManager = RunwayManager(self.Configuration) | ||||
|         self.InboundSelected = [ False ] * len(self.Configuration.Inbounds) | ||||
|         self.InboundScore = np.zeros([ len(self.Configuration.Inbounds), 1 ]) | ||||
|         self.InboundSelected = [ False ] * len(self.Nodes) | ||||
|         self.InboundScore = np.zeros([ len(self.Nodes), 1 ]) | ||||
|         self.PheromoneMatrix = pheromoneTable | ||||
|         self.SequenceDelay = timedelta(seconds = 0) | ||||
|         self.Sequence = None | ||||
|  | ||||
|     def qualifyDelay(delay, inbound, runway): | ||||
|     def qualifyDelay(delay, node, runway): | ||||
|         if 0.0 > delay.total_seconds(): | ||||
|             delay = timedelta(seconds = 0) | ||||
|  | ||||
|         # calculate the heuristic scaling to punish increased delays for single inbounds | ||||
|         scaledDelay = delay.total_seconds() / inbound.ArrivalCandidates[runway.Name].MaximumTimeToLose.total_seconds() | ||||
|         scaledDelay = delay.total_seconds() / node.ArrivalCandidates[runway.Name].MaximumTimeToLose.total_seconds() | ||||
|         return max(1.0 / (99.0 * (scaledDelay ** 2) + 1), 0.01) | ||||
|  | ||||
|     # Implements function (5), but adapted to the following logic: | ||||
| @@ -37,8 +38,8 @@ class Ant: | ||||
|     #   - Calculates a ratio between the inbound delay and the unused runway time | ||||
|     #   - Weight the overall ratio based on maximum time to lose to punish high time to lose rates while other flights are gaining time | ||||
|     def heuristicInformation(self, preceeding : int, current : int): | ||||
|         rwy, eta, unusedRunwayTime = self.RunwayManager.selectArrivalRunway(self.Configuration.Inbounds[current], True, self.Configuration.EarliestArrivalTime) | ||||
|         inboundDelay = eta - self.Configuration.Inbounds[current].ArrivalCandidates[rwy.Name].InitialArrivalTime | ||||
|         rwy, eta, unusedRunwayTime = self.RunwayManager.selectArrivalRunway(self.Nodes[current], True, self.Configuration.EarliestArrivalTime) | ||||
|         inboundDelay = eta - self.Nodes[current].ArrivalCandidates[rwy.Name].InitialArrivalTime | ||||
|         if 0.0 > inboundDelay.total_seconds(): | ||||
|             inboundDelay = timedelta(seconds = 0) | ||||
|  | ||||
| @@ -49,7 +50,7 @@ class Ant: | ||||
|         fraction /= 60.0 | ||||
|  | ||||
|         # calculate the heuristic scaling to punish increased delays for single inbounds | ||||
|         weight = Ant.qualifyDelay(inboundDelay, self.Configuration.Inbounds[current], rwy) | ||||
|         weight = Ant.qualifyDelay(inboundDelay, self.Nodes[current], rwy) | ||||
|  | ||||
|         return weight * self.PheromoneMatrix[preceeding, current] * ((1.0 / (fraction or 1)) ** self.Configuration.Beta) | ||||
|  | ||||
| @@ -86,18 +87,19 @@ class Ant: | ||||
|  | ||||
|         return None | ||||
|  | ||||
|     def associateInbound(self, inbound : Inbound, earliestArrivalTime : datetime): | ||||
|     def associateInbound(self, node : Node, earliestArrivalTime : datetime): | ||||
|         # prepare the statistics | ||||
|         rwy, eta, _ = self.RunwayManager.selectArrivalRunway(inbound, True, self.Configuration.EarliestArrivalTime) | ||||
|         rwy, eta, _ = self.RunwayManager.selectArrivalRunway(node, True, self.Configuration.EarliestArrivalTime) | ||||
|         eta = max(earliestArrivalTime, eta) | ||||
|  | ||||
|         inbound.PlannedRunway = rwy | ||||
|         inbound.PlannedStar = inbound.ArrivalCandidates[rwy.Name].Star | ||||
|         inbound.PlannedArrivalTime = eta | ||||
|         inbound.InitialArrivalTime = inbound.ArrivalCandidates[rwy.Name].InitialArrivalTime | ||||
|         self.RunwayManager.RunwayInbounds[rwy.Name] = inbound | ||||
|         node.Inbound.PlannedRunway = rwy | ||||
|         node.Inbound.PlannedStar = node.ArrivalCandidates[rwy.Name].Star | ||||
|         node.Inbound.PlannedArrivalTime = eta | ||||
|         node.Inbound.PlannedArrivalRoute = node.ArrivalCandidates[rwy.Name].ArrivalRoute | ||||
|         node.Inbound.InitialArrivalTime = node.ArrivalCandidates[rwy.Name].InitialArrivalTime | ||||
|         self.RunwayManager.RunwayInbounds[rwy.Name] = node | ||||
|  | ||||
|         delay = inbound.PlannedArrivalTime - inbound.InitialArrivalTime  | ||||
|         delay = node.Inbound.PlannedArrivalTime - node.Inbound.InitialArrivalTime  | ||||
|         if 0.0 < delay.total_seconds(): | ||||
|             return delay, rwy | ||||
|         else: | ||||
| @@ -108,8 +110,8 @@ class Ant: | ||||
|  | ||||
|         # select the first inbound | ||||
|         self.InboundSelected[first] = True | ||||
|         delay, rwy = self.associateInbound(self.Configuration.Inbounds[first], self.Configuration.EarliestArrivalTime) | ||||
|         self.InboundScore[0] = Ant.qualifyDelay(delay, self.Configuration.Inbounds[first], rwy) | ||||
|         delay, rwy = self.associateInbound(self.Nodes[first], self.Configuration.EarliestArrivalTime) | ||||
|         self.InboundScore[0] = Ant.qualifyDelay(delay, self.Nodes[first], rwy) | ||||
|         self.Sequence.append(first) | ||||
|         self.SequenceDelay += delay | ||||
|  | ||||
| @@ -119,9 +121,9 @@ class Ant: | ||||
|                 break | ||||
|  | ||||
|             self.InboundSelected[index] = True | ||||
|             delay, rwy = self.associateInbound(self.Configuration.Inbounds[index], self.Configuration.EarliestArrivalTime) | ||||
|             delay, rwy = self.associateInbound(self.Nodes[index], self.Configuration.EarliestArrivalTime) | ||||
|             self.SequenceDelay += delay | ||||
|             self.InboundScore[len(self.Sequence)] = Ant.qualifyDelay(delay, self.Configuration.Inbounds[index], rwy) | ||||
|             self.InboundScore[len(self.Sequence)] = Ant.qualifyDelay(delay, self.Nodes[index], rwy) | ||||
|             self.Sequence.append(index) | ||||
|  | ||||
|             # update the local pheromone | ||||
| @@ -130,7 +132,7 @@ class Ant: | ||||
|             self.PheromoneMatrix[self.Sequence[-2], self.Sequence[-1]] = max(self.Configuration.ThetaZero, update) | ||||
|  | ||||
|         # validate that nothing went wrong | ||||
|         if len(self.Sequence) != len(self.Configuration.Inbounds): | ||||
|         if len(self.Sequence) != len(self.Nodes): | ||||
|             self.SequenceDelay = None | ||||
|             self.SequenceScore = None | ||||
|             self.Sequence = None | ||||
|   | ||||
| @@ -1,48 +1,57 @@ | ||||
| #!/usr/bin/env python | ||||
|  | ||||
| from datetime import datetime as dt | ||||
| from datetime import datetime, timedelta | ||||
| import numpy as np | ||||
| import pytz | ||||
| import random | ||||
| import sys | ||||
| import pytz | ||||
| import time | ||||
|  | ||||
| from aman.sys.aco.Ant import Ant | ||||
| from aman.sys.aco.Configuration import Configuration | ||||
| from aman.sys.aco.Node import Node | ||||
| from aman.sys.aco.RunwayManager import RunwayManager | ||||
| from aman.types.Inbound import Inbound | ||||
|  | ||||
| # This class implements the ant colony of the following paper: | ||||
| # https://sci-hub.mksa.top/10.1109/cec.2019.8790135 | ||||
| class Colony: | ||||
|     def associateInbound(rwyManager : RunwayManager, inbound : Inbound, earliestArrivalTime : datetime, useITA : bool): | ||||
|         rwy, eta, _ = rwyManager.selectArrivalRunway(inbound, useITA, earliestArrivalTime) | ||||
|     def associateInbound(rwyManager : RunwayManager, node : Node, earliestArrivalTime : datetime, useITA : bool): | ||||
|         rwy, eta, _ = rwyManager.selectArrivalRunway(node, useITA, earliestArrivalTime) | ||||
|         eta = max(earliestArrivalTime, eta) | ||||
|  | ||||
|         inbound.PlannedRunway = rwy | ||||
|         inbound.PlannedStar = inbound.ArrivalCandidates[rwy.Name].Star | ||||
|         inbound.PlannedArrivalRoute = inbound.ArrivalCandidates[rwy.Name].ArrivalRoute | ||||
|         inbound.PlannedArrivalTime = eta | ||||
|         inbound.InitialArrivalTime = inbound.ArrivalCandidates[rwy.Name].InitialArrivalTime | ||||
|         inbound.PlannedTrackmiles = inbound.ArrivalCandidates[rwy.Name].Trackmiles | ||||
|         rwyManager.RunwayInbounds[rwy.Name] = inbound | ||||
|         node.Inbound.PlannedRunway = rwy | ||||
|         node.Inbound.PlannedStar = node.ArrivalCandidates[rwy.Name].Star | ||||
|         node.Inbound.PlannedArrivalRoute = node.ArrivalCandidates[rwy.Name].ArrivalRoute | ||||
|         node.Inbound.PlannedArrivalTime = eta | ||||
|         node.Inbound.InitialArrivalTime = node.ArrivalCandidates[rwy.Name].InitialArrivalTime | ||||
|         node.Inbound.PlannedTrackmiles = node.ArrivalCandidates[rwy.Name].Trackmiles | ||||
|         rwyManager.RunwayInbounds[rwy.Name] = node | ||||
|  | ||||
|     def calculateInitialCosts(rwyManager : RunwayManager, inbounds, earliestArrivalTime : datetime): | ||||
|     def calculateInitialCosts(rwyManager : RunwayManager, nodes, earliestArrivalTime : datetime): | ||||
|         overallDelay = timedelta(seconds = 0) | ||||
|  | ||||
|         # assume that the inbounds are sorted in FCFS order | ||||
|         for inbound in inbounds: | ||||
|             Colony.associateInbound(rwyManager, inbound, earliestArrivalTime, False) | ||||
|             overallDelay += inbound.PlannedArrivalTime - inbound.InitialArrivalTime | ||||
|         # assume that the nodes are sorted in FCFS order | ||||
|         for node in nodes: | ||||
|             Colony.associateInbound(rwyManager, node, earliestArrivalTime, False) | ||||
|             overallDelay += node.Inbound.PlannedArrivalTime - node.Inbound.InitialArrivalTime | ||||
|  | ||||
|         return overallDelay | ||||
|  | ||||
|     def __init__(self, configuration : Configuration): | ||||
|     def __init__(self, inbounds, configuration : Configuration): | ||||
|         self.Configuration = configuration | ||||
|         self.ResultDelay = None | ||||
|         self.Result = None | ||||
|         self.Nodes = [] | ||||
|  | ||||
|         # create the new planning instances | ||||
|         currentTime = dt.utcfromtimestamp(int(time.time())).replace(tzinfo = pytz.UTC) | ||||
|         for inbound in inbounds: | ||||
|             self.Nodes.append(Node(inbound, currentTime, self.Configuration.WeatherModel, self.Configuration.NavData, self.Configuration.RunwayConstraints)) | ||||
|  | ||||
|         rwyManager = RunwayManager(self.Configuration) | ||||
|         delay = Colony.calculateInitialCosts(rwyManager, self.Configuration.Inbounds, self.Configuration.EarliestArrivalTime) | ||||
|         delay = Colony.calculateInitialCosts(rwyManager, self.Nodes, self.Configuration.EarliestArrivalTime) | ||||
|         self.FcfsDelay = delay | ||||
|  | ||||
|         # run the optimization in every cycle to ensure optimal spacings based on TTG | ||||
| @@ -50,8 +59,8 @@ class Colony: | ||||
|             delay = timedelta(seconds = 1.0) | ||||
|  | ||||
|         # initial value for the optimization | ||||
|         self.Configuration.ThetaZero = 1.0 / (len(self.Configuration.Inbounds) * (delay.total_seconds() / 60.0)) | ||||
|         self.PheromoneMatrix = np.ones(( len(self.Configuration.Inbounds), len(self.Configuration.Inbounds) ), dtype=float) * self.Configuration.ThetaZero | ||||
|         self.Configuration.ThetaZero = 1.0 / (len(self.Nodes) * (delay.total_seconds() / 60.0)) | ||||
|         self.PheromoneMatrix = np.ones(( len(self.Nodes), len(self.Nodes) ), dtype=float) * self.Configuration.ThetaZero | ||||
|  | ||||
|     def optimize(self): | ||||
|         # FCFS is the best solution | ||||
| @@ -64,12 +73,12 @@ class Colony: | ||||
|         # run the optimization loops | ||||
|         for _ in range(0, self.Configuration.ExplorationRuns): | ||||
|             # select the first inbound | ||||
|             index = random.randint(1, len(self.Configuration.Inbounds)) - 1 | ||||
|             index = random.randint(1, len(self.Nodes)) - 1 | ||||
|             candidates = [] | ||||
|  | ||||
|             for _ in range(0, self.Configuration.AntCount): | ||||
|                 # let the ant find a solution | ||||
|                 ant = Ant(self.PheromoneMatrix, self.Configuration) | ||||
|                 ant = Ant(self.PheromoneMatrix, self.Configuration, self.Nodes) | ||||
|                 ant.findSolution(index) | ||||
|  | ||||
|                 # fallback to check if findSolution was successful | ||||
| @@ -111,8 +120,8 @@ class Colony: | ||||
|             # finalize the sequence | ||||
|             rwyManager = RunwayManager(self.Configuration) | ||||
|             for i in range(0, len(bestSequence[1])): | ||||
|                 self.Result.append(self.Configuration.Inbounds[bestSequence[1][i]]) | ||||
|                 Colony.associateInbound(rwyManager, self.Result[-1], self.Configuration.EarliestArrivalTime, True) | ||||
|                 self.Result.append(self.Nodes[bestSequence[1][i]].Inbound) | ||||
|                 Colony.associateInbound(rwyManager, self.Nodes[bestSequence[1][i]], self.Configuration.EarliestArrivalTime, True) | ||||
|  | ||||
|                 # the idea behind the TTL/TTG per waypoint is that the TTL and TTG needs to be achieved in the | ||||
|                 # first 2/3 of the estimated trackmiles and assign it with a linear function to the waypoints | ||||
|   | ||||
| @@ -9,9 +9,9 @@ class Configuration: | ||||
|         # the AMAN specific information | ||||
|         self.RunwayConstraints = kwargs.get('constraints', None) | ||||
|         self.PreceedingInbounds = kwargs.get('preceeding', None) | ||||
|         self.Inbounds = kwargs.get('inbounds', None) | ||||
|         self.EarliestArrivalTime = kwargs.get('earliest', None) | ||||
|         self.WeatherModel = kwargs.get('weather', None) | ||||
|         self.NavData = kwargs.get('nav', None) | ||||
|  | ||||
|         # the ACO specific information | ||||
|         self.AntCount = kwargs.get('ants', 20) | ||||
|   | ||||
							
								
								
									
										193
									
								
								aman/sys/aco/Node.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										193
									
								
								aman/sys/aco/Node.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,193 @@ | ||||
| #!/usr/bin/env python | ||||
|  | ||||
| import math | ||||
| import sys | ||||
|  | ||||
| from datetime import datetime, timedelta | ||||
|  | ||||
| from aman.config.AirportSequencing import AirportSequencing | ||||
| from aman.formats.SctEseFormat import SctEseFormat | ||||
| from aman.sys.WeatherModel import WeatherModel | ||||
| from aman.types.ArrivalData import ArrivalData | ||||
| from aman.types.ArrivalRoute import ArrivalRoute | ||||
| from aman.types.ArrivalWaypoint import ArrivalWaypoint | ||||
| from aman.types.Runway import Runway | ||||
| from aman.types.Inbound import Inbound | ||||
| from aman.types.Waypoint import Waypoint | ||||
|  | ||||
| class Node: | ||||
|     def findArrivalRoute(iaf : str, runway : Runway, navData : SctEseFormat): | ||||
|         for arrivalRunway in navData.ArrivalRoutes: | ||||
|             if arrivalRunway == runway.Name: | ||||
|                 stars = navData.ArrivalRoutes[arrivalRunway] | ||||
|                 for star in stars: | ||||
|                     if 0 != len(star.Route) and iaf == star.Iaf.Name: | ||||
|                         return star | ||||
|         return None | ||||
|  | ||||
|     def arrivalEstimation(self, runway : Runway, star : ArrivalRoute, weather : WeatherModel): | ||||
|         # calculate remaining trackmiles | ||||
|         trackmiles = self.PredictedDistanceToIAF | ||||
|         start = star.Route[0] | ||||
|         turnIndices = [ -1, -1 ] | ||||
|         constraints = [] | ||||
|         for i in range(0, len(star.Route)): | ||||
|             # identified the base turn | ||||
|             if True == star.Route[i].BaseTurn: | ||||
|                 turnIndices[0] = i | ||||
|             # identified the final turn | ||||
|             elif -1 != turnIndices[0] and True == star.Route[i].FinalTurn: | ||||
|                 turnIndices[1] = i | ||||
|             # skip waypoints until the final turn point is found | ||||
|             elif -1 != turnIndices[0] and -1 == turnIndices[1]: | ||||
|                 continue | ||||
|  | ||||
|             trackmiles += start.haversine(star.Route[i]) * 0.539957 | ||||
|  | ||||
|             # check if a new constraint is defined | ||||
|             altitude = -1 | ||||
|             speed = -1 | ||||
|             if None != star.Route[i].Altitude: | ||||
|                 altitude = star.Route[i].Altitude | ||||
|             if None != star.Route[i].Speed: | ||||
|                 speed = star.Route[i].Speed | ||||
|             if -1 != altitude or -1 != speed: | ||||
|                 constraints.append([ trackmiles, altitude, speed ]) | ||||
|  | ||||
|             start = star.Route[i] | ||||
|  | ||||
|         # add the remaining distance from the last waypoint to the runway threshold | ||||
|         trackmiles += start.haversine(runway.Start) | ||||
|  | ||||
|         if turnIndices[0] > turnIndices[1] or (-1 == turnIndices[1] and -1 != turnIndices[0]): | ||||
|             sys.stderr.write('Invalid constraint definition found for ' + star.Name) | ||||
|             sys.exit(-1) | ||||
|  | ||||
|         # calculate descend profile | ||||
|         currentHeading = Waypoint(latitude = self.Inbound.Report.position.latitude, longitude = self.Inbound.Report.position.longitude).bearing(star.Route[0]) | ||||
|         currentIAS = self.Inbound.PerformanceData.ias(self.Inbound.Report.dynamics.altitude, trackmiles) | ||||
|         currentPosition = [ self.Inbound.Report.dynamics.altitude, self.Inbound.Report.dynamics.groundSpeed ] | ||||
|         distanceToWaypoint = self.PredictedDistanceToIAF | ||||
|         flightTimeSeconds = 0 | ||||
|         nextWaypointIndex = 0 | ||||
|         flownDistance = 0.0 | ||||
|         arrivalRoute = [ ArrivalWaypoint(waypoint = star.Route[0], trackmiles = distanceToWaypoint) ] | ||||
|  | ||||
|         while True: | ||||
|             # check if a constraint cleanup is needed and if a speed-update is needed | ||||
|             if 0 != len(constraints) and flownDistance >= constraints[0][0]: | ||||
|                 if -1 != constraints[0][2]: | ||||
|                     currentIAS = min(constraints[0][2], self.Inbound.PerformanceData.ias(self.Inbound.Report.dynamics.altitude, trackmiles - flownDistance)) | ||||
|                     currentPosition[1] = min(weather.calculateGS(currentPosition[0], currentIAS, currentHeading), currentPosition[1]) | ||||
|                 constraints.pop(0) | ||||
|  | ||||
|             # search next altitude constraint | ||||
|             altitudeDistance = 0 | ||||
|             nextAltitude = 0 | ||||
|             for constraint in constraints: | ||||
|                 if -1 != constraint[1]: | ||||
|                     altitudeDistance = constraint[0] | ||||
|                     nextAltitude = constraint[1] | ||||
|                     break | ||||
|  | ||||
|             # check if update of altitude and speed is needed on 3° glide | ||||
|             if currentPosition[0] > nextAltitude and ((currentPosition[0] - nextAltitude) / 1000 * 3) > (altitudeDistance - flownDistance): | ||||
|                 oldGroundspeed = currentPosition[1] | ||||
|                 descendRate = (currentPosition[1] / 60) / 3 * 1000 / 6 | ||||
|                 newAltitude = currentPosition[0] - descendRate | ||||
|                 if 0 > newAltitude: | ||||
|                     newAltitude = 0 | ||||
|  | ||||
|                 currentPosition = [ newAltitude, min(weather.calculateGS(newAltitude, currentIAS, currentHeading), currentPosition[1]) ] | ||||
|                 distance = (currentPosition[1] + oldGroundspeed) / 2 / 60 / 6 | ||||
|             else: | ||||
|                 distance = currentPosition[1] / 60 / 6 | ||||
|  | ||||
|             # update the statistics | ||||
|             distanceToWaypoint -= distance | ||||
|             flownDistance += distance | ||||
|             newIAS = min(currentIAS, self.Inbound.PerformanceData.ias(currentPosition[0], trackmiles - flownDistance)) | ||||
|             if newIAS < currentIAS: | ||||
|                 currentPosition[1] = min(weather.calculateGS(currentPosition[0], newIAS, currentHeading), currentPosition[1]) | ||||
|                 currentIAS = newIAS | ||||
|  | ||||
|             flightTimeSeconds += 10 | ||||
|             if flownDistance >= trackmiles: | ||||
|                 break | ||||
|  | ||||
|             # check if we follow a new waypoint pair | ||||
|             if 0 >= distanceToWaypoint: | ||||
|                 lastWaypointIndex = nextWaypointIndex | ||||
|                 nextWaypointIndex += 1 | ||||
|  | ||||
|                 arrivalRoute[-1].FlightTime = timedelta(seconds = flightTimeSeconds) | ||||
|                 arrivalRoute[-1].ETA = self.Inbound.ReportTime + arrivalRoute[-1].FlightTime | ||||
|                 arrivalRoute[-1].PTA = arrivalRoute[-1].ETA | ||||
|                 arrivalRoute[-1].Altitude = currentPosition[0] | ||||
|                 arrivalRoute[-1].IndicatedAirspeed = currentIAS | ||||
|                 arrivalRoute[-1].GroundSpeed = currentPosition[1] | ||||
|  | ||||
|                 # check if a skip from base to final turn waypoints is needed | ||||
|                 if -1 != turnIndices[0] and nextWaypointIndex > turnIndices[0] and nextWaypointIndex < turnIndices[1]: | ||||
|                     nextWaypointIndex = turnIndices[1] | ||||
|  | ||||
|                 # update the statistics | ||||
|                 if nextWaypointIndex < len(star.Route): | ||||
|                     distanceToWaypoint = star.Route[lastWaypointIndex].haversine(star.Route[nextWaypointIndex]) * 0.539957 | ||||
|                     currentHeading = star.Route[lastWaypointIndex].bearing(star.Route[nextWaypointIndex]) | ||||
|                     currentPosition[1] = min(weather.calculateGS(currentPosition[0], currentIAS, currentHeading), currentPosition[1]) | ||||
|  | ||||
|                     arrivalRoute.append(ArrivalWaypoint(waypoint = star.Route[nextWaypointIndex], trackmiles = arrivalRoute[-1].Trackmiles + distanceToWaypoint)) | ||||
|  | ||||
|         return timedelta(seconds = flightTimeSeconds), trackmiles, arrivalRoute | ||||
|  | ||||
|     def __init__(self, inbound : Inbound, referenceTime : datetime, weatherModel : WeatherModel, | ||||
|                  navData : SctEseFormat, sequencingConfig : AirportSequencing): | ||||
|         self.PredictedDistanceToIAF = inbound.Report.distanceToIAF | ||||
|         self.ArrivalCandidates = {} | ||||
|         self.Inbound = inbound | ||||
|  | ||||
|         # predict the distance to IAF | ||||
|         timePrediction = (referenceTime - inbound.ReportTime).total_seconds() | ||||
|         if 0 != timePrediction and 0 != len(sequencingConfig.ActiveArrivalRunways): | ||||
|             # calculate current motion information | ||||
|             course = weatherModel.estimateCourse(inbound.Report.dynamics.altitude, inbound.Report.dynamics.groundSpeed, inbound.Report.dynamics.heading) | ||||
|             tempWaypoint = Waypoint(longitude = inbound.CurrentPosition.longitude, latitude = inbound.CurrentPosition.latitude) | ||||
|             gs = inbound.Report.dynamics.groundSpeed * 0.514444 # ground speed in m/s | ||||
|             distance = gs * timePrediction * 0.000539957 # distance back to nm | ||||
|  | ||||
|             # calculate the bearing between the current position and the IAF | ||||
|             star = Node.findArrivalRoute(inbound.Report.initialApproachFix, sequencingConfig.ActiveArrivalRunways[0].Runway, navData) | ||||
|             if None != star: | ||||
|                 bearing = tempWaypoint.bearing(star.Route[0]) | ||||
|             else: | ||||
|                 bearing = inbound.Report.dynamics.heading | ||||
|  | ||||
|             # calculate the distance based on the flown distance and update the predicted distance | ||||
|             self.PredictedDistanceToIAF -= math.cos(math.radians(bearing - course)) * distance | ||||
|             if 0.0 > self.PredictedDistanceToIAF: | ||||
|                 self.PredictedDistanceToIAF = 0.0 | ||||
|  | ||||
|         # calculate the timings for the different arrival runways | ||||
|         for identifier in sequencingConfig.ActiveArrivalRunways: | ||||
|             star = Node.findArrivalRoute(self.Inbound.Report.initialApproachFix, identifier.Runway, navData) | ||||
|  | ||||
|             if None != star: | ||||
|                 flightTime, trackmiles, arrivalRoute = self.arrivalEstimation(identifier.Runway, star, weatherModel) | ||||
|  | ||||
|                 avgSpeed = trackmiles / (float(flightTime.seconds) / 3600.0) | ||||
|                 # the closer we get to the IAF the less time delta can be achieved by short cuts, delay vectors or speeds | ||||
|                 ratio = min(2.0, max(0.0, self.PredictedDistanceToIAF / (trackmiles - self.PredictedDistanceToIAF))) | ||||
|                 possibleTimeDelta = (trackmiles / (avgSpeed * 0.9)) * 60 | ||||
|                 ttg = timedelta(minutes = (possibleTimeDelta - flightTime.total_seconds() / 60) * ratio) | ||||
|                 ttl = timedelta(minutes = (possibleTimeDelta - flightTime.total_seconds() / 60)) | ||||
|                 ita = self.Inbound.ReportTime + flightTime | ||||
|                 earliest = ita - ttg | ||||
|                 latest = ita + ttl | ||||
|  | ||||
|                 self.ArrivalCandidates[identifier.Runway.Name] = ArrivalData(ttg = ttg, star = star, ita = ita, earliest = earliest, | ||||
|                                                                              ttl = ttl, latest = latest, route = arrivalRoute, | ||||
|                                                                              trackmiles = trackmiles) | ||||
|  | ||||
|                 if None == self.Inbound.InitialArrivalTime: | ||||
|                     self.Inbound.InitialArrivalTime = self.ArrivalCandidates[identifier.Runway.Name].InitialArrivalTime | ||||
| @@ -4,7 +4,7 @@ from datetime import datetime, timedelta | ||||
|  | ||||
| from aman.sys.aco.Configuration import Configuration | ||||
| from aman.sys.aco.Constraints import SpacingConstraints | ||||
| from aman.types.Inbound import Inbound | ||||
| from aman.sys.aco.Node import Node | ||||
|  | ||||
| class RunwayManager: | ||||
|     def __init__(self, configuration : Configuration): | ||||
| @@ -20,33 +20,33 @@ class RunwayManager: | ||||
|             if not runway.Runway.Name in self.RunwayInbounds: | ||||
|                 self.RunwayInbounds[runway.Runway.Name] = None | ||||
|  | ||||
|     def calculateEarliestArrivalTime(self, runway : str, inbound : Inbound, useETA : bool, earliestArrivalTime : datetime): | ||||
|     def calculateEarliestArrivalTime(self, runway : str, node : Node, useETA : bool, earliestArrivalTime : datetime): | ||||
|         constrainedETA = None | ||||
|  | ||||
|         if None != self.RunwayInbounds[runway]: | ||||
|             # get the WTC based ETA | ||||
|             if None == self.RunwayInbounds[runway].WTC or None == inbound.WTC: | ||||
|             if None == self.RunwayInbounds[runway].Inbound.WTC or None == node.Inbound.WTC: | ||||
|                 spacingWTC = 3 | ||||
|             else: | ||||
|                 spacingWTC = self.Spacings[self.RunwayInbounds[runway].WTC][inbound.WTC] | ||||
|                 spacingWTC = self.Spacings[self.RunwayInbounds[runway].Inbound.WTC][node.Inbound.WTC] | ||||
|  | ||||
|             # get the runway time spacing | ||||
|             spacingRunway = self.Configuration.RunwayConstraints.findRunway(runway).Spacing | ||||
|             constrainedETA = self.RunwayInbounds[runway].PlannedArrivalTime + timedelta(minutes = max(spacingWTC, spacingRunway) / (inbound.PerformanceData.SpeedApproach / 60)) | ||||
|             constrainedETA = self.RunwayInbounds[runway].Inbound.PlannedArrivalTime + timedelta(minutes = max(spacingWTC, spacingRunway) / (node.Inbound.PerformanceData.SpeedApproach / 60)) | ||||
|  | ||||
|         # calculate the arrival times for the dependent inbounds | ||||
|         for dependentRunway in self.Configuration.RunwayConstraints.findDependentRunways(runway): | ||||
|             if None != self.RunwayInbounds[dependentRunway.Runway.Name]: | ||||
|                 # TODO staggered spacing variabel | ||||
|                 candidate = self.RunwayInbounds[dependentRunway.Runway.Name].PlannedArrivalTime + timedelta(minutes = 3 / (inbound.PerformanceData.SpeedApproach / 60)) | ||||
|                 candidate = self.RunwayInbounds[dependentRunway.Runway.Name].Inbound.PlannedArrivalTime + timedelta(minutes = 3 / (node.Inbound.PerformanceData.SpeedApproach / 60)) | ||||
|                 if None == constrainedETA or candidate > constrainedETA: | ||||
|                     constrainedETA = candidate | ||||
|  | ||||
|         # get the arrival time on the runway of the inbound | ||||
|         if True == useETA: | ||||
|             arrivalTime = inbound.ArrivalCandidates[runway].EarliestArrivalTime | ||||
|             arrivalTime = node.ArrivalCandidates[runway].EarliestArrivalTime | ||||
|         else: | ||||
|             arrivalTime = inbound.ArrivalCandidates[runway].InitialArrivalTime | ||||
|             arrivalTime = node.ArrivalCandidates[runway].InitialArrivalTime | ||||
|  | ||||
|         if None == constrainedETA: | ||||
|             return max(arrivalTime, earliestArrivalTime), timedelta(seconds = 0) | ||||
| @@ -57,7 +57,7 @@ class RunwayManager: | ||||
|             else: | ||||
|                 return eta, timedelta(seconds = 0) | ||||
|  | ||||
|     def selectArrivalRunway(self, inbound : Inbound, useETA : bool, earliestArrivalTime : datetime): | ||||
|     def selectArrivalRunway(self, node : Node, useETA : bool, earliestArrivalTime : datetime): | ||||
|         availableRunways = self.Configuration.RunwayConstraints.ActiveArrivalRunways | ||||
|  | ||||
|         #if 1 < len(availableRunways): | ||||
| @@ -68,7 +68,7 @@ class RunwayManager: | ||||
|         # fallback to check if we have available runways | ||||
|         if 0 == len(availableRunways): | ||||
|             runway = self.Configuration.RunwayConstraints.ActiveArrivalRunways[0] | ||||
|             return runway, self.calculateEarliestArrivalTime(runway.Runway.Name, inbound, useETA, earliestArrivalTime) | ||||
|             return runway, self.calculateEarliestArrivalTime(runway.Runway.Name, node, useETA, earliestArrivalTime) | ||||
|  | ||||
|         # start with the beginning | ||||
|         selectedRunway = None | ||||
| @@ -77,7 +77,7 @@ class RunwayManager: | ||||
|  | ||||
|         # get the runway with the earliest ETA | ||||
|         for runway in availableRunways: | ||||
|             candidate, delta = self.calculateEarliestArrivalTime(runway.Runway.Name, inbound, useETA, earliestArrivalTime) | ||||
|             candidate, delta = self.calculateEarliestArrivalTime(runway.Runway.Name, node, useETA, earliestArrivalTime) | ||||
|             if None == eta or eta > candidate: | ||||
|                 selectedRunway = runway.Runway | ||||
|                 lostTime = delta | ||||
|   | ||||
		Reference in New Issue
	
	Block a user