Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simulator logic changes - practicum22 #33

Open
wants to merge 4 commits into
base: 21practicum
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,3 +198,13 @@ each line within the function.
### TODO

Right now, we have two types of airport surface models: node-link model and graph model. The graph model (in graph_model.py) is newly added and can be used by scheduler and controller. However, right now, this script generates an arrival and a departure model for one airport. Need to find a way to merge these two information so that scheduler can use to manage aircrafts' movements.


### Controller Descriptions
Ground Controller - can be found in controller.py. This controller plays an observatory role. For every aircraft that is on the ground, it finds the nearest aircraft that is on the same path( collection of links that are present in the itinerary and are yet to be covered). And adds this information to the aircraft object in its fronter info object.

Ramp Controller - Operates at spots. When an aircraft has its precise location close to a spot, its added to the spot queue. Every tick we iterate through each of spot queues and give the aircrafts a score. The aircraft with the highest score is given access to the spot. Aircrafts get a higher score when they have been waiting in the airport for too long, or are blocking a gate that is needed by an arriving aircraft.

Intersection Controller - Operates at intersections. When an aircraft has its precise location at points of intersection its added to the intersection queue. The Queue works in FCFS fashion. An aircraft makes use of look ahead distance to reserve links ahead of it. If an aircraft can reserve all links then it is given a lock on the intersection. Once it has crossed the intersection, the lock is released.

Terminal Controller - Good description of it can be found in terminal_controller.py. It essentially handles the bidirectional links near the gates. It ensures all departure flights leave the terminal area before arrival flights enter. Needs to be improved.
13 changes: 9 additions & 4 deletions aircraft.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"""
import enum
import logging
import random
import numpy
from surface import *

from link import HoldLink
Expand Down Expand Up @@ -128,6 +128,7 @@ def __init__(self, fullname, model, location, is_departure):
self.take_off = False

self.tick_count = 0
self.no_move_tick = 0
self.ramp_distance = -1
self.ramp_flag = 1
self.calculate_ramp_distance = 0
Expand Down Expand Up @@ -253,7 +254,7 @@ def get_next_speed(self, fronter_info, state):
return new_speed

# calculate the new speed when it is following another aircraft
fronter_speed = fronter_info[0]
fronter_speed = fronter_info[0]*0.85
relative_distance = fronter_info[1]

if fronter_speed <= 0:
Expand Down Expand Up @@ -356,7 +357,11 @@ def tick(self):
self.tick_count += 1
# print("AIR %s: aircraft tick.", self)
passed_links = self.itinerary.tick(self.tick_distance)
if Config.params["uncertainty"]["enabled"]:
sign = -1 if numpy.random.random() < 0.5 else 1
self.add_speed_uncertainty(numpy.random.random()*self.speed*sign/33)
new_speed = self.get_next_speed(self.fronter_info, self.state) + self.speed_uncertainty
new_speed = min(new_speed, self.MAX_SPEED)
self.set_speed(new_speed)
if self.itinerary.is_completed:
self.logger.debug("%s: %s completed.", self, self.itinerary)
Expand Down Expand Up @@ -466,7 +471,7 @@ def current_target(self):

def get_ahead_intersections_and_link(self):
"""get the intersections and future links with certain distance"""
ahead_distance = 800.0
ahead_distance = 400.0
return self.itinerary.get_ahead_intersections_and_link(ahead_distance)

def set_quiet(self, logger):
Expand All @@ -477,7 +482,7 @@ def __hash__(self):
return hash(self.callsign)

def __eq__(self, other):
return self.callsign == other.callsign
return hasattr(other, 'callsign') and self.callsign == other.callsign

def __ne__(self, other):
return not self == other
Expand Down
25 changes: 19 additions & 6 deletions airport.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,11 +128,10 @@ def __add_aircrafts_from_queue(self):

def __add_aircrafts_from_spot_queue(self):
for spot, queue in self.ramp_control.spot_queue.items():
if self.ramp_control.spot_occupied(spot):
if not queue:
continue

aircraft = queue.popleft()
gate = self.ramp_control.spot_gate_queue[spot].popleft()
aircraft, gate = self.ramp_control.remove_aircraft_from_spot_queue(spot)
#aircraft, gate = queue.popleft()
aircraft.set_location(gate, Aircraft.LOCATION_LEVEL_COARSE)
self.add_aircraft(aircraft)
self.terminal_controller.add_departure_gate(aircraft, gate.name)
Expand Down Expand Up @@ -164,7 +163,7 @@ def __add_aircrafts_from_scenario(self, scenario, now, sim_time, scheduler):
self.gate_queue[gate] = queue
self.logger.info("Adds %s into gate queue", flight)

elif self.ramp_control.spot_occupied(spot):
elif spot and self.is_occupied_at(spot):
# Add the flight to spot queue
self.ramp_control.add_aircraft_to_spot_queue(spot, gate, aircraft)
self.logger.info("Adds %s into spot queue", flight)
Expand Down Expand Up @@ -284,6 +283,7 @@ def __get_conflicts(self, is_next=False):
# Remove departed aircraft or
__conflicts = []
__conflicts_dist = []
__conflicts_aircrafts = []
aircraft_pairs = list(itertools.combinations(self.aircrafts, 2))
for pair in aircraft_pairs:
if pair[0] == pair[1]:
Expand All @@ -300,7 +300,8 @@ def __get_conflicts(self, is_next=False):

__conflicts.append(Conflict((loc1, loc2), pair))
__conflicts_dist.append(dist)
return __conflicts, __conflicts_dist
__conflicts_aircrafts.append((pair[0], pair[1]))
return __conflicts, __conflicts_dist, __conflicts_aircrafts

def __get_next_conflict(self):
__conflicts = []
Expand Down Expand Up @@ -368,11 +369,23 @@ def tick(self, predict=False):

for aircraft in self.aircrafts:
if not terminal_spot_access[aircraft]:
aircraft.tick_count += 1
aircraft.no_move_tick += 1
continue
if self.intersection_control.is_lock_by(aircraft) is True:
fronter_info = self.controller.find_aircraft_ahead(aircraft)
if fronter_info:
new_speed = aircraft.get_next_speed(fronter_info, aircraft.state)
aircraft.set_speed(new_speed)
aircraft.no_move_tick = 0
passed_links = aircraft.tick()
# passed.append(passed_links)
passed[aircraft] = passed_links
else:
aircraft.tick_count += 1
aircraft.no_move_tick += 1
aircraft.has_conflict = True if aircraft.no_move_tick >=30 else False
aircraft.set_speed(0)

# for passed_links in passed:
# passed_intersections = []
Expand Down
32 changes: 32 additions & 0 deletions controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,38 @@ def __find_aircraft_ahead(self, aircraft):

raise NoCloseAircraftFoundError

def find_aircraft_ahead(self, aircraft):
link_index, link_distance = aircraft.itinerary.current_target_index, aircraft.itinerary.current_distance

relative_distance = -link_distance
for index in range(link_index, aircraft.itinerary.length):
link = aircraft.itinerary.targets[index]

aircraft_on_same_link = self.aircraft_location_lookup.get(link, [])
rear_aircraft, rear_dist = None, -1
for item in aircraft_on_same_link:
item_aircraft, item_distance = item
# Skip if the item is behind the aircraft
if index == link_index and item_distance <= link_distance:
continue

# Found an aircraft ahead!
relative_distance += item_distance

if relative_distance < self.CLOSE_NODE_THRESHOLD:
self.conflicts.append((aircraft, item_aircraft))

if relative_distance > self.PILOT_VISION:
continue
if rear_aircraft is None:
rear_aircraft, rear_dist = item_aircraft, relative_distance
elif relative_distance < rear_dist:
rear_aircraft, rear_dist = item_aircraft, relative_distance
if rear_aircraft is not None:
return rear_aircraft.speed, rear_dist, rear_aircraft

relative_distance += link.length

# def __find_aircraft_ahead(self, aircraft):
# link_index, link_distance = aircraft.itinerary.current_target_index, aircraft.itinerary.current_distance

Expand Down
4 changes: 3 additions & 1 deletion figure/example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@ airport_model:

uncertainty:
# Enable or disable the uncertain in simulation
enabled: false
enabled: true
# Block a random link
block_link: false
# Probability of an aircraft holds at node unexpectedly (depends on time_unit)
prob_hold: 0.7
# Sigma of the normal distribution of the speed perturbation
Expand Down
32 changes: 29 additions & 3 deletions intersection_controller.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from aircraft import Aircraft
from config import Config
import copy

Expand Down Expand Up @@ -99,16 +100,41 @@ def lock_intersections(self, aircraft):
if len(self.intersection_lock_queue[actual_intersection]) == 0 or self.intersection_lock_queue[actual_intersection][-1][1] != aircraft:
self.intersection_lock_queue[actual_intersection].append((ahead_distance, aircraft))


def lock_intersections2(self, aircraft):
ahead_intersections, distances_to_intersections = aircraft.get_ahead_intersections_and_link()
# now the aircraft can pass, lock intersections

tmp_set = set()
locked_set = set()
for idx, ahead_intersection in enumerate(ahead_intersections):
# get the actual intersection
actual_intersection = self.node_map[ahead_intersection]
# remove redundant
tmp_set.add(actual_intersection)
for actual_intersection in tmp_set:
if self.intersections_status[actual_intersection] == False and (
actual_intersection not in self.intersection_lock_queue):
locked_set.add(actual_intersection)
if not locked_set:
for actual_intersection in tmp_set:
# lock the intersection
self.intersections_status[actual_intersection] = False
ahead_distance = distances_to_intersections[idx]
if actual_intersection not in self.intersection_lock_queue:
self.intersection_lock_queue[actual_intersection] = []
if len(self.intersection_lock_queue[actual_intersection]) == 0 or self.intersection_lock_queue[actual_intersection][-1][1] != aircraft:
self.intersection_lock_queue[actual_intersection].append((ahead_distance, aircraft))

def is_lock_by(self, aircraft):
ahead_intersections, _ = aircraft.get_ahead_intersections_and_link()
if len(ahead_intersections) == 0:
return True
ahead_intersection = ahead_intersections[0]
#ahead_intersection = ahead_intersections[0]
for _, ahead_intersection in enumerate(ahead_intersections):
actual_intersection = self.node_map[ahead_intersection]
sorted_queue = sorted(self.intersection_lock_queue[actual_intersection], key=lambda t : t[0])
lock_by_aircraft = sorted_queue[0][1]
sorted_queue = sorted(self.intersection_lock_queue.get(actual_intersection, []), key=lambda t : t[0])
lock_by_aircraft = sorted_queue[0][1] if sorted_queue else None
if aircraft != lock_by_aircraft:
# print("LOCK INFO: ", aircraft, " has no lock")
# print("lock by", lock_by_aircraft)
Expand Down
28 changes: 20 additions & 8 deletions ramp_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,30 @@ class RampController:
def __init__(self, ground):
self.ground = ground
self.spot_queue = {}
self.spot_gate_queue = {}
self.spots = ground.surface.spots

def add_aircraft_to_spot_queue(self, spot, gate, aircraft):
""" Keep a spot queue for aircraft"""
if spot not in self.spot_queue:
self.spot_queue[spot] = deque()
self.spot_gate_queue[spot] = deque()
self.spot_queue[spot].append(aircraft)
self.spot_gate_queue[spot].append(gate)
self.spot_queue[spot].append((aircraft,gate))

def remove_aircraft_from_spot_queue(self, spot):
if spot not in self.spot_queue:
return None
priortizedAircraft, prioritizedGate = None, None
priortizedAircraftScore = -float('inf')
for aircraft, gate in self.spot_queue[spot]:
currAircraftScore = 0
if aircraft is ArrivalFlight and aircraft.precise_location.is_close_to_gate(gate):
currAircraftScore -= 100
currAircraftScore += aircraft.tick_count*100
if currAircraftScore > priortizedAircraftScore:
priortizedAircraftScore = currAircraftScore
priortizedAircraft, prioritizedGate = aircraft, gate
self.spot_queue[spot].remove((priortizedAircraft, prioritizedGate))
return priortizedAircraft, prioritizedGate


def spot_occupied(self, spot):
if not self.spot_queue.get(spot, None):
Expand All @@ -51,12 +65,10 @@ def __resolve_conflict(self, spot, itineraries):

def resolve_conflict(self, itineraries):
# TODO: need to solve conflict between arrival and departure flights
flag = False

for spot in self.spots:
occupied = self.__resolve_conflict(spot, itineraries)
if occupied:
flag = True
return flag
return True
return False


42 changes: 34 additions & 8 deletions routing_expert.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
"""Class file for `RoutingExpert`."""
import imp
import logging
import cache

from link import Link
from route import Route
from surface import Runway, Spot, Gate, RunwayNode

from surface import Runway, Spot, Gate
from surface import PushbackWay
import random
from config import Config
import csv

import matplotlib.pyplot as plt

Expand All @@ -25,6 +29,8 @@ def __init__(self, links, nodes, enable_cache):

self.depart_routing_table = {}
self.arrival_routing_table = {}
self.depart_unimpeded_times = {}
self.arrival_unimpeded_times = {}

# Adds the two ends of a links as a node as well
for link in links:
Expand All @@ -36,6 +42,7 @@ def __init__(self, links, nodes, enable_cache):
self.nodes = nodes
self.runway_nodes = list(map(lambda l: l.start, list(filter(lambda l: type(l) == Runway, self.links))))
self.gate_nodes = list(filter(lambda l: type(l) == Gate, self.nodes))
self.spot_nodes = set(list(filter(lambda l: type(l) == Spot, self.nodes)))
self.logger.info("%d links and %d nodes are loaded",
len(self.links), len(self.nodes))

Expand All @@ -53,12 +60,16 @@ def __build_or_load_routes(self):
cached = cache.get(hash_key)

if cached:
(self.depart_routing_table, self.arrival_routing_table) = cached
(self.depart_routing_table, self.arrival_routing_table, self.depart_unimpeded_times, self.arrival_unimpeded_times) = cached
self.logger.debug("Cached routing table is loaded")
else:
# Builds the routes
self.__build_routes()
cache.put(hash_key, (self.depart_routing_table, self.arrival_routing_table))
cache.put(hash_key, (
self.depart_routing_table,
self.arrival_routing_table,
self.depart_unimpeded_times,
self.arrival_unimpeded_times))

def __build_routes(self):
self.logger.debug("Starts building routes, # nodes: %d # links: %d",
Expand All @@ -76,8 +87,8 @@ def __build_routes(self):

# Step 3: Applies SPFA to get shortest routes for all node pairs
self.logger.debug("Starts SPFA for finding shortest routes")
self.depart_routing_table = self.__finds_shortest_route_spfa(self.runway_nodes)
self.arrival_routing_table = self.__finds_shortest_route_spfa(self.gate_nodes)
self.depart_routing_table, self.depart_unimpeded_times = self.__finds_shortest_route_spfa(self.runway_nodes)
self.arrival_routing_table, self.arrival_unimpeded_times = self.__finds_shortest_route_spfa(self.gate_nodes)

# Prints result
self.print_depart_route(self.depart_routing_table)
Expand Down Expand Up @@ -121,16 +132,18 @@ def __link_existing_links(self):

def __finds_shortest_route_spfa(self, dest_nodes):
routing_table = {}
unimpeded_times = {}
for r in dest_nodes:
routing_table[r] = {}
unimpeded_times[r] = {}

for n in self.nodes:
if n == r:
continue
routing_table[r][n] = Route(n, r, [])
unimpeded_times[r][n] = 0

candidates = CandidateNeighbors(r)

while candidates.length:
u = candidates.pop()

Expand All @@ -143,6 +156,19 @@ def __finds_shortest_route_spfa(self, dest_nodes):
routing_table[r][v].add_link(self.adjacency_map[v][u])
if r != u:
routing_table[r][v].add_links(routing_table[r][u].links)
unimpeded_time = 0
for link in routing_table[r][v].links:
if any(node in self.spot_nodes for node in link.nodes):
for i, node in enumerate(link.nodes):
if node in self.spot_nodes:
unimpeded_time += link.segment_lengths[i] / Config.params["aircraft_model"]["ramp_speed"]
else:
unimpeded_time += link.segment_lengths[i] / Config.params["aircraft_model"]["ideal_speed"]
elif type(link) == PushbackWay:
unimpeded_time += link.length / Config.params["aircraft_model"]["pushback_speed"]
else:
unimpeded_time += link.length / Config.params["aircraft_model"]["ideal_speed"]
unimpeded_times[r][v] = unimpeded_time

self.logger.debug("%s -> %s -> %s is shorter than "
"%s -> %s", v, u, r, v, r)
Expand All @@ -157,7 +183,7 @@ def __finds_shortest_route_spfa(self, dest_nodes):
continue
if not routing_table[r][node].is_completed:
raise Exception("Incomplete route found.")
return routing_table
return routing_table, unimpeded_times

def print_depart_route(self, routing_table):
"""Prints all the routes into STDOUT."""
Expand Down
Loading