use the shortcut-path as the ITA for the optimization, but use the full path as the ITA itself
This commit is contained in:
		| @@ -24,55 +24,35 @@ class Ant: | ||||
|         self.SequenceDelay = timedelta(seconds = 0) | ||||
|         self.Sequence = None | ||||
|  | ||||
|     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() / 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: | ||||
|     # An adaption of the heuristic function is used: | ||||
|     #   - Calculates the unused runway time (time between two consecutive landings) | ||||
|     #   - 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.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) | ||||
|     # Implements function (5) | ||||
|     def heuristicInformation(self, current : int): | ||||
|         _, eta, _ = self.RunwayManager.selectArrivalRunway(self.Nodes[current], self.Configuration.EarliestArrivalTime) | ||||
|         inboundDelay = eta - self.Nodes[current].Inbound.InitialArrivalTime | ||||
|  | ||||
|         # calculate the fraction with a mix of the unused runway time and the delay of single aircrafts | ||||
|         fraction = self.Configuration.RunwayOccupasionRatio * unusedRunwayTime.total_seconds() | ||||
|         fraction += (1.0 - self.Configuration.RunwayOccupasionRatio) * inboundDelay.total_seconds() | ||||
|         fraction += self.SequenceDelay.total_seconds() | ||||
|         fraction /= 60.0 | ||||
|  | ||||
|         # calculate the heuristic scaling to punish increased delays for single inbounds | ||||
|         weight = Ant.qualifyDelay(inboundDelay, self.Nodes[current], rwy) | ||||
|  | ||||
|         return weight * self.PheromoneMatrix[preceeding, current] * ((1.0 / (fraction or 1)) ** self.Configuration.Beta) | ||||
|         heuristic = inboundDelay.total_seconds() / 60.0 | ||||
|         heuristic = (1.0 / (heuristic or 1)) ** self.Configuration.Beta | ||||
|         return heuristic | ||||
|  | ||||
|     # Implements functions (3), (6) | ||||
|     def selectNextLandingIndex(self, preceedingIndex : int): | ||||
|     def selectNextLandingIndex(self): | ||||
|         q = float(random.randint(0, 100)) / 100 | ||||
|         weights = [] | ||||
|  | ||||
|         if q <= self.Configuration.PseudoRandomSelectionRate: | ||||
|             for i in range(0, len(self.InboundSelected)): | ||||
|                 if False == self.InboundSelected[i]: | ||||
|                     weights.append(self.heuristicInformation(preceedingIndex, i)) | ||||
|                     weights.append(self.heuristicInformation(i)) | ||||
|         else: | ||||
|             # roulette selection strategy | ||||
|             pheromoneScale = 0.0 | ||||
|             for i in range(0, len(self.InboundSelected)): | ||||
|                 if False == self.InboundSelected[i]: | ||||
|                     pheromoneScale += self.heuristicInformation(preceedingIndex, i) | ||||
|                     pheromoneScale += self.heuristicInformation(i) | ||||
|  | ||||
|             for i in range(0, len(self.InboundSelected)): | ||||
|                 if False == self.InboundSelected[i]: | ||||
|                     weights.append(self.heuristicInformation(preceedingIndex, i) / (pheromoneScale or 1)) | ||||
|                     weights.append(self.heuristicInformation(i) / (pheromoneScale or 1)) | ||||
|  | ||||
|         total = sum(weights) | ||||
|         cumdist = list(itertools.accumulate(weights)) + [total] | ||||
| @@ -89,7 +69,7 @@ class Ant: | ||||
|  | ||||
|     def associateInbound(self, node : Node, earliestArrivalTime : datetime): | ||||
|         # prepare the statistics | ||||
|         rwy, eta, _ = self.RunwayManager.selectArrivalRunway(node, True, self.Configuration.EarliestArrivalTime) | ||||
|         rwy, eta, _ = self.RunwayManager.selectArrivalRunway(node, self.Configuration.EarliestArrivalTime) | ||||
|         eta = max(earliestArrivalTime, eta) | ||||
|  | ||||
|         node.Inbound.PlannedRunway = rwy | ||||
| @@ -110,20 +90,18 @@ class Ant: | ||||
|  | ||||
|         # select the first inbound | ||||
|         self.InboundSelected[first] = True | ||||
|         delay, rwy = self.associateInbound(self.Nodes[first], self.Configuration.EarliestArrivalTime) | ||||
|         self.InboundScore[0] = Ant.qualifyDelay(delay, self.Nodes[first], rwy) | ||||
|         delay, _ = self.associateInbound(self.Nodes[first], self.Configuration.EarliestArrivalTime) | ||||
|         self.Sequence.append(first) | ||||
|         self.SequenceDelay += delay | ||||
|  | ||||
|         while 1: | ||||
|             index = self.selectNextLandingIndex(self.Sequence[-1]) | ||||
|             index = self.selectNextLandingIndex() | ||||
|             if None == index: | ||||
|                 break | ||||
|  | ||||
|             self.InboundSelected[index] = True | ||||
|             delay, rwy = self.associateInbound(self.Nodes[index], self.Configuration.EarliestArrivalTime) | ||||
|             delay, _ = self.associateInbound(self.Nodes[index], self.Configuration.EarliestArrivalTime) | ||||
|             self.SequenceDelay += delay | ||||
|             self.InboundScore[len(self.Sequence)] = Ant.qualifyDelay(delay, self.Nodes[index], rwy) | ||||
|             self.Sequence.append(index) | ||||
|  | ||||
|             # update the local pheromone | ||||
| @@ -134,7 +112,4 @@ class Ant: | ||||
|         # validate that nothing went wrong | ||||
|         if len(self.Sequence) != len(self.Nodes): | ||||
|             self.SequenceDelay = None | ||||
|             self.SequenceScore = None | ||||
|             self.Sequence = None | ||||
|         else: | ||||
|             self.SequenceScore = np.median(self.InboundScore) | ||||
|   | ||||
| @@ -17,8 +17,8 @@ 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, node : Node, earliestArrivalTime : datetime, useITA : bool): | ||||
|         rwy, eta, _ = rwyManager.selectArrivalRunway(node, useITA, earliestArrivalTime) | ||||
|     def associateInbound(rwyManager : RunwayManager, node : Node, earliestArrivalTime : datetime): | ||||
|         rwy, eta, _ = rwyManager.selectArrivalRunway(node, earliestArrivalTime) | ||||
|         eta = max(earliestArrivalTime, eta) | ||||
|  | ||||
|         node.Inbound.PlannedRunway = rwy | ||||
| @@ -34,7 +34,7 @@ class Colony: | ||||
|  | ||||
|         # assume that the nodes are sorted in FCFS order | ||||
|         for node in nodes: | ||||
|             Colony.associateInbound(rwyManager, node, earliestArrivalTime, False) | ||||
|             Colony.associateInbound(rwyManager, node, earliestArrivalTime) | ||||
|             overallDelay += node.Inbound.PlannedArrivalTime - node.Inbound.InitialArrivalTime | ||||
|  | ||||
|         return overallDelay | ||||
| @@ -62,6 +62,17 @@ class Colony: | ||||
|         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 sequenceAndPredictInbound(self, rwyManager : RunwayManager, node : Node): | ||||
|         self.Result.append(node) | ||||
|         Colony.associateInbound(rwyManager, node, self.Configuration.EarliestArrivalTime) | ||||
|  | ||||
|         reqTimeDelta = self.Result[-1].Inbound.InitialArrivalTime - self.Result[-1].Inbound.PlannedArrivalTime | ||||
|         self.Result[-1].Inbound.PlannedArrivalRoute[0].PTA = self.Result[-1].Inbound.PlannedArrivalRoute[0].ETA - reqTimeDelta | ||||
|         for i in range(1, len(self.Result[-1].Inbound.PlannedArrivalRoute)): | ||||
|             prev = self.Result[-1].Inbound.PlannedArrivalRoute[i - 1] | ||||
|             current = self.Result[-1].Inbound.PlannedArrivalRoute[i] | ||||
|             current.PTA = prev.PTA + (current.ETA - prev.ETA) | ||||
|  | ||||
|     def optimize(self): | ||||
|         # FCFS is the best solution | ||||
|         if None != self.Result: | ||||
| @@ -82,23 +93,21 @@ class Colony: | ||||
|                 ant.findSolution(index) | ||||
|  | ||||
|                 # fallback to check if findSolution was successful | ||||
|                 if None == ant.SequenceDelay or None == ant.Sequence or None == ant.SequenceScore: | ||||
|                 if None == ant.SequenceDelay or None == ant.Sequence: | ||||
|                     sys.stderr.write('Invalid ANT run detected!') | ||||
|                     sys.exit(-1) | ||||
|  | ||||
|                 candidates.append( | ||||
|                     [ | ||||
|                         ant.SequenceDelay, | ||||
|                         ant.Sequence, | ||||
|                         ant.SequenceScore, | ||||
|                         ant.SequenceDelay.total_seconds() / ant.SequenceScore | ||||
|                         ant.Sequence | ||||
|                     ] | ||||
|                 ) | ||||
|  | ||||
|             # find the best solution in all candidates of this generation | ||||
|             bestCandidate = None | ||||
|             for candidate in candidates: | ||||
|                 if None == bestCandidate or candidate[3] < bestCandidate[3]: | ||||
|                 if None == bestCandidate or candidate[0] < bestCandidate[0]: | ||||
|                     bestCandidate = candidate | ||||
|  | ||||
|                 dTheta = 1.0 / ((candidate[0].total_seconds() / 60.0) or 1.0) | ||||
| @@ -112,20 +121,19 @@ class Colony: | ||||
|                     bestSequence = bestCandidate | ||||
|  | ||||
|         # create the final sequence | ||||
|         if None != bestSequence: | ||||
|         self.Result = [] | ||||
|         rwyManager = RunwayManager(self.Configuration) | ||||
|  | ||||
|         # use the optimized sequence | ||||
|         if None != bestSequence and self.FcfsDelay >= bestSequence[0]: | ||||
|             # create the resulting sequence | ||||
|             self.ResultDelay = bestSequence[0] | ||||
|             self.Result = [] | ||||
|  | ||||
|             # finalize the sequence | ||||
|             rwyManager = RunwayManager(self.Configuration) | ||||
|             for i in range(0, len(bestSequence[1])): | ||||
|                 self.Result.append(self.Nodes[bestSequence[1][i]]) | ||||
|                 Colony.associateInbound(rwyManager, self.Nodes[bestSequence[1][i]], self.Configuration.EarliestArrivalTime, True) | ||||
|  | ||||
|                 reqTimeDelta = self.Result[-1].Inbound.InitialArrivalTime - self.Result[-1].Inbound.PlannedArrivalTime | ||||
|                 self.Result[-1].Inbound.PlannedArrivalRoute[0].PTA = self.Result[-1].Inbound.PlannedArrivalRoute[0].ETA - reqTimeDelta | ||||
|                 for i in range(1, len(self.Result[-1].Inbound.PlannedArrivalRoute)): | ||||
|                     prev = self.Result[-1].Inbound.PlannedArrivalRoute[i - 1] | ||||
|                     current = self.Result[-1].Inbound.PlannedArrivalRoute[i] | ||||
|                     current.PTA = prev.PTA + (current.ETA - prev.ETA) | ||||
|             for idx in bestSequence[1]: | ||||
|                 self.sequenceAndPredictInbound(rwyManager, self.Nodes[idx]) | ||||
|         # use the FCFS sequence | ||||
|         else: | ||||
|             self.ResultDelay = self.FcfsDelay | ||||
|             for node in self.Nodes: | ||||
|                 self.sequenceAndPredictInbound(node) | ||||
|   | ||||
| @@ -193,11 +193,6 @@ class Node: | ||||
|             if None != star: | ||||
|                 flightTime, trackmiles, arrivalRoute, flightTimeOnStar = self.arrivalEstimation(identifier.Runway, star, weatherModel) | ||||
|  | ||||
|                 # calculate average speed gain | ||||
|                 avgSpeed = trackmiles / (flightTime.total_seconds() / 3600.0) | ||||
|                 avgSpeedDecrease = avgSpeed * 0.80 | ||||
|                 decreasedSpeedFlighttime = (trackmiles / avgSpeedDecrease) * 3600.0 # given in seconds | ||||
|  | ||||
|                 # use the the distance to the IAF for optimizations | ||||
|                 timeUntilIAF = flightTime - flightTimeOnStar | ||||
|                 if 0.0 > timeUntilIAF.total_seconds(): | ||||
| @@ -213,13 +208,10 @@ class Node: | ||||
|                 ttg = timedelta(seconds = timeUntilIAF.total_seconds() * ttgRatio) | ||||
|                 if (ttg.total_seconds() > ttgMax): | ||||
|                     ttg = timedelta(seconds = ttgMax) | ||||
|                 ttl = timedelta(seconds = decreasedSpeedFlighttime - flightTime.total_seconds()) | ||||
|                 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, | ||||
|                 self.ArrivalCandidates[identifier.Runway.Name] = ArrivalData(star = star, ita = earliest, route = arrivalRoute, | ||||
|                                                                              trackmiles = trackmiles) | ||||
|  | ||||
|                 ita = self.ArrivalCandidates[identifier.Runway.Name].InitialArrivalTime | ||||
|   | ||||
| @@ -21,7 +21,7 @@ class RunwayManager: | ||||
|             if not runway.Runway.Name in self.RunwayInbounds: | ||||
|                 self.RunwayInbounds[runway.Runway.Name] = None | ||||
|  | ||||
|     def calculateEarliestArrivalTime(self, runway : str, node : Node, useETA : bool, earliestArrivalTime : datetime): | ||||
|     def calculateEarliestArrivalTime(self, runway : str, node : Node, earliestArrivalTime : datetime): | ||||
|         constrainedETA = None | ||||
|  | ||||
|         if None != self.RunwayInbounds[runway]: | ||||
| @@ -42,40 +42,29 @@ class RunwayManager: | ||||
|                 if None == constrainedETA or candidate > constrainedETA: | ||||
|                     constrainedETA = candidate | ||||
|  | ||||
|         # get the arrival time on the runway of the inbound | ||||
|         if True == useETA: | ||||
|             arrivalTime = node.ArrivalCandidates[runway].EarliestArrivalTime | ||||
|         else: | ||||
|             arrivalTime = node.ArrivalCandidates[runway].InitialArrivalTime | ||||
|  | ||||
|         if None == constrainedETA: | ||||
|             eta = max(arrivalTime, earliestArrivalTime) | ||||
|             eta = max(node.ArrivalCandidates[runway].InitialArrivalTime, earliestArrivalTime) | ||||
|         else: | ||||
|             eta = max(arrivalTime, max(constrainedETA, earliestArrivalTime)) | ||||
|             eta = max(node.ArrivalCandidates[runway].InitialArrivalTime, max(constrainedETA, earliestArrivalTime)) | ||||
|  | ||||
|         return eta, eta - arrivalTime | ||||
|         return eta, eta - node.ArrivalCandidates[runway].InitialArrivalTime | ||||
|  | ||||
|     def selectShallShouldMayArrivalRunway(self, node : Node, runways, useETA : bool, earliestArrivalTime : datetime): | ||||
|     def selectShallShouldMayArrivalRunway(self, node : Node, runways, earliestArrivalTime : datetime): | ||||
|         candidate = None | ||||
|         delay = None | ||||
|  | ||||
|         for runway in runways: | ||||
|             if True == useETA: | ||||
|                 reference = node.ArrivalCandidates[runway.Runway.Name].EarliestArrivalTime | ||||
|             else: | ||||
|                 reference = node.ArrivalCandidates[runway.Runway.Name].InitialArrivalTime | ||||
|  | ||||
|             eta, _ = self.calculateEarliestArrivalTime(runway.Runway.Name, node, useETA, earliestArrivalTime) | ||||
|             eta, _ = self.calculateEarliestArrivalTime(runway.Runway.Name, node, earliestArrivalTime) | ||||
|             if None == delay: | ||||
|                 delay = eta - reference | ||||
|                 delay = eta - node.ArrivalCandidates[runway.Runway.Name].InitialArrivalTime | ||||
|                 candidate = runway | ||||
|             elif delay > (eta - reference): | ||||
|                 delay = eta- reference | ||||
|             elif delay > (eta - node.ArrivalCandidates[runway.Runway.Name].InitialArrivalTime): | ||||
|                 delay = eta- node.ArrivalCandidates[runway.Runway.Name].InitialArrivalTime | ||||
|                 candidate = runway | ||||
|  | ||||
|         return candidate | ||||
|  | ||||
|     def executeShallShouldMayAssignment(self, node : Node, useETA : bool, earliestArrivalTime : datetime): | ||||
|     def executeShallShouldMayAssignment(self, node : Node, earliestArrivalTime : datetime): | ||||
|         shallRunways = [] | ||||
|         shouldRunways = [] | ||||
|         mayRunways = [] | ||||
| @@ -97,46 +86,41 @@ class RunwayManager: | ||||
|                 if node.Inbound.Report.plannedGate in runway.ShouldAssignments[RunwayAssignmentType.GateAssignment]: | ||||
|                     shouldRunways.append(runway) | ||||
|  | ||||
|             if True == useETA: | ||||
|                 reference = node.ArrivalCandidates[runway.Runway.Name].EarliestArrivalTime | ||||
|             else: | ||||
|                 reference = node.ArrivalCandidates[runway.Runway.Name].InitialArrivalTime | ||||
|  | ||||
|             # test the may assignments | ||||
|             if RunwayAssignmentType.AircraftType in runway.MayAssignments: | ||||
|                 if node.Inbound.Report.aircraft.type in runway.MayAssignments[RunwayAssignmentType.AircraftType]: | ||||
|                     eta, _ = self.calculateEarliestArrivalTime(runway.Runway.Name, node, useETA, earliestArrivalTime) | ||||
|                     if (eta - reference) <= self.Configuration.AirportConfiguration.MaxDelayMay: | ||||
|                     eta, _ = self.calculateEarliestArrivalTime(runway.Runway.Name, node, earliestArrivalTime) | ||||
|                     if (eta - node.ArrivalCandidates[runway.Runway.Name].InitialArrivalTime) <= self.Configuration.AirportConfiguration.MaxDelayMay: | ||||
|                         mayRunways.append(runway) | ||||
|             if RunwayAssignmentType.GateAssignment in runway.MayAssignments: | ||||
|                 if node.Inbound.Report.plannedGate in runway.MayAssignments[RunwayAssignmentType.GateAssignment]: | ||||
|                     eta, _ = self.calculateEarliestArrivalTime(runway.Runway.Name, node, useETA, earliestArrivalTime) | ||||
|                     if (eta - reference) <= self.Configuration.AirportConfiguration.MaxDelayMay: | ||||
|                     eta, _ = self.calculateEarliestArrivalTime(runway.Runway.Name, node, earliestArrivalTime) | ||||
|                     if (eta - node.ArrivalCandidates[runway.Runway.Name].InitialArrivalTime) <= self.Configuration.AirportConfiguration.MaxDelayMay: | ||||
|                         mayRunways.append(runway) | ||||
|  | ||||
|         runway = self.selectShallShouldMayArrivalRunway(node, shallRunways, useETA, earliestArrivalTime) | ||||
|         runway = self.selectShallShouldMayArrivalRunway(node, shallRunways, earliestArrivalTime) | ||||
|         if None != runway: | ||||
|             return [ runway ] | ||||
|         runway = self.selectShallShouldMayArrivalRunway(node, shouldRunways, useETA, earliestArrivalTime) | ||||
|         runway = self.selectShallShouldMayArrivalRunway(node, shouldRunways, earliestArrivalTime) | ||||
|         if None != runway: | ||||
|             return [ runway ] | ||||
|         runway = self.selectShallShouldMayArrivalRunway(node, mayRunways, useETA, earliestArrivalTime) | ||||
|         runway = self.selectShallShouldMayArrivalRunway(node, mayRunways, earliestArrivalTime) | ||||
|         if None != runway: | ||||
|             return [ runway ] | ||||
|  | ||||
|         return self.Configuration.RunwayConstraints.ActiveArrivalRunways | ||||
|  | ||||
|     def selectArrivalRunway(self, node : Node, useETA : bool, earliestArrivalTime : datetime): | ||||
|     def selectArrivalRunway(self, node : Node, earliestArrivalTime : datetime): | ||||
|         availableRunways = self.Configuration.RunwayConstraints.ActiveArrivalRunways | ||||
|  | ||||
|         if True == self.Configuration.RunwayConstraints.UseShallShouldMay: | ||||
|             availableRunways = self.executeShallShouldMayAssignment(node, useETA, earliestArrivalTime) | ||||
|             availableRunways = self.executeShallShouldMayAssignment(node, earliestArrivalTime) | ||||
|         else: | ||||
|             availableRunways = self.Configuration.RunwayConstraints.ActiveArrivalRunways | ||||
|  | ||||
|         if 0 == len(availableRunways): | ||||
|             runway = self.Configuration.RunwayConstraints.ActiveArrivalRunways[0] | ||||
|             return runway, self.calculateEarliestArrivalTime(runway.Runway.Name, node, useETA, earliestArrivalTime) | ||||
|             return runway, self.calculateEarliestArrivalTime(runway.Runway.Name, node, earliestArrivalTime) | ||||
|  | ||||
|         # start with the beginning | ||||
|         selectedRunway = None | ||||
| @@ -145,7 +129,7 @@ class RunwayManager: | ||||
|  | ||||
|         # get the runway with the earliest ETA | ||||
|         for runway in availableRunways: | ||||
|             candidate, delta = self.calculateEarliestArrivalTime(runway.Runway.Name, node, useETA, earliestArrivalTime) | ||||
|             candidate, delta = self.calculateEarliestArrivalTime(runway.Runway.Name, node, earliestArrivalTime) | ||||
|             if None == eta or eta > candidate: | ||||
|                 selectedRunway = runway.Runway | ||||
|                 lostTime = delta | ||||
|   | ||||
		Reference in New Issue
	
	Block a user