From 9d69a6039658d71133d488f1c73804161400bb5f Mon Sep 17 00:00:00 2001 From: Sven Czarnian Date: Tue, 12 Oct 2021 22:30:20 +0200 Subject: [PATCH] introduce classes for the ACO algorithm --- aman/sys/aco/Ant.py | 7 +++ aman/sys/aco/Colony.py | 12 +++++ aman/sys/aco/Configuration.py | 15 ++++++ aman/sys/aco/Constraints.py | 26 ++++++++++ aman/sys/aco/CostFunction.py | 96 +++++++++++++++++++++++++++++++++++ aman/sys/aco/__init__.py | 0 setup.py | 1 + 7 files changed, 157 insertions(+) create mode 100644 aman/sys/aco/Ant.py create mode 100644 aman/sys/aco/Colony.py create mode 100644 aman/sys/aco/Configuration.py create mode 100644 aman/sys/aco/Constraints.py create mode 100644 aman/sys/aco/CostFunction.py create mode 100644 aman/sys/aco/__init__.py diff --git a/aman/sys/aco/Ant.py b/aman/sys/aco/Ant.py new file mode 100644 index 0000000..807ebbf --- /dev/null +++ b/aman/sys/aco/Ant.py @@ -0,0 +1,7 @@ +#!/usr/bin/env python + +from aman.sys.aco.Colony import Colony + +class Ant: + def __init__(self, colony : Colony): + self.Colony = colony \ No newline at end of file diff --git a/aman/sys/aco/Colony.py b/aman/sys/aco/Colony.py new file mode 100644 index 0000000..8676439 --- /dev/null +++ b/aman/sys/aco/Colony.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python + +import numpy as np + +from aman.sys.aco.Ant import Ant +from aman.sys.aco.Configuration import Configuration +from aman.sys.aco.CostFunction import CostFunction + +class Colony: + def __init__(self, configuration : Configuration): + self.Configuration = configuration + self.PheromoneMatrix = np.zeros(( len(configuration.Inbounds), len(configuration.Inbounds) ), dtype=float) \ No newline at end of file diff --git a/aman/sys/aco/Configuration.py b/aman/sys/aco/Configuration.py new file mode 100644 index 0000000..2087be5 --- /dev/null +++ b/aman/sys/aco/Configuration.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python + +from aman.config.AirportSequencing import AirportSequencing + +class Configuration: + def __init__(self, runwayInfo : AirportSequencing, antCount : int, explorationCount : int): + # the AMAN specific information + self.RunwayConstraints = runwayInfo + self.PreceedingInbounds = None + self.Inbounds = None + + # the ACO specific information + self.AntCount = antCount + self.ExplorationRuns = explorationCount + self.PheromoneEvaporationRate = 0.9 \ No newline at end of file diff --git a/aman/sys/aco/Constraints.py b/aman/sys/aco/Constraints.py new file mode 100644 index 0000000..6b87dbb --- /dev/null +++ b/aman/sys/aco/Constraints.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python + +class SpacingConstraints: + def __init__(self): + self.WtcSpacing = {} + self.WtcSpacing['L'] = {} + self.WtcSpacing['M'] = {} + self.WtcSpacing['H'] = {} + self.WtcSpacing['J'] = {} + + self.WtcSpacing['L']['L'] = 3 + self.WtcSpacing['L']['M'] = 3 + self.WtcSpacing['L']['S'] = 3 + self.WtcSpacing['L']['J'] = 3 + self.WtcSpacing['M']['L'] = 5 + self.WtcSpacing['M']['M'] = 3 + self.WtcSpacing['M']['S'] = 3 + self.WtcSpacing['M']['J'] = 3 + self.WtcSpacing['H']['L'] = 6 + self.WtcSpacing['H']['M'] = 5 + self.WtcSpacing['H']['S'] = 4 + self.WtcSpacing['H']['J'] = 4 + self.WtcSpacing['J']['L'] = 8 + self.WtcSpacing['J']['M'] = 7 + self.WtcSpacing['J']['S'] = 6 + self.WtcSpacing['J']['J'] = 6 \ No newline at end of file diff --git a/aman/sys/aco/CostFunction.py b/aman/sys/aco/CostFunction.py new file mode 100644 index 0000000..9a88c8c --- /dev/null +++ b/aman/sys/aco/CostFunction.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python + +from datetime import timedelta + +from aman.sys.aco.Colony import Colony +from aman.sys.aco.Constraints import SpacingConstraints +from aman.types.Inbound import Inbound + +class CostFunction: + def __init__(self, colony : Colony): + self.Spacings = SpacingConstraints() + self.Colony = colony + + # initialize the tracker which inbound arrives at which runway + self.RunwayInbounds = {} + for runway in self.Colony.Configuration.PreceedingInbounds: + self.RunwayInbounds[runway] = self.Colony.Configuration.PreceedingInbounds[runway] + for runway in self.Colony.Configuration.RunwayConstraints.ActiveArrivalRunways: + if not runway in self.RunwayInbounds: + self.RunwayInbounds[runway] = None + + return + + def validateWtc(inbound : Inbound): + wtc = inbound.Report.aircraft.wtc.upper() + if 'L' == wtc or 'M' == wtc or 'H' == None or 'J' == None: + return wtc + else: + return None + + def calculateEta(self, runway : str, inbound : Inbound): + if None == self.RunwayInbounds[runway]: + return inbound.EstimatedArrivalTime + + preceedingInbound = self.RunwayInbounds[runway] + + # get the WTC constrained ETA + wtcPre = CostFunction.validateWtc(preceedingInbound) + wtcThis = CostFunction.validateWtc(inbound) + if None == wtcPre or None == wtcThis: + spacing = 3 + else: + spacing = self.Spacings[wtcPre][wtcThis] + delay = timedelta(seconds = round(spacing / inbound.PerformanceData.speedApproach + 0.5)) + wtcETA = self.RunwayInbounds[runway].EstimatedArrivalTime + delay + + # get the staggered time spacing + dependentRunway = self.Colony.Configuration.RunwayConstraints.findDependentRunway(runway) + if None != dependentRunway and None != self.RunwayInbounds[dependentRunway.Runway.name]: + if preceedingInbound.EstimatedArrivalTime < self.RunwayInbounds[dependentRunway.Runway.name].EstimatedArrivalTime: + delay = timedelta(seconds = round(3 / inbound.PerformanceData.speedApproach + 0.5)) + staggeredETA = self.RunwayInbounds[dependentRunway.Runway.name].EstimatedArrivalTime + delay + else: + staggeredETA = wtcETA + else: + staggeredETA = wtcETA + + # get the runway time spacing + spacing = self.Colony.Configuration.RunwayConstraints.findRunway(runway).Spacing + delay = timedelta(seconds = round(spacing / inbound.PerformanceData.speedApproach + 0.5)) + runwayETA = self.RunwayInbounds[runway].EstimatedArrivalTime + delay + + # get the biggest ETA to define the maximum but ensure that we are not earlier than the ITA + # TODO model the TTG-concept depending on the distance to the IAF + candidate = max(max(wtcETA, staggeredETA), runwayETA) + if candidate < inbound.InitialArrivalTime: + return inbound.InitialArrivalTime + else: + return candidate + + def selectArrivalRunway(self, inbound : Inbound): + availableRunways = [] + for runway in self.Colony.Configuration.RunwayConstraints.ActiveArrivalRunways: + availableRunways.append(runway) + + #if 1 < len(availableRunways): + # TODO filter based on type + # TODO filter based on airline + # ensure that at least one runway is available + + # fallback to check if we have available runways + if 0 == len(availableRunways): + return self.Colony.Configuration.RunwayConstraints.ActiveArrivalRunways[0] + + # start with the beginning + selectedRunway = self.Colony.Configuration.RunwayConstraints.findRunway(availableRunways[0]) + eta = self.calculateEta(availableRunways[0], inbound) + + # get the runway with the earliest ETA + for runway in availableRunways: + candidate = self.calculateEta(runway, inbound) + if eta > candidate: + selectedRunway = self.Colony.Configuration.RunwayConstraints.findRunway(runway) + eta = candidate + + return selectedRunway diff --git a/aman/sys/aco/__init__.py b/aman/sys/aco/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/setup.py b/setup.py index 24d2acc..4356fc1 100644 --- a/setup.py +++ b/setup.py @@ -77,6 +77,7 @@ setup( 'aman.config', 'aman.formats', 'aman.sys', + 'aman.sys.aco', 'aman.tools', 'aman.types' ],