def rank_origins(origins, destinations): p('debug', 'Ranking origins: %s' % origins) midpoint_origins = midpoint(origins) midpoint_destinations = midpoint(destinations) hub_route = Segment(midpoint_origins, midpoint_destinations) for origin in origins: AC = Segment(midpoint_origins, origin) orthogonal_heading = hub_route.get_initial_bearing() + 90 (a, b) = project_segment( abs(orthogonal_heading - AC.get_initial_bearing()), AC.get_length()) projection = midpoint_origins.get_position(orthogonal_heading, a) midpoint_to_projection = Segment(midpoint_origins, projection) angle = abs(hub_route.get_initial_bearing() - midpoint_to_projection.get_initial_bearing()) if abs(angle - 90) < 0.1: distance = -1 * midpoint_to_projection.get_length() else: distance = midpoint_to_projection.get_length() origin.distance_to_midpoint = distance return sorted(origins, key=lambda point: point.distance_to_midpoint)
def arrive(self): """Placeholder for the aircraft's arrival.""" self.position = self.route.waypoints[0] self.waypoints_passed.append(self.position) p('Deleting waypoint %s from %s due to %s of aircraft %s' %\ (self.position, self.route, 'arrival', self)) del self.route.waypoints[0]
def handle_lock(event): """Upon lock, find a formation (if exists) for current aircraft.""" aircraft = event.sender global allocator # Find the formation having self. formation = allocator.find_formation(aircraft) # If no formation is possible if formation is None or not len(formation) > 1: p('No formation was possible: %s' % formation) return # If a formation was found, aircraft is no longer a candidate allocator.remove_aircraft(aircraft) # Register which hub this formation belongs to formation.hub = formation[0].hub p('Formation init: %s' % formation) # Remove 'enter-lock-area' events for all buddies sim.events = filter( lambda e: e.label != 'enter-lock-area' or e.sender not in formation, sim.events) # Prevent buddies from being allocated somewhere else. for buddy in formation: # Self was already removed if buddy is not aircraft: allocator.remove_aircraft(buddy) global synchronizer synchronizer.synchronize(formation)
def rank_origins(origins, destinations): p('debug', 'Ranking origins: %s' % origins) midpoint_origins = midpoint(origins) midpoint_destinations = midpoint(destinations) hub_route = Segment(midpoint_origins, midpoint_destinations) for origin in origins: AC = Segment(midpoint_origins, origin) orthogonal_heading = hub_route.get_initial_bearing() + 90 (a, b) = project_segment( abs(orthogonal_heading - AC.get_initial_bearing()), AC.get_length() ) projection = midpoint_origins.get_position( orthogonal_heading, a ) midpoint_to_projection = Segment( midpoint_origins, projection ) angle = abs( hub_route.get_initial_bearing() - midpoint_to_projection.get_initial_bearing() ) if abs(angle - 90) < 0.1: distance = -1 * midpoint_to_projection.get_length() else: distance = midpoint_to_projection.get_length() origin.distance_to_midpoint = distance return sorted(origins, key = lambda point: point.distance_to_midpoint)
def handle_departure(event): """Adds the aircraft to the candidate stack and schedules lock event.""" aircraft = event.sender global allocator # Register which hub this aircraft will fly to aircraft.hub = aircraft.route.waypoints[0] #assert aircraft.time_to_waypoint() > config.lock_time # If the origin lies within the hub lock area, the aircraft cannot # reach cruise before reaching the hub, so instead we ignore it altogether # and tell it to fly directly to its destination instead of via the hub. if (aircraft.time_to_waypoint() < config.lock_time): # Reset the aircraft route aircraft.route.waypoints = [aircraft.position, aircraft.destination] aircraft.route.init_segments() aircraft.controller.calibrate() aircraft.is_excluded = True p('warning', ('Excluded from flying to the hub: %s' % (aircraft))) return allocator.add_aircraft(aircraft) sim.events.append( sim.Event( 'enter-lock-area', aircraft, # If aircraft departs from within lock area, set lock time to now sim.time + max(aircraft.time_to_waypoint() - config.lock_time, 0)))
def handle_lock(event): """Upon lock, find a formation (if exists) for current aircraft.""" aircraft = event.sender global allocator # Find the formation having self. formation = allocator.find_formation(aircraft) # If no formation is possible if formation is None or not len(formation) > 1: p('No formation was possible: %s' % formation) return # If a formation was found, aircraft is no longer a candidate allocator.remove_aircraft(aircraft) # Register which hub this formation belongs to formation.hub = formation[0].hub p('Formation init: %s' % formation) # Remove 'enter-lock-area' events for all buddies sim.events = filter(lambda e: e.label != 'enter-lock-area' or e.sender not in formation, sim.events) # Prevent buddies from being allocated somewhere else. for buddy in formation: # Self was already removed if buddy is not aircraft: allocator.remove_aircraft(buddy) global synchronizer synchronizer.synchronize(formation)
def schedule_departure(self): p('Scheduling departure of %s at %s. Waypoint: %s' % (self.aircraft, self.aircraft.departure_time, self.aircraft.route.waypoints[0])) self.add_event( sim.Event('aircraft-depart', self.aircraft, self.aircraft.departure_time))
def depart(self): """Sets the current position and increments to the first waypoint.""" self.position = self.route.waypoints[0] self.waypoints_passed.append(self.position) p('Deleting waypoint %s from %s due to %s of aircraft %s' %\ (self.position, self.route, 'departure', self)) del self.route.waypoints[0]
def at_waypoint(self): """Sets the current position and increments to the next segment.""" self.position = self.route.waypoints[0] self.waypoints_passed.append(self.position) p('Deleting waypoint %s from %s due to %s of aircraft %s' %\ (self.position, self.route, 'waypoint-reach', self)) del self.route.waypoints[0] del self.route.segments[0]
def w(d, W, model): result = W * (1 - math.exp(-d * model['c_T'] / (model['V'] * model['L_D']))) p('validate', 'Calculating fuel burned.') p('validate', 'Distance: %.2f' % d) p('validate', 'Starting weight: %.2f' % W) p('validate', 'Result: %d' % result) p('validate', '===========================') return result
def schedule_waypoint(self): self.aircraft.waypoint_eta = sim.time + self.aircraft.time_to_waypoint( ) p('Schedule waypoint-arrive. WP: %s. ETA: %d. Aircraft: %s' % (self.aircraft.route.waypoints[0], self.aircraft.waypoint_eta, self.aircraft)) self.add_event( sim.Event('aircraft-at-waypoint', self.aircraft, self.aircraft.waypoint_eta))
def time_to_waypoint(self): """Calculates the time left to fly to the current waypoint.""" waypoint = self.route.waypoints[0] distance = self.position.distance_to(waypoint) ttwp = distance / self.speed p('Time to waypoint %s (d=%d, curtime=%d) for aircraft %s (v=%s, pos=%s) = %d' % ('%s {%d, %d}' % (waypoint, waypoint.lat, waypoint.lon), distance, sim.time, self, self.speed, '{%d, %d}' % (self.position.lat, self.position.lon), ttwp)) return ttwp
def schedule_departure(self): p('Scheduling departure of %s at %s. Waypoint: %s' % ( self.aircraft, self.aircraft.departure_time, self.aircraft.route.waypoints[0] )) self.add_event(sim.Event( 'aircraft-depart', self.aircraft, self.aircraft.departure_time ))
def handle_waypoint(event): aircraft = event.sender aircraft.at_waypoint() p('Aircraft at waypoint: %s (%s)' % ( aircraft.position, aircraft )) p('Need to calibrate aircraft %s (%s)' % ( aircraft, aircraft.route )) aircraft.controller.calibrate()
def heading_filter(buddy): segment = Segment(buddy.hub, buddy.destination) buddy_heading = segment.get_initial_bearing() phi_obs = abs(leader_heading - buddy_heading) p( 'debug', 'delta phi observed for %s (phi: %.2f) against %s (phi: %.2f)' ': %.2f degrees' % (aircraft, leader_heading, buddy, buddy_heading, phi_obs)) return phi_obs <= (config.phi_max / 2)
def schedule_waypoint(self): self.aircraft.waypoint_eta = sim.time + self.aircraft.time_to_waypoint() p('Schedule waypoint-arrive. WP: %s. ETA: %d. Aircraft: %s' % ( self.aircraft.route.waypoints[0], self.aircraft.waypoint_eta, self.aircraft )) self.add_event(sim.Event( 'aircraft-at-waypoint', self.aircraft, self.aircraft.waypoint_eta ))
def update_position(self): """Calculates the position of the aircraft according to the simtime""" # Assume that the flight has been properly set up with a hub. assert len(self.aircraft.route.segments) > 0 flight_time = sim.time - self.aircraft.departure_time distance_flown = flight_time * self.aircraft.speed segment = self.aircraft.route.segments[0] new_pos = segment.start.get_position(bearing=segment.initial_bearing, distance=distance_flown) p('Updating position of %s from %s to %s (d_flown=%d, flt_time=%d)' % ('%s (%s)' % (self.aircraft, self.aircraft.route.segments), self.aircraft.position, new_pos, distance_flown, flight_time)) self.aircraft.position = new_pos
def heading_filter(buddy): segment = Segment(buddy.hub, buddy.destination) buddy_heading = segment.get_initial_bearing() phi_obs = abs(leader_heading - buddy_heading) p( 'debug', 'delta phi observed for %s (phi: %.2f) against %s (phi: %.2f)' ': %.2f degrees' % ( aircraft, leader_heading, buddy, buddy_heading, phi_obs ) ) return phi_obs <= (config.phi_max/2)
def schedule_arrival(self): self.aircraft.arrival_time = sim.time + self.aircraft.time_to_waypoint( ) p('Scheduling arrival of aircraft %s at destination %s at time %s' %\ ( self.aircraft, self.aircraft.destination, self.aircraft.arrival_time, )) self.add_event( sim.Event('aircraft-arrive', self.aircraft, self.aircraft.arrival_time))
def time_to_waypoint(self): """Calculates the time left to fly to the current waypoint.""" waypoint = self.route.waypoints[0] distance = self.position.distance_to(waypoint) ttwp = distance / self.speed p('Time to waypoint %s (d=%d, curtime=%d) for aircraft %s (v=%s, pos=%s) = %d' % ( '%s {%d, %d}' % (waypoint, waypoint.lat, waypoint.lon), distance, sim.time, self, self.speed, '{%d, %d}' % ( self.position.lat, self.position.lon ), ttwp )) return ttwp
def schedule_arrival(self): self.aircraft.arrival_time = sim.time + self.aircraft.time_to_waypoint() p('Scheduling arrival of aircraft %s at destination %s at time %s' %\ ( self.aircraft, self.aircraft.destination, self.aircraft.arrival_time, )) self.add_event(sim.Event( 'aircraft-arrive', self.aircraft, self.aircraft.arrival_time ))
def construct_hub(origins, destinations, Z): midpoint_origins = midpoint(origins) midpoint_destinations = midpoint(destinations) hub_route = Segment(midpoint_origins, midpoint_destinations) hub = hub_route.start.get_position(hub_route.get_initial_bearing(), hub_route.get_length() * Z) hub.origins = origins hub.destinations = destinations p('debug', 'Constructed hub at %s' % (hub)) return hub
def construct_hub(origins, destinations, Z): midpoint_origins = midpoint(origins) midpoint_destinations = midpoint(destinations) hub_route = Segment(midpoint_origins, midpoint_destinations) hub = hub_route.start.get_position( hub_route.get_initial_bearing(), hub_route.get_length() * Z ) hub.origins = origins hub.destinations = destinations p('debug', 'Constructed hub at %s' % (hub)) return hub
def update_position(self): """Calculates the position of the aircraft according to the simtime""" # Assume that the flight has been properly set up with a hub. assert len(self.aircraft.route.segments) > 0 flight_time = sim.time - self.aircraft.departure_time distance_flown = flight_time * self.aircraft.speed segment = self.aircraft.route.segments[0] new_pos = segment.start.get_position( bearing = segment.initial_bearing, distance = distance_flown ) p('Updating position of %s from %s to %s (d_flown=%d, flt_time=%d)' % ( '%s (%s)' % (self.aircraft, self.aircraft.route.segments), self.aircraft.position, new_pos, distance_flown, flight_time )) self.aircraft.position = new_pos
def distance_to(self, point): R = Earth.R lat1 = math.radians(self.lat) lat2 = math.radians(point.lat) lon1 = math.radians(self.lon) lon2 = math.radians(point.lon) param = \ math.sin(lat1)*math.sin(lat2)+\ math.cos(lat1)*math.cos(lat2)*\ math.cos(lon2-lon1) # Reduce precision to avoid 1.00000000000000034734 being out of domain param = float('%.6f' % param) d = math.acos(param) * R p('validate', 'Distance between %s and %s is %.2f NM' % (self, point, d)) return d
def handle_alive(event): global vars formation = event.sender # We should have a hookoff point for each participant, and it should be # the current segment for aircraft in formation: assert aircraft.hookoff_point # The remaining segment should be hookoff-destination #debug.print_object(aircraft) assert len(aircraft.route.segments) > 0 # Let the aircraft know in which formation it belongs aircraft.formation = formation #assert aircraft.route.segments[0].end.coincides( # aircraft.hookoff_point #) vars["formation_count"] += 1 vars['formation_aircraft_count'] += len(formation) formation_phi = 0 for aircraft in formation: hub_to_dest = Segment(aircraft.hub, aircraft.destination) hub_to_hookoff = Segment(aircraft.hub, aircraft.hookoff_point) bearing = hub_to_dest.get_initial_bearing() formation_bearing = hub_to_hookoff.get_initial_bearing() phi_obs = abs(bearing - formation_bearing) p('debug', 'Aircraft %s, phi_obs: %.2f' % (aircraft, phi_obs)) #assert phi_obs <= config.phi_max formation_phi += phi_obs #print aircraft.route.segments[0].initial_bearing() vars['phi_obs_sum'] += formation_phi for aircraft in formation: vars['Q_sum'] = vars['Q_sum'] + aircraft.Q
def distance_to(self, point): R = Earth.R lat1 = math.radians(self.lat) lat2 = math.radians(point.lat) lon1 = math.radians(self.lon) lon2 = math.radians(point.lon) param = \ math.sin(lat1)*math.sin(lat2)+\ math.cos(lat1)*math.cos(lat2)*\ math.cos(lon2-lon1) # Reduce precision to avoid 1.00000000000000034734 being out of domain param = float('%.6f' % param) d = math.acos(param)*R p('validate', 'Distance between %s and %s is %.2f NM' % ( self, point, d )) return d
def handle_departure(event): """Adds the aircraft to the candidate stack and schedules lock event.""" aircraft = event.sender global allocator # Register which hub this aircraft will fly to aircraft.hub = aircraft.route.waypoints[0] #assert aircraft.time_to_waypoint() > config.lock_time # If the origin lies within the hub lock area, the aircraft cannot # reach cruise before reaching the hub, so instead we ignore it altogether # and tell it to fly directly to its destination instead of via the hub. if(aircraft.time_to_waypoint() < config.lock_time): # Reset the aircraft route aircraft.route.waypoints = [ aircraft.position, aircraft.destination ] aircraft.route.init_segments() aircraft.controller.calibrate() aircraft.is_excluded = True p('warning', ( 'Excluded from flying to the hub: %s' % ( aircraft ) )) return allocator.add_aircraft(aircraft) sim.events.append(sim.Event( 'enter-lock-area', aircraft, # If aircraft departs from within lock area, set lock time to now sim.time + max(aircraft.time_to_waypoint() - config.lock_time, 0) ))
def delay_events(self, delay): p('Delaying all events for aircraft %s by delay = %s.' % (self.aircraft, delay)) p('Event list before delayal: %s' % self.aircraft.events) # Quick an dirty: expose delay for statistics. # @todo decouple. Idea: create a separate delay event that sends the # amount this aircraft was delayed to anybody who listens? # In order to make this quick-and-dirty fix bug free, we can only # delay an aircraft once in its lifetime to prevent the variable being # overwritten by multiple calls to this methods. assert not hasattr(self.aircraft, 'hub_delay') self.aircraft.hub_delay = delay if hasattr(self.aircraft, 'waypoint_eta'): self.aircraft.waypoint_eta = self.aircraft.waypoint_eta + delay if hasattr(self.aircraft, 'arrival_time'): self.aircraft.arrival_time = self.aircraft.arrival_time + delay for event in self.aircraft.events: event.time = event.time + delay p('Event list after delayal: %s' % self.aircraft.events)
def delay_events(self, delay): p('Delaying all events for aircraft %s by delay = %s.' % ( self.aircraft, delay )) p('Event list before delayal: %s' % self.aircraft.events) # Quick an dirty: expose delay for statistics. # @todo decouple. Idea: create a separate delay event that sends the # amount this aircraft was delayed to anybody who listens? # In order to make this quick-and-dirty fix bug free, we can only # delay an aircraft once in its lifetime to prevent the variable being # overwritten by multiple calls to this methods. assert not hasattr(self.aircraft, 'hub_delay') self.aircraft.hub_delay = delay if hasattr(self.aircraft, 'waypoint_eta'): self.aircraft.waypoint_eta = self.aircraft.waypoint_eta + delay if hasattr(self.aircraft, 'arrival_time'): self.aircraft.arrival_time = self.aircraft.arrival_time + delay for event in self.aircraft.events: event.time = event.time + delay p('Event list after delayal: %s' % self.aircraft.events)
def get_via_stdin(): """Set up the planes list, assume tab-separated columns via stdin. Can be piped, example: $ cat data/flights.tsv | ./thesis.py""" planes = [] if len(input_history) == 0: for row in csv.reader(sys.stdin, delimiter='\t'): input_history.append(row) for row in input_history: departure_time = int(row[0]) label = row[1] waypoints = row[2].split('-') aircraft_type = row[3] # If a flight has been calibrated, the formation probability will be # given. Otherwise it will be set to None. try: probability = float(row[4]) # Disregard row if config tells us to if probability < config.min_P: p( 'warning', 'Probability of row does not match criterium %s, row %s' % (config.min_P, row)) continue p('warning', 'Yes! Row %s can be included!' % row) except IndexError: probability = None # Keep track of scheduled departure time for later retrieval departure_time_scheduled = departure_time # Departure times are randomly distributed # In some rare cases (only for early aircraft) the departure time might # become negative so we restrict it to being only positive departure_time = departure_time +\ random.uniform(-1 * config.dt, config.dt) departure_time = max(0, departure_time) p('debug', 'sched: %d, real: %d' % (departure_time_scheduled, departure_time)) aircraft = Aircraft( label=label, route=Route([Waypoint(waypoints[0]), Waypoint(waypoints[1])]), departure_time=departure_time, aircraft_type=aircraft_type) aircraft.departure_time_scheduled = departure_time_scheduled # If a flight has been calibrated, the formation probability will be # given. Otherwise it will be set to None. aircraft.probability = probability ## Find a random hub #hub = random.choice(config.hubs), ## Find the closest hub #hub = min(config.hubs, key = lambda x: x.distance_to(aircraft.origin)) planes.append(aircraft) return planes
def handle_waypoint(event): aircraft = event.sender aircraft.at_waypoint() p('Aircraft at waypoint: %s (%s)' % (aircraft.position, aircraft)) p('Need to calibrate aircraft %s (%s)' % (aircraft, aircraft.route)) aircraft.controller.calibrate()
def allocate(self, aircraft): p('debug', 'Starting formation allocation for %s' % aircraft) # Do not perform allocation if no hub exists in the flight route. if len(aircraft.route.segments) == 0: return self.formations = [] intervals = [] candidates = self.aircraft_queue hub = aircraft.route.waypoints[0] # This is bad. We don't want to filter anything. # @todo: pre-process at a higher level. # Only consider other aircraft flying to the same hub candidates = filter(lambda a: a.route.waypoints[0] is hub, candidates) p('debug', 'Full candidate set: %s' % candidates) # Only consider aircraft having a maximum heading difference between # the hub and their destination segment = Segment(aircraft.hub, aircraft.destination) leader_heading = segment.get_initial_bearing() def heading_filter(buddy): segment = Segment(buddy.hub, buddy.destination) buddy_heading = segment.get_initial_bearing() phi_obs = abs(leader_heading - buddy_heading) p( 'debug', 'delta phi observed for %s (phi: %.2f) against %s (phi: %.2f)' ': %.2f degrees' % (aircraft, leader_heading, buddy, buddy_heading, phi_obs)) return phi_obs <= (config.phi_max / 2) candidates = filter(heading_filter, candidates) # Other interesting filters if 'same-airline' in config.restrictions: airline = aircraft.label[0:2] candidates = filter(lambda a: a.label[0:2] == airline, candidates) if 'same-aircraft-type' in config.restrictions: aircraft_type = aircraft.aircraft_type candidates = filter(lambda a: a.aircraft_type == aircraft_type, candidates) p('debug', 'Reduced candidate set: %s' % candidates) for candidate in candidates: # Quick and dirty: recalc position. Instead, pull eta from var. candidate.controller.update_position() tth = candidate.time_to_waypoint() # time to hub hub_eta = sim.time + tth # From the moment the aircraft enters the lock area, the slack # decreases linearly to zero upon hub arrival. if tth < config.lock_time: slack = tth * config.etah_slack / config.lock_time else: slack = config.etah_slack p('Time = %s, Hub (= %s) eta %s for candidate %s' %\ (sim.time, hub, hub_eta, candidate)) intervals.append( Interval(candidate, int(hub_eta) - slack, int(hub_eta) + slack)) for interval_group in group(intervals): formation = Formation() for interval in interval_group: formation.append(interval.obj) self.formations.append(formation)
def render(segments): # create new figure, axes instances. fig = plt.figure() ax = fig.add_axes([0.1,0.1,0.8,0.8], axisbg = '#a5bfdd') llcrnrlon = config.map_dimensions['lon'][0] urcrnrlon = config.map_dimensions['lon'][1] llcrnrlat = config.map_dimensions['lat'][0] urcrnrlat = config.map_dimensions['lat'][1] # setup mercator map projection. m = Basemap( llcrnrlon = llcrnrlon, llcrnrlat = llcrnrlat, urcrnrlon = urcrnrlon, urcrnrlat = urcrnrlat, rsphere = (6378137.00,6356752.3142), resolution = 'c', projection = 'merc', #lat_0 = 40.,lon_0 = -20.,lat_ts = 20. ) for segment in segments['benchmark']: m.drawgreatcircle(segment.start.lon, segment.start.lat, segment.end.lon, segment.end.lat, linewidth = 1, color='#00458A') x, y = m(segment.start.lon, segment.start.lat) m.plot(x, y, 'bo', ms = 4) x, y = m(segment.end.lon, segment.end.lat) m.plot(x, y, 'bo', ms = 4) for segment in segments['formation']: p('geo-debug', 'Start to plot a formation trajectory') p('geo-debug', 'Trajectory: (%s, %s) -> (%s, %s)' % ( segment.start.lon, segment.start.lat, segment.end.lon, segment.end.lat )) m.drawgreatcircle(segment.start.lon, segment.start.lat, segment.end.lon, segment.end.lat, linewidth = 1, color='g') x, y = m(segment.start.lon, segment.start.lat) m.plot(x, y, 'go', ms = 4) x, y = m(segment.end.lon, segment.end.lat) m.plot(x, y, 'go', ms = 4) p('geo-debug', 'Done with plotting a formation trajectory') for segment in segments['solo']: m.drawgreatcircle(segment.start.lon, segment.start.lat, segment.end.lon, segment.end.lat, linewidth = 1, color='r') x, y = m(segment.start.lon, segment.start.lat) m.plot(x, y, 'ro', ms = 4) x, y = m(segment.end.lon, segment.end.lat) m.plot(x, y, 'ro', ms = 4) m.drawcoastlines(color='#8f8457') m.fillcontinents(color='#f5f0db') m.drawcountries(color='#a9a06d') m.drawparallels(config.map['parallels'], labels = [1,1,0,1]) m.drawmeridians(config.map['meridians'], labels = [1,1,0,1]) return plt, ax
def handle_arrive(event): global vars, hubs aircraft = event.sender # Temporarily use one model for everything model = copy.deepcopy(config.model) # Also calculate the direct distance segment = Segment(aircraft.origin, aircraft.destination) direct = segment.get_length() #direct = 3193 #temp overwrite for validation p('validate', 'Distance direct for %s is %dNM' % ( aircraft, direct )) p('validate', 'Getting the benchmark fuel') vars['distance_direct'] += direct vars['fuel_direct'] += get_fuel_burned_during_cruise(direct, model) p('validate', 'OK, we have the benchmark fuel now') hub = aircraft.hub assert hub in config.hubs # Sometimes (especially with very low Z and high L) aircraft are excluded # from formation flight due to the origin being within the lock area. if hasattr(aircraft, 'is_excluded') and aircraft.is_excluded: vars['distance_solo'] += direct vars['fuel_actual'] += get_fuel_burned_during_cruise(direct, model) return #key = 'flight_count_%s' % hub #if key not in vars: # vars[key] = 0 #vars[key] = vars[key] + 1 # Aircraft always fly solo to the hub segment = Segment(aircraft.origin, hub) origin_to_hub = segment.get_length() p('Distance origin_to_hub for %s is %dNM' % ( aircraft, origin_to_hub )) vars['distance_solo'] += origin_to_hub # If in formation if hasattr(aircraft, 'formation'): segment = Segment(hub, aircraft.hookoff_point) hub_to_hookoff = segment.get_length() p('Distance hub_to_hookoff for %s is %dNM' % ( aircraft, hub_to_hookoff )) vars['distance_formation'] += hub_to_hookoff segment = Segment(aircraft.hookoff_point, aircraft.destination) hookoff_to_destination = segment.get_length() p('Distance hookoff_to_destination for %s is %dNM' % ( aircraft, hookoff_to_destination )) vars['distance_solo'] += hookoff_to_destination # Collect all hub delays # The calibration aircraft was never delayed if hasattr(aircraft, 'hub_delay'): vars['hub_delay_sum'] = vars['hub_delay_sum'] +\ aircraft.hub_delay if aircraft.incurs_benefits: discount = config.alpha else: discount = 0 p('validate', 'Discount = %s for %s' % (discount, aircraft)) fuel_formation = formationburn( int(origin_to_hub), int(hub_to_hookoff), int(hookoff_to_destination), model = model, discount = discount ) p('validate', 'Fuel burn formation = %d for %s' % ( fuel_formation, aircraft )) vars['fuel_actual'] += fuel_formation # If fully solo else: p('validate', 'Discount = false for %s' % (aircraft)) segment = Segment(hub, aircraft.destination) hub_to_destination = segment.get_length() p('Distance hub_to_destination for %s is %dNM' % ( aircraft, hub_to_destination )) vars['distance_solo'] += hub_to_destination fuel_solo = get_fuel_burned_during_cruise( origin_to_hub + hub_to_destination, model = model, ) p('validate', 'Fuel burn solo = %d for %s' % ( fuel_solo, aircraft )) vars['fuel_actual'] += fuel_solo
def remove_aircraft(self, aircraft): try: self.aircraft_queue.remove(aircraft) except ValueError: p('Could not remove %s from queue because not present' % aircraft)
def handle_waypoint(event): # Aircraft is no longer a candidate when it arrives at any waypoint # @todo Only remove from queue if at hub. But it's unlikely that aircraft # reach another waypoint before the hub so this is fine for now. p('Aircraft %s at waypoint, no longer formation candidate' % event.sender) allocator.remove_aircraft(event.sender)
def allocate(self, aircraft): p('debug', 'Starting formation allocation for %s' % aircraft) # Do not perform allocation if no hub exists in the flight route. if len(aircraft.route.segments) == 0: return self.formations = [] intervals = [] candidates = self.aircraft_queue hub = aircraft.route.waypoints[0] # This is bad. We don't want to filter anything. # @todo: pre-process at a higher level. # Only consider other aircraft flying to the same hub candidates = filter(lambda a: a.route.waypoints[0] is hub, candidates) p('debug', 'Full candidate set: %s' % candidates) # Only consider aircraft having a maximum heading difference between # the hub and their destination segment = Segment(aircraft.hub, aircraft.destination) leader_heading = segment.get_initial_bearing() def heading_filter(buddy): segment = Segment(buddy.hub, buddy.destination) buddy_heading = segment.get_initial_bearing() phi_obs = abs(leader_heading - buddy_heading) p( 'debug', 'delta phi observed for %s (phi: %.2f) against %s (phi: %.2f)' ': %.2f degrees' % ( aircraft, leader_heading, buddy, buddy_heading, phi_obs ) ) return phi_obs <= (config.phi_max/2) candidates = filter(heading_filter, candidates) # Other interesting filters if 'same-airline' in config.restrictions: airline = aircraft.label[0:2] candidates = filter(lambda a: a.label[0:2] == airline, candidates) if 'same-aircraft-type' in config.restrictions: aircraft_type = aircraft.aircraft_type candidates = filter(lambda a: a.aircraft_type == aircraft_type, candidates) p('debug', 'Reduced candidate set: %s' % candidates) for candidate in candidates: # Quick and dirty: recalc position. Instead, pull eta from var. candidate.controller.update_position() tth = candidate.time_to_waypoint() # time to hub hub_eta = sim.time + tth # From the moment the aircraft enters the lock area, the slack # decreases linearly to zero upon hub arrival. if tth < config.lock_time: slack = tth * config.etah_slack / config.lock_time else: slack = config.etah_slack p('Time = %s, Hub (= %s) eta %s for candidate %s' %\ (sim.time, hub, hub_eta, candidate)) intervals.append(Interval( candidate, int(hub_eta) - slack, int(hub_eta) + slack )) for interval_group in group(intervals): formation = Formation() for interval in interval_group: formation.append(interval.obj) self.formations.append(formation)
vars['formation_size'] = 6 vars['origin_to_hub'] = 242 vars['origin_to_destination'] = 3193 vars['hookoff_to_destination'] = 0 vars['hub_to_hookoff'] = ( vars['origin_to_destination'] - vars['origin_to_hub'] - vars['hookoff_to_destination'] ) vars['fuel_benchmark'] = 0 vars['fuel_formation'] = 0 p('validate', 'Getting the benchmark fuel') vars['fuel_benchmark'] = vars['formation_size'] *\ get_fuel_burned_during_cruise(vars['origin_to_destination'], model) p('validate', 'OK, we have the benchmark fuel now') incurs_benefit = False for i in range(0, vars['formation_size']): model = copy.deepcopy(model) if incurs_benefit is True: discount = .13 else: discount = 0
vars = {} vars['formation_size'] = 6 vars['origin_to_hub'] = 242 vars['origin_to_destination'] = 3193 vars['hookoff_to_destination'] = 0 vars['hub_to_hookoff'] = (vars['origin_to_destination'] - vars['origin_to_hub'] - vars['hookoff_to_destination']) vars['fuel_benchmark'] = 0 vars['fuel_formation'] = 0 p('validate', 'Getting the benchmark fuel') vars['fuel_benchmark'] = vars['formation_size'] *\ get_fuel_burned_during_cruise(vars['origin_to_destination'], model) p('validate', 'OK, we have the benchmark fuel now') incurs_benefit = False for i in range(0, vars['formation_size']): model = copy.deepcopy(model) if incurs_benefit is True: discount = .13 else: discount = 0
def handle_arrive(event): global vars, hubs aircraft = event.sender # Temporarily use one model for everything model = copy.deepcopy(config.model) # Also calculate the direct distance segment = Segment(aircraft.origin, aircraft.destination) direct = segment.get_length() #direct = 3193 #temp overwrite for validation p('validate', 'Distance direct for %s is %dNM' % (aircraft, direct)) p('validate', 'Getting the benchmark fuel') vars['distance_direct'] += direct vars['fuel_direct'] += get_fuel_burned_during_cruise(direct, model) p('validate', 'OK, we have the benchmark fuel now') hub = aircraft.hub assert hub in config.hubs # Sometimes (especially with very low Z and high L) aircraft are excluded # from formation flight due to the origin being within the lock area. if hasattr(aircraft, 'is_excluded') and aircraft.is_excluded: vars['distance_solo'] += direct vars['fuel_actual'] += get_fuel_burned_during_cruise(direct, model) return #key = 'flight_count_%s' % hub #if key not in vars: # vars[key] = 0 #vars[key] = vars[key] + 1 # Aircraft always fly solo to the hub segment = Segment(aircraft.origin, hub) origin_to_hub = segment.get_length() p('Distance origin_to_hub for %s is %dNM' % (aircraft, origin_to_hub)) vars['distance_solo'] += origin_to_hub # If in formation if hasattr(aircraft, 'formation'): segment = Segment(hub, aircraft.hookoff_point) hub_to_hookoff = segment.get_length() p('Distance hub_to_hookoff for %s is %dNM' % (aircraft, hub_to_hookoff)) vars['distance_formation'] += hub_to_hookoff segment = Segment(aircraft.hookoff_point, aircraft.destination) hookoff_to_destination = segment.get_length() p('Distance hookoff_to_destination for %s is %dNM' % (aircraft, hookoff_to_destination)) vars['distance_solo'] += hookoff_to_destination # Collect all hub delays # The calibration aircraft was never delayed if hasattr(aircraft, 'hub_delay'): vars['hub_delay_sum'] = vars['hub_delay_sum'] +\ aircraft.hub_delay if aircraft.incurs_benefits: discount = config.alpha else: discount = 0 p('validate', 'Discount = %s for %s' % (discount, aircraft)) fuel_formation = formationburn(int(origin_to_hub), int(hub_to_hookoff), int(hookoff_to_destination), model=model, discount=discount) p('validate', 'Fuel burn formation = %d for %s' % (fuel_formation, aircraft)) vars['fuel_actual'] += fuel_formation # If fully solo else: p('validate', 'Discount = false for %s' % (aircraft)) segment = Segment(hub, aircraft.destination) hub_to_destination = segment.get_length() p('Distance hub_to_destination for %s is %dNM' % (aircraft, hub_to_destination)) vars['distance_solo'] += hub_to_destination fuel_solo = get_fuel_burned_during_cruise( origin_to_hub + hub_to_destination, model=model, ) p('validate', 'Fuel burn solo = %d for %s' % (fuel_solo, aircraft)) vars['fuel_actual'] += fuel_solo
def calibrate(self): """Determines the trunk route and hookoff points""" # Determine formation trunk route destinations = [] for aircraft in self: destinations.append(aircraft.destination) arrival_midpoint = midpoint(destinations) p('destinations: %s' % destinations) p('midpoint = %s' % arrival_midpoint) hub_to_midpoint = Segment(aircraft.hub, arrival_midpoint) # Determine hookoff point for each aircraft, except the last for aircraft in self: hub_to_destination = Segment(aircraft.hub, aircraft.destination) p('flight %s hub %s to destination: %s' % ( aircraft, '%s{%d, %d}' % ( aircraft.hub, aircraft.hub.lat, aircraft.hub.lon ), aircraft.destination )) p('flight %s hub %s to midpoint: %s' % ( aircraft, '%s{%d, %d}' % ( aircraft.hub, aircraft.hub.lat, aircraft.hub.lon ), arrival_midpoint )) aircraft.hookoff_point = get_hookoff( hub_to_midpoint, aircraft.destination, config.alpha ) hub_to_hookoff = Segment(aircraft.hub, aircraft.hookoff_point) aircraft.Q = hub_to_hookoff.get_length() /\ hub_to_midpoint.get_length() p('flight %s, hub %s to hook-off point: %s' % ( aircraft, '%s{%d, %d}' % ( aircraft.hub, aircraft.hub.lat, aircraft.hub.lon ), aircraft.hookoff_point )) aircraft.hookoff_point.name = 'hookoff-%s' % aircraft.hookoff_point # Place aircraft in order, ascending with Q, to fulfill LIFO condition. formation = sorted(self, key = lambda item: item.Q) # All aircraft at the front of the formation having the same destination # should hook off where the previous buddy (having a different # destination) hooked off. # Example: formation AMS-SFO, BRU-SFO, LHR-ATL. # AMS-SFO and BRU-SFO should hook off where LHR-ATL hooked off. # @todo Let AMS-SFO and BRU-SFO continue together along a new average # formation trajectory (in this case directly to the destination) # First find the leading set of aircraft having the same destination formation.reverse() leading_destination = formation[0].destination leaders = [] for aircraft in formation: # Start with always incurring benefits aircraft.incurs_benefits = True if not aircraft.destination.coincides(leading_destination): aircraft.is_leader = False continue aircraft.is_leader = True # Only the first leader incurs no benefits at all if len(leaders) == 0: aircraft.incurs_benefits = False leaders.append(aircraft) p('Leaders of formation %s are %s' % ( formation, leaders )) # Then find the buddy just before the set of leading aircraft, if # it exists. try: # The leaders: same hookoff point as last buddy. last_buddy = formation[len(leaders)] for aircraft in leaders: aircraft.Q = last_buddy.Q #aircraft.P = last_buddy.P aircraft.hookoff_point = last_buddy.hookoff_point except IndexError: pass # Change reversed formation back to normal formation.reverse() for aircraft in formation: p('Adjusting waypoints of %s. Initial waypoints: %s' % ( aircraft, aircraft.route.waypoints )) aircraft.route.waypoints = [ #aircraft.hub, aircraft.hookoff_point, aircraft.destination] aircraft.route.init_segments() p('Adjusted waypoints of %s. New waypoints: %s' % ( aircraft, aircraft.route.waypoints )) p('Need to calibrate aircraft %s (%s) in formation %s' % ( aircraft, aircraft.route, formation )) aircraft.controller.calibrate()
def get_via_stdin(): """Set up the planes list, assume tab-separated columns via stdin. Can be piped, example: $ cat data/flights.tsv | ./thesis.py""" planes = [] if len(input_history) == 0: for row in csv.reader(sys.stdin, delimiter = '\t'): input_history.append(row) for row in input_history: departure_time = int(row[0]) label = row[1] waypoints = row[2].split('-') aircraft_type = row[3] # If a flight has been calibrated, the formation probability will be # given. Otherwise it will be set to None. try: probability = float(row[4]) # Disregard row if config tells us to if probability < config.min_P: p('warning', 'Probability of row does not match criterium %s, row %s' % ( config.min_P, row )) continue p('warning', 'Yes! Row %s can be included!' % row) except IndexError: probability = None # Keep track of scheduled departure time for later retrieval departure_time_scheduled = departure_time # Departure times are randomly distributed # In some rare cases (only for early aircraft) the departure time might # become negative so we restrict it to being only positive departure_time = departure_time +\ random.uniform(-1 * config.dt, config.dt) departure_time = max(0, departure_time) p('debug', 'sched: %d, real: %d' % ( departure_time_scheduled, departure_time )) aircraft = Aircraft( label = label, route = Route([ Waypoint(waypoints[0]), Waypoint(waypoints[1]) ]), departure_time = departure_time, aircraft_type = aircraft_type) aircraft.departure_time_scheduled = departure_time_scheduled # If a flight has been calibrated, the formation probability will be # given. Otherwise it will be set to None. aircraft.probability = probability ## Find a random hub #hub = random.choice(config.hubs), ## Find the closest hub #hub = min(config.hubs, key = lambda x: x.distance_to(aircraft.origin)) planes.append(aircraft) return planes
def synchronize(self, formation): for aircraft in formation: # Ensure position is up-to-date for eta calculation later aircraft.controller.update_position() # Ensure all participants have the hub as their active waypoint assert aircraft.route.waypoints[0].coincides(formation.hub) # Select the aircraft that arrives last at the hub last_aircraft = max(formation, key = lambda a: a.time_to_waypoint()) time_to_hub = last_aircraft.time_to_waypoint() p('The last aircraft in formation %s is: %s.' % ( formation, last_aircraft )) p('The time to hub for formation %s is %d.' % ( formation, time_to_hub )) for aircraft in formation: if aircraft is last_aircraft: continue # Make sure the position of the aircraft is current aircraft.controller.update_position() # Compute time difference between arrivals delay = time_to_hub - aircraft.time_to_waypoint() p('Original hub arrival time for aircraft %s: %d.' % ( aircraft, sim.time + aircraft.time_to_waypoint() )) p('Required hub arrival time for aircraft %s: %d.' % ( aircraft, sim.time + time_to_hub )) p('Required hub arrival delay for aircraft %s: %d.' % ( aircraft, delay )) # We should only delay, never expedite assert delay >= 0 # Delay arrival of participant to match required arrival. aircraft.controller.delay_events(delay) # Add a tiny delay to make sure that all aircraft-at-waypoint # events are fired before the formation-alive event. formation_alive_time = sim.time + time_to_hub + 0.0001 p('Set formation-alive time at %d + %d = %d for formation %s' % ( sim.time, time_to_hub, formation_alive_time, formation )) sim.events.append(sim.Event( 'formation-alive', formation, formation_alive_time ))
def calibrate(self): """Determines the trunk route and hookoff points""" # Determine formation trunk route destinations = [] for aircraft in self: destinations.append(aircraft.destination) arrival_midpoint = midpoint(destinations) p('destinations: %s' % destinations) p('midpoint = %s' % arrival_midpoint) hub_to_midpoint = Segment(aircraft.hub, arrival_midpoint) # Determine hookoff point for each aircraft, except the last for aircraft in self: hub_to_destination = Segment(aircraft.hub, aircraft.destination) p('flight %s hub %s to destination: %s' % (aircraft, '%s{%d, %d}' % (aircraft.hub, aircraft.hub.lat, aircraft.hub.lon), aircraft.destination)) p('flight %s hub %s to midpoint: %s' % (aircraft, '%s{%d, %d}' % (aircraft.hub, aircraft.hub.lat, aircraft.hub.lon), arrival_midpoint)) aircraft.hookoff_point = get_hookoff(hub_to_midpoint, aircraft.destination, config.alpha) hub_to_hookoff = Segment(aircraft.hub, aircraft.hookoff_point) aircraft.Q = hub_to_hookoff.get_length() /\ hub_to_midpoint.get_length() p('flight %s, hub %s to hook-off point: %s' % (aircraft, '%s{%d, %d}' % (aircraft.hub, aircraft.hub.lat, aircraft.hub.lon), aircraft.hookoff_point)) aircraft.hookoff_point.name = 'hookoff-%s' % aircraft.hookoff_point # Place aircraft in order, ascending with Q, to fulfill LIFO condition. formation = sorted(self, key=lambda item: item.Q) # All aircraft at the front of the formation having the same destination # should hook off where the previous buddy (having a different # destination) hooked off. # Example: formation AMS-SFO, BRU-SFO, LHR-ATL. # AMS-SFO and BRU-SFO should hook off where LHR-ATL hooked off. # @todo Let AMS-SFO and BRU-SFO continue together along a new average # formation trajectory (in this case directly to the destination) # First find the leading set of aircraft having the same destination formation.reverse() leading_destination = formation[0].destination leaders = [] for aircraft in formation: # Start with always incurring benefits aircraft.incurs_benefits = True if not aircraft.destination.coincides(leading_destination): aircraft.is_leader = False continue aircraft.is_leader = True # Only the first leader incurs no benefits at all if len(leaders) == 0: aircraft.incurs_benefits = False leaders.append(aircraft) p('Leaders of formation %s are %s' % (formation, leaders)) # Then find the buddy just before the set of leading aircraft, if # it exists. try: # The leaders: same hookoff point as last buddy. last_buddy = formation[len(leaders)] for aircraft in leaders: aircraft.Q = last_buddy.Q #aircraft.P = last_buddy.P aircraft.hookoff_point = last_buddy.hookoff_point except IndexError: pass # Change reversed formation back to normal formation.reverse() for aircraft in formation: p('Adjusting waypoints of %s. Initial waypoints: %s' % (aircraft, aircraft.route.waypoints)) aircraft.route.waypoints = [ #aircraft.hub, aircraft.hookoff_point, aircraft.destination ] aircraft.route.init_segments() p('Adjusted waypoints of %s. New waypoints: %s' % (aircraft, aircraft.route.waypoints)) p('Need to calibrate aircraft %s (%s) in formation %s' % (aircraft, aircraft.route, formation)) aircraft.controller.calibrate()